Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/pkg_resolver/c/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ choicepoint restoration across ordinary and tail calls. Merge readiness is
determined by the current compiled test gates and PR review, rather than the
historical work-in-progress notes retained below.

### Atom codes reverse-mode parity slice (2026-09-14)

The C runtime now accepts `atom_codes(-Atom, +Codes)` for finite, fully bound
proper lists of Unicode scalar values. It handles both WAM list cell layouts,
including a compiled `"[|]/2"` tail, and binds the atom through the normal
trail so caller backtracking restores the variable. Unsupported shapes such
as open or cyclic lists, invalid scalar values, and U+0000 report
`WAM_ERR_UNSUPPORTED`; C atoms are NUL-terminated and cannot represent U+0000.
The existing forward mode remains unchanged. The focused generated-C/SWI suite
is `tests/test_wam_c_atom_codes.pl`.

## Reverse implementation history (2026-09-09)

This section records the earlier reverse implementation checkpoint. Its stale
Expand Down
82 changes: 76 additions & 6 deletions src/unifyweaver/targets/wam_c_target.pl
Original file line number Diff line number Diff line change
Expand Up @@ -5113,15 +5113,85 @@
return cell->tag == VAL_ATOM && cell->data.atom && strcmp(cell->data.atom, "[]") == 0;
}

/* atom_codes/2: forward atom-to-codes mode only.
Decompose atom text into a list of Unicode code point integers.
Unbound atom or non-atom inputs are diagnosed as WAM_ERR_UNSUPPORTED.
Unification failure rolls back trail and heap bindings made by this builtin. */
/* The reverse mode accepts only a finite, fully bound proper code list. A
bounded walk also rejects cyclic lists without mutating the query. */
static bool wam_encode_single_utf8_char(int cp, char buf[5]);

static bool wam_atom_codes_to_text(WamState *state, WamValue *codes,
char **text_out) {
WamValue *cell = wam_deref_ptr(state, codes);
size_t bytes = 0;
int count = 0;
for (;;) {
if ((cell->tag == VAL_LIST || cell->tag == VAL_STR) &&
(cell->data.ref_addr < 0 || cell->data.ref_addr >= state->H))
return false;
int base = wam_cons_head_addr(state, cell);
if (base < 0) break;
if (++count > 262144 || base >= state->H - 1)
return false;
WamValue *head = wam_deref_ptr(state, &state->H_array[base]);
char encoded[5];
if (head->tag != VAL_INT ||
!wam_encode_single_utf8_char(head->data.integer, encoded))
return false;
bytes += strlen(encoded);
cell = wam_deref_ptr(state, &state->H_array[base + 1]);
}
if (cell->tag != VAL_ATOM || !cell->data.atom ||
strcmp(cell->data.atom, "[]") != 0)
return false;

char *text = (char *)malloc(bytes + 1);
if (!text) return false;
size_t at = 0;
cell = wam_deref_ptr(state, codes);
for (;;) {
int base = wam_cons_head_addr(state, cell);
if (base < 0) break;
WamValue *head = wam_deref_ptr(state, &state->H_array[base]);
char encoded[5];
if (head->tag != VAL_INT ||
!wam_encode_single_utf8_char(head->data.integer, encoded)) {
free(text);
return false;
}
size_t len = strlen(encoded);
memcpy(text + at, encoded, len);
at += len;
cell = wam_deref_ptr(state, &state->H_array[base + 1]);
}
text[at] = 0;
*text_out = text;
return true;
}

/* atom_codes/2: atom-to-codes and fully ground codes-to-atom modes.
Unsupported shapes diagnose WAM_ERR_UNSUPPORTED; unification failures
roll back trail and heap bindings made by this builtin. */
static bool wam_execute_atom_codes(WamState *state) {
WamValue *a1 = wam_deref_ptr(state, &state->A[0]);
if (val_is_unbound(*a1)) {
wam_set_unsupported_builtin(state, "atom_codes/2", 2);
return false;
char *text = NULL;
if (!wam_atom_codes_to_text(state, &state->A[1], &text)) {
wam_set_unsupported_builtin(state, "atom_codes/2", 2);
return false;
}
const char *interned = wam_intern_atom(state, text);
if (interned == text) {
free(text);
return false;
}
free(text);
int trail_mark = state->TR;
int heap_mark = state->H;
WamValue atom_val = val_atom(interned);
if (!wam_unify(state, &state->A[0], &atom_val)) {
unwind_trail(state, trail_mark);
state->H = heap_mark;
return false;
}
return true;
}
if (a1->tag != VAL_ATOM || !a1->data.atom) {
wam_set_unsupported_builtin(state, "atom_codes/2", 2);
Expand Down
125 changes: 114 additions & 11 deletions tests/test_wam_c_atom_codes.pl
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
% Copyright (c) 2026 John William Creighton (@s243a)
%
% Focused C atom_codes/2: generated/compiled C vs SWI for forward
% atom-to-codes mode (ASCII, empty, multibyte code points).
% atom-to-codes and fully bound reverse codes-to-atom modes.
% Compatible and incompatible prebound outputs, rollback after mismatch,
% caller continuation, and variable identity are verified.
% Unbound atom, unsupported reverse mode, and non-atom inputs
% Both-unbound, malformed reverse lists, and non-atom inputs
% explicitly diagnose WAM_ERR_UNSUPPORTED, not silent logical failure.
%
% swipl -q -g run_tests -t halt tests/test_wam_c_atom_codes.pl
% swipl --on-error=halt -q -g run_tests -t halt tests/test_wam_c_atom_codes.pl

:- use_module('../src/unifyweaver/targets/wam_c_target').
:- use_module('../src/unifyweaver/targets/wam_target').
Expand All @@ -22,6 +22,7 @@
:- dynamic user:wam_atom_codes_continuation/3.
:- dynamic user:wam_atom_codes_bind_control/1.
:- dynamic user:wam_atom_codes_backtrack/1.
:- dynamic user:wam_atom_codes_reverse_backtrack/1.
:- dynamic user:wam_atom_codes_mismatch_positive/1.
:- dynamic user:wam_atom_codes_mismatch_rollback/1.

Expand Down Expand Up @@ -56,6 +57,7 @@
retractall(user:wam_atom_codes_continuation(_, _, _)),
retractall(user:wam_atom_codes_bind_control(_)),
retractall(user:wam_atom_codes_backtrack(_)),
retractall(user:wam_atom_codes_reverse_backtrack(_)),
retractall(user:wam_atom_codes_mismatch_positive(_)),
retractall(user:wam_atom_codes_mismatch_rollback(_)).

Expand All @@ -70,6 +72,10 @@
( atom_codes(foo, C), C == [99, 99], fail
; ( var(C) -> Out = restored ; Out = bad )
), Out == restored)),
assertz((user:wam_atom_codes_reverse_backtrack(Out) :-
( atom_codes(A, [97, 98]), A == ab, fail
; ( var(A) -> Out = restored ; Out = bad )
), Out == restored)),
assertz((user:wam_atom_codes_mismatch_positive(Out) :-
atom_codes(hello, C), Out = C)),
assertz((user:wam_atom_codes_mismatch_rollback(Out) :-
Expand Down Expand Up @@ -109,6 +115,8 @@
user:wam_atom_codes_bind_control([102, 111, 111]).
token_swi(backtrack_rollback, ok, backtrack_ok) :-
user:wam_atom_codes_backtrack(restored).
token_swi(reverse_backtrack_rollback, ok, reverse_backtrack_ok) :-
user:wam_atom_codes_reverse_backtrack(restored).
token_swi(mismatch_positive, ok, mismatch_positive_ok) :-
user:wam_atom_codes_mismatch_positive([104, 101, 108, 108, 111]).
token_swi(mismatch_rollback, ok, mismatch_rollback_ok) :-
Expand All @@ -119,7 +127,16 @@
S1 == start, S2 == done.
token_swi(repeated_mismatch_rollback, ok, repeated_rollback_ok).
token_swi(unbound_atom, ok, unbound_atom_ok).
token_swi(reverse_mode, ok, reverse_mode_ok).
token_swi(reverse_mode, ok, reverse_mode_ok) :-
atom_codes(Atom, [97, 98]), Atom == ab.
token_swi(reverse_empty, ok, reverse_empty_ok) :-
atom_codes(Atom, []), Atom == ''.
token_swi(reverse_unicode, ok, reverse_unicode_ok) :-
atom_codes(Atom, [955, 128640]), Atom == 'λ🚀'.
token_swi(reverse_bad_code, ok, reverse_bad_code_ok).
token_swi(reverse_nul, ok, reverse_nul_ok).
token_swi(reverse_open_list, ok, reverse_open_list_ok).
token_swi(reverse_cyclic_list, ok, reverse_cyclic_list_ok).
token_swi(compound_input, ok, compound_ok).
token_swi(nonempty_list, ok, nonempty_list_ok).
token_swi(integer_input, ok, integer_ok).
Expand Down Expand Up @@ -275,6 +292,7 @@
compile_one(user:wam_atom_codes_continuation/3, ContCode),
compile_one(user:wam_atom_codes_bind_control/1, BindCode),
compile_one(user:wam_atom_codes_backtrack/1, BackCode),
compile_one(user:wam_atom_codes_reverse_backtrack/1, ReverseBackCode),
compile_one(user:wam_atom_codes_mismatch_positive/1, MisPosCode),
compile_one(user:wam_atom_codes_mismatch_rollback/1, MismatchCode),
compile_wam_runtime_to_c([], RuntimeCode),
Expand All @@ -287,7 +305,8 @@
format(atom(ExePath), '~w_bin', [TmpBase]),
write_text_file(RuntimePath, RuntimeCode),
atomic_list_concat([QCode, "\n\n", ContCode, "\n\n", BindCode, "\n\n",
BackCode, "\n\n", MisPosCode, "\n\n", MismatchCode],
BackCode, "\n\n", ReverseBackCode, "\n\n",
MisPosCode, "\n\n", MismatchCode],
AllPredCode),
format(atom(PredTranslationUnit), '#include "wam_runtime.h"~n~n~w', [AllPredCode]),
write_text_file(PredPath, PredTranslationUnit),
Expand All @@ -312,9 +331,13 @@
prebound_empty_mismatch,
aliased_var_mismatch, preserve_input, reference_chain,
repeated_calls, bind_control, backtrack_rollback,
reverse_backtrack_rollback,
mismatch_positive, mismatch_rollback, c_unifier_rollback,
caller_continuation, repeated_mismatch_rollback,
unbound_atom, reverse_mode, compound_input, nonempty_list,
unbound_atom, reverse_mode, reverse_empty, reverse_unicode,
reverse_bad_code, reverse_nul, reverse_open_list,
reverse_cyclic_list,
compound_input, nonempty_list,
integer_input, distinguish_mismatch_vs_unsupported,
prebound_heap_stable, malformed_utf8, heap_alloc_failure],
findall(Id, (member(Id, Ground), \+ compare_ground(Id, CCases)), GroundBads),
Expand All @@ -334,6 +357,7 @@
void setup_wam_atom_codes_continuation_3(WamState *state);
void setup_wam_atom_codes_bind_control_1(WamState *state);
void setup_wam_atom_codes_backtrack_1(WamState *state);
void setup_wam_atom_codes_reverse_backtrack_1(WamState *state);
void setup_wam_atom_codes_mismatch_positive_1(WamState *state);
void setup_wam_atom_codes_mismatch_rollback_1(WamState *state);

Expand Down Expand Up @@ -554,6 +578,7 @@
setup_wam_atom_codes_continuation_3(&state);
setup_wam_atom_codes_bind_control_1(&state);
setup_wam_atom_codes_backtrack_1(&state);
setup_wam_atom_codes_reverse_backtrack_1(&state);
setup_wam_atom_codes_mismatch_positive_1(&state);
setup_wam_atom_codes_mismatch_rollback_1(&state);

Expand Down Expand Up @@ -715,7 +740,18 @@
ok ? "backtrack_ok" : "backtrack_bad");
}

/* 15. Mismatch positive control via compiled predicate */
/* 15. Reverse binding unwinds on caller backtracking. */
{
WamValue out_ref = make_out_ref(&state, "Out");
WamValue args[1] = { out_ref };
int rc = wam_run_predicate(&state, "wam_atom_codes_reverse_backtrack/1", args, 1);
int ok = rc == 0 && state.error == 0 &&
same_atom(wam_deref_ptr(&state, &out_ref), "restored");
emit_token("reverse_backtrack_rollback", ok ? "ok" : "fail",
ok ? "reverse_backtrack_ok" : "reverse_backtrack_bad");
}

/* 16. Mismatch positive control via compiled predicate */
{
WamValue out_ref = make_out_ref(&state, "Out");
WamValue args[1] = { out_ref };
Expand Down Expand Up @@ -803,15 +839,82 @@
wam_clear_error(&state);
}

/* 21. Reverse mode (unbound atom, bound codes) -> WAM_ERR_UNSUPPORTED */
/* 21. Reverse mode produces an atom from a bound code list. */
{
static const int ab_codes[] = {97, 98};
WamValue codes = build_int_list(&state, ab_codes, 2);
int rc = run_atom_codes(&state, val_unbound("A"), codes);
int ok = (rc == WAM_ERR_UNSUPPORTED && state.error == WAM_ERR_UNSUPPORTED &&
state.error_op != NULL && strcmp(state.error_op, "atom_codes/2") == 0);
WamValue atom_ref = make_out_ref(&state, "A");
int rc = run_atom_codes(&state, atom_ref, codes);
int ok = rc == 0 && state.error == 0 &&
same_atom(wam_deref_ptr(&state, &atom_ref), "ab");
emit_token("reverse_mode", ok ? "ok" : "fail",
ok ? "reverse_mode_ok" : "reverse_mode_bad");
}

/* 21a. Empty and multibyte reverse conversions. */
{
WamValue atom_ref = make_out_ref(&state, "A");
int rc = run_atom_codes(&state, atom_ref, val_atom("[]"));
int ok = rc == 0 && state.error == 0 &&
same_atom(wam_deref_ptr(&state, &atom_ref), "");
emit_token("reverse_empty", ok ? "ok" : "fail",
ok ? "reverse_empty_ok" : "reverse_empty_bad");
}
{
static const int unicode_codes[] = {955, 128640};
WamValue codes = build_int_list(&state, unicode_codes, 2);
WamValue atom_ref = make_out_ref(&state, "A");
int rc = run_atom_codes(&state, atom_ref, codes);
int ok = rc == 0 && state.error == 0 &&
same_atom(wam_deref_ptr(&state, &atom_ref), "λ🚀");
emit_token("reverse_unicode", ok ? "ok" : "fail",
ok ? "reverse_unicode_ok" : "reverse_unicode_bad");
}

/* Invalid scalar and open list are diagnosed, never partial atoms. */
{
static const int invalid_codes[] = {97, 55296};
WamValue codes = build_int_list(&state, invalid_codes, 2);
WamValue atom_ref = make_out_ref(&state, "A");
int rc = run_atom_codes(&state, atom_ref, codes);
int ok = rc == WAM_ERR_UNSUPPORTED && state.error == WAM_ERR_UNSUPPORTED &&
state.error_op && strcmp(state.error_op, "atom_codes/2") == 0 &&
val_is_unbound(*wam_deref_ptr(&state, &atom_ref));
emit_token("reverse_bad_code", ok ? "ok" : "fail",
ok ? "reverse_bad_code_ok" : "reverse_bad_code_bad");
wam_clear_error(&state);
}
{
static const int nul_codes[] = {97, 0};
WamValue codes = build_int_list(&state, nul_codes, 2);
WamValue atom_ref = make_out_ref(&state, "A");
int rc = run_atom_codes(&state, atom_ref, codes);
int ok = rc == WAM_ERR_UNSUPPORTED && state.error == WAM_ERR_UNSUPPORTED &&
val_is_unbound(*wam_deref_ptr(&state, &atom_ref));
emit_token("reverse_nul", ok ? "ok" : "fail",
ok ? "reverse_nul_ok" : "reverse_nul_bad");
wam_clear_error(&state);
}
{
WamValue open_tail = make_out_ref(&state, "Tail");
WamValue codes = cons(&state, val_int(97), open_tail);
WamValue atom_ref = make_out_ref(&state, "A");
int rc = run_atom_codes(&state, atom_ref, codes);
int ok = rc == WAM_ERR_UNSUPPORTED && state.error == WAM_ERR_UNSUPPORTED &&
val_is_unbound(*wam_deref_ptr(&state, &atom_ref));
emit_token("reverse_open_list", ok ? "ok" : "fail",
ok ? "reverse_open_list_ok" : "reverse_open_list_bad");
wam_clear_error(&state);
}
{
WamValue codes = cons(&state, val_int(97), val_atom("[]"));
state.H_array[codes.data.ref_addr + 1] = codes;
WamValue atom_ref = make_out_ref(&state, "A");
int rc = run_atom_codes(&state, atom_ref, codes);
int ok = rc == WAM_ERR_UNSUPPORTED && state.error == WAM_ERR_UNSUPPORTED &&
val_is_unbound(*wam_deref_ptr(&state, &atom_ref));
emit_token("reverse_cyclic_list", ok ? "ok" : "fail",
ok ? "reverse_cyclic_list_ok" : "reverse_cyclic_list_bad");
wam_clear_error(&state);
}

Expand Down
Loading