Quad (Query Using Answer Description) is Flowlog’s conformance testing framework for ISO Prolog behavior. This document describes the quad test format, the test suites, and how to run them.
Code references (section markers in flowlog.c):
Quad tests are embedded REPL transcripts in .pl files
that verify Prolog queries produce expected results. The format is
simple and readable:
?- member(X, [1,2,3]).
X = 1
; X = 2
; X = 3.The framework parses queries (?- Goal.) and their answer
descriptions, executes the queries, and verifies the results match.
From the project root:
# Run all quad tests (Flowlog)
make quad
# Run all quad tests in other Flowlog engines
make quad-wam
make quad-wamvm
# Run all quad tests with tracing enabled (Flowlog; output on stderr)
make quad-trace
make quad-itrace
# Run against reference engines (Trealla, Scryer)
make quad-tpl
make quad-scryer
# Run a single quad file (two equivalent forms)
make quad tests/predicates/assertz_quad.pl
make quad QUAD_FILE=tests/predicates/assertz_quad.plThese make targets invoke tests/quad.sh:
make quad runs ./flowlog with
--parallel-profile iso --engine tree (SUBSECTION 26.1)make quad-wam runs ./flowlog with
--parallel-profile iso --engine wam (SUBSECTION 26.1)make quad-wamvm runs ./flowlog with
--parallel-profile iso --engine wamvm (SUBSECTION
26.1)make quad-tpl runs tplmake quad-scryer runs scryer-prologYou can also run the runner directly if you want different engine flags:
bash tests/quad.sh ./flowlog --parallel-profile iso --engine wamvmQuad tests are run by tests/quad.sh, which launches a
fresh Prolog process per *_quad.pl file.
This means a crash or hang in one test file does not prevent later test
files from running.
If timeout(1) is available, each quad file is limited to
QUAD_TIMEOUT seconds (default: 30). Timeouts
are reported as a CRASH for that file.
When a quad file FAILs or CRASHes, the
runner writes a per-test log file containing that test’s output (with
internal runner markers removed).
results/<test_path>.logresults/<prolog>/<test_path>.logresults/predicates/arg_quad.pl.logresults/tpl/ulrich/conformity_quad.pl.logresults/scryer-prolog/predicates/open_quad.pl.logtests/predicates/arg_quad.pl and replace the leading
tests/ with results/ to find the corresponding
log.QUAD_RESULTS_DIR
(default: results). Set it to an empty string to disable
per-test logs entirely.Quad test files are organized under tests/:
tests/
├── quad.sh # Shell runner used by `make quad*`
├── ulrich/ # Ulrich Neumerkel's ISO conformity tests
│ ├── conformity_quad.pl # Syntax conformity tests
│ ├── number_chars_quad.pl # number_chars/2 tests
│ ├── variable_names_quad.pl # read_term variable_names option tests
│ ├── member_quad.pl # member/2 prologue tests
│ ├── append_quad.pl # append/3 prologue tests
│ ├── length_quad.pl # length/2 prologue tests
│ ├── between_quad.pl # between/3 prologue tests
│ ├── select_quad.pl # select/3 prologue tests
│ ├── succ_quad.pl # succ/2 prologue tests
│ ├── maplist_quad.pl # maplist/2..4 prologue tests
│ ├── nth_quad.pl # nth0/3, nth1/3 prologue tests
│ ├── foldl_quad.pl # foldl/4..6 prologue tests
│ ├── call_nth_quad.pl # call_nth/2 prologue tests
│ └── countall_quad.pl # countall/2 prologue tests
└── predicates/ # ISO built-in predicate tests
├── abolish_quad.pl
├── arg_quad.pl
├── ... (140+ predicate test files)
└── writeq_quad.pl
% Query succeeds once with no bindings
?- true.
true.
% Query fails
?- fail.
false.
% Query succeeds with bindings
?- X = 1.
X = 1.
% Multiple solutions (disjunction)
?- member(X, [a,b]).
X = a
; X = b.\+ \+)When a test has variables but expects just true (we only
care that it succeeds, not what the bindings are), wrap the query with
\+ \+:
% Without wrapper - framework captures bindings, test fails
?- T = (X, Y), write_term(T, [variable_names(['X'=X, 'Y'=Y])]).
true. % FAILS: got T = (X,Y), X = X, Y = Y
% With wrapper - double negation discards bindings
?- \+ \+ (T = (X, Y), write_term(T, [variable_names(['X'=X, 'Y'=Y])])).
true. % PASSES: goal succeeded, bindings discardedHow it works: \+ \+ Goal succeeds if
and only if Goal succeeds at least once, but because
\+ (negation as failure) doesn’t export bindings, the
double negation effectively tests “does this succeed?” without capturing
any variable bindings.
When to use: Use \+ \+ when: - The
query contains variables (like T, X,
N) - The expected answer is just true - You
don’t care about the specific variable bindings
This pattern is commonly needed for write_term/2,
writeq/1, and other I/O predicates where the test verifies
the predicate succeeds but the variables in the query are
incidental.
Errors use the ISO error(Formal, ImplDefined)
format:
% Instantiation error
?- length(L, N).
error(instantiation_error, _).
% Type error
?- atom_length(123, N).
error(type_error(atom, 123), _).
% Domain error
?- length(L, -1).
error(domain_error(not_less_than_zero, -1), _).
% Existence error
?- current_input(foo).
error(existence_error(stream, foo), _).
% Permission error
?- open('/nonexistent', read, S).
error(permission_error(open, source_sink, '/nonexistent'), _).Shorthand error descriptions are also supported:
?- atom_length(X, N).
instantiation_error. % Expands to error(instantiation_error, _)
?- atom_length(123, N).
type_error(atom, 123). % Expands to error(type_error(atom,123), _)|)When multiple answers are acceptable (implementation-defined behavior):
?- catch(throw(ball), E, true).
E = ball
| error(system_error, _). % Some systems may wrap in system_error..., ad_infinitum)For queries with infinite solutions:
?- repeat.
true
; true
; true
; ..., ad_infinitum. % Continues indefinitelyloops)For queries that don’t terminate:
?- loop. % Assuming loop :- loop.
loops....)To match any additional solutions:
?- between(1, 100, X).
X = 1
; X = 2
; X = 3
; .... % More solutions followThe sto annotation marks tests whose behavior depends on
occurs-check settings:
?- X = f(X).
sto, X = f(X) % With occurs-check: fails or error
| sto, loops. % Without occurs-check: may loopMark answers that should NOT occur:
?- safe_operation(X).
X = result
; unexpected, error(_, _). % Test fails if this matchesBased on https://www.complang.tuwien.ac.at/ulrich/iso-prolog/
These tests verify strict ISO Prolog conformance:
| File | Description | Source |
|---|---|---|
conformity_quad.pl |
363 syntax conformity tests | conformity_testing |
number_chars_quad.pl |
number_chars/2 edge cases |
number_chars |
variable_names_quad.pl |
read_term/3 variable_names tests |
variable_names |
Based on https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue
These test library predicates proposed for ISO standardization:
| File | Predicate(s) | Description |
|---|---|---|
member_quad.pl |
member/2 |
List membership |
append_quad.pl |
append/3 |
List concatenation |
length_quad.pl |
length/2 |
List length |
between_quad.pl |
between/3 |
Integer range enumeration |
select_quad.pl |
select/3 |
Element selection/insertion |
succ_quad.pl |
succ/2 |
Successor relation |
maplist_quad.pl |
maplist/2..4 |
Apply predicate to list elements |
nth_quad.pl |
nth0/3, nth1/3 |
Index-based list access |
foldl_quad.pl |
foldl/4..6 |
Left fold with accumulators |
call_nth_quad.pl |
call_nth/2 |
Get Nth solution |
countall_quad.pl |
countall/2 |
Count solutions |
The tests/predicates/ directory contains comprehensive
tests for all ISO built-in predicates, organized one file per predicate
family.
% Test file header
% ISO Prolog [Predicate] Tests
% Based on [source URL if applicable]
% Optional: predicate definitions for testing
helper(X) :- member(X, [1,2,3]).
% Optional: directives
:- set_prolog_flag(double_quotes, chars).
% Test queries
?- helper(X).
X = 1
; X = 2
; X = 3.
?- helper(4).
false.Create a new *_quad.pl file anywhere under
tests/. The runner discovers quad tests by filename, so no
additional registration step is required.
error(Type, _) wrapper, not bare error typessto for occurs-check sensitive
tests| for implementation-defined
behaviorThe quad framework is implemented in lib/quad.pl and
provides:
.pl filesKey configuration:
| Setting | Default | Description |
|---|---|---|
quad_ad_infinitum_witness |
8 | Extra solutions to witness for infinite sequences |
quad_default_solution_cap |
64 | Maximum solutions to collect |
quad_inference_limit_min |
50000 | Base inference limit for loop detection |
CONFORMANCE.md - ISO
conformance goalsTESTS.md - Full test suite
documentationISO_CHECKLIST.md - ISO
predicate implementation status