eyeprolog 1.3.67 → 1.3.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/lib/si.pl ADDED
@@ -0,0 +1,62 @@
1
+ /** Sufficient-instantiation tests compatible with Scryer library(si). */
2
+
3
+ :- module(si, [
4
+ atom_si/1,
5
+ integer_si/1,
6
+ atomic_si/1,
7
+ list_si/1,
8
+ character_si/1,
9
+ term_si/1,
10
+ chars_si/1,
11
+ dif_si/2,
12
+ not_si/1,
13
+ when_si/2
14
+ ]).
15
+
16
+ :- meta_predicate(not_si(0)).
17
+ :- meta_predicate(when_si(+, 0)).
18
+
19
+ atom_si(A) :- ( var(A) -> throw(error(instantiation_error, atom_si/1)) ; atom(A) ).
20
+ integer_si(I) :- ( var(I) -> throw(error(instantiation_error, integer_si/1)) ; integer(I) ).
21
+ atomic_si(A) :- ( var(A) -> throw(error(instantiation_error, atomic_si/1)) ; atomic(A) ).
22
+
23
+ list_si(List) :- si__list(List).
24
+ si__list([]).
25
+ si__list([_|Tail]) :- !, si__list(Tail).
26
+ si__list(Term) :-
27
+ ( var(Term) -> throw(error(instantiation_error, list_si/1)) ; fail ).
28
+
29
+ character_si(C) :-
30
+ ( var(C) -> throw(error(instantiation_error, character_si/1))
31
+ ; atom(C), atom_length(C, 1)
32
+ ).
33
+
34
+ term_si(Term) :-
35
+ ( ground(Term) -> acyclic_term(Term)
36
+ ; throw(error(instantiation_error, term_si/1))
37
+ ).
38
+
39
+ chars_si(Chars) :-
40
+ list_si(Chars),
41
+ si__chars(Chars).
42
+ si__chars([]).
43
+ si__chars([C|Cs]) :- character_si(C), si__chars(Cs).
44
+
45
+ dif_si(X, Y) :-
46
+ X \== Y,
47
+ ( X \= Y -> true ; throw(error(instantiation_error, dif_si/2)) ).
48
+
49
+ not_si(Goal) :- term_si(Goal), \+ Goal.
50
+
51
+ when_si(Condition, Goal) :-
52
+ ( si__condition(Condition) ->
53
+ ( call(Condition) -> call(Goal)
54
+ ; throw(error(instantiation_error, when_si/2)) )
55
+ ; throw(error(domain_error(when_condition_si, Condition), when_si/2))
56
+ ).
57
+
58
+ si__condition(Condition) :- var(Condition), !, throw(error(instantiation_error, when_si/2)).
59
+ si__condition(ground(_)).
60
+ si__condition(nonvar(_)).
61
+ si__condition((A,B)) :- si__condition(A), si__condition(B).
62
+ si__condition((A;B)) :- si__condition(A), si__condition(B).
@@ -0,0 +1,18 @@
1
+ /** Term helpers compatible with the Scryer library surface used by CLP(Z). */
2
+
3
+ :- module(terms, [numbervars/3, copy_term_nat/2]).
4
+
5
+ :- use_module(library(error), [must_be/2, can_be/2]).
6
+
7
+ copy_term_nat(Term, Copy) :- copy_term(Term, Copy).
8
+
9
+ numbervars(Term, N0, N) :-
10
+ must_be(integer, N0),
11
+ can_be(integer, N),
12
+ term_variables(Term, Variables),
13
+ terms__numbervars(Variables, N0, N).
14
+
15
+ terms__numbervars([], N, N).
16
+ terms__numbervars(['$VAR'(N0)|Variables], N0, N) :-
17
+ N1 is N0 + 1,
18
+ terms__numbervars(Variables, N1, N).
package/src/parser.js CHANGED
@@ -1119,6 +1119,32 @@ class Parser {
1119
1119
  head = items.pop();
1120
1120
  while (items.length > 0) head = compound(',', [items.pop(), head]);
1121
1121
  }
1122
+ // The clause grammar keeps priority-1200 neck/DCG tokens outside the
1123
+ // initial head parse. A user-defined operator at priority 1199 or 1200,
1124
+ // however, can itself be the outermost source term. Scryer's CLP(Z) uses
1125
+ // exactly this shape for its private `++>` Duo-DCG rules. Fold such a
1126
+ // custom low-strength infix operator back into the source term while
1127
+ // leaving the processor-defined :-, -->, and ?- dispatch below intact.
1128
+ const topLevelOperator = this.operatorTokenName();
1129
+ const topLevelInfo = topLevelOperator == null ? null : this.infixOperators.get(topLevelOperator);
1130
+ if (topLevelInfo && topLevelInfo.precedence < 3 &&
1131
+ ![':-', '-->', '?-'].includes(topLevelOperator)) {
1132
+ this.advance();
1133
+ const right = this.parseTerm(
1134
+ topLevelInfo.associativity === 'right' ? topLevelInfo.precedence : topLevelInfo.precedence + 1,
1135
+ true,
1136
+ true,
1137
+ false,
1138
+ );
1139
+ head = compound(topLevelOperator, [head, right]);
1140
+ if (topLevelInfo.associativity === 'none') {
1141
+ const nextOperator = this.operatorTokenName();
1142
+ if (this.infixOperators.get(nextOperator)?.precedence === topLevelInfo.precedence) {
1143
+ throw new Error(`parse line ${this.token.line}: non-associative operator ${topLevelOperator} requires parentheses`);
1144
+ }
1145
+ }
1146
+ }
1147
+
1122
1148
  // Parentheses and other ordinary term syntax may hide the surface ?-
1123
1149
  // token from the program-level dispatch above. Once the complete head
1124
1150
  // term has been parsed, recognize the same ?-/1 or ?-/2 structure here.
package/src/program.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  eyePrologInteropLibraryModules,
22
22
  } from './standard-library.js';
23
23
  import { expandDcgRuleClause } from './dcg.js';
24
+ import { expandClauseGoals, expandSourceClause } from './source-expansion.js';
24
25
  import {
25
26
  CompactBinaryClause, clauseBodyLength, clauseHasCut, compactHeadArgName, compactHeadArgType, modulePredicateKey,
26
27
  indexCompactOne, indexOne, isCompactBinaryClause, makeArgumentIndex, rebuildGroupIndexes, termContainsCut, termHasNoVariables,
@@ -1069,18 +1070,56 @@ function loadSourceIntoBuilder(builder, source, options, ensured, loadedModules,
1069
1070
  builder.addClauses(batch);
1070
1071
  batch.length = 0;
1071
1072
  };
1072
- const accept = (clause) => {
1073
+
1074
+ const processClause = (inputClause) => {
1075
+ let clause = inputClause;
1073
1076
  if (clause?.kind === 'quad') {
1074
1077
  flush();
1075
1078
  clause.module = context.module;
1076
1079
  builder.addClauses([clause]);
1077
1080
  return;
1078
1081
  }
1082
+
1083
+ // User source expansion is a normal-profile compile-time facility. Hooks
1084
+ // execute against the clauses that have already been prepared, matching
1085
+ // Prolog's left-to-right source loading model. Strict ISO Part 1 keeps its
1086
+ // processor-defined preparation semantics unchanged.
1087
+ if (!builder.program.strictIso) {
1088
+ const expansion = expandSourceClause(builder.program, clause, context.module, {
1089
+ module: context.module,
1090
+ filename: options.filename ?? '<input>',
1091
+ });
1092
+ if (expansion.expanded) {
1093
+ for (const generated of expansion.clauses) processPreparedClause(generated);
1094
+ return;
1095
+ }
1096
+ }
1097
+ processPreparedClause(clause);
1098
+ };
1099
+
1100
+ const processPreparedClause = (inputClause) => {
1101
+ let clause = inputClause;
1102
+ const lexicalModule = context.module;
1103
+
1079
1104
  // Grammar-rule expansion belongs to ISO/IEC TS 13211-3 rather than the
1080
1105
  // Part 1 strict-core language. In strict core mode -->/2 remains the
1081
1106
  // ordinary predefined operator term from Table 7 and is not rewritten.
1082
- const grammarClause = builder.program.strictIso ? null : expandDcgRuleClause(clause, context.module);
1107
+ const grammarClause = builder.program.strictIso ? null : expandDcgRuleClause(clause, lexicalModule);
1083
1108
  if (grammarClause) clause = grammarClause;
1109
+
1110
+ // A source module qualifier on a clause head selects the procedure owner,
1111
+ // while unqualified calls in the body retain the lexical module of the
1112
+ // source text. This distinction is essential for conventional hooks such
1113
+ // as user:term_expansion/2 defined from library modules.
1114
+ clause = normalizeQualifiedClauseHead(clause, lexicalModule);
1115
+
1116
+ if (!builder.program.strictIso && !isDirectiveClause(clause)) {
1117
+ expandClauseGoals(builder.program, clause, lexicalModule, {
1118
+ module: lexicalModule,
1119
+ filename: options.filename ?? '<input>',
1120
+ });
1121
+ }
1122
+
1084
1123
  const moduleDeclaration = moduleDirective(clause);
1085
1124
  if (moduleDeclaration) {
1086
1125
  flush();
@@ -1121,10 +1160,20 @@ function loadSourceIntoBuilder(builder, source, options, ensured, loadedModules,
1121
1160
  clause.module ??= context.module;
1122
1161
  clause.textUnit ??= context.textUnit;
1123
1162
  if (!isDirectiveClause(clause)) {
1124
- for (const goal of clause.body) annotateGoalModule(goal, clause.module);
1163
+ const bodyModule = clause.lexicalModule ?? lexicalModule;
1164
+ for (const goal of clause.body) annotateGoalModule(goal, bodyModule);
1165
+ }
1166
+ const isExpansionHook = !isDirectiveClause(clause) &&
1167
+ clause.head?.arity === 2 && ['term_expansion', 'goal_expansion'].includes(clause.head?.name);
1168
+ if (isExpansionHook) {
1169
+ // A hook becomes visible to the very next source term, so it cannot
1170
+ // wait in the large streaming build batch.
1171
+ flush();
1172
+ builder.addClauses([clause]);
1173
+ } else {
1174
+ batch.push(clause);
1175
+ if (batch.length >= PROGRAM_BUILD_BATCH_SIZE) flush();
1125
1176
  }
1126
- batch.push(clause);
1127
- if (batch.length >= PROGRAM_BUILD_BATCH_SIZE) flush();
1128
1177
  return;
1129
1178
  }
1130
1179
  flush();
@@ -1152,15 +1201,32 @@ function loadSourceIntoBuilder(builder, source, options, ensured, loadedModules,
1152
1201
  batch[batch.length - 1].textUnit = context.textUnit;
1153
1202
  if (batch.length >= PROGRAM_BUILD_BATCH_SIZE) flush();
1154
1203
  };
1155
- const parsed = tryParseClausesFastInto(source, accept, acceptBinary, options);
1204
+ const parsed = tryParseClausesFastInto(source, processClause, acceptBinary, options);
1156
1205
  if (parsed) flush();
1157
1206
  return parsed;
1158
1207
  }
1159
- parseClausesInto(source, options, accept);
1208
+ parseClausesInto(source, options, processClause);
1160
1209
  flush();
1161
1210
  return true;
1162
1211
  }
1163
1212
 
1213
+ function normalizeQualifiedClauseHead(clause, lexicalModule = 'user') {
1214
+ if (clause == null || clause.kind === 'quad' || isDirectiveClause(clause)) return clause;
1215
+ const head = clause.head;
1216
+ if (head?.type !== COMPOUND || head.name !== ':' || head.arity !== 2) return clause;
1217
+ const qualifier = head.args[0];
1218
+ const qualifiedHead = head.args[1];
1219
+ if (qualifier.type === VAR) throw new PrologError('instantiation_error');
1220
+ if (qualifier.type !== ATOM) throw new PrologError('type_error(atom)', qualifier);
1221
+ if (qualifiedHead.type !== ATOM && qualifiedHead.type !== COMPOUND) {
1222
+ throw new PrologError('type_error(callable)', qualifiedHead);
1223
+ }
1224
+ clause.head = qualifiedHead;
1225
+ clause.module = qualifier.name;
1226
+ clause.lexicalModule = lexicalModule;
1227
+ return clause;
1228
+ }
1229
+
1164
1230
  function includeDirective(clause) {
1165
1231
  if (isCompactBinaryClause(clause) || !isDirectiveClause(clause)) return null;
1166
1232
  const directive = clause.head.args[0];
@@ -0,0 +1,39 @@
1
+ // Small host primitives needed by Scryer-style declarative libraries.
2
+ //
3
+ // Keep these facilities generic: they are not part of the CLP(Z) engine. The
4
+ // backtrackable blackboard lives in Env so ordinary Prolog choice points undo
5
+ // writes automatically.
6
+
7
+ import { ATOM, NUMBER, STRING, deref, unify } from './term.js';
8
+ import { PrologError } from './errors.js';
9
+
10
+ function blackboardKey(term, env) {
11
+ const value = deref(term, env);
12
+ if (value.type === ATOM) return `a:${value.name}`;
13
+ if (value.type === NUMBER) return `n:${value.name}`;
14
+ if (value.type === STRING) return `s:${value.name}`;
15
+ if (value.type === 'var') throw new PrologError('instantiation_error');
16
+ throw new PrologError('type_error(atomic)', value);
17
+ }
18
+
19
+ function* bbGetBuiltin({ goal, env }) {
20
+ const key = blackboardKey(goal.args[0], env);
21
+ const stored = env.getBacktrackableBlackboard(key);
22
+ if (stored === undefined) return;
23
+ const next = env.clone();
24
+ if (unify(goal.args[1], stored, next)) yield next;
25
+ }
26
+
27
+ function* bbPutBuiltin({ goal, env }) {
28
+ const next = env.clone();
29
+ const key = blackboardKey(goal.args[0], next);
30
+ next.putBacktrackableBlackboard(key, deref(goal.args[1], next));
31
+ yield next;
32
+ }
33
+
34
+ export const scryerCompatibilityBuiltins = {
35
+ register(registry) {
36
+ registry.add('eyeprolog__bb_get', 2, bbGetBuiltin, { eyePrologLibrary: true });
37
+ registry.add('eyeprolog__bb_b_put', 2, bbPutBuiltin, { deterministic: true, eyePrologLibrary: true });
38
+ },
39
+ };
package/src/solver.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  import { numberValueKey, sameNumberValue } from './number-value.js';
9
9
  import { PrologError, getStrictIsoRegistry } from './iso.js';
10
10
  import { getEyePrologRegistry } from './standard-library.js';
11
- import { selectClauseCandidates, selectClauseCandidatesForValues, selectGroundClauseCandidates } from './program.js';
11
+ import { selectClauseCandidates, selectClauseCandidatesForValues, selectGroundClauseCandidates } from './program-indexing.js';
12
12
  import { StreamManager } from './io.js';
13
13
  import { clpzStateConsistent } from './clpz.js';
14
14
  import { hardHeapLimit, softHeapLimit, usedHeapSize } from './platform.js';
@@ -0,0 +1,173 @@
1
+ // User-defined source expansion for the normal EyeProlog profile.
2
+ //
3
+ // This deliberately sits beside program preparation rather than inside the
4
+ // term kernel. A small, fresh Solver executes already-loaded expansion hooks
5
+ // against the partially built Program. The solver itself imports clause-index
6
+ // helpers directly, so this compiler service does not introduce a module cycle.
7
+
8
+ import {
9
+ ATOM, COMPOUND, Env, Term, VAR, atom, compound, deref, variable, variantTerms,
10
+ } from './term.js';
11
+ import { modulePredicateKey } from './program-indexing.js';
12
+ import { Solver } from './solver.js';
13
+ import { clauseToSourceTerm, sourceTermToClauses } from './expansion-builtins.js';
14
+ import { PrologError } from './errors.js';
15
+
16
+ let expansionFresh = 0;
17
+ const MAX_EXPANSION_DEPTH = 64;
18
+ const MAX_EXPANSION_INFERENCES = 1_000_000;
19
+
20
+ function exactHookGroup(program, module, name) {
21
+ return program.groups.get(modulePredicateKey(module, name, 2)) ?? null;
22
+ }
23
+
24
+ function hookModules(module) {
25
+ return module === 'user' ? ['user'] : [module, 'user'];
26
+ }
27
+
28
+ function inputVariableNames(term) {
29
+ const names = new Set();
30
+ const stack = [term];
31
+ const seen = new Set();
32
+ while (stack.length > 0) {
33
+ const current = stack.pop();
34
+ if (current == null || seen.has(current)) continue;
35
+ seen.add(current);
36
+ if (current.type === VAR) {
37
+ names.add(current.name);
38
+ continue;
39
+ }
40
+ if (current.type === COMPOUND) {
41
+ for (const arg of current.args) stack.push(arg);
42
+ }
43
+ }
44
+ return names;
45
+ }
46
+
47
+ function copyHookResult(term, env, inputNames) {
48
+ const rootNames = new Map();
49
+ for (const name of inputNames) {
50
+ const root = deref(variable(name), env);
51
+ if (root.type === VAR && !rootNames.has(root.name)) rootNames.set(root.name, name);
52
+ }
53
+ const copy = (value) => {
54
+ const resolved = deref(value, env);
55
+ if (resolved.type === VAR) return variable(rootNames.get(resolved.name) ?? resolved.name);
56
+ const copied = resolved.type === COMPOUND && resolved.arity === 0
57
+ ? atom(resolved.name)
58
+ : new Term(resolved.type, resolved.name, resolved.args.map(copy));
59
+ if (resolved.module != null) copied.module = resolved.module;
60
+ return copied;
61
+ };
62
+ return copy(term);
63
+ }
64
+
65
+ function runHook(program, module, name, input, loadContext = null) {
66
+ for (const owner of hookModules(module)) {
67
+ const group = exactHookGroup(program, owner, name);
68
+ if (group == null || group.clauses.length === 0) continue;
69
+
70
+ const output = variable(`\u0000source-expansion:${++expansionFresh}`);
71
+ const goal = compound(name, [input, output]);
72
+ goal.module = owner;
73
+ const solver = new Solver(program, {
74
+ solutionLimit: 1,
75
+ maxInferences: MAX_EXPANSION_INFERENCES,
76
+ sourceLoadContext: loadContext,
77
+ });
78
+ const inputNames = inputVariableNames(input);
79
+ for (const answer of solver.solve([goal], new Env())) {
80
+ return copyHookResult(output, answer, inputNames);
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function sourceTermsAreVariants(left, right) {
87
+ return variantTerms(left, new Env(), right, new Env());
88
+ }
89
+
90
+ function expandTermRecursively(program, module, term, loadContext, depth) {
91
+ if (depth > MAX_EXPANSION_DEPTH) throw new PrologError('resource_error(source_expansion)');
92
+ const expanded = runHook(program, module, 'term_expansion', term, loadContext);
93
+ if (expanded == null) return sourceTermToClauses(term);
94
+
95
+ const clauses = sourceTermToClauses(expanded);
96
+ if (clauses.length === 1 && sourceTermsAreVariants(term, clauseToSourceTerm(clauses[0]))) return clauses;
97
+ const result = [];
98
+ for (const clause of clauses) {
99
+ const generatedTerm = clauseToSourceTerm(clause);
100
+ result.push(...expandTermRecursively(program, module, generatedTerm, loadContext, depth + 1));
101
+ }
102
+ return result;
103
+ }
104
+
105
+ export function expandSourceClause(program, clause, module = 'user', loadContext = null) {
106
+ const sourceTerm = clauseToSourceTerm(clause);
107
+ if (sourceTerm == null) return { expanded: false, clauses: [clause] };
108
+ if (exactHookGroup(program, module, 'term_expansion') == null &&
109
+ (module === 'user' || exactHookGroup(program, 'user', 'term_expansion') == null)) {
110
+ return { expanded: false, clauses: [clause] };
111
+ }
112
+ const first = runHook(program, module, 'term_expansion', sourceTerm, loadContext);
113
+ if (first == null) return { expanded: false, clauses: [clause] };
114
+
115
+ const firstClauses = sourceTermToClauses(first);
116
+ if (firstClauses.length === 1 && sourceTermsAreVariants(sourceTerm, clauseToSourceTerm(firstClauses[0]))) {
117
+ return { expanded: false, clauses: [clause] };
118
+ }
119
+ const result = [];
120
+ for (const generated of firstClauses) {
121
+ const generatedTerm = clauseToSourceTerm(generated);
122
+ result.push(...expandTermRecursively(program, module, generatedTerm, loadContext, 1));
123
+ }
124
+ return { expanded: true, clauses: result };
125
+ }
126
+
127
+ function isCallable(term) {
128
+ return term?.type === ATOM || term?.type === COMPOUND;
129
+ }
130
+
131
+ function expandGoalChildren(program, goal, module, loadContext, depth) {
132
+ if (goal.type !== COMPOUND) return goal;
133
+
134
+ if (goal.name === ':' && goal.arity === 2) {
135
+ const qualifier = deref(goal.args[0], new Env());
136
+ if (qualifier.type === VAR) return goal;
137
+ if (qualifier.type !== ATOM) return goal;
138
+ const child = expandGoalRecursively(program, goal.args[1], qualifier.name, loadContext, depth + 1);
139
+ return compound(':', [goal.args[0], child]);
140
+ }
141
+
142
+ if ([',', ';', '->'].includes(goal.name) && goal.arity === 2) {
143
+ return compound(goal.name, [
144
+ expandGoalRecursively(program, goal.args[0], module, loadContext, depth + 1),
145
+ expandGoalRecursively(program, goal.args[1], module, loadContext, depth + 1),
146
+ ]);
147
+ }
148
+ if (goal.name === '\\+' && goal.arity === 1) {
149
+ return compound('\\+', [expandGoalRecursively(program, goal.args[0], module, loadContext, depth + 1)]);
150
+ }
151
+ return goal;
152
+ }
153
+
154
+ function expandGoalRecursively(program, goal, module, loadContext, depth = 0) {
155
+ if (depth > MAX_EXPANSION_DEPTH) throw new PrologError('resource_error(source_expansion)');
156
+ const resolved = deref(goal, new Env());
157
+ if (!isCallable(resolved)) return resolved;
158
+
159
+ const expanded = runHook(program, module, 'goal_expansion', resolved, loadContext);
160
+ if (expanded != null) {
161
+ if (!isCallable(expanded)) throw new PrologError('type_error(callable)', expanded);
162
+ if (sourceTermsAreVariants(resolved, expanded)) return expandGoalChildren(program, resolved, module, loadContext, depth);
163
+ return expandGoalRecursively(program, expanded, module, loadContext, depth + 1);
164
+ }
165
+ return expandGoalChildren(program, resolved, module, loadContext, depth);
166
+ }
167
+
168
+ export function expandClauseGoals(program, clause, lexicalModule = 'user', loadContext = null) {
169
+ if (!Array.isArray(clause.body) || clause.body.length === 0) return clause;
170
+ clause.body = clause.body.map((goal) =>
171
+ expandGoalRecursively(program, goal, lexicalModule, loadContext));
172
+ return clause;
173
+ }
@@ -5,23 +5,36 @@
5
5
  import { PrologError, createDefaultRegistry, eyePrologLibraryBuiltins } from './iso.js';
6
6
  import { clpzBuiltins } from './clpz.js';
7
7
  import { attsBuiltins } from './atts.js';
8
+ import { expansionBuiltins } from './expansion-builtins.js';
9
+ import { scryerCompatibilityBuiltins } from './scryer-compat.js';
8
10
  import { registerCleanupBuiltins } from './cleanup.js';
9
11
  import { fs, isNode, memoryStatistics } from './platform.js';
10
12
  import { ATOM, VAR, atom, deref, numberTerm, unify } from './term.js';
11
13
 
12
14
  const moduleFiles = Object.freeze({
13
15
  aggregate: 'aggregate.pl',
16
+ arithmetic: 'arithmetic.pl',
17
+ assoc: 'assoc.pl',
14
18
  atts: 'atts.pl',
19
+ between: 'between.pl',
15
20
  clpz: 'clpz.pl',
16
21
  comparison: 'comparison.pl',
17
22
  dates: 'dates.pl',
23
+ dcgs: 'dcgs.pl',
24
+ debug: 'debug.pl',
25
+ error: 'error.pl',
26
+ format: 'format.pl',
27
+ freeze: 'freeze.pl',
18
28
  iso_ext: 'iso_ext.pl',
19
29
  lambda: 'lambda.pl',
20
30
  lists: 'lists.pl',
31
+ pairs: 'pairs.pl',
21
32
  primes: 'primes.pl',
22
33
  prologue: 'prologue.pl',
23
34
  random: 'random.pl',
35
+ si: 'si.pl',
24
36
  strings: 'strings.pl',
37
+ terms: 'terms.pl',
25
38
  uuid: 'uuid.pl',
26
39
  });
27
40
 
@@ -188,6 +201,8 @@ export function createEyePrologRegistry() {
188
201
  registerCleanupBuiltins(registry);
189
202
  eyePrologLibraryBuiltins.register(registry);
190
203
  attsBuiltins.register(registry);
204
+ expansionBuiltins.register(registry);
205
+ scryerCompatibilityBuiltins.register(registry);
191
206
  clpzBuiltins.register(registry);
192
207
  registry.eyePrologLibrary = true;
193
208
  return registry;
package/src/term.js CHANGED
@@ -109,6 +109,9 @@ export class Env {
109
109
  this._prologAttributes = null;
110
110
  this._attributeHookRunner = null;
111
111
  this._pendingAttributeGoals = null;
112
+ // Backtrackable blackboard entries used by Scryer-compatible libraries.
113
+ // Values are logical terms and the map is copy-on-write across Env clones.
114
+ this._backtrackableBlackboard = null;
112
115
  this._clpz = null;
113
116
  this._occursCheckHandler = null;
114
117
  this._localVariables = null;
@@ -127,6 +130,7 @@ export class Env {
127
130
  clone._prologAttributes = this._prologAttributes;
128
131
  clone._attributeHookRunner = this._attributeHookRunner;
129
132
  clone._pendingAttributeGoals = this._pendingAttributeGoals;
133
+ clone._backtrackableBlackboard = this._backtrackableBlackboard;
130
134
  clone._clpz = this._clpz;
131
135
  clone._occursCheckHandler = this._occursCheckHandler;
132
136
  clone._localVariables = this._localVariables;
@@ -148,12 +152,21 @@ export class Env {
148
152
  this._variableAnnotations = other._variableAnnotations;
149
153
  this._prologAttributes = other._prologAttributes;
150
154
  this._pendingAttributeGoals = other._pendingAttributeGoals;
155
+ this._backtrackableBlackboard = other._backtrackableBlackboard;
151
156
  this._clpz = other._clpz;
152
157
  // Execution callbacks belong to the Solver driving this Env, not to the
153
158
  // logical branch being adopted from an inner attribute-hook call.
154
159
  this._localVariables = other._localVariables;
155
160
  return this;
156
161
  }
162
+ getBacktrackableBlackboard(key) {
163
+ return this._backtrackableBlackboard?.get(key);
164
+ }
165
+ putBacktrackableBlackboard(key, value) {
166
+ const next = new Map(this._backtrackableBlackboard ?? []);
167
+ next.set(key, value);
168
+ this._backtrackableBlackboard = next;
169
+ }
157
170
  hasLocalVariables() {
158
171
  return this._localVariables != null && this._localVariables.size !== 0;
159
172
  }