This page bundles Flowlog’s regression tests and ISO conformance
runners found under tests/.
Quick run (from the flowlog/
directory):
make test
make test-inria
make test-inria-wamvm-vmonlyNotes:
tests/setup_inria_suite.sh
into build/inria_suite/.unknown=warning messages), set
FLOWLOG_INRIA_SHOW_STDERR=1.tar -czf flowlog_tests.tar.gz tests/.tests/and_par_user_pred.pltests/findall_non_ground.pltests/findall_vars.pltests/length_long.pltests/repl_ctrld_tty.ctests/repl_siginfo_key_tty.ctests/run.shtests/run_ctrld_repl.shtests/run_bracket_consult_repl.shtests/run_inria_suite.shtests/run_orpar_nesting.shtests/run_orpar_nesting_wam.shtests/run_siginfo_key_repl.shtests/run_siginfo_repl.shtests/run_sigint_repl.shtests/setup_inria_suite.shtests/wam_bagof_setof.pltests/wam_control_constructs.pltests/wam_conversions.pltests/wam_copy_vars.pltests/wam_dynamic_db.pltests/wam_engine_smoke.pltests/wam_exceptions.pltests/wam_extra_builtins.pltests/wam_flags.pltests/wam_ops.pltests/wam_properties.pltests/wam_reflective.pltests/wam_sorting.pltests/wam_stream_io.pltests/wam_sub_string.pltests/wam_tabling.pltests/wam_term_ops.pltests/wamvm_exceptions_vm.pltests/wamvm_findall_var_goal_vm.pltests/wamvm_flags.pltests/wamvm_select1_vm.pltests/wamvm_term_ops_vm.pltests/wamvm_vm_smoke.pltests/and_par_user_pred.pl
% AND-par "flow analysis" regression: independent deterministic user predicates
% should run concurrently when flowlog_parallel_and is enabled.
:- set_prolog_flag(flowlog_parallel_and, on).
complex(I, O) :-
$flowlog_sleep_ms(250),
O is I + 1.
merge(A, B, O) :-
O is A + B.
doubleComplex(I, O) :-
complex(I, A),
complex(I, B),
merge(A, B, O).tests/findall_non_ground.pl
% Regression: findall/3 must work with a non-ground template that becomes ground
% per-solution (ISO semantics).
p(1).
p(2).
q(L) :- findall(X, p(X), L).
ok :-
q(L),
L = [1,2].tests/findall_vars.pl
% WAM findall/3: non-ground template instances should be copied with fresh,
% bindable variables (ISO semantics).
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
p(1).
p(2).
q(L) :- findall(pair(X,Y), p(X), L).
ok :-
q(L),
L = [pair(1,Y1), pair(2,Y2)],
var(Y1),
var(Y2),
Y1 \== Y2,
Y1 = a,
Y2 = b,
findall(X, (p(X), !), Lcut),
Lcut = [1],
findall(Z, p(3), Lnone),
Lnone = [],
findall(T, \+ true, Lneg),
Lneg = [],
X0 = outer,
findall(W, p(W), _),
X0 == outer.tests/length_long.pl
% Regression test: `length/2` must handle lists longer than internal guards.
ok :-
length(L, 70000),
length(L, N),
N =:= 70000.tests/repl_ctrld_tty.c
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <libutil.h>
#else
#include <pty.h>
#endif
static bool wait_for_substr(int fd, const char *needle, int timeout_ms) {
char buf[8192];
size_t len = 0;
buf[0] = '\0';
const int step_ms = 20;
int remaining = timeout_ms;
while (remaining > 0) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
struct timeval tv;
tv.tv_sec = step_ms / 1000;
tv.tv_usec = (step_ms % 1000) * 1000;
int rc = select(fd + 1, &rfds, NULL, NULL, &tv);
if (rc < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
if (rc == 0) {
remaining -= step_ms;
continue;
}
if (!FD_ISSET(fd, &rfds)) {
remaining -= step_ms;
continue;
}
ssize_t n = read(fd, buf + len, sizeof(buf) - 1 - len);
if (n <= 0) {
return false;
}
len += (size_t)n;
buf[len] = '\0';
if (strstr(buf, needle)) {
return true;
}
if (len > sizeof(buf) / 2) {
const size_t keep = sizeof(buf) / 3;
memmove(buf, buf + (len - keep), keep);
len = keep;
buf[len] = '\0';
}
}
return false;
}
static bool wait_for_exit(pid_t pid, int timeout_ms, int *status_out) {
const int step_ms = 10;
int remaining = timeout_ms;
while (remaining > 0) {
int st = 0;
pid_t w = waitpid(pid, &st, WNOHANG);
if (w == pid) {
if (status_out) {
*status_out = st;
}
return true;
}
if (w < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
usleep(step_ms * 1000);
remaining -= step_ms;
}
return false;
}
int main(int argc, char **argv) {
if (argc < 2 || !argv[1] || !*argv[1]) {
fprintf(stderr, "usage: %s /path/to/flowlog\n", argv[0]);
return 2;
}
const char *bin = argv[1];
int master = -1;
pid_t pid = forkpty(&master, NULL, NULL, NULL);
if (pid < 0) {
perror("forkpty");
return 2;
}
if (pid == 0) {
execl(bin, bin, NULL);
perror("exec");
_exit(127);
}
if (master < 0) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "no pty master\n");
return 2;
}
if (!wait_for_substr(master, "?- ", 2000)) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "did not see prompt\n");
return 1;
}
const unsigned char ctrl_d = 0x04;
ssize_t wr = write(master, &ctrl_d, 1);
if (wr != 1) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "failed to write Ctrl-D\n");
return 1;
}
int st = 0;
if (!wait_for_exit(pid, 1000, &st)) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "timeout waiting for exit\n");
return 1;
}
if (WIFEXITED(st) && WEXITSTATUS(st) == 0) {
return 0;
}
if (WIFSIGNALED(st)) {
fprintf(stderr, "exited via signal: %d\n", WTERMSIG(st));
} else if (WIFEXITED(st)) {
fprintf(stderr, "unexpected exit code: %d\n", WEXITSTATUS(st));
} else {
fprintf(stderr, "unexpected exit status: %d\n", st);
}
return 1;
}tests/repl_siginfo_key_tty.c
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <libutil.h>
#else
#include <pty.h>
#endif
static bool wait_for_substr(int fd, const char *needle, int timeout_ms) {
char buf[8192];
size_t len = 0;
buf[0] = '\0';
const int step_ms = 20;
int remaining = timeout_ms;
while (remaining > 0) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
struct timeval tv;
tv.tv_sec = step_ms / 1000;
tv.tv_usec = (step_ms % 1000) * 1000;
int rc = select(fd + 1, &rfds, NULL, NULL, &tv);
if (rc < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
if (rc == 0) {
remaining -= step_ms;
continue;
}
if (!FD_ISSET(fd, &rfds)) {
remaining -= step_ms;
continue;
}
ssize_t n = read(fd, buf + len, sizeof(buf) - 1 - len);
if (n <= 0) {
return false;
}
len += (size_t)n;
buf[len] = '\0';
if (strstr(buf, needle)) {
return true;
}
if (len > sizeof(buf) / 2) {
const size_t keep = sizeof(buf) / 3;
memmove(buf, buf + (len - keep), keep);
len = keep;
buf[len] = '\0';
}
}
return false;
}
static void drain_fd(int fd) {
char tmp[4096];
for (;;) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
int rc = select(fd + 1, &rfds, NULL, NULL, &tv);
if (rc <= 0 || !FD_ISSET(fd, &rfds)) {
return;
}
ssize_t n = read(fd, tmp, sizeof(tmp));
if (n <= 0) {
return;
}
}
}
static bool wait_for_exit(int fd, pid_t pid, int timeout_ms, int *status_out) {
const int step_ms = 10;
int remaining = timeout_ms;
while (remaining > 0) {
int st = 0;
pid_t w = waitpid(pid, &st, WNOHANG);
if (w == pid) {
if (status_out) {
*status_out = st;
}
return true;
}
if (w < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
drain_fd(fd);
usleep(step_ms * 1000);
remaining -= step_ms;
}
return false;
}
int main(int argc, char **argv) {
if (argc < 2 || !argv[1] || !*argv[1]) {
fprintf(stderr, "usage: %s /path/to/flowlog\n", argv[0]);
return 2;
}
const char *bin = argv[1];
int master = -1;
pid_t pid = forkpty(&master, NULL, NULL, NULL);
if (pid < 0) {
perror("forkpty");
return 2;
}
if (pid == 0) {
execl(bin, bin, NULL);
perror("exec");
_exit(127);
}
if (master < 0) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "no pty master\n");
return 2;
}
if (!wait_for_substr(master, "?- ", 5000)) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "did not see prompt\n");
return 1;
}
// Ctrl-T: Flowlog SIGINFO keybinding (BSD SIGINFO equivalent).
const unsigned char ctrl_t = 0x14;
if (write(master, &ctrl_t, 1) != 1) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "failed to write Ctrl-T\n");
return 1;
}
// In raw mode, the terminal must still translate '\n' to CRLF, otherwise
// the status report "staircases" to the right.
if (!wait_for_substr(master, "\r\nFlowlog SIGINFO (status)\r\n", 5000)) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "siginfo output missing CRLF framing\n");
return 1;
}
// Exit via Ctrl-D at a fresh prompt.
const unsigned char ctrl_d = 0x04;
if (write(master, &ctrl_d, 1) != 1) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "failed to write Ctrl-D\n");
return 1;
}
int st = 0;
if (!wait_for_exit(master, pid, 3000, &st)) {
kill(pid, SIGKILL);
(void)waitpid(pid, NULL, 0);
fprintf(stderr, "timeout waiting for exit\n");
return 1;
}
if (WIFEXITED(st) && WEXITSTATUS(st) == 0) {
return 0;
}
if (WIFSIGNALED(st)) {
fprintf(stderr, "exited via signal: %d\n", WTERMSIG(st));
} else if (WIFEXITED(st)) {
fprintf(stderr, "unexpected exit code: %d\n", WEXITSTATUS(st));
} else {
fprintf(stderr, "unexpected exit status: %d\n", st);
}
return 1;
}tests/run.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$ROOT" -s >/dev/null
mkdir -p "$ROOT/build"
BIN="$ROOT/flowlog"
PROG="$ROOT/tests/and_par_user_pred.pl"
fail() {
echo "fail: $1" >&2
exit 1
}
run_out() {
"$BIN" --threads 2 -f "$PROG" -g "$1" | tr -d '\r'
}
run_prog_out() {
local prog="$1"
local goal="$2"
"$BIN" --threads 2 -f "$prog" -g "$goal" | tr -d '\r'
}
time_real() {
if command -v /usr/bin/time >/dev/null 2>&1; then
(/usr/bin/time -p "$@" >/dev/null) 2>&1 | awk '/^real[[:space:]]+/{print $2; exit}'
return 0
fi
# Fallback: coarse (seconds).
local t0 t1
t0="$(date +%s)"
"$@" >/dev/null
t1="$(date +%s)"
echo "$((t1 - t0))"
}
out="$("$BIN" --threads 1 --engine interp -g '\+ (true -> fail).' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "parse_not_if_then"
fi
out="$(run_out 'doubleComplex(1,O).')"
out="${out%$'\n'}"
if [[ "$out" != "O = 4" ]]; then
echo "--- expected ---" >&2
echo "O = 4" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "and_par_user_pred_output"
fi
seq="$(time_real "$BIN" --threads 2 --engine interp -f "$PROG" -g 'set_prolog_flag(flowlog_parallel_and,off), doubleComplex(1,O).')"
par="$(time_real "$BIN" --threads 2 --engine interp -f "$PROG" -g 'set_prolog_flag(flowlog_parallel_and,on), doubleComplex(1,O).')"
awk -v seq="$seq" -v par="$par" 'BEGIN {
if (seq + 0.0 <= 0.0 || par + 0.0 <= 0.0) exit 1;
# Expect a noticeable wall-time reduction when AND-par is enabled.
if ((seq / par) < 1.3) exit 2;
}' || fail "and_par_user_pred_timing (seq=$seq par=$par)"
seq_wam="$(time_real "$BIN" --threads 2 --engine wam -f "$PROG" -g 'current_prolog_flag(flowlog_engine_active,wam), set_prolog_flag(flowlog_parallel_and,off), doubleComplex(1,O).')"
par_wam="$(time_real "$BIN" --threads 2 --engine wam -f "$PROG" -g 'current_prolog_flag(flowlog_engine_active,wam), set_prolog_flag(flowlog_parallel_and,on), doubleComplex(1,O).')"
awk -v seq="$seq_wam" -v par="$par_wam" 'BEGIN {
if (seq + 0.0 <= 0.0 || par + 0.0 <= 0.0) exit 1;
# Expect a noticeable wall-time reduction when AND-par is enabled.
if ((seq / par) < 1.3) exit 2;
}' || fail "and_par_user_pred_timing_wam (seq=$seq_wam par=$par_wam)"
bash "$ROOT/tests/run_sigint_repl.sh" >/dev/null
bash "$ROOT/tests/run_siginfo_repl.sh" >/dev/null
bash "$ROOT/tests/run_siginfo_key_repl.sh" >/dev/null
bash "$ROOT/tests/run_ctrld_repl.sh" >/dev/null
bash "$ROOT/tests/run_bracket_consult_repl.sh" >/dev/null
bash "$ROOT/tests/run_orpar_nesting.sh" >/dev/null
bash "$ROOT/tests/run_orpar_nesting_wam.sh" >/dev/null
out="$("$BIN" --threads 2 -f "$ROOT/tests/length_long.pl" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "length_long"
fi
WAM_PROG="$ROOT/tests/wam_engine_smoke.pl"
out="$(run_prog_out "$WAM_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_engine_ok"
fi
out="$(run_prog_out "$WAM_PROG" 'fact(5,F).')"
out="${out%$'\n'}"
if [[ "$out" != "F = 120" ]]; then
echo "--- expected ---" >&2
echo "F = 120" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_engine_fact"
fi
out="$(run_prog_out "$WAM_PROG" 'count(C).')"
out="${out%$'\n'}"
if [[ "$out" != "C = 2" ]]; then
echo "--- expected ---" >&2
echo "C = 2" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_engine_findall_length"
fi
WAM_TABLING_PROG="$ROOT/tests/wam_tabling.pl"
out="$(run_prog_out "$WAM_TABLING_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_tabling"
fi
FINDALL_PROG="$ROOT/tests/findall_non_ground.pl"
out="$(run_prog_out "$FINDALL_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "findall_non_ground_fallback"
fi
FINDALL_VARS_PROG="$ROOT/tests/findall_vars.pl"
out="$(run_prog_out "$FINDALL_VARS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "findall_vars_wam"
fi
WAM_CTRL_PROG="$ROOT/tests/wam_control_constructs.pl"
out="$(run_prog_out "$WAM_CTRL_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_control_constructs"
fi
WAM_TERM_OPS_PROG="$ROOT/tests/wam_term_ops.pl"
out="$(run_prog_out "$WAM_TERM_OPS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_term_ops"
fi
WAM_SORTING_PROG="$ROOT/tests/wam_sorting.pl"
out="$(run_prog_out "$WAM_SORTING_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_sorting"
fi
WAM_SUB_STRING_PROG="$ROOT/tests/wam_sub_string.pl"
out="$(run_prog_out "$WAM_SUB_STRING_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_sub_string"
fi
WAM_EXTRA_BUILTINS_PROG="$ROOT/tests/wam_extra_builtins.pl"
out="$(run_prog_out "$WAM_EXTRA_BUILTINS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_extra_builtins"
fi
WAM_EXC_PROG="$ROOT/tests/wam_exceptions.pl"
out="$(run_prog_out "$WAM_EXC_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_exceptions"
fi
WAM_COPY_VARS_PROG="$ROOT/tests/wam_copy_vars.pl"
out="$(run_prog_out "$WAM_COPY_VARS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_copy_vars"
fi
WAM_CONV_PROG="$ROOT/tests/wam_conversions.pl"
out="$(run_prog_out "$WAM_CONV_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_conversions"
fi
WAM_BAGOF_PROG="$ROOT/tests/wam_bagof_setof.pl"
out="$(run_prog_out "$WAM_BAGOF_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_bagof_setof"
fi
WAM_OPS_PROG="$ROOT/tests/wam_ops.pl"
out="$(run_prog_out "$WAM_OPS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_ops"
fi
WAM_FLAGS_PROG="$ROOT/tests/wam_flags.pl"
out="$(run_prog_out "$WAM_FLAGS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_flags"
fi
WAMVM_FLAGS_PROG="$ROOT/tests/wamvm_flags.pl"
out="$(run_prog_out "$WAMVM_FLAGS_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wamvm_flags"
fi
WAMVM_VM_SMOKE_PROG="$ROOT/tests/wamvm_vm_smoke.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_VM_SMOKE_PROG" -g 'findall(X,p(X),Xs).' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "Xs = [1,2]" ]]; then
echo "--- expected ---" >&2
echo "Xs = [1,2]" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wamvm_vm_smoke"
fi
WAMVM_FINDALL_VAR_GOAL_PROG="$ROOT/tests/wamvm_findall_var_goal_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_FINDALL_VAR_GOAL_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wamvm_findall_var_goal_vm"
fi
WAMVM_SELECT1_VM_PROG="$ROOT/tests/wamvm_select1_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_SELECT1_VM_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wamvm_select1_vm"
fi
WAMVM_TERM_OPS_VM_PROG="$ROOT/tests/wamvm_term_ops_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_TERM_OPS_VM_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wamvm_term_ops_vm"
fi
WAMVM_EXCEPTIONS_VM_PROG="$ROOT/tests/wamvm_exceptions_vm.pl"
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm -f "$WAMVM_EXCEPTIONS_VM_PROG" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wamvm_exceptions_vm"
fi
# VM-only compatibility pass: ensure common WAM-era ISO tests run entirely in WAMVM mode.
for f in "$ROOT"/tests/wam_*.pl; do
base="$(basename "$f")"
case "$base" in
wam_flags.pl|wam_tabling.pl) continue ;;
esac
out="$(FLOWLOG_WAMVM_REQUIRE_VM=1 "$BIN" --threads 2 --engine wamvm --parallel-profile off -f "$f" -g 'ok.' | tr -d '\r')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wamvm_vm_only_${base%.pl}"
fi
done
WAM_REFL_PROG="$ROOT/tests/wam_reflective.pl"
out="$(run_prog_out "$WAM_REFL_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_reflective"
fi
WAM_PROP_PROG="$ROOT/tests/wam_properties.pl"
out="$(run_prog_out "$WAM_PROP_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_properties"
fi
WAM_DYN_DB_PROG="$ROOT/tests/wam_dynamic_db.pl"
out="$(run_prog_out "$WAM_DYN_DB_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_dynamic_db"
fi
WAM_IO_PROG="$ROOT/tests/wam_stream_io.pl"
out="$(run_prog_out "$WAM_IO_PROG" 'ok.')"
out="${out%$'\n'}"
if [[ "$out" != "true" ]]; then
echo "--- expected ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "wam_stream_io"
fi
echo "ok"tests/run_ctrld_repl.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$ROOT" -s >/dev/null
BIN="$ROOT/flowlog"
fail() {
echo "fail: $1" >&2
exit 1
}
tmpdir="$(mktemp -d -t flowlog_ctrld_repl.XXXXXX)"
cleanup() {
rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT
tester="$tmpdir/ctrld_repl_tty"
cc -O2 -Wall -Wextra "$ROOT/tests/repl_ctrld_tty.c" -lutil -o "$tester" || fail "ctrld_repl_build"
"$tester" "$BIN" >/dev/null 2>&1 || fail "ctrld_repl_exit"
echo "ok"tests/run_bracket_consult_repl.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN="$ROOT/flowlog"
make -C "$ROOT" -s >/dev/null
out="$(
printf '%s\n' \
'[bench/nqueens_length].' \
'queens_solutions(4,2).' \
'halt.' \
| "$BIN" --threads 1 \
| tr -d '\r'
)"
if ! printf '%s\n' "$out" | grep -Eq '^true$'; then
echo "--- expected output containing ---" >&2
echo "true" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
exit 1
fitests/run_inria_suite.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FLOWLOG_BIN="${FLOWLOG_BIN:-$ROOT/flowlog}"
FLOWLOG_TIMEOUT="${FLOWLOG_TIMEOUT:-120s}"
FLOWLOG_ENGINE="${FLOWLOG_ENGINE:-interp}"
FLOWLOG_PARALLEL_PROFILE="${FLOWLOG_PARALLEL_PROFILE:-off}"
# By default, suppress per-test stderr noise (the INRIA suite intentionally
# triggers a couple of "unknown procedure" warnings when `unknown=warning`).
# Set to 1 to see stderr for every file.
FLOWLOG_INRIA_SHOW_STDERR="${FLOWLOG_INRIA_SHOW_STDERR:-0}"
if [[ ! -x "$FLOWLOG_BIN" ]]; then
make -C "$ROOT" >/dev/null
fi
suite_dir="$ROOT/build/inria_suite/inriasuite"
suite_driver="$suite_dir/inriasuite.pl"
suite_wrapper="$ROOT/build/inria_suite/flowlog_inriasuite.pl"
if [[ ! -f "$suite_driver" || ! -f "$suite_wrapper" ]]; then
"$ROOT/tests/setup_inria_suite.sh" >/dev/null
fi
if [[ ! -f "$suite_driver" || ! -f "$suite_wrapper" ]]; then
echo "inria: suite not set up (missing: $suite_driver and/or $suite_wrapper)" >&2
exit 1
fi
mapfile -t files < <(
rg '^file\(' "$suite_driver" \
| sed -E 's/^file\((.*)\)\..*/\1/' \
| sed -E 's/[[:space:]]+$//'
)
failures=0
total_files=0
echo "INRIA suite file-by-file, timeout=$FLOWLOG_TIMEOUT"
err_tmp=""
if [[ "$FLOWLOG_INRIA_SHOW_STDERR" == "0" || "$FLOWLOG_INRIA_SHOW_STDERR" == "off" || "$FLOWLOG_INRIA_SHOW_STDERR" == "false" ]]; then
err_tmp="$(mktemp "$ROOT/build/inria_suite/flowlog_inria_stderr.XXXXXX")"
trap 'rm -f "$err_tmp"' EXIT
fi
for f in "${files[@]}"; do
[[ -n "$f" ]] || continue
total_files=$((total_files + 1))
out=""
st=0
set +e
if [[ -n "$err_tmp" ]]; then
: >"$err_tmp"
out="$(
timeout "$FLOWLOG_TIMEOUT" \
"$FLOWLOG_BIN" --engine "$FLOWLOG_ENGINE" --parallel-profile "$FLOWLOG_PARALLEL_PROFILE" \
"$suite_wrapper" -g "run_tests_score($f,T,W)." 2>"$err_tmp"
)"
else
out="$(
timeout "$FLOWLOG_TIMEOUT" \
"$FLOWLOG_BIN" --engine "$FLOWLOG_ENGINE" --parallel-profile "$FLOWLOG_PARALLEL_PROFILE" \
"$suite_wrapper" -g "run_tests_score($f,T,W)."
)"
fi
st=$?
set -e
if [[ "$st" -ne 0 ]]; then
echo " $f: exit=$st" >&2
if [[ -n "$err_tmp" && -s "$err_tmp" ]]; then
sed -e 's/^/ /' "$err_tmp" >&2
fi
failures=$((failures + 1))
continue
fi
t="$(printf '%s' "$out" | sed -nE 's/.*T = ([0-9]+).*/\1/p' | head -n 1 || true)"
w="$(printf '%s' "$out" | sed -nE 's/.*W = ([0-9]+).*/\1/p' | head -n 1 || true)"
if [[ -z "$t" || -z "$w" ]]; then
echo " $f: unable to parse output: $out" >&2
if [[ -n "$err_tmp" && -s "$err_tmp" ]]; then
sed -e 's/^/ /' "$err_tmp" >&2
fi
failures=$((failures + 1))
continue
fi
if [[ "$w" != "0" ]]; then
echo " $f: wrong=$w/$t" >&2
if [[ -n "$err_tmp" && -s "$err_tmp" ]]; then
sed -e 's/^/ /' "$err_tmp" >&2
fi
failures=$((failures + 1))
fi
done
if [[ "$failures" -eq 0 ]]; then
echo "OK: $total_files files, 0 failing"
else
echo "FAIL: $failures/$total_files files failing" >&2
exit 1
fitests/run_orpar_nesting.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$ROOT" -s >/dev/null
BIN="$ROOT/flowlog"
fail() {
echo "fail: $1" >&2
exit 1
}
tmpdir="$(mktemp -d -t flowlog_orpar_nesting.XXXXXX)"
cleanup() {
rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT
log="$tmpdir/orpar.log"
# Use a small deterministic goal that triggers nested select/3 OR-par:
# - outer select_: 9 alternatives (spawns OR-par)
# - inner select_: 8 alternatives within an OR-par chunk (requires nesting)
out="$(
FLOWLOG_DEBUG_ORPAR=all \
FLOWLOG_DEBUG_ORPAR_LOG="$log" \
"$BIN" --threads 10 -g \
'set_prolog_flag(flowlog_parallel_profile, fast), set_prolog_flag(flowlog_parallel_or_nesting, 1), countall((select_(X,[1,2,3,4,5,6,7,8,9],R), select_(Y,R,_)), C).' \
| tr -d '\r'
)"
out="${out%$'\n'}"
if [[ "$out" != "C = 72" ]]; then
echo "--- expected ---" >&2
echo "C = 72" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "orpar_nesting_output"
fi
starts="$(grep -c '^flowlog: ORPAR start' "$log" 2>/dev/null || true)"
if [[ "${starts:-0}" -lt 2 ]]; then
echo "--- expected ORPAR starts >= 2 ---" >&2
echo "--- got log ---" >&2
cat "$log" >&2 || true
fail "orpar_nesting_missing_nested_start"
fi
echo "ok"tests/run_orpar_nesting_wam.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$ROOT" -s >/dev/null
BIN="$ROOT/flowlog"
fail() {
echo "fail: $1" >&2
exit 1
}
tmpdir="$(mktemp -d -t flowlog_orpar_nesting_wam.XXXXXX)"
cleanup() {
rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT
log="$tmpdir/orpar.log"
# Use a small deterministic goal that triggers nested select/3 OR-par:
# - outer select_: 9 alternatives (spawns OR-par)
# - inner select_: 8 alternatives within an OR-par chunk (requires nesting)
out="$(
FLOWLOG_DEBUG_ORPAR=all \
FLOWLOG_DEBUG_ORPAR_LOG="$log" \
"$BIN" --threads 10 --engine wam --parallel-profile fast -g \
'set_prolog_flag(flowlog_parallel_or_nesting, 1), countall((select_(X,[1,2,3,4,5,6,7,8,9],R), select_(Y,R,_)), C).' \
| tr -d '\r'
)"
out="${out%$'\n'}"
if [[ "$out" != "C = 72" ]]; then
echo "--- expected ---" >&2
echo "C = 72" >&2
echo "--- got ---" >&2
printf '%s\n' "$out" >&2
fail "orpar_nesting_output_wam"
fi
starts="$(grep -c '^flowlog: ORPAR start' "$log" 2>/dev/null || true)"
if [[ "${starts:-0}" -lt 2 ]]; then
echo "--- expected ORPAR starts >= 2 ---" >&2
echo "--- got log ---" >&2
cat "$log" >&2 || true
fail "orpar_nesting_missing_nested_start_wam"
fi
echo "ok"tests/run_siginfo_key_repl.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$ROOT" -s >/dev/null
BIN="$ROOT/flowlog"
fail() {
echo "fail: $1" >&2
exit 1
}
tmpdir="$(mktemp -d -t flowlog_siginfo_key.XXXXXX)"
cleanup() {
rm -rf "$tmpdir" >/dev/null 2>&1 || true
}
trap cleanup EXIT
tester="$tmpdir/siginfo_key_tty"
cc -O2 -Wall -Wextra "$ROOT/tests/repl_siginfo_key_tty.c" -lutil -o "$tester" || fail "siginfo_key_build"
"$tester" "$BIN" >/dev/null 2>&1 || fail "siginfo_key_output"
echo "ok"tests/run_siginfo_repl.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$ROOT" -s >/dev/null
BIN="$ROOT/flowlog"
fail() {
echo "fail: $1" >&2
exit 1
}
if ! kill -l INFO >/dev/null 2>&1; then
echo "skip (no SIGINFO)"
exit 0
fi
out_file="$(mktemp -t flowlog_siginfo_repl.XXXXXX)"
cleanup() {
rm -f "$out_file"
}
trap cleanup EXIT
coproc "$BIN" --threads 2 2>&1
pid="$COPROC_PID"
infd="${COPROC[1]}"
outfd="${COPROC[0]}"
printf 'write(start), nl, flush_output, repeat, fail.\n' >&"$infd"
seen_start=0
for ((i = 0; i < 200; i++)); do
if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
line="${line%$'\r'}"
printf '%s\n' "$line" >>"$out_file"
if [[ "$line" == "start" ]]; then
seen_start=1
break
fi
fi
done
if [[ "$seen_start" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
fail "siginfo_repl_no_start"
fi
kill -INFO "$pid" 2>/dev/null || kill -SIGINFO "$pid" 2>/dev/null || true
seen_info=0
seen_pred=0
timeouts_after_info=0
for ((i = 0; i < 400; i++)); do
if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
line="${line%$'\r'}"
printf '%s\n' "$line" >>"$out_file"
timeouts_after_info=0
if [[ "$line" == "Flowlog SIGINFO (status)" ]]; then
seen_info=1
elif [[ "$line" == " predicate:"* ]]; then
seen_pred=1
fi
continue
else
rc=$?
if [[ "$rc" -eq 142 ]]; then
if [[ "$seen_info" -eq 1 ]]; then
timeouts_after_info=$((timeouts_after_info + 1))
if [[ "$timeouts_after_info" -ge 5 ]]; then
break
fi
fi
continue
fi
break
fi
done
if [[ "$seen_info" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
fail "siginfo_repl_no_status"
fi
if [[ "$seen_pred" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
fail "siginfo_repl_no_predicate"
fi
# Cancel the running query and ensure the REPL continues to accept input.
kill -INT "$pid"
printf 'true.\n' >&"$infd"
# Wait until the REPL produces output for the next query before closing stdin.
seen_true=0
for ((i = 0; i < 400; i++)); do
if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
line="${line%$'\r'}"
printf '%s\n' "$line" >>"$out_file"
if [[ "$line" == "true" ]]; then
seen_true=1
break
fi
continue
else
rc=$?
if [[ "$rc" -eq 142 ]]; then
continue
fi
break
fi
done
if [[ "$seen_true" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
echo "--- expected output containing ---" >&2
echo "true" >&2
echo "--- got ---" >&2
cat "$out_file" >&2
fail "siginfo_repl_missing_true"
fi
printf 'halt.\n' >&"$infd"
exec {infd}>&-
seen_eof=0
for ((i = 0; i < 400; i++)); do
if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
line="${line%$'\r'}"
printf '%s\n' "$line" >>"$out_file"
continue
else
rc=$?
if [[ "$rc" -eq 142 ]]; then
continue
fi
seen_eof=1
break
fi
done
if [[ "$seen_eof" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
fail "siginfo_repl_timeout"
fi
wait "$pid" 2>/dev/null || true
{ exec {outfd}<&-; } 2>/dev/null || true
echo "ok"tests/run_sigint_repl.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$ROOT" -s >/dev/null
BIN="$ROOT/flowlog"
fail() {
echo "fail: $1" >&2
exit 1
}
out_file="$(mktemp -t flowlog_sigint_repl.XXXXXX)"
cleanup() {
rm -f "$out_file"
}
trap cleanup EXIT
coproc "$BIN" --threads 2 2>&1
pid="$COPROC_PID"
infd="${COPROC[1]}"
outfd="${COPROC[0]}"
# An infinite loop query (ISO: repeat/0 + fail/0) gives us time to deliver SIGINT.
# Print and flush a sentinel so we know the query is executing (not still reading input).
printf 'write(start), nl, flush_output, repeat, fail.\n' >&"$infd"
seen_start=0
for ((i = 0; i < 200; i++)); do
if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
line="${line%$'\r'}"
printf '%s\n' "$line" >>"$out_file"
if [[ "$line" == "start" ]]; then
seen_start=1
break
fi
fi
done
if [[ "$seen_start" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
fail "sigint_repl_no_start"
fi
kill -INT "$pid"
# The REPL should stay alive, keep the loaded program, and accept more queries.
printf 'true.\n' >&"$infd"
# Wait until the REPL produces output for the next query before closing stdin.
seen_true=0
for ((i = 0; i < 400; i++)); do
if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
line="${line%$'\r'}"
printf '%s\n' "$line" >>"$out_file"
if [[ "$line" == "true" ]]; then
seen_true=1
break
fi
continue
else
rc=$?
if [[ "$rc" -eq 142 ]]; then
continue
fi
break
fi
done
if [[ "$seen_true" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
echo "--- expected output containing ---" >&2
echo "true" >&2
echo "--- got ---" >&2
cat "$out_file" >&2
fail "sigint_repl_missing_true"
fi
printf 'halt.\n' >&"$infd"
exec {infd}>&-
# Read output until EOF (process exit), with an overall timeout.
seen_eof=0
for ((i = 0; i < 400; i++)); do
if IFS= read -r -t 0.05 -u "$outfd" line 2>/dev/null; then
line="${line%$'\r'}"
printf '%s\n' "$line" >>"$out_file"
continue
else
rc=$?
if [[ "$rc" -eq 142 ]]; then
continue
fi
seen_eof=1
break
fi
done
if [[ "$seen_eof" -ne 1 ]]; then
kill -KILL "$pid" 2>/dev/null || true
fail "sigint_repl_timeout"
fi
wait "$pid" 2>/dev/null || true
{ exec {outfd}<&-; } 2>/dev/null || true
echo "ok"tests/setup_inria_suite.sh
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SUITE_URL="${FLOWLOG_INRIA_SUITE_URL:-http://pauillac.inria.fr/~deransar/prolog/inriasuite.tar.gz}"
DEST_DIR="$ROOT/build/inria_suite"
TARBALL="$DEST_DIR/inriasuite.tar.gz"
SUITE_DIR="$DEST_DIR/inriasuite"
DRIVER="$SUITE_DIR/inriasuite.pl"
WRAPPER="$DEST_DIR/flowlog_inriasuite.pl"
force=0
case "${1:-}" in
"" ) ;;
--force ) force=1 ;;
* )
echo "usage: $0 [--force]" >&2
exit 2
;;
esac
mkdir -p "$DEST_DIR"
if [[ "$force" -eq 1 || ! -f "$TARBALL" ]]; then
echo "inria: fetching: $SUITE_URL" >&2
if command -v fetch >/dev/null 2>&1; then
fetch -o "$TARBALL" "$SUITE_URL"
elif command -v curl >/dev/null 2>&1; then
curl -fsSL -o "$TARBALL" "$SUITE_URL"
elif command -v wget >/dev/null 2>&1; then
wget -qO "$TARBALL" "$SUITE_URL"
else
echo "inria: cannot fetch suite (need one of: fetch, curl, wget)" >&2
exit 1
fi
fi
if [[ "$force" -eq 1 || ! -d "$SUITE_DIR" || ! -f "$DRIVER" ]]; then
rm -rf "$SUITE_DIR" >/dev/null 2>&1 || true
tar -xzf "$TARBALL" -C "$DEST_DIR"
fi
if [[ ! -f "$DRIVER" ]]; then
echo "inria: driver not found after extract: $DRIVER" >&2
exit 1
fi
# Known INRIA-suite expectations that diverge from modern ISO implementations
# (confirmed with Trealla and Scryer). Patch these locally so `tests/run_inria_suite.sh`
# can be used as a regression suite for Flowlog's ISO behavior.
patch_inria_suite() {
local tmp
if [[ -f "$SUITE_DIR/catch-and-throw" ]]; then
tmp="$(mktemp "$DEST_DIR/.catch-and-throw.XXXXXX")"
awk -f - "$SUITE_DIR/catch-and-throw" >"$tmp" <<'AWK'
{
if ($0 ~ /throw\(blabla\)/ && $0 ~ /system_error/) {
gsub(/system_error/, "unexpected_ball(blabla)")
}
print
}
AWK
mv "$tmp" "$SUITE_DIR/catch-and-throw"
fi
if [[ -f "$SUITE_DIR/sub_atom" ]]; then
tmp="$(mktemp "$DEST_DIR/.sub_atom.XXXXXX")"
awk -f - "$SUITE_DIR/sub_atom" >"$tmp" <<'AWK'
BEGIN { in_block = 0 }
in_block == 0 && $0 ~ /^[[:space:]]*\[sub_atom\('ab', Before, Length, After, Sub_atom\),/ {
print "[sub_atom('ab', Before, Length, After, Sub_atom),"
print "\t[[Before <-- 0, Length <-- 0, Sub_atom <-- ''],"
print "\t [Before <-- 0, Length <-- 1, Sub_atom <-- 'a'],"
print "\t [Before <-- 0, Length <-- 2, Sub_atom <-- 'ab'],"
print "\t [Before <-- 1, Length <-- 0, Sub_atom <-- ''],"
print "\t [Before <-- 1, Length <-- 1, Sub_atom <-- 'b'],"
print "\t [Before <-- 2, Length <-- 0, Sub_atom <-- '']]]."
in_block = 1
next
}
in_block == 1 {
if ($0 ~ /\]\]\]\./) {
in_block = 0
}
next
}
{ print }
AWK
mv "$tmp" "$SUITE_DIR/sub_atom"
fi
}
patch_inria_suite
suite_abs="$(cd "$SUITE_DIR" && pwd)"
suite_abs_slash="${suite_abs%/}/"
{
printf '%% Auto-generated by tests/setup_inria_suite.sh. Do not edit.\n'
printf '%% Source: %s\n\n' "$SUITE_URL"
cat <<'EOF'
% The INRIA suite driver is intended to be “vanilla ISO Prolog”, but its harness
% uses a couple of common library predicates that are not ISO/IEC 13211-1 core.
% Provide tiny reference definitions so the suite can run without depending on
% system-specific libraries/modules.
append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).
% Flowlog-specific harness overrides
%
% The upstream INRIA harness uses recursion for `loop_through/2`, which can
% tickle edge cases in some embedded runtimes. Use a repeat/fail loop instead.
%
% It also collects substitutions for *all* goal variables, but the INRIA test
% expectations typically mention only the variables of interest (via `<--/2`).
% Derive the variable set from the Expected substitutions to match the suite.
loop_through(F, S) :-
repeat,
catch(read(S, X), B, X = B),
( X = end_of_file ->
!
; reset_flags,
( test(F, X) -> true ; update_score(F, non_null, non_null) ),
fail
).
test(F, [G, Expected]) :-
!,
result_expected(G, Expected, R),
compare_subst_lists(R, Expected, Extra, Missing),
write_if_wrong(F, G, Expected, Extra, Missing),
update_score(F, Missing, Extra).
test(F, [G, ProgFile, Expected]) :-
!,
[ProgFile],
result_expected(G, Expected, R),
compare_subst_lists(R, Expected, Extra, Missing),
write_if_wrong(F, G, Expected, Extra, Missing),
update_score(F, Missing, Extra).
result_expected(G, Expected, Res) :-
expected_lhs_vars(Expected, Want),
vars_in_term(G, GoalVars),
filter_vars_eq(GoalVars, Want, Vars),
get_all_subs_vars(G, Vars, Subs),
special_ans_forms(Subs, Res).
expected_lhs_vars(Expected, Vars) :-
( Expected = [_|_] ; Expected == [] ),
!,
expected_lhs_vars_list(Expected, [], Vars).
expected_lhs_vars(_Expected, []).
expected_lhs_vars_list([], Vars, Vars).
expected_lhs_vars_list([Set|Rest], Vars0, Vars) :-
expected_lhs_vars_set(Set, Vars0, Vars1),
expected_lhs_vars_list(Rest, Vars1, Vars).
expected_lhs_vars_set([], Vars, Vars).
expected_lhs_vars_set([Sub|Rest], Vars0, Vars) :-
( Sub = <--(V, Val) ->
vars_in_term(V, Vars0, Vars1),
vars_in_term(Val, Vars1, Vars2)
; Vars2 = Vars0
),
expected_lhs_vars_set(Rest, Vars2, Vars).
var_member_eq(V, [H|_]) :- V == H, !.
var_member_eq(V, [_|T]) :- var_member_eq(V, T).
filter_vars_eq([], _Set, []).
filter_vars_eq([V|Vs], Set, [V|Out]) :-
var_member_eq(V, Set),
!,
filter_vars_eq(Vs, Set, Out).
filter_vars_eq([_|Vs], Set, Out) :- filter_vars_eq(Vs, Set, Out).
get_all_subs_vars(G, Vars, AllSubs) :-
copy_term(Vars-G, Vars2-G2),
findall(Ans, protect_call_vars(G2, Vars2, Ans), AnsList),
list_make_subs_list(Vars, AnsList, AllSubs).
protect_call_vars(G, Vars2, R) :-
catch((call(G), R = Vars2), B, extract_error(B, R)).
EOF
printf '\n\n'
cat "$DRIVER"
printf '\n\n'
printf "inria_suite_dir('%s').\n\n" "$suite_abs_slash"
cat <<'EOF'
run_tests_score(Test, T, W) :-
inria_suite_dir(Dir),
atom_concat(Dir, Test, Path),
run_tests(Path),
score(Path, total(T), wrong(W)).
EOF
} >"$WRAPPER"
echo "inria: ready: $WRAPPER" >&2tests/wam_bagof_setof.pl
:- set_prolog_flag(flowlog_engine, wam).
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).
ok :-
bagof(X, member(X, [b,a]), Bag0),
Bag0 == [b,a],
setof(X, member(X, [b,a,a]), Set0),
Set0 == [a,b],
Ps = [p(1,a), p(1,b), p(2,c)],
findall(Y-Bag, bagof(X, member(p(Y,X), Ps), Bag), Groups),
Groups == [1-[a,b], 2-[c]],
bagof(X, Y^member(p(Y,X), Ps), Bag1),
Bag1 == [a,b,c],
\+ bagof(_, member(_, []), _),
\+ setof(_, member(_, []), _).tests/wam_control_constructs.pl
% WAM engine: ISO control constructs (;)/2 and (->)/2.
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
p(X) :- (X = 1 ; X = 2).
p_cut(X) :- (X = 1, ! ; X = 2).
p_ite(X, Y) :- (X = 1 -> Y = a ; Y = b).
p_ite_fail(Y) :- (fail -> Y = a ; Y = b).
p_if_then_only(Y) :- ((1 = 1) -> Y = a).
p_call(X) :- call(p(X)).
p_once(X) :- once(p(X)).
ok :-
findall(X, p(X), L1),
L1 == [1, 2],
findall(X, p_cut(X), L2),
L2 == [1],
findall(pair(X,Y), p_ite(X, Y), L3),
L3 == [pair(1, a)],
p_ite_fail(Y4),
Y4 == b,
p_if_then_only(Y5),
Y5 == a,
findall(X, p_call(X), L4),
L4 == [1, 2],
findall(X, p_once(X), L5),
L5 == [1].tests/wam_conversions.pl
:- set_prolog_flag(flowlog_engine, wam).
len([], 0).
len([_|T], N) :- len(T, N0), N is N0 + 1.
ok :-
% char_code/2
char_code('a', 97),
char_code('a', C1),
C1 == 97,
char_code(Ch, 97),
Ch == 'a',
\+ char_code('a', 98),
catch(char_code(_,_), Ecc0, true),
Ecc0 == error(instantiation_error, char_code/2),
catch(char_code(97,_), Ecc1, true),
Ecc1 == error(type_error(character, 97), char_code/2),
catch(char_code(_, 256), Ecc2, true),
Ecc2 == error(representation_error(character_code), char_code/2),
% atom_length/2
atom_length(hello, 5),
atom_length(hello, L0),
L0 == 5,
\+ atom_length(hello, 6),
catch(atom_length(_, _), Eal0, true),
Eal0 == error(instantiation_error, atom),
% atom_concat/3 (both directions)
atom_concat(he, llo, hello),
atom_concat(he, llo, C2),
C2 == hello,
findall(A-B, atom_concat(A, B, ab), Splits),
Splits == [''-ab, a-b, ab-''],
catch(atom_concat(_, _, _), Eac0, true),
Eac0 == error(instantiation_error, atom_concat/3),
% sub_atom/5
sub_atom(abc, 0, 1, 2, a),
sub_atom(abc, 1, 1, 1, b),
sub_atom(abc, 2, 1, 0, c),
findall(S, sub_atom(ab, _, _, _, S), Subs),
len(Subs, 6),
% atom_chars/2 and atom_codes/2
atom_chars(abc, [a,b,c]),
atom_chars(abc, Chs0),
Chs0 == [a,b,c],
atom_chars(A3, [a,b]),
A3 == ab,
catch(atom_chars(_, [_|_]), Echars0, true),
Echars0 == error(instantiation_error, atom_chars/2),
catch(atom_chars(abc, [97]), Echars1, true),
Echars1 == error(type_error(character, 97), atom_chars/2),
atom_codes(abc, [97,98,99]),
atom_codes(abc, Codes0),
Codes0 == [97,98,99],
atom_codes(A4, [97,98]),
A4 == ab,
catch(atom_codes(abc, [a,98,99]), Ecodes0, true),
Ecodes0 == error(type_error(integer, a), atom_codes/2),
catch(atom_codes(_, [-1]), Ecodes1, true),
Ecodes1 == error(representation_error(character_code), atom_codes/2),
% number_chars/2 and number_codes/2
number_chars(123, ['1','2','3']),
number_chars(123, NChs0),
NChs0 == ['1','2','3'],
number_chars(N1, ['4','2']),
N1 == 42,
catch(number_chars(_, _), Enc0, true),
Enc0 == error(instantiation_error, number_chars/2),
catch(number_chars(a, _), Enc1, true),
Enc1 == error(type_error(number, a), number_chars/2),
catch(number_chars(_, [x]), Enc2, true),
Enc2 == error(syntax_error(illegal_number), number_chars/2),
number_codes(42, [52,50]),
number_codes(42, NCodes0),
NCodes0 == [52,50],
number_codes(N2, [45,49]),
N2 == -1.tests/wam_copy_vars.pl
:- set_prolog_flag(flowlog_engine, wam).
ok :-
% copy_term/2 preserves sharing and makes fresh variables.
copy_term(foo(X,Y), T1),
T1 = foo(A,B),
A \== B,
copy_term(foo(X,X), T2),
T2 = foo(C,D),
C == D,
copy_term(X, Y),
X \== Y,
X = foo(Z),
copy_term(X, Y2),
Y2 = foo(A2),
var(A2),
A2 \== Z,
% term_variables/2 follows bindings and returns unique vars in order.
X3 = foo(U,V),
term_variables(X3, Vars0),
Vars0 == [U,V],
X4 = foo(W),
X5 = X4,
term_variables(X5, Vars1),
Vars1 == [W],
% numbervars/3 binds vars to '$VAR'(N) terms.
numbervars(foo(N1,N2), 0, End),
N1 == '$VAR'(0),
N2 == '$VAR'(1),
End == 2,
% unify_with_occurs_check/2 rejects cyclic bindings.
\+ unify_with_occurs_check(Q, f(Q)),
unify_with_occurs_check(P, R),
P == R,
% subsumes_term/2 uses rigid spec variables (Spec must be an instance of Gen).
subsumes_term(foo(S,1), foo(2,1)),
\+ subsumes_term(foo(_S,1), foo(2,2)),
subsumes_term(foo(S2,S2), foo(1,1)),
\+ subsumes_term(foo(S3,S3), foo(1,2)),
\+ subsumes_term(f(_), X6).tests/wam_dynamic_db.pl
% WAM-lite dynamic database predicates (assert*/retract*/abolish/clause) smoke tests.
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
ok :-
set_prolog_flag(unknown, fail),
% Start from a clean slate (undefined predicates are ok for retractall/1).
retractall(p(_)),
retractall(q(_)),
asserta(p(a)),
assertz(p(b)),
findall(X, p(X), Xs),
Xs = [a, b],
retract(p(a)),
findall(X, p(X), Xs2),
Xs2 = [b],
assertz((q(b) :- p(b))),
clause(q(b), Body),
Body = p(b),
abolish(q/1),
\+ current_predicate(q/1),
!.tests/wam_engine_smoke.pl
% Smoke tests for the WAM-lite execution engine.
% Standalone Flowlog defaults to WAM; the directive below keeps this file stable
% if defaults change in the future.
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
fact(0, 1).
fact(N, F) :-
N > 0,
N1 is N - 1,
fact(N1, F1),
F is N * F1.
p(1).
p(2).
count(Count) :-
findall(1, p(_), Ones),
length(Ones, Count).
ok :-
% length/2 should be able to instantiate an open list tail when N is fixed.
length([a|T], 3),
T = [_,_],
fact(5, F),
F = 120,
count(C),
C = 2.tests/wam_exceptions.pl
:- set_prolog_flag(flowlog_engine, wam).
ok :-
% Basic throw/catch.
catch(throw(foo(a)), foo(X), X == a),
% throw/1 argument must be nonvar -> instantiation_error.
catch(throw(_), E1, E1 = error(instantiation_error, nonvar)),
% Catcher can bind to a thrown term containing fresh variables.
catch(throw(foo(_X)), E2, (E2 = foo(V), var(V))),
% Inner catch does not catch mismatched exception; it must propagate.
catch(catch(throw(bar), foo(_), true), bar, true),
% Recover goal exceptions are not caught by the original catch/3.
catch(catch(throw(foo), foo, throw(baz)), baz, true).tests/wam_extra_builtins.pl
:- set_prolog_flag(flowlog_engine, wam).
p.
ok :-
bounds_possible([1, 2, 3, 4], 2, 3),
\+ bounds_possible([1, 2, 3, 4], 2, 100),
current_rule(true/0),
current_rule(p/0),
\+ current_rule(no_such_pred/0),
flowlog_cpuproc_id(CP), integer(CP),
flowlog_n_cpuproc(NCP), integer(NCP),
flowlog_in_cpuproc(In), (In == true ; In == false),
flowlog_taskjob_id(TJ), integer(TJ),
flowlog_n_taskjob(NTJ), integer(NTJ),
flowlog_last_tc_cp_mask(Mask), integer(Mask),
flowlog_last_tc_cp_count(Cnt), integer(Cnt),
flowlog_last_tc_worker_count(WC), integer(WC),
flowlog_last_tc_start_ok(Ok), integer(Ok),
flowlog_last_tc_start_error_code(EC), integer(EC),
flowlog_last_tc_start_error_text(ET), atom(ET),
flowlog_db_epochs(CE, IE), integer(CE), integer(IE),
flowlog_and_safe(p/0, Safe0), (Safe0 == true ; Safe0 == false),
flowlog_and_safe_goal(p, SafeG0), (SafeG0 == true ; SafeG0 == false),
current_output(S0),
open('/dev/null', write, S),
set_output(S),
listing,
listing(p),
set_output(S0),
close(S).tests/wam_flags.pl
% WAM-lite ISO prolog_flag + char_conversion coverage.
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
:- catch(set_prolog_flag(occurs_check, true), _, true).
ok :-
current_prolog_flag(flowlog_engine_active, Active),
Active == wam,
current_prolog_flag(occurs_check, OC0),
OC0 == true,
\+ (X = f(X)),
current_prolog_flag(max_arity, A),
A == 255,
set_prolog_flag(unknown, fail),
current_prolog_flag(unknown, U),
U == fail,
set_prolog_flag(unknown, error),
set_prolog_flag(char_conversion, on),
current_prolog_flag(char_conversion, CC),
CC == on,
char_conversion(a, b),
current_char_conversion(a, b),
current_prolog_flag(Name1, _),
Name1 == unknown,
!,
current_prolog_flag(Name2, _),
Name2 == char_conversion,
!.tests/wam_ops.pl
:- set_prolog_flag(flowlog_engine, wam).
ok :-
op(500, xfx, foo),
current_op(500, xfx, foo),
findall(P, current_op(P, xfx, foo), Ps),
Ps == [500],
catch(op(500, xfx, ','), E0, true),
E0 == error(permission_error(modify, operator, ','), op/3),
catch(op(500, xfx, []), E1, true),
E1 == error(permission_error(create, operator, []), op/3),
catch(op(500, xfx, '|'), E2, true),
E2 == error(permission_error(create, operator, '|'), op/3),
catch(op(1201, xfx, bar), E3, true),
E3 == error(domain_error(operator_priority, 1201), op/3),
catch(op(500, zzz, bar), E4, true),
E4 == error(domain_error(operator_specifier, zzz), op/3).tests/wam_properties.pl
% WAM-lite predicate_property/2 + evaluable_property/2 smoke tests.
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
p(a).
ok :-
predicate_property(true, built_in),
predicate_property(true, static),
\+ predicate_property(true, dynamic),
predicate_property(p(_), static),
dynamic(foo/1),
predicate_property(foo(_), dynamic),
predicate_property(write(_), template(Tw)),
functor(Tw, write, 1),
evaluable_property(+(1, 2), template(T, R)),
functor(T, '+', 2),
arg(1, T, A), A == number,
arg(2, T, B), B == number,
R == number.tests/wam_reflective.pl
% WAM-lite reflective predicates (dynamic/current_predicate) smoke tests.
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
p(a).
q(a, b).
ok :-
dynamic(foo/1),
current_predicate(foo/1),
current_predicate(p/1),
current_predicate(q/2),
current_predicate(/(Name, 1)),
Name == p,
!.tests/wam_sorting.pl
:- set_prolog_flag(flowlog_engine, wam).
ok :-
sort([2, 1, 1], S),
S == [1, 2],
msort([2, 1, 1], MS),
MS == [1, 1, 2],
keysort([2-b, 1-a, 1-c], KS),
KS == [1-a, 1-c, 2-b].tests/wam_stream_io.pl
% WAM-lite stream/I/O smoke tests.
ok :-
current_output(S0),
set_output(S0),
current_output(S1),
S0 == S1,
File = 'build/tmp_wam_io_test.txt',
open(File, write, S),
tab(S, 3),
write(S, hello),
nl(S),
flush_output(S),
close(S),
open(File, read, S2),
stream_property(S2, file_name(FN)),
FN == File,
set_stream(S2, alias(foo)),
stream_property(foo, alias(foo)),
close(S2),
Empty = 'build/tmp_wam_io_empty.txt',
open(Empty, write, S3),
close(S3),
open(Empty, read, S4),
at_end_of_stream(S4),
set_input(S4),
at_end_of_stream,
close(S4),
Txt = 'build/tmp_wam_io_text.txt',
open(Txt, write, TW),
put_code(TW, 65),
put_char(TW, b),
nl(TW),
close(TW),
open(Txt, read, TR),
peek_code(TR, C0), C0 == 65,
get_code(TR, C1), C1 == 65,
get_char(TR, Ch0), Ch0 == b,
unget_char(TR, b),
peek_char(TR, Ch1), Ch1 == b,
get_char(TR, Ch2), Ch2 == b,
get_code(TR, NLCode), NLCode == 10,
get_char(TR, EOF), EOF == end_of_file,
close(TR),
Bin = 'build/tmp_wam_io_bin.dat',
open(Bin, write, BW, [type(binary)]),
put_byte(BW, 255),
close(BW),
open(Bin, read, BR, [type(binary)]),
peek_byte(BR, B0), B0 == 255,
get_byte(BR, B1), B1 == 255,
unget_byte(BR, 255),
get_byte(BR, B2), B2 == 255,
get_byte(BR, B3), B3 == -1,
close(BR),
ReadFile = 'build/tmp_wam_read_term.pl',
open(ReadFile, write, RW),
write(RW, 'foo(a).'), nl(RW),
write(RW, 'bar(X,_).'), nl(RW),
close(RW),
open(ReadFile, read, RR),
read_term(RR, T1, []), T1 = foo(a),
read_term(RR, T2, [variables(Vs), variable_names(VNs), singletons(Ss)]),
functor(T2, bar, 2),
arg(1, T2, A1), var(A1),
arg(2, T2, A2), var(A2),
A1 \== A2,
Vs = [V1, V2], V1 == A1, V2 == A2,
VNs = [Name=Var], Name == 'X', Var == A1,
Ss = [Name2=Var2], Name2 == 'X', Var2 == A1,
read_term(RR, EOF2, []), EOF2 == end_of_file,
close(RR).tests/wam_sub_string.pl
:- set_prolog_flag(flowlog_engine, wam).
ok :-
sub_string(hello, 1, 3, 1, Sub),
Sub == ell,
findall(S, sub_string(ab, _, _, _, S), Ss),
sort(Ss, U),
U == ['', a, ab, b].tests/wam_tabling.pl
% WAM tabling smoke tests (call-by-variant).
:- catch(set_prolog_flag(flowlog_engine, wam), _, true).
:- table(path/2).
:- table(p/1).
edge(a,b).
edge(b,c).
edge(c,a).
edge(c,d).
path(X,Y) :- edge(X,Y).
path(X,Y) :- edge(X,Z), path(Z,Y).
% Left recursion: should terminate under tabling.
p(X) :- p(X).
p(1).
ok :-
current_prolog_flag(flowlog_engine_active, wam),
setof(X, path(a,X), Xs),
Xs = [a,b,c,d],
setof(Y, p(Y), Ys),
Ys = [1].tests/wam_term_ops.pl
:- set_prolog_flag(flowlog_engine, wam).
ok :-
functor(foo(a,b), N, A),
N == foo,
A == 2,
functor(T, foo, 2),
arg(1, T, X),
arg(2, T, Y),
var(X),
var(Y),
X \== Y,
T =.. [foo, X, Y],
foo(a,b) =.. L,
L = [foo, a, b],
5 =.. [5],
foo =.. [foo],
U =.. [foo, a, b],
U == foo(a,b),
compare('<', a, b),
compare('>', b, a),
compare('=', a, a),
compare(C, a, b),
C == '<',
\+ compare('>', a, b),
\+ arg(0, foo(a,b), _),
functor(T2, 5, 0),
T2 == 5.tests/wamvm_exceptions_vm.pl
% WAMVM exception/catch handling regression tests.
%
% Run with:
% FLOWLOG_WAMVM_REQUIRE_VM=1 ./flowlog --engine wamvm -f tests/wamvm_exceptions_vm.pl -g ok.
%
% This covers a subtle VM bug where catching an exception thrown inside a
% conjunction could corrupt the exception term (due to premature release of
% query-temporary allocations).
:- catch(set_prolog_flag(flowlog_engine, wamvm), _, true).
ok :-
% Catch an exception thrown from within a conjunction: the caught term must
% remain valid and matchable by the catcher pattern.
catch((call(asserta(_)), R0 = []), error(instantiation_error, _), R0 = instantiation_error),
R0 == instantiation_error,
% Type errors must preserve their structure/culprit.
catch(call(asserta((foo :- 4))), error(type_error(callable, 4), _), true),
% Database op errors should be catchable and stable too.
catch(abolish(foo/a), error(type_error(integer, a), _), true),
catch(abolish(foo/ -1), error(domain_error(not_less_than_zero, -1), _), true).tests/wamvm_findall_var_goal_vm.pl
% WAMVM VM test: `findall/3` where the goal is passed via a variable.
p(1).
p(2).
p(3).
count_solutions(Goal, Count) :-
findall(1, Goal, Ones),
length(Ones, Count).
ok :-
count_solutions(p(_), Count),
Count == 3.tests/wamvm_flags.pl
% Engine selection smoke test for `flowlog_engine=wamvm`.
:- catch(set_prolog_flag(flowlog_engine, wamvm), _, true).
ok :-
current_prolog_flag(flowlog_engine_active, Active),
Active == wamvm,
X is 1+2,
X == 3,
sort([2, 1, 1], S),
S == [1, 2].tests/wamvm_select1_vm.pl
% WAMVM VM test: built-in `select_/3` (choicepoint + OR-par split target).
% The check is order-insensitive so it remains valid under unordered OR-par.
sum_list([], 0).
sum_list([X|Xs], S) :-
sum_list(Xs, S0),
S is S0 + X.
sum_squares([], 0).
sum_squares([X|Xs], S) :-
sum_squares(Xs, S0),
S is S0 + X*X.
ok :-
findall(X, select_(X, [1,2,3,4,5,6,7,8], _), Xs),
length(Xs, 8),
sum_list(Xs, 36),
sum_squares(Xs, 204).tests/wamvm_term_ops_vm.pl
% WAMVM VM-required term ops + control constructs smoke tests.
%
% Run with:
% FLOWLOG_WAMVM_REQUIRE_VM=1 ./flowlog --engine wamvm -f tests/wamvm_term_ops_vm.pl -g ok.
p(1).
p(2).
q(1).
q(2).
add1(X, Y) :-
Y is X + 1.
ok :-
% once/1 commits to the first solution.
findall(X, once(p(X)), XsOnce),
XsOnce = [1],
% if-then commits to the first success of If.
( q(X) -> true ),
X = 1,
% if-then-else: Else is not considered once If succeeds.
( q(Y) -> true ; Y = 99 ),
Y = 1,
% Else branch runs when If fails.
( fail -> Z = then ; Z = else ),
Z = else,
% If succeeds but Then fails: do NOT run Else.
( (true -> fail ; true) -> fail ; true ),
% call/N (closure expansion).
call(add1, 1, R),
R = 2,
% Module qualification is accepted (Flowlog ignores modules).
:(m, true),
% =../2 (univ) round-trips.
foo(a, b) =.. L1,
L1 = [foo, a, b],
T2 =.. [foo, a, b],
T2 = foo(a, b),
% functor/3 deconstruct + construct.
functor(foo(a, b), F, N),
F = foo,
N = 2,
functor(T3, foo, 2),
T3 = foo(_, _),
% arg/3.
arg(2, foo(a, b, c), A2),
A2 = b,
% compare/3.
compare(C1, 1, 2),
C1 = '<',
compare('>', 2, 1),
compare('=', 1, 1),
% term_variables/2.
term_variables(f(Xv, Yv, Xv, 1), Vars),
Vars = [Xv, Yv],
% copy_term/2 makes fresh variables.
copy_term(f(Xc), f(Yc)),
Xc = 1,
var(Yc),
Yc = 2,
% unify_with_occurs_check/2.
( unify_with_occurs_check(U, f(U)) -> fail ; var(U) ),
% acyclic_term/1 detects cycles (rational trees).
V = f(V),
\+ acyclic_term(V),
% ground/1.
ground(f(1, 2)),
\+ ground(f(G)),
var(G).tests/wamvm_vm_smoke.pl
% Smoke test that forces the WAMVM bytecode VM path (no fallback).
%
% The test harness runs Flowlog with:
% FLOWLOG_WAMVM_REQUIRE_VM=1 --engine wamvm
%
% so any fallback to the WAM-lite solver is treated as a failure.
q(1).
q(2).
r(1).
r(2).
p(X) :-
q(X),
r(X).