Git Product home page Git Product logo

trealla's Introduction

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

Available from: trealla-prolog.org.

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.

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 \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/chess.pl 2>chess.csv
	$ head -1 chess.csv >chess_sorted.csv && tail -n+2 chess.csv | sort -k 3 -t ',' -n -r >> chess_sorted.csv
	$ cat chess_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
	...

trealla's People

Contributors

guregu avatar infradig avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

trealla's Issues

Dropping cyclic-terms, consideration

I am considering dropping support in Trealla for working with cyclic-terms/rational-trees. Trealla would just detect cycles and error, like GNU Prolog does.

Can't read from sockets

I'm having some trouble getting the HTTP server example to run:

tpl samples/http_server.pl -g main

It seems to accept connections fine, but getline/3 repeatedly fails with EAGAIN, leaving the client hanging forever. I get this behavior with threads enabled and disabled.

Not sure if this is actually a bug or an issue with my environment. Could someone else give this a try? I'm on Ubuntu 20.04 under WSL.

Reset history for version 2

Time for a clean slate after two years of code-cruft. A gigabyte of git commit history was just ridiculous.

Segmentation fault (core dumped)

v2.3.11

?- [user].
add3(A,B,C,D) :- D is A+B+C.

?- add3(1,2,3,X).
   X = 6.

?- add3(1+(2;3),4,5,X).
Segmentation fault (core dumped)

findall/3 perf test

$ cat isl.pl

isl({L},N) :- L = (_ is _+N).

test1 :- time(findall(Z,(between(1,5,N), isl({X}, N),
                         X = (Y is B+_), findall(Y,(between(1,3,B),X),Z)),
	              M)), write(M).

test2 :- time(findall(Z,(between(1,5_000_000,N), isl({X}, N),
                         X = (Y is B+_), findall(Y,(between(1,3,B),X),Z)),
	              M)), length(M,L), L1 is L*3, write(L1), write(' Elements processed').
$ tpl

?- "isl".
   true.

?- nl,test1,nl,nl,test2.

Time elapsed 9.2e-05s
[[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8]]   true.

Time elapsed 26.9s
15000000 Elements processed   true.
?-

Is this adequate? I've no idea.

CLP(Z) - all_distinct/1

Really just a progress note to self here:

~/trealla (devel) $ tpl ~/scryer-prolog/src/lib/clpz.pl
?- all_different([A,B]).
   clpz:all_different([A,B]),clpz:all_different([A,B]).
?- all_different([A,B]), A=1, B=1.
   false.

Ok, that's good.

?- all_distinct([A,B]).
   true.

Oops, not good, where are the residuals?

?- all_distinct([A,B]), A=1, B=1.
   A = 1, B = A.

They are really not there it seems. But freezing them first then they now appear...

?- freeze(A,true),freeze(B,true),all_distinct([A,B]).
   clpz:all_distinct([A,B]),freeze:freeze(A,true),clpz:all_distinct([A,B]),freeze:freeze(B,true).
?- freeze(A,true),freeze(B,true),all_distinct([A,B]),A=1,B=1.
   false.
?- freeze(A,true),freeze(B,true),all_distinct([A,B]),A=1,B=2.
   A = 1, B = 2.

Yes, that works. Very odd!

findall/3 builtin serious accident

I'm sorry, tried but did not find the issue in a different way.

v2.0.1-2-g8cbe

#
# Simple Fraud Detection 
#
# Short Docu:
#
# Outside there exist a sqlite3 database containing 4 tables
#
# 3 Values(columns) are recorded: Name, Months(1,2,3), Score.
# Entries allowed: One Score Value per Person and Month.
# Table 'data' contains manipulated figures(impermissible entries)

# SQL:
# Strictly prohibited: Conventional SQL
# Only Advanced Single Statement Solutions are allowed.
# Reduces to efficient one pass table scan.
# No transaction control(Commit/Rollback logic) needed.
# The (advanced) SQL-Programmer has to be an advanced C-Programmer, too.
# In some circumstances the single statement of a business processing
# solution demands enhancement of sqlite3 (to write in C).

# The example uses sql to transform tables back and forth
# table data -> pivoting -> table result
# table result -> folding -> table atad
#
# Folding is the inverse of pivoting and
# table 'atad' should have the same content as table 'data'
#
# PROLOG:
# As Trealla does collect 'select * from ..' into a list of lists
# 'atad' and 'data' are checked:
# A member of atad must be a member of 'data'.
# If not, something is suspect.
#
#
# Save this all as a Shell-Script(copy/paste) test.sh
# Make sure 'tpl' is invocable.
# Run: sh test.sh

rm -f test.db test.db-wal test.db-shm test.pl


sqlite3 test.db <<-"EOFDB"
create table data(name varchar(255), month int, score int);
insert into data(name,month,score) values('Jack', 1, 5500);
insert into data(name,month,score) values('Jones', 1, 5000);
insert into data(name,month,score) values('Jack', 2, 6500);
insert into data(name,month,score) values('Jones', 2, 4500);
insert into data(name,month,score) values('Jack', 3, 6000);
insert into data(name,month,score) values('Jones', 3, 4750);

/* These are valid but impermissible entries */
insert into data(name,month,score) values('Jack', 1, -500);
insert into data(name,month,score) values('Jones', 1, 500);
insert into data(name,month,score) values('Jack', 2, -2000);
insert into data(name,month,score) values('Jones', 2, 2000);
insert into data(name,month,score) values('Jack', 3, -1250);
insert into data(name,month,score) values('Jones', 3, 1250);

create table months(month int);
insert into months(month) values(1);
insert into months(month) values(2);
insert into months(month) values(3);

/* Used for pivoting */
create table result(name varchar(255), jan int, feb int, mar int);

/* Used for folding, with helper table months */
create table atad(name varchar(255), month int, score int);

EOFDB

cat <<-"EOFPL" > test.pl
:- use_module(library(sqlite3)).

run :-
        test('./test.db').

test(Database) :-
        flag('SQLITE_OK', SQLITE_OK),
        sqlite3_open(Database, Connection, Ret_open),
        Ret_open =:= SQLITE_OK,

        QueryData = 'select * from data',
        findall(Row1, sqlite3_query(Connection, QueryData, Row1, _), Data),
        nl, write('Data:   '), writeq(Data), nl,

        PrepResult = 'delete from result;vacuum',
        sqlite3_exec(Connection,PrepResult, 0, 0, _, Ret_prep),
        Ret_prep =:= SQLITE_OK,

        GenResult = '\
insert into result(name,jan,feb,mar) \
select \
name,  \
SUM(score*(1-abs(sign(month-1)))), \
SUM(score*(1-abs(sign(month-2)))), \
SUM(score*(1-abs(sign(month-3))))  \
from data group by name',

        sqlite3_exec(Connection,GenResult, 0, 0, _, Ret_gen),
        Ret_gen =:= SQLITE_OK,
        QueryResult = 'select * from result',
        findall(Row2, sqlite3_query(Connection, QueryResult, Row2, _), Result),
        write('Result: '), writeq(Result), nl,

        QueryMonths = 'select * from months',
        findall(Row3, sqlite3_query(Connection, QueryMonths, Row3, _), Months),
        write('Months: '), writeq(Months), nl,

        PrepAtad = 'delete from atad;vacuum',
        sqlite3_exec(Connection,PrepAtad, 0, 0, _, Ret_atad),
        Ret_atad =:= SQLITE_OK,
        
	GenAtad = '\
insert into atad(name,month,score) \
select \
result.name, \
1*(1-abs(sign(months.month-1)))+ \
2*(1-abs(sign(months.month-2)))+ \
3*(1-abs(sign(months.month-3))),\
result.jan*(1-abs(sign(months.month-1)))+ \
result.feb*(1-abs(sign(months.month-2)))+ \
result.mar*(1-abs(sign(months.month-3))) \
from result cross join months',

        sqlite3_exec(Connection,GenAtad, 0, 0, _, Ret_genatad),
        Ret_genatad =:= SQLITE_OK,

        QueryAtad = 'select * from atad',
        findall(Row4, sqlite3_query(Connection, QueryAtad, Row4, _), Atad),
        write('Atad:   '), writeq(Atad), nl, nl,

        !, /* SQL done*/

	member(X,Atad), \+member(X,Data),

	[A,B,_] = X, /* get Name,Month and findall Fraud in Data (Var C) */

	findall([C,A,B],member([A,B,C],Data),Fraud),

        /* These findall(s) show a kind of Information-Hopping, Content-Jumping */
        /* Please edit findall above, just change the first parameter and run again (sh test.sh)
	/*findall([C,B,_], ..., ... Problem! */
	/*findall([A,_,_], ..., ... Problem! */

        write('Fraud detected: '), writeq(X/Fraud), nl,
       
        fail.

EOFPL

tpl -g run ./test.pl

# Output SQL processing, No problem!
#
# Data:   [['Jack',1,5500],['Jones',1,5000],['Jack',2,6500],['Jones',2,4500],['Jack',3,6000],['Jones',3,4750],['Jack',1,-500],['Jones',1,500],['Jack',2,-2000],['Jones',2,2000],['Jack',3,-1250],['Jones',3,1250]]
# Result: [['Jack',5000,4500,4750],['Jones',5500,6500,6000]]
# Months: [[1],[2],[3]]
# Atad:   [['Jack',1,5000],['Jack',2,4500],['Jack',3,4750],['Jones',1,5500],['Jones',2,6500],['Jones',3,6000]]

# Output Prolog processing
#
# This findall generates the correct answer!
#
# findall([C,A,B],member([A,B,C],Data),Fraud),
#
# Fraud detected: ['Jack',1,5000]/[[5500,'Jack',1],[-500,'Jack',1]]
# Fraud detected: ['Jack',2,4500]/[[6500,'Jack',2],[-2000,'Jack',2]]
# Fraud detected: ['Jack',3,4750]/[[6000,'Jack',3],[-1250,'Jack',3]]
# Fraud detected: ['Jones',1,5500]/[[5000,'Jones',1],[500,'Jones',1]]
# Fraud detected: ['Jones',2,6500]/[[4500,'Jones',2],[2000,'Jones',2]]
# Fraud detected: ['Jones',3,6000]/[[4750,'Jones',3],[1250,'Jones',3]]

# Perhaps Jones has manipulated the recording, Jack/Jones scores are swapped.

# Figures displayed nicely(...,5000]/[[5500, ...)
# ['Jack' ,1,5000]/[[5500,'Jack',1],[-500,'Jack',1]]
# ['Jones',1,5500]/[[5000,'Jones',1],[500,'Jones',1]]


# Now a report must be passed on (anonymized somehow):

# These findall(s) have a problem

# findall([C,B,_],member([A,B,C],Data),Fraud)
#
# Fraud detected: ['Jack',1,5000]/[[5500,1,'insert into atad(name,month,score) select result.name, 1*(1-abs(sign(months.month-1)))+ 2*(1-abs(sign(months.month-2)))+ 3*(1-abs(sign(months.month-3))),result.jan*(1-abs(sign(months.month-1)))+ result.feb*(1-abs(sign(months.month-2)))+ result.mar*(1-abs(sign(months.month-3))) from result cross join months'],[-500,1,'insert into atad(name,month,score) select result.name, 1*(1-abs(sign(months.month-1)))+ 2*(1-abs(sign(months.month-2)))+ 3*(1-abs(sign(months.month-3))),result.jan*(1-abs(sign(months.month-1)))+ result.feb*(1-abs(sign(months.month-2)))+ result.mar*(1-abs(sign(months.month-3))) from result cross join months']]
# 
# As you can see, SQL Statements are in the list
#
# Expecting: ['Jack',1,5000]/[[5500,1,_111],[-500,1,_112]]


# findall([A,_,_],member([A,B,C],Data),Fraud)
#
# Fraud detected: ['Jack',1,5000]/[['Jack',0,'select * from atad'],['Jack',0,'select * from atad']]
# Fraud detected: ['Jack',2,4500]/[['Jack',0,'select * from atad'],['Jack',0,'select * from atad']]
#
# Expecting:      ['Jack',1,5000]/[['Jack',_111,_112],['Jack',_113,_114]]

# Use bagof/3 instead of findall/3 to see expected results
# bagof([C,B,_],A^member([A,B,C],Data),Fraud)
# bagof([A,_,_],B^C^member([A,B,C],Data),Fraud)

core benchmark gives segmentation fault

Hi,

Was doing some regression testing:

$ ../tpl -v
Trealla Prolog (c) Infradig 2020-2022, v2.1.11
$ ../tpl
?- ['suite.p'].
   true.
?- suite, nl, suite.
nrev Time elapsed 0.716s
crypt Time elapsed 0.611s
deriv Time elapsed 0.708s
poly Time elapsed 0.607s
sortq Time elapsed 0.825s
tictac Time elapsed 0.786s
queens Time elapsed 0.769s
query Time elapsed 1.52s
mtak Segmentation fault

Trailing command line options are unexpectedly processed

For example, we get:

$ touch test.pl
$ ./tpl test.pl -g halt
$ 

However, the only way to make the system interpret the -g command line option in this way should be if it is specified before any source files, such as:

$ ./tpl -g halt test.pl 

The reason is that command line options that come after the source file(s) should not be processed by the interpreter itself, but rather passed in some way to the program so that the program (in this case: test.pl) can process them in any way it wants.

I noticed this issue due to #34, which uses:

$ tpl samples/http_server.pl -g main

This should not cause Trealla to actually run main. It should run main only if this is specified as:

$ tpl -g main samples/http_server.pl 

findall/3 bagof/3 performance

$ cat ffbb.pl 

isl({L},N) :- L = (_ is _+N).

test1 :- time(findall(Z,(between(1,5,N), isl({X}, N),
                         X = (Y is B+_), findall(Y,(between(1,3,B),X),Z)),
	              M)), write(M).

test2 :- time(bagof(Z,N^X^W^(between(1,5,N), isl({X}, N),
                             X = (Y is B+W), bagof(Y,X^(between(1,3,B),X),Z)),
	            M)), write(M).

testff :- time(findall(Z,(between(1,5_000_00,N), isl({X}, N),
                          X = (Y is B+_), findall(Y,(between(1,3,B),X),Z)
		         ),
	               M)), length(M,L), L1 is L*3, write(L1), write(' findall/findall Elements').

testbb :- time(bagof(Z,N^X^W^(between(1,5_000_00,N), isl({X}, N),
                              X = (Y is B+W), bagof(Y,X^(between(1,3,B),X),Z)
	                     ),
	             M)), length(M,L), L1 is L*3, write(L1), write(' bagof/bagof Elements').


test1, test2 are just there to show what is processed.(Of course the same data sets).

v2.1.8

$ tpl ffbb.pl 
?- nl,test1,nl,nl,test2,nl,nl,testff,nl,nl,testbb,nl.

Time elapsed 0.000116s
[[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8]]

Time elapsed 0.000542s
[[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8]]

Time elapsed 2.71s
1500000 findall/findall Elements

Time elapsed 26.2s
1500000 bagof/bagof Elements  % Performance loss
   true.
?- 

Memory streams

For the JS integration, it'd be nice if I could set_output/1 to a memory buffer. This would make it a lot easier to wrangle the specially formatted output for the programmatic toplevel vs. user code writing to stdout. Currently there's some hairy logic using ASCII control codes to work around it.

% kind of like this in SWI but without the implicit once/1?
with_output_to(chars(Cs), call(Goal)),
write({"stdout": Cs}).

I'd like to avoid the OS filesystem as some WASM runtimes may not have a FS at all.

Logtalk port of FCube exposes a parsing bug

Trying to load the Logtalk port of FCube using the main branch (520add9) exposes a parsing bug:

?- {fcube(loader)}.
...
!     Syntax error: operator_expected
!       in term
!       in file /Users/pmoura/logtalk/ports/fcube/fcube.lgt at or above line 1559
...

The clause at that position is:

pruning(swff(t, (X & Y)), PRUNED) :-
	/*calcola il contesto congiuntivo della
	formula	*/ ccTAnd((X & Y), CCX), !,
		list_to_set(CCX, CCXSET),
		pruningSetSwff(CCXSET, CCXSetPruned),
		simplification(CCXSetPruned, CCXSetPruned, RES), !,
		list_to_set(RES, SET),
	/*
	La formula di input e' di segno T,
	quindi in SET ci devono essere solo
	sottoformule con segno T
	*/ fromSetTotSwff(SET, H),
		/*esegue semplificazioni booleane*/ valSWFF(H, PRUNED).

not precise enough float printing

Currently I get:

$ ./tpl -v
Trealla Prolog (c) Infradig 2020-2022, v2.2.10
$ ./tpl
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890.
   N9 = 3.0.
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890 - 3.
   N9 = 4.440892098500626e-16.
?-

Because the number is not 3.0, I expect something else than 3.0 is printed.

Like here:

/* Jekejeke Prolog 1.5.4 */
?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890.
N9 = 3.0000000000000004.

?- N9 is 370370367037037036703703703670 / 123456789012345678901234567890 - 3.
N9 = 4.440892098500626E-16.

#12: nth0/3 incorrect

?- nth0(N, [[]|Es], Es), ground(Es).
   N = 0, Es = []
;  N = 2, Es = [2|2], unexpected
;  ... .

Odd behavior on backtracking

Backtracking here seems to use the wrong variable substitutions (as mentioned here: #46 (comment)).
Here's the smallest way to reproduce it I could find.
See comment below for smaller version.

/* Old version of the WASM toplevel stripped down. */
:- module(js_toplevel, [js_ask/1]).

:- use_module(library(lists)).

js_ask(Input) :-
	read_term_from_chars(Query, [variable_names(Vars)], Input),
	query(Query, Vars, Status, Solution),
	write_result(Status, Solution),
	flush_output.

write_result(Status, Solution) :-
	write(Status), write(' '), write(Solution), nl.

query(Query, Vars, Status, Solution) :-
	(   catch(call(Query), Error, true)
	*-> OK = true
	;   OK = false
	),
	query_status(OK, Error, Status),
	(  nonvar(Error)
	-> Solution = Error
	;  Solution = Vars
	).

query_status(_OK, Error, error) :- nonvar(Error), !.
query_status(true, _, success).
query_status(false, _, failure).

person(socrates).
person(plato).
mortal(X) :- person(X).
?- use_module(js_toplevel).
   true.

% bug?
?- js_ask("mortal(X)").
success [X=socrates]
   true
;  false. % expected plato

% this is ok:
?- js_ask("person(X)").
success [X=socrates]
   true
; success [X=plato]
 true.

% this is ok:
?- js_ask("findall(X, mortal(X), Xs)").
success [X=_5,Xs=[socrates,plato]]
   true.

Trace shows odd backtracking:

?- trace, js_ask("mortal(X)").
[js_toplevel:0:f0:fp:1:cp0:sp0:hp0:tp0] EXIT trace
[js_toplevel:1:f0:fp:1:cp0:sp0:hp0:tp0] CALL js_ask("mortal(X)")
[js_toplevel:2:f1:fp:2:cp0:sp5:hp0:tp0] CALL read_term_from_chars(_0,[variable_names(_1)],"mortal(X)")
[js_toplevel:3:f1:fp:2:cp0:sp6:hp7:tp2] EXIT read_term_from_chars(mortal(_5),[variable_names(['X'=_5])],"mortal(X)")
[js_toplevel:4:f1:fp:2:cp0:sp6:hp7:tp2] CALL query(mortal(_5),['X'=_5],_3,_4)
[js_toplevel:5:f2:fp:3:cp1:sp12:hp18:tp0] CALL catch(call(mortal(_5)),_7,true)
[js_toplevel:6:f3:fp:4:cp1:sp15:hp18:tp0] CALL '$catch'(call(call(mortal(_5))),_7,call(true))
[js_toplevel:7:f3:fp:4:cp2:sp15:hp24:tp0] EXIT '$catch'(call(call(mortal(_5))),_7,call(true))
[js_toplevel:8:f3:fp:4:cp2:sp15:hp24:tp0] CALL call(call(mortal(_5)))
[js_toplevel:9:f3:fp:4:cp3:sp15:hp30:tp0] EXIT call(call(mortal(_5)))
[js_toplevel:10:f3:fp:4:cp3:sp15:hp30:tp0] CALL call(mortal(_5))
[js_toplevel:11:f3:fp:4:cp4:sp15:hp35:tp0] EXIT call(mortal(_5))
[js_toplevel:12:f3:fp:4:cp4:sp15:hp35:tp0] CALL mortal(_5)
[js_toplevel:13:f4:fp:5:cp4:sp16:hp35:tp0] CALL person(_5)
[js_toplevel:14:f5:fp:6:cp5:sp16:hp35:tp1] EXIT person(socrates)
[js_toplevel:15:f3:fp:5:cp5:sp16:hp35:tp1] CALL '$drop_barrier'
[js_toplevel:16:f3:fp:5:cp5:sp16:hp35:tp1] EXIT '$drop_barrier'
[js_toplevel:17:f3:fp:5:cp5:sp16:hp35:tp1] CALL '$drop_barrier'
[js_toplevel:18:f3:fp:5:cp5:sp16:hp35:tp1] EXIT '$drop_barrier'
[js_toplevel:19:f3:fp:5:cp5:sp16:hp35:tp1] CALL '$block_catcher'(1)
[js_toplevel:20:f3:fp:5:cp6:sp16:hp35:tp1] EXIT '$block_catcher'(1)
[js_toplevel:21:f3:fp:5:cp6:sp16:hp35:tp1] EXIT person(socrates)
[js_toplevel:22:f2:fp:5:cp6:sp16:hp35:tp1] CALL '$soft_cut'
[js_toplevel:23:f2:fp:5:cp6:sp16:hp35:tp1] EXIT '$soft_cut'
[js_toplevel:24:f2:fp:5:cp6:sp16:hp35:tp1] CALL _8=true
[js_toplevel:25:f2:fp:5:cp6:sp16:hp35:tp2] EXIT true=true
[js_toplevel:26:f2:fp:5:cp6:sp16:hp35:tp2] CALL query_status(true,_7,_3)
[js_toplevel:27:f5:fp:6:cp7:sp18:hp35:tp5] CALL nonvar(_7)
[js_toplevel:28:f5:fp:6:cp7:sp18:hp35:tp5] FAIL nonvar(_7)
[js_toplevel:29:f2:fp:5:cp6:sp16:hp35:tp2] REDO query_status(true,_7,_3)
[js_toplevel:30:f5:fp:6:cp6:sp17:hp35:tp4] EXIT query_status(true,_7,success)
[js_toplevel:31:f2:fp:4:cp7:sp15:hp43:tp4] CALL nonvar(_7)
[js_toplevel:32:f2:fp:4:cp7:sp15:hp43:tp4] FAIL nonvar(_7)
[js_toplevel:33:f2:fp:4:cp6:sp15:hp43:tp4] CALL _4=['X'=socrates]
[js_toplevel:34:f2:fp:4:cp6:sp15:hp43:tp5] EXIT ['X'=socrates]=['X'=socrates]
[js_toplevel:35:f2:fp:4:cp6:sp15:hp43:tp5] EXIT query_status(true,mortal(socrates),success)
[js_toplevel:36:f1:fp:4:cp6:sp15:hp43:tp5] CALL write_result(success,['X'=socrates])
[js_toplevel:37:f4:fp:5:cp6:sp17:hp43:tp5] CALL write(success)
success[js_toplevel:38:f4:fp:5:cp6:sp17:hp43:tp5] EXIT write(success)
[js_toplevel:39:f4:fp:5:cp6:sp17:hp43:tp5] CALL write(' ')
 [js_toplevel:40:f4:fp:5:cp6:sp17:hp43:tp5] EXIT write(' ')
[js_toplevel:41:f4:fp:5:cp6:sp17:hp43:tp5] CALL write(['X'=socrates])
[X=socrates][js_toplevel:42:f4:fp:5:cp6:sp17:hp43:tp5] EXIT write(['X'=socrates])
[js_toplevel:43:f4:fp:5:cp6:sp17:hp43:tp5] CALL nl

[js_toplevel:44:f4:fp:5:cp6:sp17:hp43:tp5] EXIT nl
[js_toplevel:45:f4:fp:5:cp6:sp17:hp43:tp5] EXIT write_result(success,['X'=socrates])
[js_toplevel:46:f1:fp:5:cp6:sp17:hp43:tp5] CALL flush_output
[js_toplevel:47:f1:fp:5:cp6:sp17:hp43:tp5] EXIT flush_output
[js_toplevel:48:f1:fp:5:cp6:sp17:hp43:tp5] EXIT write_result(mortal(socrates),['X'=socrates])
   true
; [js_toplevel:49:f3:fp:5:cp5:sp16:hp35:tp1] REDO '$block_catcher'(1)
[js_toplevel:50:f3:fp:5:cp5:sp16:hp35:tp1] FAIL '$block_catcher'(1)
[js_toplevel:51:f4:fp:5:cp4:sp16:hp35:tp0] REDO person(success) <---------------- why is it using 'success' here?
[js_toplevel:52:f4:fp:5:cp4:sp16:hp35:tp0] FAIL person(success)
[js_toplevel:53:f3:fp:4:cp3:sp15:hp35:tp0] REDO call(mortal(_5))
[js_toplevel:54:f3:fp:4:cp3:sp15:hp35:tp0] FAIL call(mortal(_5))
[js_toplevel:55:f3:fp:4:cp2:sp15:hp30:tp0] REDO call(call(mortal(_5)))
[js_toplevel:56:f3:fp:4:cp2:sp15:hp30:tp0] FAIL call(call(mortal(_5)))
[js_toplevel:57:f3:fp:4:cp1:sp15:hp24:tp0] REDO '$catch'(call(call(mortal(_5))),_7,call(true))
[js_toplevel:58:f3:fp:4:cp1:sp15:hp24:tp0] FAIL '$catch'(call(call(mortal(_5))),_7,call(true))
 false.

In particular step 51 looks suspect.

Dropping attributed variables, consideration

I am considering dropping support for attributed variables, such as they are, in Trealla. Trealla used to do freeze/2 natively and could do so again. Ditto eventually for dif/2 & when/2. Maybe revisit it later.

freeze/2 uninstantiation_error

v2.0.16

?- member(A,Z), freeze(B,B), B=!, writeq(A/Z/B).
_0/[_0|_4]/ !   Z = [A|_A], B = !
;   error(uninstantiation_error(frozen),'$put_attributes'/2).
?- 

At present I can't interpret the error message: uninstantiation_error(frozen)?

And if freeze freezes together it unfreezes:

?- member(A,Z), freeze(B,B), freeze(B,B), B=!, writeq(A/Z/B).
_0/[_0|_4]/ !   Z = [A|_A], B = !
; _0/[_5,_0|_7]/ ! Z = [_A,A|_B], B = !
; _0/[_5,_8,_0|_10]/ ! Z = [_A,_B,A|_C], B = !
;  ... .
?-

API for controlling queries

I'm working on the last major feature for the WASM ports: returning one query result at a time instead of findall'ing the query.
Ideally we could just use stdin and wait for some input to continue the query, but unfortunately the majority of WASM runtimes don't implement stdin as a stream, so this approach doesn't work. I was considering doing something clever with history_getch as an exported host function but I think that'll just lead it to block forever in the browser.

Is there a way to control the redo process for queries with the current API?

I am imagining an API kind of like this:

// works like pl_eval, except returns a handle to the query instead of waiting for toplevel input
*query pl_query(*prolog, const char *expr);
// redo a given query, returning false if there are no more choice points
bool pl_redo(*prolog, *query);

And the WASM host would call it like so:

class Prolog {
    ptr; // *prolog ptr

    // example: javascript generator function that returns one result at a tinme
    *query(expr) {
        // eval once and grab ptr to query handle
        const query = this.pl_query(this.ptr, expr);
        do {
            const answer = /* read from stdout */;
            yield answer;
        } while (this.pl_redo(this.ptr, query))
    }
}

// usage
const query = pl.query("member(X, [1,2,3]).");
for (const answer of query) {
    console.log(answer) // X=1, then X=2 next iteration, etc
}

Ideally these would be silent, not dumping vars or printing prompts so it's easy to parse the output. We can use Prolog code to marshal the answers to an easy-to-parse format, so we don't need a C results API, just a way to run redos.

#1: Recreation of old issues

As of now, the old issues are still gone. These issues are pretty valuable, as they contain many useful test cases.

Missing rounding in float/1 for bigint argument

I get these results (note the 119042423827613008 is wrong):

/* Trealla Prolog 2.2.10 */
?- X is 51^10, Z is float(X).
   X = 119042423827613008, Z = 1.19042423827613e+17.
?- Z is float(51^10).
   Z = 1.19042423827613e+17.

But one would rather expect that float/1 performs HALF_EVEN rounding.
The results should then be (1.1904242382761301E17 instead of 1.19042423827613e+17):

?- X is 51^10, Z is float(X).
X = 119042423827613001, Z = 1.1904242382761301E17.
?- Z is float(51^10).
Z = 1.1904242382761301E17.

That the (^)/2 result is wrong, is rather irrelevant, it is also:

?- Z is float(119042423827613008).
Z = 1.1904242382761301E17.

findall/3 member/2 core dump

$ cat testmem.pl

isl({L},N) :- L = (_ is _+N).

testmem(Lim) :- findall(Z,(between(1,Lim,N), isl({X}, N),
                           X = (Y is B+_), findall(Y,(between(1,3,B),X),Z)),
	                M),
	        M1 =[[2,3,3],[3,4,4],[4,5,5],[5,6,6],[6,7,7]],
	        !,
	        member(Q,M1), \+member(Q,M),
	        findall([_,_,Q3], member([_,_,Q3],M),F),
                length(F,Lf), write(Lf), nl, false.
v2.1.8 (main)
$ tpl testmem.pl

?- testmem(1000).
1000
1000
1000
1000
1000
   false.

?- testmem(2000).
2000
2000
malloc(): invalid size (unsorted) 
Aborted (core dumped)

% I see the malloc() error when running testmem(1000) first
% Standalone I get the realloc() error as below

v2.1.8-3-g994c (devel) 
?- testmem(2000).
2000
2000
realloc(): invalid next size
Aborted (core dumped)

must_be/2 "a collection of fragments?"

v2.0.10

?- must_be(positive_int, +-).
   true.
?- must_be(positive_int, \0 + - 1).
   true.

?- must_be(flix,flux).
   true.
%  may_be
?- must_be(flux,flix).
   true.

Ludwig van B's Symphony No. 10 in E♭ major is a hypothetical work (1827) ... In 2019, artificial intelligence technology was used to reconstruct ... (wikipedia).
Not all hope is lost.

Wrong result in (^)/2 bigint computation

According to Corrigendum 2 I guess the idea of the evaluable predicate
(^)/2 is to be exact for bigint arguments. But I find for example:

$ ./tpl -v
Trealla Prolog (c) Infradig 2020-2022, v2.2.10
$ ./tpl
?- X is 51^10, Y is 51*51*51*51*51*51*51*51*51*51.
   X = 119042423827613008, Y = 119042423827613001.

Two different results! :-(

Undefined posix_spawn_file_actions_addchdir_np

While trying to build this project on Ubuntu 18.04 64bits I'm getting this error:

make
...
src/streams.c: In function ‘fn_process_create_3’:
src/streams.c:793:5: warning: implicit declaration of function ‘posix_spawn_file_actions_addchdir_np’; did you mean ‘posix_spawn_file_actions_adddup2’? [-Wimplicit-function-declaration]
  793 |     posix_spawn_file_actions_addchdir_np(&file_actions, cwd);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |     posix_spawn_file_actions_adddup2
...
src/streams.o: In function `fn_process_create_3':
streams.c:(.text+0x183d4): undefined reference to `posix_spawn_file_actions_addchdir_np'
collect2: error: ld returned 1 exit status
Makefile:114: recipe for target 'tpl' failed
make: *** [tpl] Error 1
...

garbage collection

Hi! Is there any documentation about the GC algorithm implemented in Trealla?

I'm curious about this: "Dynamic atoms are automatically garbage collected"

Syntax error in json library

?- use_module(library(json)).
Error: syntax error, mismatched parens/brackets/braces, line 153
   error(existence_error(procedure,dcg_translate/0),top_level/0).

From one of the recent changes. I'll try and figure out what the issue is.

Leak in term writing?

The following program aborts with a malloc error (likely from a buffer overflow):

:- op(201, fy, '@').

test :-
	Result = dict('.'(:(valid,@(true)),'.'(:(fields,dict('.'(:(revision,dict('.'(:(valid,@(true)),'.'(:(ignored,@(true)),[])))),'.'(:(creator,dict('.'(:(valid,@(true)),'.'(:(ignored,@(true)),[])))),'.'(:(updated_at,dict('.'(:(valid,@(true)),'.'(:(ignored,@(true)),[])))),'.'(:(created_at,dict('.'(:(valid,@(true)),'.'(:(ignored,@(true)),[])))),'.'(:(qux_abbreviation,dict('.'(:(valid,@(true)),[]))),'.'(:(qux_name,dict('.'(:(valid,@(false)),'.'(:(error,'256文字以下の値を入力してください'),[])))),'.'(:(baz_id,dict('.'(:(valid,@(false)),'.'(:(error,'256文字以下の値を入力してください'),[])))),'.'(:(owner_id,dict('.'(:(valid,@(true)),'.'(:(ignored,@(true)),[])))),'.'(:(qux_type_code,dict('.'(:(valid,@(true)),[]))),'.'(:(foo,dict('.'(:(fields,dict('.'(:('foo/bar_groups',dict('.'(:(valid,@(true)),[]))),'.'(:('foo/acme_notification_url',dict('.'(:(valid,@(false)),'.'(:(error,'「https://」で始まらなければなりません'),[])))),'.'(:('foo/qux_api_version',dict('.'(:(valid,@(true)),[]))),'.'(:('foo/xyz_unit_id',dict('.'(:(valid,@(false)),'.'(:(error,'256文字以下の値を入力してください'),[])))),'.'(:('foo/abc_label',dict('.'(:(fields,dict('.'(:('foo/abc_label/print_abc_list',dict('.'(:(valid,@(true)),[]))),'.'(:('foo/abc_label/report_id',dict('.'(:(valid,@(true)),[]))),'.'(:('foo/abc_label/report_setting_key',dict('.'(:(valid,@(true)),[]))),[]))))),'.'(:(valid,@(true)),[])))),'.'(:('foo/xyz_notes',dict('.'(:(valid,@(true)),[]))),[])))))))),'.'(:(valid,@(true)),[])))),'.'(:(system_id,dict('.'(:(valid,@(false)),'.'(:(error,'256文字以下の値を入力してください'),[])))),'.'(:(qux_status,dict('.'(:(valid,@(true)),[]))),[])))))))))))))),'.'(:(spec,dict('.'(:(type,model),'.'(:(model,qux_config),[])))),[])))),
	write(Result).
$ tpl leak.pl -g test,halt
malloc(): invalid size (unsorted)
Aborted

Writing with ignore_ops(true) seems to avoid it, so my guess would be it has to do with space insertion for the @ operator.

Valgrind log

==2116== Memcheck, a memory error detector
==2116== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2116== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==2116== Command: tpl leak.pl -g test,halt
==2116==
==2116== Invalid write of size 2
==2116==    at 0x4842B33: memmove (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1AA12D: plain (print.c:232)
==2116==    by 0x1AEF4F: print_term_to_buf (print.c:844)
==2116==    by 0x1B1344: print_term_to_buf (print.c:1033)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f1930 is 1,200 bytes inside a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4842B63: memmove (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1AA12D: plain (print.c:232)
==2116==    by 0x1AEF4F: print_term_to_buf (print.c:844)
==2116==    by 0x1B1344: print_term_to_buf (print.c:1033)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f1932 is 1 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x1AA13B: plain (print.c:233)
==2116==    by 0x1AEF4F: print_term_to_buf (print.c:844)
==2116==    by 0x1B1344: print_term_to_buf (print.c:1033)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==  Address 0x59f1933 is 2 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05F81: __vsnprintf_internal (vsnprintf.c:112)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AEFD2: print_term_to_buf (print.c:848)
==2116==    by 0x1B1344: print_term_to_buf (print.c:1033)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f1933 is 2 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05FAB: __vsnprintf_internal (vsnprintf.c:117)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AEFD2: print_term_to_buf (print.c:848)
==2116==    by 0x1B1344: print_term_to_buf (print.c:1033)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f1933 is 2 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4842B63: memmove (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1AA12D: plain (print.c:232)
==2116==    by 0x1B19DE: print_term_to_buf (print.c:1084)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==  Address 0x59f1933 is 2 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x1AA13B: plain (print.c:233)
==2116==    by 0x1B19DE: print_term_to_buf (print.c:1084)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==  Address 0x59f1934 is 3 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05F81: __vsnprintf_internal (vsnprintf.c:112)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AEB60: print_term_to_buf (print.c:806)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f1934 is 3 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05FAB: __vsnprintf_internal (vsnprintf.c:117)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AEB60: print_term_to_buf (print.c:806)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f1934 is 3 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 2
==2116==    at 0x4842B33: memmove (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1AA12D: plain (print.c:232)
==2116==    by 0x1AEF4F: print_term_to_buf (print.c:844)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f1934 is 3 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x1AA13B: plain (print.c:233)
==2116==    by 0x1AEF4F: print_term_to_buf (print.c:844)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==  Address 0x59f193e is 13 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05F81: __vsnprintf_internal (vsnprintf.c:112)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AEFD2: print_term_to_buf (print.c:848)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f193e is 13 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05FAB: __vsnprintf_internal (vsnprintf.c:117)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AEFD2: print_term_to_buf (print.c:848)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f193e is 13 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05F81: __vsnprintf_internal (vsnprintf.c:112)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AC98D: print_iso_list (print.c:586)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==  Address 0x59f193e is 13 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E0C134: _IO_default_xsputn (genops.c:394)
==2116==    by 0x4E0C134: _IO_default_xsputn (genops.c:370)
==2116==    by 0x4DF10FB: __vfprintf_internal (vfprintf-internal.c:1688)
==2116==    by 0x4E05F99: __vsnprintf_internal (vsnprintf.c:114)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AC98D: print_iso_list (print.c:586)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==  Address 0x59f193e is 13 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05FAB: __vsnprintf_internal (vsnprintf.c:117)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AC98D: print_iso_list (print.c:586)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==  Address 0x59f193f is 14 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05F81: __vsnprintf_internal (vsnprintf.c:112)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AF5E0: print_term_to_buf (print.c:909)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==  Address 0x59f193f is 14 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05FAB: __vsnprintf_internal (vsnprintf.c:117)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AF5E0: print_term_to_buf (print.c:909)
==2116==    by 0x1B21D8: print_term_to_buf (print.c:1127)
==2116==    by 0x1ABDA3: print_iso_list (print.c:520)
==2116==    by 0x1AE067: print_term_to_buf (print.c:741)
==2116==    by 0x1AF47E: print_term_to_buf (print.c:895)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==  Address 0x59f1940 is 15 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05F81: __vsnprintf_internal (vsnprintf.c:112)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AF5E0: print_term_to_buf (print.c:909)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==  Address 0x59f1941 is 16 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
==2116== Invalid write of size 1
==2116==    at 0x4E05FAB: __vsnprintf_internal (vsnprintf.c:117)
==2116==    by 0x4DDBDF5: snprintf (snprintf.c:31)
==2116==    by 0x1AF5E0: print_term_to_buf (print.c:909)
==2116==    by 0x1B2EFE: print_term_to_stream (print.c:1317)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==  Address 0x59f1942 is 17 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
dict([valid: @true,fields:dict([revision:dict([valid: @true,ignored: @true]),creator:dict([valid: @true,ignored: @true]),updated_at:dict([valid: @true,ignored: @true]),created_at:dict([valid: @true,ignored: @true]),qux_abbreviation:dict([valid: @true]),qux_name:dict([valid: @false,error:256文字以下の値を入力してください]),baz_id:dict([valid: @false,error:256文字以下の値を入力してください]),owner_id:dict([valid: @true,ignored: @true]),qux_type_code:dict([valid: @true]),foo:dict([fields:dict([foo/bar_groups:dict([valid: @true]),foo/acme_notification_url:dict([valid: @false,error:「https://」で始まらなければなりません]),foo/qux_api_version:dict([valid: @true]),foo/xyz_unit_id:dict([valid: @false,error:256文字以下 の値を入力してください]),foo/abc_label:dict([fields:dict([foo/abc_label/print_abc_list:dict([valid: @true]),foo/abc_label/report_id:dict([valid: @true]),foo/abc_label/report_setting_key:dict([valid: @true])]),valid: @true]),foo/xyz_notes:dict([v==2116== Invalid read of size 1
==2116==    at 0x4E0C01E: _IO_default_xsputn (genops.c:399)
==2116==    by 0x4E0C01E: _IO_default_xsputn (genops.c:370)
==2116==    by 0x4E096F9: _IO_new_file_xsputn (fileops.c:1265)
==2116==    by 0x4E096F9: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1197)
==2116==    by 0x4DFD3C0: fwrite (iofwrite.c:39)
==2116==    by 0x15286D: net_write (network.c:330)
==2116==    by 0x1B2F27: print_term_to_stream (print.c:1321)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==  Address 0x59f1931 is 0 bytes after a block of size 1,201 alloc'd
==2116==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x1B2E8C: print_term_to_stream (print.c:1313)
==2116==    by 0x1D4E88: fn_iso_write_1 (streams.c:1998)
==2116==    by 0x1BC369: start (query.c:1749)
==2116==    by 0x1BCB84: execute (query.c:1949)
==2116==    by 0x1654CA: run (parser.c:3615)
==2116==    by 0x1B393B: pl_eval (prolog.c:122)
==2116==    by 0x1164F2: main (tpl.c:274)
==2116==
alid: @true])]),valid: @true]),system_id:dict([valid: @false,error:256文字以下の値を入力してください]),qux_status:dict([valid: @true])]),spec:dict([type:model,model:qux_config])])==2116==
==2116== HEAP SUMMARY:
==2116==     in use at exit: 11,526 bytes in 68 blocks
==2116==   total heap usage: 5,701 allocs, 5,633 frees, 56,428,199 bytes allocated
==2116==
==2116== 3,299 (2,960 direct, 339 indirect) bytes in 10 blocks are definitely lost in loss record 3 of 4
==2116==    at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x154DC9: directives (parser.c:397)
==2116==    by 0x161B9A: process_term (parser.c:2887)
==2116==    by 0x16283E: tokenize (parser.c:3046)
==2116==    by 0x14FF6A: load_text (module.c:1276)
==2116==    by 0x156E29: directives (parser.c:678)
==2116==    by 0x161B9A: process_term (parser.c:2887)
==2116==    by 0x16283E: tokenize (parser.c:3046)
==2116==    by 0x14FF6A: load_text (module.c:1276)
==2116==    by 0x156E29: directives (parser.c:678)
==2116==    by 0x161B9A: process_term (parser.c:2887)
==2116==    by 0x16283E: tokenize (parser.c:3046)
==2116==
==2116== 8,227 (7,104 direct, 1,123 indirect) bytes in 24 blocks are definitely lost in loss record 4 of 4
==2116==    at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2116==    by 0x154DC9: directives (parser.c:397)
==2116==    by 0x161B9A: process_term (parser.c:2887)
==2116==    by 0x16283E: tokenize (parser.c:3046)
==2116==    by 0x14FF6A: load_text (module.c:1276)
==2116==    by 0x1B553E: pl_create (prolog.c:533)
==2116==    by 0x115C14: main (tpl.c:165)
==2116==
==2116== LEAK SUMMARY:
==2116==    definitely lost: 10,064 bytes in 34 blocks
==2116==    indirectly lost: 1,462 bytes in 34 blocks
==2116==      possibly lost: 0 bytes in 0 blocks
==2116==    still reachable: 0 bytes in 0 blocks
==2116==         suppressed: 0 bytes in 0 blocks
==2116==
==2116== For lists of detected and suppressed errors, rerun with: -s
==2116== ERROR SUMMARY: 54 errors from 23 contexts (suppressed: 0 from 0)

O(retract/1) is O(N^2) in simple test case

Keep up the good work and fix this issue:

stefankral@Stefans-MacBook-Air trealla-main % ./tpl
?- time((between(1,100000,X),assertz(f(X)),false;true)).
Time elapsed 0.125s
   true.
?- time((retract(f(X)),false;true)).
Time elapsed 1.81s
   true.
?- time((between(1,200000,X),assertz(f(X)),false;true)).
Time elapsed 0.217s
   true.
?- time((retract(f(X)),false;true)).
Time elapsed 7.12s
   true.
?- time((between(1,400000,X),assertz(f(X)),false;true)).
Time elapsed 0.395s
   true.
?- time((retract(f(X)),false;true)).
Time elapsed 28.4s
   true.

freeze/2 spooky backtracking

Correct...

$ tpl
?- freeze(Y,write(ok)),(X=Y;true),Y=123.
ok   Y = 123, X = Y
; ok Y = 123.
?- $ tpl

Incorrect...

?- _=[X,Y],freeze(Y,write(ok)),(X=Y;true),Y=123.
ok   X = 123, Y = X
; ok Y = 123, freeze:freeze(X,write(ok)).
?- 

Reading multiple terms from a string

Is it possible to read multiple terms from a string? read_term_from_chars/3 only returns one.
Maybe it could backtrack and return multiple terms.
Or maybe position(Start,End) could work for strings too?

% maybe like this?
?- read_term_from_chars(X, [], "foo. bar.").
   X = foo
;  X = bar.
% or this?
?- read_term_from_chars(X, [position(Start,End)], "foo. bar.").
   X = foo, Start = 0, End = 3.

Use-case: trying to execute Prolog code inside webpage templates. Currently look for . in a DCG but that breaks when a float has a dot in it.

Trealla in the browser

Hello, I finally got around to getting Trealla running in the browser. Repo is here: https://github.com/guregu/trealla-js
Quick and dirty demo here: https://php.energy/trealla.html

It has the same flaw as the Go+WASM port in that it can't finely control the toplevel so it starts the interpreter fresh for each query and findalls everything. I was hoping this JS WASI library would give us better stdin control and use that, but alas it does not. I think I'll have to end up exporting "tell me the query results" functions from the native hosts to WASM and call that from Trealla somehow.

Anyway, just wanted to post this here because it's pretty cool. The discussions section is missing so I made this an issue. There are no particular problems with Trealla, just need to figure out a way to wrangle I/O in WASM better.

CLP(Z) - top-level first time failure

Note to self:

?- ~/trealla (devel) $ tpl ~/scryer-prolog/src/lib/clpz.pl f1.pl
?- X #> 3, X #< 20.
   error(type_error(integer,clpz_attr(no,no,no,from_to(n(4),sup),fd_props([],[],[]),queue(_579,_580,_581,_582))),unknown(clpz_attr(no,no,no,from_to(n(4),sup),fd_props([],[],[]),queue(_579,_580,_581,_582)))-1).
?- X #> 3, X #< 20.
   clpz:(X in 4..19).
?- X #> 3, X #< 20.
   clpz:(X in 4..19).

Upstreaming WASM changes

Thanks for merging my WASM PRs. I'm making this issue to help me keep track of what hasn't been upstreamed yet.
All of this stuff is available at: https://github.com/guregu/trealla

  • WASM build action: #57
    • This should help us know when we break the WASM build.
    • I'll keep doing releases to WAPM from guregu/trealla until the fork no longer needs to exist.
  • Use Wizer to pre-initialize a global interpreter for WASM
    • This brings the startup time of new interpreters from ~180ms to ~5ms in browsers.
    • Includes one Apache 2.0 licensed header file. It's a very simple macro so I might just rewrite it to keep us all MIT.
  • library(js) JSON-based programmatic toplevel & javascript native predicates
    • Need to split this into 2 modules: JSON toplevel (js_ask/1) and Javsascript-specific helpers like js_fetch/3 (trealla-prolog/go needs the toplevel but not the JS helpers)
  • JSON related hacks: guregu#5
    • Solved in #92
  • Host call stuff: '$host_call'/2 and '$host_resume'/1. These are two native predicates for passing data between guest → host → guest, used for calling JS from Prolog.
    • Includes Makefile changes to build the pure WASI version and the 'libtpl.wasm' version with host call predicates.
    • If we use better WASI libraries for trealla-js, we might not need these at all, and we can use pipes/sockets/whatever instead.
  • Toplevel formatting hacks for WASM/pl_query.
    • In pl_query mode, don't print spaces before var dumps.
    • In pl_query mode, always include the dot for terms and don't print "; " on redo, etc. This gives us uniform results in the WASM library query iterators.
    • For WASM, print warnings to stderr instead of stdout (keeps stdout clean and easy to parse).
  • Export all of trealla.h and add a query_did_yield API in trealla.h

Wow, that was a lot of stuff. Anyway, instead of dumping one huge PR on you I will make small ones that add things as they are stabilized.
Of course, I'd be happy to change how things work so feel free to reject anything. My aim is to be minimally invasive :-)

bagof/3 setof/3 findall/3 syntax analysis

bagof/3, setof/3

GNU
| ?- B^bagof(Y,(between(1,3,B)),X).
uncaught exception: error(existence_error(procedure,(^)/2),top_level/0)

Swipl
?- B^bagof(Y,(between(1,3,B)),X).
ERROR: Unknown procedure: (^)/2
ERROR:   ^/2 can only appear as the 2nd argument of setof/3 and bagof/3


Trealla
?- B^bagof(Y,(between(1,3,B)),X).
   B = 1, X = [_A]
;  ... .
?-

findall/3

| ?- findall(Y,B^(between(1,3,B)),X).
uncaught exception: error(existence_error(procedure,(^)/2),findall/3)

?- findall(Y,B^(between(1,3,B)),X).
ERROR: Unknown procedure: (^)/2
ERROR:   ^/2 can only appear as the 2nd argument of setof/3 and bagof/3

?- findall(Y,B^(between(1,3,B)),X).
   X = [_A,_B,_C].
?-

bagof/3 issue

v2.1.4

$ cat xyz.pl

isl({L},N) :- L = (_ is _+N).

testf :- findall(Z,(between(1,5,N),
                    (isl({X}, N), X = (Y is B+_), bagof(Y,X^B^(between(1,3,B),X),Z))
		   ),
	         M), write(M).

testb :- bagof(Z,N^X^Y^B^(between(1,5,N),
                          (isl({X}, N), X = (Y is B+_), bagof(Y,X^B^(between(1,3,B),X),Z))
	                 ),
	       M), write(M).
$ tpl xyz.pl
?- testf.
[[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8]]   true.

?- testb.
[[2,3,4]]   true  % unexpected? expecting the whole list(see testf).
; [[3,4,5]] true
; [[4,5,6]] true
;  ... .
?-

testf: findall/3 works as expected and gathers up Z of (sub-)bagof/3, which works as expected, into M.

testb: bagof/3 does not collecting Z as findall/3 does but it should, shouldn't it?

Can define operator '.', but it doesn't parse

Tried to run this:

{"quote":"Winning isn’t everything, but wanting to win is.","author":"Vince Lombardi"}.
{"quote":"I am not a product of my circumstances. I am a product of my decisions.","author":"Stephen Covey"}.
{"quote":"You miss 100% of the shots you don’t take.","author":"Wayne Gretzky"}.
{"quote":"Every strike brings me closer to the next home run.","author":"Babe Ruth"}.
{"quote":"Whether you think you can or you think you can’t, you’re right.","author":"Henry Ford"}.

member_comma(Z, (X,_)) :- member_comma(Z, X).
member_comma(Z, (_,Y)) :- !, member_comma(Z, Y).
member_comma(X, X).

:- op(100,yfx,'.').
:- op(800,xfx,':=').
X := A.B :- member_comma(B:X, A).

main :- statistics(wall, W), N is W mod 5+1, call_nth({ X }, N), Q := X."quote", A := X."author",
   write(Q-A), nl.

But I got errors:

?- ['quotes.pl'].
Warning: singleton: X, near line 13, file 'quotes.pl'
Warning: singleton: A, near line 13, file 'quotes.pl'
Warning: singleton: X, near line 13, file 'quotes.pl'
Warning: singleton: A, near line 13, file 'quotes.pl'
Error: '.', line 13, '
'
   true.

If Trealla cannot parser operator '.', wouldn't it make more sense to
throw already an error during op/3.

call_nth/2 leaves a spurious choice point

I just tried to verify this issue:
#23

And I see a spurious choice point:

?- main.
"Every strike brings me closer to the next home run."-"Babe Ruth"
   true
;  ... .

But there is not much in main/0 that would leave a choice point, or does it?
Maybe call_nth/2 is the problem?

feature request ¤ should behave like $ during writing

Trealla gives me:

$ ./tpl -v
Trealla Prolog (c) Infradig 2020-2022, v2.0.16
$ ./tpl
?- X = $$$ .
   X = $$$ .
?- X = $$$6 .
Error: syntax error, near '6', operator expected
?- X = '$$$6' .
   X = '$$$6'.

But then it gives me:

$ ./tpl
?- X = ¤¤¤ .
   X = '¤¤¤'.
?- X = ¤¤¤6 .
Error: syntax error, near '6', operator expected
?- X = '¤¤¤6' .
   X = '¤¤¤6'.

So the reading seems no problem. But the writing, I am not sure about,
but if ¤ is in the same graphic char Prolog classification as $, there wouldn't
be a need to quote a sequence ¤ during output.

At least SWI-Prolog does the same, see also:

mthom/scryer-prolog#1515

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.