Skip to content

A compact, efficient Prolog interpreter written in plain-old C.

License

Notifications You must be signed in to change notification settings

trealla-prolog/trealla

Repository files navigation

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 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) ##EXPERIMENTAL##

Available from: https://github.com/trealla-prolog/trealla.

Runs with Jupyter Notebooks.

Logo

Trealla Logo: Trealla

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.

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 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)

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,<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
alias/2						# alias(?integer,+atom)

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_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
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)

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...

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)
	?- http_get("https://github.com/trealla-prolog/trealla", Data, [status_code(Code)]).
	   Data = "\n\n\n\n\n\n<!DOCTYPE html>\n<html\n...", Code = 200.

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).

Additional server options can include keyfile(filespec). If just one concatenated file (keyfile+certfiles) is supplied, use keyfile(filespec) only.

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 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...

	?- 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?

	$ 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
	...