trealla A compact, efficient Prolog interpreter written in plain-old C. Project maintained by trealla-prolog Hosted on GitHub Pages — Theme by mattgraham

Trealla Prolog

A compact, efficient Prolog interpreter with ISO Prolog aspirations.

MIT licensed Integers & Rationals are unbounded Atoms are UTF-8 of unlimited length The default double-quoted representation is *chars* list Strings & slices are super-efficient (especially with mmap'd files) REPL with history Runs on Linux, Android, FreeBSD, macOS, and WebAssembly (WASI) & Go API for calling from C (or by using WASM from Go & JS) Foreign function interface (FFI) for calling out to user C code Access SQLITE databases using builtin module (uses FFI) Concurrency via tasks / linda / futures / engines (generators) Pre-emptive multi-threading Blackboarding primitives ... Delimited continuations ##EXPERIMENTAL## Rational trees ##EXPERIMENTAL## CLP(Z) ##EXPERIMENTAL## CLP(B) ##UNSUPPORTED##

Trealla is not WAM-based. It uses tree-walking, structure-sharing and deep-binding. Source is byte-code compiled to a flattened AST that is interpreted at runtime. The intent and continued aim of Trealla is to be a small, easily ported, Prolog core.

Trealla is thread-safe, but single-threaded internally.

Available from: trealla-prolog.org.

Runs with Jupyter Notebooks.

Logo

Usage

tpl [options] [files] [-- args]

where options can be:

-O0, --noopt - no optimization -f file - load file (*~/.tplrc* not loaded) -l file - load file (*~/.tplrc* loaded) file - load file (*~/.tplrc* loaded) -g goal - query goal (only used once) --library path - alt to TPL_LIBRARY_PATH env var -t, --trace - trace -q, --quiet - quiet mode (no banner) -v, --version - version -h, --help - help -d, --daemonize - daemonize -w, --watchdog - create watchdog --consult - consult from STDIN

For example:

tpl -g test2,halt samples/sieve

Invocation without any goal presents the REPL.

The default path to the library is relative to the executable location.

The file ~/.tplrc is consulted on startup unless the -f option is present.

When consulting, reconsulting and deconsulting files the .pl version of the filename is always preferred (if not specified) when looking for a file.

A note on UTF-8

Trealla uses UTF-8 internally and this works well with modern operating systems that are already [1], or moving to [2], native UTF-8.

It aligns well with standard C as functions like strcmp/memcmp that require no special handling to respect codepoint order. This also works seamlessly with the implementation of double-quoted strings (ie. chars-list), DCGs, and mmap’d files. Any code-point specific requirements, like get_char, get_code, sub_atom, atom_length, atom_codes, atom_chars & _upper/_lower are handled on the fly.

UTF-8 atoms do not need to be quoted unless they contain breaking characters…

?- [user]. 是. % be: means, approximately, "True". 不是 :- \+ 是. % not be: means, approximately, "False". <CTRL-D> true. ?- 是. true. ?- 不是. false. ``` ```console ?- X = 国字. X = 国字. ?-

Trealla accepts as a var any atom beginning with an uppercase character…

?- atom_upper(δ,C). C = Δ. ?- Δ is 123456-123455. Δ = 1. ?-

Building

Written in plain-old C99.

git clone https://github.com/trealla-prolog/trealla.git cd trealla

On Debian+ systems you may need to install GNU readline, xxd & libffi

sudo apt install libreadline-dev xxd libffi-dev

Then…

make

To build without libffi:

make NOFFI=1

On Debian+ systems you may need to install OpenSSL:

sudo apt install libssl-dev

unless you choose to build without SSL/TLS support:

make NOSSL=1

To build without pre-emptive multi-threading support:

make NOTHREADS=1

To build with the included ISOCLINE sources (default is to use GNU Readline):

make ISOCLINE=1

Older compilers may require:

make NOPEDANTIC=1

to avoid issues with newer flags.

Then…

make test

There should be no errors, Further (if valgrind is installed)…

make leaks

Should show no memory out-of-bounds, null-pointer, use after free or memory leaks (there may be one perhaps spurious error).

On BSD systems use gmake to build and do

pkg install xxd

or

pkg install editors/vim # if necessary

to get the xxd utility.

For unbounded arithmetic Trealla uses a modified fork of the imath library, which is partially included in the source. Note, unbounded integers (aka. bigints) are for arithmetic purposes only and will give a type_error when used in places not expected. The imath library has a bug whereby printing large numbers becomes exponentially slower (100K+ digits) and will require a switch to libtomath at some point to remedy.

WebAssembly (WASI)

Trealla has support for WebAssembly System Interface (WASI).

For an easy build envrionment, set up wasi-sdk. Binaryen is needed for optimization.

To build the WebAssembinary binary, set CC to wasi-sdk’s clang:

make CC=/opt/wasi-sdk/bin/clang wasm

Setting WASI_CC also works as an alternative to CC.

Cross-compile for Windows x64

To cross-compile on Linux and produce a Windows/x86-64 executable…

sudo apt install mingw-w64 make WIN=1

$ file tpl.exe tpl.exe: PE32+ executable (console) x86-64, for MS Windows

Some have reported success with a native Windows build using msys2.

Cross-compile for Linux x86

To cross-compile on Linux and produce a Linux/x86-32 executable…

sudo apt install gcc-multilib sudo apt install libssl-dev:i386 libffi-dev:i386 libreadline-dev:i386 make OPT=-m32

$ file tpl tpl: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=31f643d7a4cfacb0a34e81b7c12c78410493de60, for GNU/Linux 3.2.0, with debug_info, not stripped

Contributions

Contributions are welcome.

Acknowledgements

This project (in current incarnation) started in March 2020 and it would not be where it is today without help from these people:

- [Xin Wang](https://github.com/dram) - [Paulo Moura](https://github.com/pmoura) - [Markus Triska](https://github.com/triska) - [Jos De Roo](https://github.com/josd) - [Ulrich Neumerkel](https://github.com/uwn) - [Guregu](https://github.com/guregu)

Strings

Double-quoted strings, when set_prolog_flag(double_quotes,chars) is set (which is the default) are stored as packed UTF-8 byte arrays. This is compact and efficient. Such strings emulate a list representation and from the programmer point of view are very much indistinguishable from lists.

A good use of such strings is open(filename,read,Str,[mmap(Ls)) which gives a memory-mapped view of a file as a string Ls. List operations on files are now essentially zero-overhead! DCG applications will gain greatly (phrase_from_file/[2-3] uses this).

Both strings and atoms make use of low-overhead reflist-counted byte slices where appropriate.

Non-standard predicates

help/0 help/1 # help(+functor) or help(+PI) help/2 # help(+PI,+atom) where *atom* can be *swi* or *tau* module_help/1 # help(+module) module_help/2 # help(+module,+functor) or help(+module,+PI) module_help/3 # help(+module,+PI,+atom) where *atom* can bw *swi* or *tau* source_info/2 # source_info(+PI, -list) module_info/2 # module_info(+atom, -list) module/1 # module(?atom) modules/1 # modules(-list) listing/0 listing/1 # listing(+PI) abolish/2 # abolish(+pi,+list) pretty/1 # pretty-print version of listing/1 between/3 msort/2 # version of sort/3 with duplicates samsort/2 # same as msort/2 merge/3 format/[1-3] portray_clause/[1-2] predicate_property/2 evaluable_property/2 numbervars/[1,3-4] e/0 name/2 tab/[1,2] get_unbuffered_code/1 # read a single unbuffered code get_unbuffered_char/1 # read a single unbuffered character read_from_atom/2 # read_from_atom(+atom,?term) read_from_chars/2 # read_from_chars(+chars,?term) read_term_from_atom/3 # read_term_from_atom(+atom,?term,+optlist) read_term_from_chars/3 # read_term_from_chars(+chars,?term,+optlist) read_from_chars_/3 # read_from_chars+(?term,+chars,-rest) read_term_from_chars_/4 # read_term_from_chars+(?term,+optlist,+chars,-rest) write_term_to_atom/3 # write_term_to_atom(?atom,?term,+oplist) write_canonical_to_atom/3 # write_canonical_to_atom(?atom,?term,+oplist) term_to_atom/2 # term_to_atom(?atom,?term) setrand/1 # set_seed(+integer) set random number seed srandom/1 # set_seed(+integer) set random number seed set_seed/1 # set_seed(+integer) set random number seed get_seed/1 # get_seed(-integer) get random number seed rand/1 # rand(-integer) integer [0,RAND_MAX] random/1 # random(-float) float [0.0,<1.0] random_between/3 # random_between(+int,+int,-int) integer [arg1,<arg2] random_float/0 # function returning float [0.0,<1.0] random_integer/0 # function returning integer [0,RAND_MAX] rand/0 # function returning integer [0,RAND_MAX] gensym/2 # gensym(+atom,-atom) reset_gensym/1 # reset_gensym(+atom) call_residue_vars/2 expand_term/2 # expand_term(+rule,-Term) sub_string/5 # sub_string(+string,?before,?len,?after,?substring) atomic_concat/3 # atomic_concat(+atom,+list,-list) atomic_list_concat/2 # atomic_list_concat(L,Atom) atomic_list_concat/3 # atomic_list_concat(L,Sep,Atom) write_term_to_chars/3 # write_term_to_chars(?chars,?term,+list) write_canonical_to_chars/3 # write_canonical_to_chars(?chars,?term,+list) chars_base64/3 # currently options are ignored chars_urlenc/3 # currently options are ignored hex_chars/2 # as number_chars, but in hex octal_chars/2 # as number_chars, but in octal partial_string/2 # partial_string(+string,-String) partial_string/3 # partial_string(+string,-String,-Var) if/3, (*->)/2 # soft-cut call_det/2 # call_det(+call,?boolean) term_attvars/2 # term_attvars(+term,-Vs) copy_term_nat/2 # doesn't copy attrs copy_term/3 # copy_term(+term1,-term2,-Goals) unifiable/3 # unifiable(+term1,+term2,-Goals) ?=/2 # ?=(+term1,+term2) term_expansion/2 goal_expansion/2 cyclic_term/1 term_singletons/2 findall/4 sort/4 ignore/1 is_list/1 is_partial_list/1 is_list_or_partial_list/1 is_stream/1 term_hash/2 term_hash/3 # ignores arg2 (options) time/1 inf/0 nan/0 \uXXXX and \UXXXXXXXX # Unicode escapes for JSON) gcd/2 uuid/1 # uuid(-string) load_files/[1,2] split_string/4 # SWI-compatible module/1 line_count/2 atom_number/2 repeat/1 # repeat(+integer) make/0 rdiv/2 # evaluable numerator/1 # evaluable denominator/1 # evaluable rational/1 with_output_to(chars(Cs), Goal) # SWI-compatible with_output_to(string(Cs), Goal) # SWI-compatible with_output_to(atom(Atom), Goal) # SWI-compatible call_with_time_limit/2 # SWI-compatible time_out/3 # SICStus-compatible bb_b_put/2 # bb_b_put(:atom, +term) bb_put/2 # bb_put(:atom, +term) bb_get/2 # bb_get(:atom, ?term) bb_update/3 # bb_update(:atom, ?term, ?term) bb_delete/2 # bb_delete(:atom, ?term) posix_strftime/3 # posix_strftime(+format,-text,+tm(NNN,...)) posix_strptime/3 # posix_strptime(+format,+text,-tm(NNN,...)) posix_mktime/2 # posix_mktime(+tm(NNN,...),-seconds) posix_gmtime/2 # posix_gmtime(+seconds,-tm(NNN,...)) posix_localtime/2 # posix_localtime(+seconds,-tm(NNN,...)) posix_ctime/2 # posix_time(+seconds,-atom) posix_time/1 # posix_time(-seconds) posix_getpid/1 # posix_pid(-pid) posix_getppid/1 # posix_ppid(-pid) posix_fork/1 # posix_fork(-pid) nb_setval(K,V) nb_getval(K,V) nb_delete(K) nb_current(K,V) b_setval(K,V) b_getval(K,V) b_delete(K) call_nth/2 offset/2 limit/2 getenv/2 setenv/2 unsetenv/1 directory_files/2 delete_file/1 exists_file/1 # also file_exists/1 rename_file/2 copy_file/2 time_file/2 size_file/2 exists_directory/1 # also directory_exists/1 make_directory/1 make_directory_path/1 working_directory/2 chdir/1 absolute_file_name/[2,3] # expand(Bool) & relative_to(file) options is_absolute_file_name/1 access_file/2 set_stream/2 # only supports alias/1 property recorda/2-3 recordz/2-3 recorded/2-3 instance/2 erase/1 string_upper/2 string_lower/2 atom_upper/2 atom_lower/2 divmod/4 # SWI-compatible popcount/1 # function returning number of 1 bits lsb/1 # function returning the least significant bit of a positive integer (count from zero) msb/1 # function returning the most significant bit of a positive integer (count from zero) log10/1 # function returning log10 of arg now/0 # function returning C-time in secs as integer now/1 # now (-integer) C-time in secs as integer get_time/1 # get_time(-Var) elapsed wall time in secs as float cpu_time/1 # cpu_time(-Var) elapsed CPU time in secs as float current_key/1 string_length/2 sleep/1 # sleep time in secs split/4 # split(+string,+sep,?left,?right) shell/1 shell/2 wall_time/1 date_time/6 date_time/7 loadfile/2 # loadfile(+filename,-string) savefile/2 # savefile(+filename,+string) getfile/2 # getfile(+filename,-strings) getfile/3 # getfile(+filename,-strings,+opts) getline/1 # getline(-string) getline/2 # getline(+stream,-string) getline/3 # getline(+stream,-string,+opts) getlines/1 # getlines(-strings) getlines/2 # getlines(+stream,-strings) getlines/3 # getlines(+stream,-strings,+opts) read_line_to_codes/2 # removes terminator read_line_to_string/2 # removes terminator read_file_to_string/3 bread/3 # bread(+stream,?len,-string) bwrite/2 # bwrite(+stream,+string) replace/4 # replace(+string,+old,+new,-string) open(stream(Str),...) # with open/4 reopen a stream open(F,M,S,[mmap(Ls)]) # with open/4 mmap() the file to Ls reset/3 # reset(:goal,?ball,-cont) shift/1 # shift(+ball)

Note: consult/1 and load_files/2 support lists of files as args. Also support loading into modules eg. consult(MOD:FILE-SPEC).

Use these POSIX system calls for interprocess creation and communication…

popen/3 # popen(+cmd,+mode,-stream) popen/4 # popen(+cmd,+mode,-stream,+opts)

For example…

tpl -g "use_module(library(apply)),popen('ps -a',read,S,[]),getlines(S,Ls),close(S),maplist(print,Ls),halt" PID TTY TIME CMD 2806 tty2 00:00:00 gnome-session-b 31645 pts/0 00:00:00 tpl 31646 pts/0 00:00:00 sh 31647 pts/0 00:00:00 ps

For general POSIX process creation use these SWI-compatible calls…

process_create/3 # process_create(+cmd,+args,+opts) process_wait/2 # process_wait(+pid,+opts) process_wait/1 # process_wait(+pid) process_kill/2 # process_kill(+pid,+sigint) process_kill/1 # process_kill(+pid)

For example…

?- process_create('ls',['-l'],[process(Pid)]),process_wait(Pid). total 2552 4 -rw-rw-r-- 1 andrew andrew 1813 Aug 25 10:18 ATTRIBUTION 4 -rw-rw-r-- 1 andrew andrew 1093 Aug 25 10:18 LICENSE 8 -rw-rw-r-- 1 andrew andrew 7259 Sep 18 18:27 Makefile 24 -rw-rw-r-- 1 andrew andrew 23709 Sep 19 08:56 README.md 4 -rw-rw-r-- 1 andrew andrew 28 Aug 25 10:18 _config.yml 4 drwxrwxr-x 2 andrew andrew 4096 Sep 17 10:41 docs 4 drwxrwxr-x 2 andrew andrew 4096 Sep 18 21:29 library 4 drwxrwxr-x 2 andrew andrew 4096 Sep 3 13:02 samples 4 drwxrwxr-x 6 andrew andrew 4096 Sep 19 09:38 src 4 drwxrwxr-x 5 andrew andrew 4096 Sep 14 20:49 tests 1448 -rwxrwxr-x 1 andrew andrew 1478712 Sep 19 09:38 tpl 8 -rw-rw-r-- 1 andrew andrew 7671 Aug 25 10:18 tpl.c 16 -rw-rw-r-- 1 andrew andrew 13928 Sep 18 18:28 tpl.o 36 -rw-rw-r-- 1 andrew andrew 33862 Aug 25 10:18 trealla.png Pid = 735602. ?-

Note: read_term/[2,3] supports the positions(Start,End) and the line_counts(Start,End) property options to report file information. This is analogous to stream_property/2 use of position(Pos) and line_count(Line) options.

Note: read_term, write_term & friends support the json(Boolean) option to make more sympathetic support for JSON using the builtin parsing and printing mechanisms.

Definite Clause Grammars

Uses Ulrich Neumerkel’s standard reference library. DCG rules are translated automatically as this library is auto-included.

:- use_module(library(dcgs)).

Crypto functions

Hash a plain-text data string to a hexadecimal byte string representing the cryptographic strength hashed value. The options are algorithm(Name) where Name can be sha256, sha384 or sha512, and optionally hmac(Key) where Key is a list of byte values. This predicate is only available when compiled with OpenSSL…

crypto_data_hash/3 # crypto_data_hash(+data,-hash,+options)

Generate ‘N’ random bytes.

crypto_n_random_bytes(N, Bs) # crypto_n_random_bytes(+integer, -codes)

Convert a hexadecimal string to a byte-list. At least one arg must be instantiated…

hex_bytes/2 # hex_bytes(?hash,?bytes)

Parsing CSV with builtins

Fast, efficient parsing of CSV files…

parse_csv_line/2 # parse_csv_line(+atom,-list) parse_csv_line/3 # parse_csv_line(+atom,-compound,+options) parse_csv_file/2 # parse_csv_file(+filename,+options)

Where options can be:

trim(Boolean) # default false, trims leading and trailing whitespace numbers(Boolean) # default false, converts integers and floats header(Boolean) # default false, skip first (header) line in file comments(Boolean) # default false, skip lines beginning with comment character in file comment(Char) # default '#', set the comment character strings(Boolean) # default depends on type of input (atom or string) arity(Integer) # default to not checking arity, otherwise throw domain_error assert(Boolean) # default false, assertz to database instead (assumed for files, needs a functor) functor(Atom) # default output is a list, create a structure (mandatory for files and with assert) quote(Char) # default to double-quote sep(Char) # default to comma for .csv or unknown files & TAB for .tsv files

Examples…

?- parse_csv_line('123,2.345,3456789',T). T = ['123','2.345','3456789']. ?- parse_csv_line("123,2.345,3456789",T). T = ["123","2.345","3456789"]. ?- parse_csv_line('123,2.345,3456789',T,[functor(f)]). T = f('123','2.345','3456789'). ?- parse_csv_line('123,2.345,3456789',T,[functor(f),numbers(true)]). T = f(123,2.345,3456789). ?- parse_csv_line('abc, abc, a b c ',T). T = [abc,' abc',' a b c ']. ?- parse_csv_line('abc, abc, a b c ',T,[trim(true)]). T = [abc,abc,'a b c']. ?- parse_csv_line('123,2.345,3456789',T,[functor(f),numbers(true),assert(true)]). true. ?- f(A,B,C). A = 123, B = 2.345, C = 3456789. ?- time(parse_csv_file('../logtalk3/library/csv/test_files/tickers.csv',[functor(f),quote('\'')])). % Parsed 35193 lines % Time elapsed 0.096s, 3 Inferences, 0.000 MLips) true. ?- f(A,B,C,D,E,F). A = '1125:HK', B = 'OTCGREY', C = 'Stock', D = 'USD', E = '1999-06-22', F = '2019-10-22' ; A = '6317:TK' , B = 'PINK' , C = 'Stock' , D = 'USD' , E = '2018-06-27' , F = '2020-03-02' ; A = 'A' , B = 'NYSE' , C = 'Stock' , D = 'USD' , E = '1999-11-18' , F = '2021-06-25' ; A = 'AA' , B = 'NYSE' , C = 'Stock' , D = 'USD' , E = '2016-11-01' , F = '2021-06-25' ; A = 'AA-W' , B = 'NYSE' , C = 'Stock' , D = 'USD' , E = '2016-10-18' , F = '2016-11-08' ; A = 'AAA' , B = 'NYSEARCA' , C = 'ETF' , D = 'USD' , E = '2020-09-09' , F = '2021-06-25' ;

Application maps (dictionaries)

Maps use atomic key/value pairs only and are represented as pseudo-streams:

map_create/2 # map_create(-skiplist,+opts) map_create/1 # map_create(-skiplist) map_set/3 # map_set(+skiplist,+key,+value) map_get/3 # map_get(+skiplist,+key,?value) map_del/2 # map_del(+skiplist,+key) map_count/2 # map_count(+skiplist,-count) map_list/2 # map_list(+skiplist,?list) map_close/1 # map_close(+skiplist)

$ tpl ?- map_create(S,[alias(foo)]). S = <$ stream> ( 4 ) . ?- map_set(foo,1,111), map_set(foo,two,222), map_set(foo,3,333). true. ?- map_get(foo,3,V). V = 333. ?- map_del(foo,3). true. ?- map_list(foo,L). L = [1=111,two=222]. ?- map_close(foo). true.

Maps can store virtually unlimited amounts of volatile data in an efficient indexed manner.

Maps don’t require syntactic extensions to Prolog as found in other non-standard systems.

A possible future extension would be to load a CSV file directly in a very efficient manner.

HTTP 1.1

:- use_module(library(http)). http_get/3 # http_get(Url, Data, Opts) http_post/4 # http_post(Url, Data, Opts) http_patch/4 # http_patch(Url, Data, Opts) http_put/4 # http_put(Url, Data, Opts) http_delete/3 # http_delete(Url, Data, Opts) http_server/2 # http_server(Goal,Opts), http_request/5 # http_request(S, Method, Path, Ver, Hdrs)

A server Goal takes a single arg, the connection stream.

Networking ##EXPERIMENTAL##

These two are bidirectional…

http_location/2 # http_location(?list,?url) parse_url/2 # parse_url(?url,?list)

$ tpl ?- parse_url('http://www.xyz.org:81/hello?msg=Hello+World%21&foo=bar# xyz ',P). P = [search([msg='Hello World!',foo=bar]),protocol(http),host('www.xyz.org'),port(81),path('/hello'),fragment(xyz)]. ?- parse_url(U,[search([msg='Hello World!',foo=bar]),protocol(http),host('www.xyz.org'),port(81),path('/hello'),fragment(xyz)]). U = 'http://www.xyz.org:81/hello?msg=Hello+World%21&foo=bar# xyz '. ?-

server/2 # server(+host,-stream) server/3 # server(+host,-stream,+list) accept/2 # accept(+stream,-stream) client/2 # client(+url,-stream) client/4 # client(+url,-host,-path,-stream) client/5 # client(+url,-host,-path,-stream,+list)

The options list can include udp(bool) (default is false), nodelay(bool) (default is true), ssl(bool) (default is false) and certfile(filespec).

The additional server options can include keyfile(filespec) and certfile(filespec). If just one concatenated file is supplied, use keyfile(filespec) only.

The optional schemes ‘unix://’, ‘http://’ (the default) and ‘https://’ can be provided in the client URL.

With bread/3 the ‘len’ arg can be an integer > 0 meaning return that many bytes, = 0 meaning return what is there (if non-blocking) or a var meaning return all bytes until end end of file,

Simple regular expressions

This is meant as a place-holder until a proper regex package is included.

sre_compile/2 # sre_compile(+pattern,-reg) sre_matchp/4 # sre_matchp(+reg,+text,-match,-rest) sre_substp/4 # sre_substp(+reg,+text,-prefix,-rest) sre_match/4 # sre_match(+pattern,+text,-match,-rest) sre_match_all/3 # sre_matchall(+pattern,+text,-list) sre_match_all_pos/3 # sre_matchall_pos(+pattern,+text,-pairs) sre_match_all_in_file/3 # sre_matchall_in_file(+pattern,+filename,-list) sre_match_all_pos_in_file/3 # sre_matchall_pos_in_file(+pattern,+filename,-pairs) sre_subst/4 # sre_subst(+pattern,+text,-prefix,-rest) sre_subst_all/4 # sre_subst(+pattern,+text,+subst,-text) sre_subst_all_in_file/4 # sre_subst_in_file(+pattern,+filename,+subst,-text)

* Supports: * --------- * '.' Dot, matches any character * '^' Start anchor, matches beginning of string * '$' End anchor, matches end of string * '*' Asterisk, match zero or more (greedy) * '+' Plus, match one or more (greedy) * '?' Question, match zero or one (non-greedy) * '[abc]' Character class, match if one of {'a', 'b', 'c'} * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z } * '\s' Whitespace, \t \f \r

\v and spaces * '\S' Non-whitespace * '\w' Alphanumeric, [a-zA-Z0-9_] * '\W' Non-alphanumeric * '\d' Digits, [0-9] * '\D' Non-digits

For example…

?- sre_compile("d.f", Reg), sre_matchp(Reg, "abcdefghi", M, Rest). Reg = <$ blob> ( 0x6AC5AAF0 ) , M = "def" , Rest = "ghi" . ?- sre_match("d.f", "abcdefghi", M, Rest). M = "def", Rest = "ghi". ?- sre_match_all("d.f", "xdafydbfzdcf-", L). L = ["daf","dbf","dcf"]. ?- sre_match_all_pos("d.f", "xdafydbfzdcf-", L). L = [1-3,2-3,3-3]. ?- sre_match_all("d[^c]f", "xdafydbfzdcfxddf-", L). L = ["daf","dbf","ddf"]. ?- sre_subst("d.f", "xdafydbfzdcf-", P, L). P = "x", L = "ydbfzdcf-". ?- sre_subst_all("d.f", "xdafydbfzdcf-", "$ ", L). L = "x$ y $z$- " . ?- sre_match_all("\\S", "Needle In A Haystack", L). L = ["N","e","e","d","l","e","I","n","A",...]. ?- sre_match_all_pos("\\s", "Needle In A Haystack", L). L = [6-1,9-1,11-1]. ?- time(sre_match_all_in_file("t\\We",'thesaurus.txt',L)), length(L,Len), format("Occurrs: ~w times~n",[Len]), halt. Time elapsed 0.0463s Occurrs: 749 times

Note: if no match is found the returned match, text (and list) is [] indicating an empty string.

Note: if the input text arg is a string then the output text arg is a no-copy slice of the string. So if the input is a memory-mapped file then regex searches can be performed quickly and efficiently over huge files.

Foreign Function Interface (libffi)

Allows the loading of dynamic libraries and calling of foreign functions written in C from within Prolog…

'$dlopen'/3 # '$dlopen(+name, +flag, -handle)

These predicates register a foreign function as a builtin and use a wrapper to validate arg types at call/runtime…

'$register_function'/4 # '$ffi_reg'(+handle,+symbol,+types,+ret_type) '$register_predicate'/4 # '$ffi_reg'(+handle,+symbol,+types,+ret_type)

The allowed types are sint8, sint16, sint32, sint64, sint (native signed int), uint8, uint16, uint32, uint64, uint (native unsigned int), ushort, sshort, float, double, bool, (use integer 0/1 to align with C bool pseudo-type) void (a return type only), cstr (a char pointer), and ptr (for arbitrary pointers/handles).

Assuming the following C-code in samples/foo.c:

double foo ( double x , int64_t y ) { return pow ( x , ( double ) y ); } int bar ( double x , int64_t y , double * result ) { * result = pow ( x , ( double ) y ); return 0 ; } char * baz ( const char * x , const char * y ) { char * s = malloc ( strlen ( x ) + strlen ( y ) + 1 ); strcpy ( s , x ); strcat ( s , y ); return s ; }

$ gcc -fPIC -c foo.c $ gcc -shared -o libfoo.so foo.o

Register a builtin function…

?- '$ dlopen '(' samples/libfoo.so ', 0, H), '$ register_function' ( H, foo, [ double, sint64], double ) . H = 94051868794416. ?- R is foo(2.0, 3). R = 8.0. ?- R is foo(abc,3). error(type_error(float,abc),foo/2).

Register a builtin predicate…

?- '$ dlopen '(' samples/libfoo.so ', 0, H), '$ register_predicate' ( H, bar, [ double, sint64, -double ] , sint64 ) , '$ register_predicate '(H, baz, [cstr, cstr], cstr), H = 94051868794416. ?- bar(2.0, 3, X, Return). X = 8.0, Return = 0. ?- baz('abc', '123', Return). Return = abc123.

Note: the foreign function return value is passed as an extra argument to the predicate call, unless it was specified to be of type void.

Foreign Module Interface (libffi)

This is a simplified interface to FFIs inspired by Adrián Arroyo Calle and largely supercedes the implementation given above.

foreign_struct(+atom, +list) use_foreign_module(+atom, +list)

For example…

:- use_foreign_module ( 'samples/libfoo.so' , [ bar ([ double , sint64 , - double ], sint64 ), baz ([ cstr , cstr ], cstr ) ]).

See the library/raylib.pl and samples/test_raylib.pl for an example usage including passing and returning structs by value.

See the library/curl.pl and samples/test_curl.pl for an example usage downloading a file.

This is an example using SQLITE. Given the code in samples/sqlite3.pl…

:- use_module ( library ( sqlite3 )). run :- test ( 'samples/sqlite3.db' , 'SELECT * FROM company' ). test ( Database , Query ) :- sqlite_flag ( 'SQLITE_OK' , SQLITE_OK ), sqlite3_open ( Database , Connection , Ret ), Ret =:= SQLITE_OK , bagof ( Row , sqlite3_query ( Connection , Query , Row , _ ), Results ), writeq ( Results ), nl .

Run…

$ tpl -g run,halt samples/sqlite3.pl [[1,'Paul',32,'California',20000.0],[2,'Allen',25,'Texas',15000.0],[3,'Teddy',23,'Norway',20000.0],[4,'Mark',25,'Rich-Mond ',65000.0],[5,'David',27,'Texas',85000.0],[6,'Kim',22,'South-Hall',45000.0]]

Concurrent Tasks ##EXPERIMENTAL##

Co-operative multi-tasking is available in the form of light-weight coroutines that run until they yield either explicitly or implicitly (when waiting on an event of some kind). They are called a task here.

call_task/[1-n] # concurrent form of call/1-n tasklist/[2-8] # concurrent form of maplist/1-n

An example:

:- use_module ( library ( http )). geturl ( Url ) :- http_get ( Url , _Data ,[ status_code ( Code ), final_url ( Location )]), format ( "Job [~w] ~w ==> ~w done~n" ,[ Url , Code , Location ]). % Fetch each URL in list sequentially... test54 :- L = [ 'www.google.com' , 'www.bing.com' , 'www.duckduckgo.com' ], maplist ( geturl , L ), writeln ( 'Finished' ). $ tpl samples / test - g "time(test54),halt" Job [ www . google . com ] 200 ==> www . google . com done Job [ www . bing . com ] 200 ==> www . bing . com done Job [ www . duckduckgo . com ] 200 ==> https :// duckduckgo . com done Finished Time elapsed 0.663 secs % Fetch each URL in list concurrently... test56 :- L = [ 'www.google.com' , 'www.bing.com' , 'www.duckduckgo.com' ], tasklist ( geturl , L ), writeln ( 'Finished' ). $ tpl samples / test - g "time(test56),halt" Job [ www . duckduckgo . com ] 200 ==> https :// duckduckgo . com done Job [ www . bing . com ] 200 ==> www . bing . com done Job [ www . google . com ] 200 ==> www . google . com done Finished Time elapsed 0.33 secs

Linda Co-ordination Language ##EXPERIMENTAL##

Implements a toy (local-only) version of Linda using tasks. See: swi-prolog.

linda_eval/1 # linda_eval(:goal) out/1 # out(+tuple) in/1 # in(?tuple) rd/1 # rd(?tuple) in_noblock/1 # in_noblock(?tuple) rd_noblock/1 # rd_noblock(?tuple) bagof_in_noblock/3 # bagof_in_noblock(+term,+tuple,?list) bagof_rd_noblock/3 # bagof_rd_noblock(+term,+tuple,?list) wait/0 end_wait/0

For example:

:- use_module ( library ( linda )). :- initialization ( main ). main :- linda_eval ( consumer ( 'A' )), linda_eval ( consumer ( 'B' )), linda_eval ( producer ), wait , in ( producer ), % verify it finished normally writeq ( done ), nl , halt . producer :- between ( 1 , 10 , I ), out ({ msg : I }), sleep ( 0.25 ), fail . producer :- forall ( rd_noblock ({ msg : _ }), sleep ( 0.001 )), end_wait . consumer ( N ) :- in ({ msg : I }), write ([ 'consumer' , N , 'got=' , I ]), nl , random ( R ), sleep ( R ), fail .

$ tpl samples/test_linda.pl [consumer,B,got=,1] [consumer,B,got=,2] [consumer,B,got=,3] [consumer,A,got=,4] [consumer,B,got=,5] [consumer,A,got=,6] [consumer,B,got=,7] [consumer,A,got=,8] [consumer,A,got=,9] [consumer,B,got=,10] done

Concurrent Futures ##EXPERIMENTAL##

Inspired by Tau-Prolog concurrent futures. Uses co-operative tasks.

future/3 – Make a Future from a Prolog goal. future_all/2 – Make a Future that resolves to a list of the results of an input list of futures. future_any/2 – Make a Future that resolves as soon as any of the futures in a list succeeds. future_cancel/1 – Cancel unfinished future. future_done/1 – Check if a future finished. await/2 – Wait for a Future.

For example:

:- use_module ( library ( concurrent )). :- use_module ( library ( http )). test :- future ( Status1 , geturl ( "www.google.com" , Status1 ), F1 ), future ( Status2 , geturl ( "www.bing.com" , Status2 ), F2 ), future ( Status3 , geturl ( "www.duckduckgo.com" , Status3 ), F3 ), future_all ([ F1 , F2 , F3 ], F ), await ( F , StatusCodes ), C = StatusCodes .

See samples/test_concurrent.pl .

Engines ##EXPERIMENTAL##

Inspired by SWI-Prolog engines. Uses co-operative tasks.

engine_create/[3,4] engine_next/2 engine_yield/1 engine_post/[2,3] engine_fetch/1 engine_self/1 is_engine/1 current_engine/1 engine_destroy/1

Pre-emptive Multi-threading

Start independent (shared state) Prolog queries as dedicated POSIX threads and communicate via message queues. Note: the database is shared. These predicates conform to the ISO Prolog multi-threading support standards proposal (ISO/IEC DTR 13211–5:2007), now lapsed.

thread_create/3 # thread_create(:callable,-thread,+options) thread_create/2 # thread_create(:callable,-thread) thread_signal/2 # thread_signal(+thread,:callable) thread_join/2 # thread_join(+thread,-term) thread_cancel/1 # thread_cancel(+thread) thread_detach/1 # thread_detach(+thread) thread_self/1 # thread_self(-thread) thread_exit/1 # thread_exit(+term) thread_sleep/1 # thread_sleep(+integer) thread_yield/0 # thread_yield thread_property/2 # thread_property(+thread,+term) thread_property/1 # thread_property(+term)

Where ‘options’ can be alias(+atom), at_exit(:term) and/or detached(+boolean) (the default is NOT detached, ie. joinable).

Create a stand-alone message queue…

message_queue_create/2 # message_queue_create(-queue,+options) message_queue_create/1 # message_queue_create(-queue) message_queue_destroy/1 # message_queue_destroy(+queue) message_queue_property/2 # message_queue_property(+queue,+term) thread_send_message/2 # thread_send_message(+queue,+term) thread_send_message/1 # thread_send_message(+term) thread_get_message/2 # thread_get_message(+queue,?term) thread_get_message/1 # thread_get_message(?term) thread_peek_message/2 # thread_peek_message(+queue,?term) thread_peek_message/1 # thread_peek_message(?term)

Where ‘options’ can be alias(+atom).

Create a stand-alone mutex…

mutex_create/2 # mutex_create(-mutex,+options) mutex_create/1 # mutex_create(-mutex) mutex_destroy/1 # mutex_destroy(+mutex) mutex_property/2 # mutex_property(+mutex,+term) with_mutex/2 # with_mutex(+mutex,:callable) mutex_trylock/1 # mutex_trylock(+mutex) mutex_lock/1 # mutex_lock(+mutex) mutex_unlock/1 # mutex_unlock(+mutex) mutex_unlock_all/0 # mutex_unlock_all

Where ‘options’ can be alias(+atom). Use of mutexes other than with_mutex/2 should generally be avoided.

For example…

```console ?- thread_create((format("thread_hello~n",[]),sleep(1),format("thread_done~n",[]),thread_exit(99)), Tid, []), format("joining~n",[]), thread_join(Tid,Status), format("join_done~n",[]). joining thread_hello thread_done join_done Tid = 1, Status = exited(99). ?- ```

Prolog instances ##EXPERIMENTAL##

Start independent (no shared state) Prolog instances as dedicated pre-emptive threads and communicate via message queues. Each thread has it’s own message queue associated with it. Note: the database is not shared. For shared state consider using the blackboard.

pl_thread/3 # pl_thread(-thread,+filename,+options) pl_thread/2 # pl_thread(-thread,+filename)

Where ‘options’ can be (currently just) alias(+atom).

pl_msg_send/2 # pl_msg_send(+thread,+term) pl_msg_recv/2 # pl_msg_recv(-thread,-term)

For example…

$ cat samples/thread_calc.pl :- initialization(main). % At the moment we only do sqrt here... main :- write('Calculator running...'), nl, repeat, pl_msg_recv(Tid, Term), Term = sqrt(X, Y), Y is sqrt(X), pl_msg_send(Tid, Term), fail. $ tpl ?- pl_thread(_, 'samples/thread_calc.pl', [alias(calc)]). Calculator running... ?- Term = sqrt(2, V), pl_msg_send(calc, Term), pl_msg_recv(_, Term). Term = sqrt(2,1.4142135623731), V = 1.4142135623731. ?-

Profile

Why did I put this here?