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, BSD, 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 code Access SQLITE databases using builtin module (uses FFI) Concurrency via tasks / linda / futures / engines (generators) Pre-emptive multi-threading Blackboarding primitives ... FFIs for GNU Scientific Library (GSL), SQLite, Raylib Delimited continuations ##EXPERIMENTAL## Rational trees ##EXPERIMENTAL## CLP(Z) Available from: [https://github.com/trealla-prolog/trealla](https://github.com/trealla-prolog/trealla). Runs with [Jupyter Notebooks](https://github.com/LogtalkDotOrg/logtalk-jupyter-kernel#readme). Logo ==== ![Trealla Logo: Trealla](trealla.png) 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 --autofail - autofail queries at the toplevel --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](https://www.utf8everywhere.org/)], or moving to [[2](https://en.wikipedia.org/wiki/Unicode_in_Microsoft_Windows#UTF-8)], 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... ```console ?- [user]. 是. % be: means, approximately, "True". 不是 :- \+ 是. % not be: means, approximately, "False". true. ?- 是. true. ?- 不是. false. ``` ```console ?- X = 国字. X = 国字. ?- ``` Trealla accepts as a var any atom beginning with an uppercase character... ```console ?- 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-like systems, you will need to install (if not alread( the following packages to set up a build environment: sudo apt install build-essential git libreadline-dev libffi-dev libssl-dev Then... make To build without FFI: make NOFFI=1 To build without SSL: make NOSSL=1 To build without pre-emptive multi-threading support: make NOTHREADS=1 To build (as a last resort) with the included ISOCLINE sources (default is to use GNU Readline, except Windows): make ISOCLINE=1 Older compilers may require: make NOPEDANTIC=1 to avoid issues with newer flags. Finally... make install to install locally. Optionally... make test and there should be no errors, Further (if valgrind is installed)... make check # or, more extensively make leaks Should show no memory out-of-bounds, null-pointer, use after free or memory leaks (there may a few spurious errors). On macOS: brew install libffi openssl coreutils readline and it's important to use the `brew` version of `readline`. Unbounded integers (Bigints) and Rationals ========================================== For unbounded arithmetic Trealla uses a modified fork of the [imath](https://github.com/infradig/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). WebAssembly (WASI) ================== Trealla has support for WebAssembly System Interface (WASI). For an easy build envrionment, set up [wasi-sdk](https://github.com/WebAssembly/wasi-sdk). [Binaryen](https://github.com/WebAssembly/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 NOFFI=1 NOSSL=1 ```console $ 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 ```console $ 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) load_text/2 # load_text(+atom,+opts) 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,)/2 # soft-cut call_det/2 # call_det(+call,?boolean) copy_term_nat/2 # doesn't copy attrs (same as copy_term/2) 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 cfor/3 # cfor(+evaluable,+evaluable,-var) repeat/1 # repeat(+integer) make/0 argv/1 # argv(-list) raw_argv/1 # raw_argv(-list) rdiv/2 # evaluable numerator/1 # evaluable denominator/1 # evaluable rational/1 rationalize/2 # rationalize(+number,-rational) 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 posix_strftime/3 # posix_strftime(+format,-string,+tm(NNN,...)) posix_strptime/3 # posix_strptime(+format,+string,-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) call_nth/2 offset/2 limit/2 getenv/2 setenv/2 unsetenv/1 directory_files/2 delete_file/1 exists_file/1 rename_file/2 copy_file/2 time_file/2 size_file/2 exists_directory/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 alias/2 # alias(?integer,+atom) 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 Unix epoch in whole secs now/1 # now(-integer) Unix epoch in whole secs get_time/1 # get_time(-float) Unix epoch in secs wall_time/1 # wall_time(-float) elapsed clock time in secs cpu_time/1 # cpu_time(-float) elapsed CPU time in secs current_key/1 string_concat/3 # string_concat(+string,+string,?string) string_length/2 sleep/1 # sleep time in secs split/4 # split(+string,+sep,?left,?right) shell/1 shell/2 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 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 # parser_reset(:goal,?ball,-cont) shift/1 # shift(+ball) term_variables/3 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... ```console tpl -g "popen('ps -a',read,S,[]),getlines(S,Ls),close(S),maplist(println,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... ```console ?- 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. :- use_module(library(dcgs)). Blackboard functions ==================== 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) 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. Reading: 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 Writing: write_csv_file/3 # write_csv_file(+filename,+list,+options) Where options can be: append(Boolean) # default is to truncate file, or append to file strings(Boolean) # default depends on type of input (atom or string) sep(Char) # default to comma for .csv or unknown files & TAB for .tsv files Examples... ```console ? L=[["1 1",12,'1 3'],[],['21','','23']], write_csv_file('x.csv',L,[]). $ cat x.csv "1 1",12,1 3 21,,23 ?- Row=["1 1",12,'1 3'], L=[Row], write_csv_file('x.csv',L,[]). $ cat x.csv "1 1",12,1 3 ?- 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) ```console $ 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) ```console ?- http_get("https://github.com/trealla-prolog/trealla", Data, [status_code(Code)]). Data = "\n\n\n\n\n\n\n 0 meaning return that many bytes, = 0 meaning return whatever 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 \n \v and spaces * '\S' Non-whitespace * '\w' Alphanumeric, [a-zA-Z0-9_] * '\W' Non-alphanumeric * '\d' Digits, [0-9] * '\D' Non-digits ``` For example... ```console ?- 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*: ```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; } ``` ```console $ gcc -fPIC -c foo.c $ gcc -shared -o libfoo.so foo.o ``` Register a builtin function... ```console ?- '$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... ```console ?- '$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... ```prolog :- 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*... ```prolog :- 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... ```console $ 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]] ``` ISO Prolog Multithreading ========================= 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 multithreading 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... ```console $ 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. ?- ``` Concurrent Tasks ##EXPERIMENTAL## ================ Co-operative multitasking 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: ```prolog :-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), write('Finished\n'). $ 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), write('Finished\n'). $ 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](https://www.swi-prolog.org/pldoc/man?section=tipc-linda-clients). linda_eval/1 # linda_eval(:goal) out/1 # out(+tuple) in/1 # in(?tuple) rd/1 # rd(?tuple) inp/1 # inp(?tuple) rdp/1 # rdp(?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: ```prolog :- 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(rdp({msg:_}), sleep(0.001)), end_wait. consumer(N) :- in({msg:I}), write(['consumer',N,'got=',I]), nl, random(R), sleep(R), fail. ``` ```console $ 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](http://tau-prolog.org/documentation#concurrent) 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: ```prolog :- 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](https://www.swi-prolog.org/pldoc/man?section=engine-predicates) 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 Compile to standalone ##EXPERIMENTAL## ===================== ``` ✗ cat samples/main.pl ``` ```prolog :- initialization(main). main :- writeln('Hello, world!'), halt. ``` ``` ✗ make compile main=samples/main.pl ✗ ./tpl Hello, world! ``` Profile ======= Why did I put this here? ```console $ time tpl -q -g 'main,statistics(profile,_),halt' -f ~/trealla/samples/out.pl 2>out.csv $ head -1 out.csv >out_sorted.csv && tail -n+2 out.csv | sort -k 3 -t ',' -n -r >> out_sorted.csv $ cat out_sorted.csv #functor/arity,match_attempts,matched,tcos 'member_/3',20505037,20036023,19362515 'can_step/5',1149136,288705,189915 'can_move/5',164848,98905,32873 'strength/4',1074794,63382,31691 'minus_one/2',1621942,1621942,0 'make_move/6',1086892,1086892,0 'member_/3',20709531,730369,0 'member/2',673508,673508,0 'occupied_by/4',673316,673316,0 ... ```