eyeprolog 1.3.91 → 1.3.93
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/examples/book/README.md +1 -0
- package/examples/book/chapter-40/02-program.pl +5 -4
- package/examples/book/chapter-40/03-program-2.pl +4 -4
- package/examples/book/chapter-40/04-program-3.pl +5 -0
- package/index.d.ts +6 -1
- package/package.json +1 -1
- package/src/iso.js +198 -64
- package/src/quads.js +23 -1
- package/src/scryer-compat.js +22 -12
- package/src/solver.js +100 -82
- package/src/standard-library.js +15 -3
- package/src/term.js +135 -13
- package/test/run-regression.mjs +123 -27
- package/the-art-of-eyeprolog.md +27 -10
package/examples/book/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
% From The Art of EyeProlog, Chapter 40.
|
|
2
|
-
?-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
?- dif(X,Y), X = a.
|
|
3
|
+
true, unexpected.
|
|
4
|
+
X = a, unexpected.
|
|
5
|
+
X = a, maybe.
|
|
6
|
+
maybe, unexpected.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
% From The Art of EyeProlog, Chapter 40.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
?- read(X).
|
|
3
|
+
inputs("1."), X = 1, unexpected.
|
|
4
|
+
inputs("1."), peeks(" "), X = 1.
|
|
5
|
+
inputs("1. "), peeks(" "), X = 1, unexpected.
|
package/index.d.ts
CHANGED
|
@@ -182,7 +182,12 @@ export interface BuiltinDefinition {
|
|
|
182
182
|
eyePrologLibrary: boolean;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
export
|
|
185
|
+
export interface BuiltinIterator extends IterableIterator<Env> {
|
|
186
|
+
hasPendingAlternatives?: () => boolean;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type BuiltinHandler =
|
|
190
|
+
(context: { solver: Solver; goal: EyePrologTerm; env: Env }) => BuiltinIterator;
|
|
186
191
|
|
|
187
192
|
export class BuiltinRegistry {
|
|
188
193
|
constructor();
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -627,16 +627,30 @@ function validPredicateIndicator(term) {
|
|
|
627
627
|
(term.args[1].type === NUMBER && isDecimalInteger(term.args[1].name) && BigInt(term.args[1].name) >= 0n));
|
|
628
628
|
}
|
|
629
629
|
|
|
630
|
-
function
|
|
630
|
+
function currentPredicateBuiltin(context) {
|
|
631
|
+
const state = { pending: false };
|
|
632
|
+
return withPendingState(currentPredicateSolutions(context, state), state);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function* currentPredicateSolutions({ solver, goal, env }, state) {
|
|
631
636
|
const indicator = copyResolved(goal.args[0], env);
|
|
632
637
|
if (indicator.type !== VAR && !validPredicateIndicator(indicator)) {
|
|
633
638
|
throw new PrologError('type_error(predicate_indicator)', indicator);
|
|
634
639
|
}
|
|
635
|
-
|
|
640
|
+
const groups = [...solver.program.groups.values()].filter((group) =>
|
|
641
|
+
indicator.type === VAR ||
|
|
642
|
+
(indicator.args[0].type === VAR || indicator.args[0].name === group.name) &&
|
|
643
|
+
(indicator.args[1].type === VAR || BigInt(indicator.args[1].name) === BigInt(group.arity)));
|
|
644
|
+
for (let index = 0; index < groups.length; index++) {
|
|
645
|
+
const group = groups[index];
|
|
636
646
|
const next = env.clone();
|
|
637
647
|
const candidate = compound('/', [atom(group.name), numberTerm(group.arity)]);
|
|
638
|
-
if (unify(goal.args[0], candidate, next))
|
|
648
|
+
if (unify(goal.args[0], candidate, next)) {
|
|
649
|
+
state.pending = index + 1 < groups.length;
|
|
650
|
+
yield next;
|
|
651
|
+
}
|
|
639
652
|
}
|
|
653
|
+
state.pending = false;
|
|
640
654
|
}
|
|
641
655
|
|
|
642
656
|
function callableOrVariable(term, env) {
|
|
@@ -652,7 +666,12 @@ function clauseBodyTerm(body) {
|
|
|
652
666
|
return result;
|
|
653
667
|
}
|
|
654
668
|
|
|
655
|
-
function
|
|
669
|
+
function clauseBuiltin(context) {
|
|
670
|
+
const state = { pending: false };
|
|
671
|
+
return withPendingState(clauseSolutions(context, state), state);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function* clauseSolutions({ solver, goal, env }, state) {
|
|
656
675
|
const head = deref(goal.args[0], env);
|
|
657
676
|
if (head.type === VAR) throw new PrologError('instantiation_error');
|
|
658
677
|
if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
|
|
@@ -667,12 +686,17 @@ function* clauseBuiltin({ solver, goal, env }) {
|
|
|
667
686
|
}
|
|
668
687
|
callableOrVariable(goal.args[1], env);
|
|
669
688
|
if (!group) return;
|
|
670
|
-
for (
|
|
689
|
+
for (let index = 0; index < group.clauses.length; index++) {
|
|
690
|
+
const clause = group.clauses[index];
|
|
671
691
|
const pair = compound('$clause', [clause.head, clauseBodyTerm(clause.body)]);
|
|
672
692
|
const copied = freshCopy(pair, new Env());
|
|
673
693
|
const next = env.clone();
|
|
674
|
-
if (unify(goal.args[0], copied.args[0], next) && unify(goal.args[1], copied.args[1], next))
|
|
694
|
+
if (unify(goal.args[0], copied.args[0], next) && unify(goal.args[1], copied.args[1], next)) {
|
|
695
|
+
state.pending = index + 1 < group.clauses.length;
|
|
696
|
+
yield next;
|
|
697
|
+
}
|
|
675
698
|
}
|
|
699
|
+
state.pending = false;
|
|
676
700
|
}
|
|
677
701
|
|
|
678
702
|
function clauseParts(term, env) {
|
|
@@ -845,21 +869,32 @@ function* abolishBuiltin({ solver, goal, env }) {
|
|
|
845
869
|
yield env;
|
|
846
870
|
}
|
|
847
871
|
|
|
848
|
-
function
|
|
872
|
+
function currentPrologFlagBuiltin(context) {
|
|
873
|
+
const state = { pending: false };
|
|
874
|
+
return withPendingState(currentPrologFlagSolutions(context, state), state);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function* currentPrologFlagSolutions({ solver, goal, env }, state) {
|
|
849
878
|
const flag = deref(goal.args[0], env);
|
|
850
879
|
if (flag.type !== VAR && flag.type !== ATOM) throw new PrologError('type_error(atom)', flag);
|
|
851
880
|
if (flag.type === ATOM && !solver.prologFlags.has(flag.name)) {
|
|
852
881
|
throw new PrologError('domain_error(prolog_flag)', flag);
|
|
853
882
|
}
|
|
854
|
-
|
|
883
|
+
const definitions = [...solver.prologFlags]
|
|
884
|
+
.filter(([name, definition]) => definition.value != null && (flag.type === VAR || flag.name === name));
|
|
885
|
+
for (let index = 0; index < definitions.length; index++) {
|
|
886
|
+
const [name, definition] = definitions[index];
|
|
855
887
|
// ISO 7.11.1.1: when bounded=false, max_integer and min_integer have no
|
|
856
888
|
// current value and current_prolog_flag/2 must therefore not enumerate
|
|
857
889
|
// them. The definitions remain registered so attempts to change these
|
|
858
890
|
// non-changeable flags still receive the normal flag error handling.
|
|
859
|
-
if (definition.value == null) continue;
|
|
860
891
|
const next = env.clone();
|
|
861
|
-
if (unify(goal.args[0], atom(name), next) && unify(goal.args[1], definition.value, next))
|
|
892
|
+
if (unify(goal.args[0], atom(name), next) && unify(goal.args[1], definition.value, next)) {
|
|
893
|
+
state.pending = index + 1 < definitions.length;
|
|
894
|
+
yield next;
|
|
895
|
+
}
|
|
862
896
|
}
|
|
897
|
+
state.pending = false;
|
|
863
898
|
}
|
|
864
899
|
|
|
865
900
|
function* setPrologFlagBuiltin({ solver, goal, env }) {
|
|
@@ -946,7 +981,12 @@ function* opBuiltin({ solver, goal, env }) {
|
|
|
946
981
|
yield env;
|
|
947
982
|
}
|
|
948
983
|
|
|
949
|
-
function
|
|
984
|
+
function currentOpBuiltin(context) {
|
|
985
|
+
const state = { pending: false };
|
|
986
|
+
return withPendingState(currentOpSolutions(context, state), state);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function* currentOpSolutions({ solver, goal, env }, state) {
|
|
950
990
|
const priority = deref(goal.args[0], env);
|
|
951
991
|
const specifier = deref(goal.args[1], env);
|
|
952
992
|
const name = deref(goal.args[2], env);
|
|
@@ -962,12 +1002,21 @@ function* currentOpBuiltin({ solver, goal, env }) {
|
|
|
962
1002
|
throw new PrologError('domain_error(operator_specifier)', specifier);
|
|
963
1003
|
}
|
|
964
1004
|
if (name.type !== VAR && name.type !== ATOM) throw new PrologError('type_error(atom)', name);
|
|
965
|
-
|
|
1005
|
+
const definitions = [...solver.program.operators.values()].filter((definition) =>
|
|
1006
|
+
(priority.type === VAR || BigInt(priority.name) === BigInt(definition.priority)) &&
|
|
1007
|
+
(specifier.type === VAR || specifier.name === definition.specifier) &&
|
|
1008
|
+
(name.type === VAR || name.name === definition.name));
|
|
1009
|
+
for (let index = 0; index < definitions.length; index++) {
|
|
1010
|
+
const definition = definitions[index];
|
|
966
1011
|
const next = env.clone();
|
|
967
1012
|
if (unify(goal.args[0], numberTerm(definition.priority), next) &&
|
|
968
1013
|
unify(goal.args[1], atom(definition.specifier), next) &&
|
|
969
|
-
unify(goal.args[2], atom(definition.name), next))
|
|
1014
|
+
unify(goal.args[2], atom(definition.name), next)) {
|
|
1015
|
+
state.pending = index + 1 < definitions.length;
|
|
1016
|
+
yield next;
|
|
1017
|
+
}
|
|
970
1018
|
}
|
|
1019
|
+
state.pending = false;
|
|
971
1020
|
}
|
|
972
1021
|
|
|
973
1022
|
function conversionCharacter(term, env, current = false, solver = null) {
|
|
@@ -998,13 +1047,25 @@ function* charConversionBuiltin({ solver, goal, env }) {
|
|
|
998
1047
|
else solver.charConversions.set(input.name, output.name);
|
|
999
1048
|
yield env;
|
|
1000
1049
|
}
|
|
1001
|
-
function
|
|
1050
|
+
function currentCharConversionBuiltin(context) {
|
|
1051
|
+
const state = { pending: false };
|
|
1052
|
+
return withPendingState(currentCharConversionSolutions(context, state), state);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function* currentCharConversionSolutions({ solver, goal, env }, state) {
|
|
1002
1056
|
const input = conversionCharacter(goal.args[0], env, true, solver);
|
|
1003
1057
|
const output = conversionCharacter(goal.args[1], env, true, solver);
|
|
1004
|
-
|
|
1058
|
+
const conversions = [...solver.charConversions].filter(([from, to]) =>
|
|
1059
|
+
(input.type === VAR || input.name === from) && (output.type === VAR || output.name === to));
|
|
1060
|
+
for (let index = 0; index < conversions.length; index++) {
|
|
1061
|
+
const [from, to] = conversions[index];
|
|
1005
1062
|
const next = env.clone();
|
|
1006
|
-
if (unify(input, atom(from), next) && unify(output, atom(to), next))
|
|
1063
|
+
if (unify(input, atom(from), next) && unify(output, atom(to), next)) {
|
|
1064
|
+
state.pending = index + 1 < conversions.length;
|
|
1065
|
+
yield next;
|
|
1066
|
+
}
|
|
1007
1067
|
}
|
|
1068
|
+
state.pending = false;
|
|
1008
1069
|
}
|
|
1009
1070
|
function* haltBuiltin({ goal, env }) {
|
|
1010
1071
|
const code = goal.arity === 0 ? 0n : requireInteger(goal.args[0], env);
|
|
@@ -1293,7 +1354,12 @@ function isStreamPropertyPattern(value) {
|
|
|
1293
1354
|
].includes(value.name);
|
|
1294
1355
|
}
|
|
1295
1356
|
|
|
1296
|
-
function
|
|
1357
|
+
function streamPropertyBuiltin(context) {
|
|
1358
|
+
const state = { pending: false };
|
|
1359
|
+
return withPendingState(streamPropertySolutions(context, state), state);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function* streamPropertySolutions({ solver, goal, env }, state) {
|
|
1297
1363
|
const reference = deref(goal.args[0], env);
|
|
1298
1364
|
const propertyPattern = deref(goal.args[1], env);
|
|
1299
1365
|
let fixedStreamId = null;
|
|
@@ -1311,12 +1377,24 @@ function* streamPropertyBuiltin({ solver, goal, env }) {
|
|
|
1311
1377
|
const streams = reference.type === VAR
|
|
1312
1378
|
? [...solver.io.streams.values()]
|
|
1313
1379
|
: [solver.io.resolve(fixedStreamId)].filter(Boolean);
|
|
1380
|
+
const candidates = [];
|
|
1314
1381
|
for (const stream of streams) {
|
|
1315
1382
|
for (const property of streamProperties(stream)) {
|
|
1316
|
-
|
|
1317
|
-
|
|
1383
|
+
if (propertyPattern.type === VAR ||
|
|
1384
|
+
(property.type === propertyPattern.type && property.name === propertyPattern.name)) {
|
|
1385
|
+
candidates.push([stream, property]);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
1390
|
+
const [stream, property] = candidates[index];
|
|
1391
|
+
const next = env.clone();
|
|
1392
|
+
if (unify(goal.args[0], streamHandle(stream.id), next) && unify(goal.args[1], property, next)) {
|
|
1393
|
+
state.pending = index + 1 < candidates.length;
|
|
1394
|
+
yield next;
|
|
1318
1395
|
}
|
|
1319
1396
|
}
|
|
1397
|
+
state.pending = false;
|
|
1320
1398
|
}
|
|
1321
1399
|
|
|
1322
1400
|
function inputStreamFor(solver, goal, env) {
|
|
@@ -1919,7 +1997,12 @@ function* atomLengthBuiltin({ goal, env }) {
|
|
|
1919
1997
|
if (unify(goal.args[1], numberTerm(characters(value.name).length), next)) yield next;
|
|
1920
1998
|
}
|
|
1921
1999
|
|
|
1922
|
-
function
|
|
2000
|
+
function atomConcatBuiltin(context) {
|
|
2001
|
+
const state = { pending: false };
|
|
2002
|
+
return withPendingState(atomConcatSolutions(context, state), state);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function* atomConcatSolutions({ goal, env }, state) {
|
|
1923
2006
|
const first = deref(goal.args[0], env);
|
|
1924
2007
|
const second = deref(goal.args[1], env);
|
|
1925
2008
|
const whole = deref(goal.args[2], env);
|
|
@@ -1942,11 +2025,16 @@ function* atomConcatBuiltin({ goal, env }) {
|
|
|
1942
2025
|
} else if (second.type === ATOM && whole.type === ATOM && whole.name.endsWith(second.name)) {
|
|
1943
2026
|
candidates.push([whole.name.slice(0, whole.name.length - second.name.length), second.name, whole.name]);
|
|
1944
2027
|
}
|
|
1945
|
-
for (
|
|
2028
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
2029
|
+
const [a, b, c] = candidates[index];
|
|
1946
2030
|
const next = env.clone();
|
|
1947
2031
|
if (unify(goal.args[0], atom(a), next) && unify(goal.args[1], atom(b), next) &&
|
|
1948
|
-
unify(goal.args[2], atom(c), next))
|
|
2032
|
+
unify(goal.args[2], atom(c), next)) {
|
|
2033
|
+
state.pending = index + 1 < candidates.length;
|
|
2034
|
+
yield next;
|
|
2035
|
+
}
|
|
1949
2036
|
}
|
|
2037
|
+
state.pending = false;
|
|
1950
2038
|
}
|
|
1951
2039
|
|
|
1952
2040
|
function optionalInteger(term, env) {
|
|
@@ -1956,7 +2044,12 @@ function optionalInteger(term, env) {
|
|
|
1956
2044
|
return BigInt(value.name);
|
|
1957
2045
|
}
|
|
1958
2046
|
|
|
1959
|
-
function
|
|
2047
|
+
function subAtomBuiltin(context) {
|
|
2048
|
+
const state = { pending: false };
|
|
2049
|
+
return withPendingState(subAtomSolutions(context, state), state);
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function* subAtomSolutions({ goal, env }, state) {
|
|
1960
2053
|
const source = deref(goal.args[0], env);
|
|
1961
2054
|
if (source.type === VAR) throw new PrologError('instantiation_error');
|
|
1962
2055
|
if (source.type !== ATOM) throw new PrologError('type_error(atom)', source);
|
|
@@ -1977,11 +2070,34 @@ function* subAtomBuiltin({ goal, env }) {
|
|
|
1977
2070
|
throw new PrologError('domain_error(not_less_than_zero)', deref(goal.args[3], env));
|
|
1978
2071
|
}
|
|
1979
2072
|
const chars = characters(source.name);
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2073
|
+
let starts;
|
|
2074
|
+
if (before != null) {
|
|
2075
|
+
starts = before <= BigInt(chars.length) ? [Number(before)] : [];
|
|
2076
|
+
} else if (length != null && after != null) {
|
|
2077
|
+
const onlyStart = BigInt(chars.length) - length - after;
|
|
2078
|
+
starts = onlyStart >= 0n ? [Number(onlyStart)] : [];
|
|
2079
|
+
} else {
|
|
2080
|
+
const maximumStart = length != null
|
|
2081
|
+
? BigInt(chars.length) - length
|
|
2082
|
+
: after != null ? BigInt(chars.length) - after : BigInt(chars.length);
|
|
2083
|
+
starts = maximumStart < 0n
|
|
2084
|
+
? []
|
|
2085
|
+
: Array.from({ length: Number(maximumStart) + 1 }, (_, index) => index);
|
|
2086
|
+
}
|
|
2087
|
+
for (let startIndex = 0; startIndex < starts.length; startIndex++) {
|
|
2088
|
+
const start = starts[startIndex];
|
|
2089
|
+
let sizes;
|
|
2090
|
+
if (length != null) {
|
|
2091
|
+
sizes = length <= BigInt(chars.length - start) ? [Number(length)] : [];
|
|
2092
|
+
} else if (after != null) {
|
|
2093
|
+
const onlySize = BigInt(chars.length - start) - after;
|
|
2094
|
+
sizes = onlySize >= 0n ? [Number(onlySize)] : [];
|
|
2095
|
+
} else {
|
|
2096
|
+
sizes = Array.from({ length: chars.length - start + 1 }, (_, index) => index);
|
|
2097
|
+
}
|
|
2098
|
+
for (let sizeIndex = 0; sizeIndex < sizes.length; sizeIndex++) {
|
|
2099
|
+
const size = sizes[sizeIndex];
|
|
1983
2100
|
const remaining = chars.length - start - size;
|
|
1984
|
-
if (length != null && length !== BigInt(size)) continue;
|
|
1985
2101
|
if (after != null && after !== BigInt(remaining)) continue;
|
|
1986
2102
|
const text = chars.slice(start, start + size).join('');
|
|
1987
2103
|
if (sub.type === ATOM && sub.name !== text) continue;
|
|
@@ -1989,9 +2105,13 @@ function* subAtomBuiltin({ goal, env }) {
|
|
|
1989
2105
|
if (unify(goal.args[1], numberTerm(start), next) &&
|
|
1990
2106
|
unify(goal.args[2], numberTerm(size), next) &&
|
|
1991
2107
|
unify(goal.args[3], numberTerm(remaining), next) &&
|
|
1992
|
-
unify(goal.args[4], atom(text), next))
|
|
2108
|
+
unify(goal.args[4], atom(text), next)) {
|
|
2109
|
+
state.pending = sizeIndex + 1 < sizes.length || startIndex + 1 < starts.length;
|
|
2110
|
+
yield next;
|
|
2111
|
+
}
|
|
1993
2112
|
}
|
|
1994
2113
|
}
|
|
2114
|
+
state.pending = false;
|
|
1995
2115
|
}
|
|
1996
2116
|
|
|
1997
2117
|
function listElements(term, env) {
|
|
@@ -2406,42 +2526,52 @@ function sortedUnique(items) {
|
|
|
2406
2526
|
}
|
|
2407
2527
|
|
|
2408
2528
|
function allSolutionsBuiltin(asSet) {
|
|
2409
|
-
return function
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2529
|
+
return function allSolutions(context) {
|
|
2530
|
+
const state = { pending: false };
|
|
2531
|
+
return withPendingState(allSolutionsGroups(context, asSet, state), state);
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
function* allSolutionsGroups({ solver, goal, env }, asSet, state) {
|
|
2536
|
+
// EyeProlog validates the iterated goal before Instances. ISO 7.12 makes
|
|
2537
|
+
// simultaneous-error selection implementation dependent; keep this stable as
|
|
2538
|
+
// a processor choice rather than describing it as mandatory table ordering.
|
|
2539
|
+
const { iterated, quantified } = bagGoalParts(goal.args[1], env);
|
|
2540
|
+
assertListOrPartial(goal.args[2], env);
|
|
2541
|
+
const free = freeVariables(iterated, goal.args[0], quantified, env);
|
|
2542
|
+
const collector = solver.cloneForInnerGoal(10000000);
|
|
2543
|
+
const groups = [];
|
|
2544
|
+
for (const answerEnv of collector.solve([iterated], env.clone(), 0)) {
|
|
2545
|
+
const copied = freshCopy(compound('$bag', [
|
|
2546
|
+
compound('$witness', free),
|
|
2547
|
+
goal.args[0],
|
|
2548
|
+
]), answerEnv);
|
|
2549
|
+
let group = groups.find((candidate) => sameWitness(candidate.witness, copied.args[0]));
|
|
2550
|
+
if (!group) {
|
|
2551
|
+
group = { witness: copied.args[0], templates: [] };
|
|
2552
|
+
groups.push(group);
|
|
2553
|
+
group.templates.push(copied.args[1]);
|
|
2554
|
+
} else {
|
|
2555
|
+
const alignment = new Env();
|
|
2556
|
+
unify(copied.args[0], group.witness, alignment);
|
|
2557
|
+
group.templates.push(copyResolved(copied.args[1], alignment));
|
|
2433
2558
|
}
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
if (matches && unify(goal.args[2], listFromItems(templates), next)) yield next;
|
|
2559
|
+
}
|
|
2560
|
+
solver.absorbStatsFrom(collector);
|
|
2561
|
+
for (let index = 0; index < groups.length; index++) {
|
|
2562
|
+
const group = groups[index];
|
|
2563
|
+
const next = env.clone();
|
|
2564
|
+
let matches = true;
|
|
2565
|
+
for (let i = 0; i < free.length; i++) {
|
|
2566
|
+
if (!unify(free[i], group.witness.args[i], next)) { matches = false; break; }
|
|
2443
2567
|
}
|
|
2444
|
-
|
|
2568
|
+
const templates = asSet ? sortedUnique(group.templates) : group.templates;
|
|
2569
|
+
if (matches && unify(goal.args[2], listFromItems(templates), next)) {
|
|
2570
|
+
state.pending = index + 1 < groups.length;
|
|
2571
|
+
yield next;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
state.pending = false;
|
|
2445
2575
|
}
|
|
2446
2576
|
const bagofBuiltin = allSolutionsBuiltin(false);
|
|
2447
2577
|
const setofBuiltin = allSolutionsBuiltin(true);
|
|
@@ -2964,8 +3094,12 @@ function* onceBuiltin({ solver, goal, env }) {
|
|
|
2964
3094
|
}
|
|
2965
3095
|
solver.absorbStatsFrom(child);
|
|
2966
3096
|
}
|
|
2967
|
-
function
|
|
2968
|
-
|
|
3097
|
+
function repeatBuiltin({ env }) {
|
|
3098
|
+
const iterator = (function* repeatSolutions() {
|
|
3099
|
+
while (true) yield env;
|
|
3100
|
+
})();
|
|
3101
|
+
iterator.hasPendingAlternatives = () => true;
|
|
3102
|
+
return iterator;
|
|
2969
3103
|
}
|
|
2970
3104
|
function* negationBuiltin({ solver, goal, env }) {
|
|
2971
3105
|
const invoked = callable(goal.args[0], env);
|
package/src/quads.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Embedded quad tests: a query followed by one or more answer descriptions.
|
|
2
2
|
// The syntax follows the portable "queries using answer descriptions" format
|
|
3
3
|
// used by Trealla and the ISO Prolog working examples linked from issue #1.
|
|
4
|
+
// A `maybe` annotation denotes a successful answer with residual constraints;
|
|
5
|
+
// it does not relax the ordinary answer-substitution comparison.
|
|
4
6
|
import {
|
|
5
7
|
ATOM, COMPOUND, VAR, Env, atom, compound, copyResolved, deref,
|
|
6
8
|
flattenConjunction, listFromItems, properListItems, termIsGround,
|
|
@@ -203,6 +205,7 @@ function describeLeaf(term) {
|
|
|
203
205
|
sto: false,
|
|
204
206
|
false: false,
|
|
205
207
|
truth: false,
|
|
208
|
+
maybe: false,
|
|
206
209
|
loops: false,
|
|
207
210
|
waits: false,
|
|
208
211
|
error: null,
|
|
@@ -218,6 +221,7 @@ function describeLeaf(term) {
|
|
|
218
221
|
if (item.name === 'unexpected' || item.name === 'inattendue') leaf.unexpected = true;
|
|
219
222
|
else if (item.name === '...' || item.name === 'ad_infinitum') leaf.more = true;
|
|
220
223
|
else if (item.name === 'sto') leaf.sto = true;
|
|
224
|
+
else if (item.name === 'maybe') leaf.maybe = true;
|
|
221
225
|
else if (item.name === 'loops') leaf.loops = true;
|
|
222
226
|
else if (item.name === 'waits') leaf.waits = true;
|
|
223
227
|
else if (item.name === 'other_answer_sequence') {
|
|
@@ -253,10 +257,11 @@ function describeLeaf(term) {
|
|
|
253
257
|
if (isErrorDescription(item)) leaf.error = item;
|
|
254
258
|
else leaf.malformed ??= item;
|
|
255
259
|
}
|
|
256
|
-
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.loops || leaf.waits ||
|
|
260
|
+
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.maybe || leaf.loops || leaf.waits ||
|
|
257
261
|
leaf.error != null || leaf.output != null;
|
|
258
262
|
if (!leaf.hasExpectation && !leaf.more && !leaf.sto && leaf.unsupported == null) leaf.malformed ??= term;
|
|
259
263
|
if ([leaf.false, leaf.truth, leaf.error != null].filter(Boolean).length > 1) leaf.malformed ??= term;
|
|
264
|
+
if (leaf.maybe && (leaf.false || leaf.loops || leaf.waits || leaf.error != null)) leaf.malformed ??= term;
|
|
260
265
|
return leaf;
|
|
261
266
|
}
|
|
262
267
|
|
|
@@ -386,9 +391,26 @@ function matchLeaf(program, query, leaf, actual, position) {
|
|
|
386
391
|
}
|
|
387
392
|
const solution = actual.solutions[position];
|
|
388
393
|
if (!solution || !outputMatches(program, leaf.output, solution.output)) return false;
|
|
394
|
+
// Portable answer descriptions distinguish plain success from success with
|
|
395
|
+
// residual constraints. `maybe` requires at least one pending residue, while
|
|
396
|
+
// its absence requires an unconstrained answer. In either case the stated
|
|
397
|
+
// substitutions remain exact and are checked below.
|
|
398
|
+
if (leaf.maybe !== hasPendingConstraints(solution.env)) return false;
|
|
389
399
|
return substitutionMatches(query, leaf.bindings, solution.env);
|
|
390
400
|
}
|
|
391
401
|
|
|
402
|
+
function hasPendingConstraints(env) {
|
|
403
|
+
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
404
|
+
if (constraint.status?.(env) === 'pending') return true;
|
|
405
|
+
}
|
|
406
|
+
// Prolog attributed variables and delayed goals are the other residual-state
|
|
407
|
+
// mechanisms exposed by call_residue_vars/2. Treat either as pending residue
|
|
408
|
+
// even when a library does not provide a printable projection goal.
|
|
409
|
+
if ((env.attributedVariableNames?.() ?? []).length > 0) return true;
|
|
410
|
+
if ((env.delayedVariableNames?.() ?? []).length > 0) return true;
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
392
414
|
function descriptionDeclaresSto(description) {
|
|
393
415
|
return splitOperator(description, '|').some((alternative) =>
|
|
394
416
|
splitOperator(alternative, ';').some((term) => describeLeaf(term).sto));
|
package/src/scryer-compat.js
CHANGED
|
@@ -103,7 +103,14 @@ function validCharType(term) {
|
|
|
103
103
|
(term.type === COMPOUND && ['lower', 'upper'].includes(term.name) && term.arity === 1);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function
|
|
106
|
+
function charTypeBuiltin(context) {
|
|
107
|
+
const state = { pending: false };
|
|
108
|
+
const iterator = charTypeSolutions(context, state);
|
|
109
|
+
iterator.hasPendingAlternatives = () => state.pending;
|
|
110
|
+
return iterator;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function* charTypeSolutions({ goal, env }, state) {
|
|
107
114
|
const char = deref(goal.args[0], env);
|
|
108
115
|
const type = deref(goal.args[1], env);
|
|
109
116
|
if (char.type === VAR && type.type === VAR) throw new PrologError('instantiation_error');
|
|
@@ -112,20 +119,23 @@ function* charTypeBuiltin({ goal, env }) {
|
|
|
112
119
|
}
|
|
113
120
|
if (type.type !== VAR && !validCharType(type)) throw new PrologError('domain_error(char_type)', type);
|
|
114
121
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
for (const candidate of charTypeCandidates(ch)) {
|
|
122
|
+
const firstCode = char.type === VAR ? 0 : char.name.codePointAt(0);
|
|
123
|
+
const lastCode = char.type === VAR ? 0x10ffff : firstCode;
|
|
124
|
+
for (let code = firstCode; code <= lastCode; code++) {
|
|
125
|
+
if (code >= 0xd800 && code <= 0xdfff) continue;
|
|
126
|
+
const ch = String.fromCodePoint(code);
|
|
127
|
+
const candidates = charTypeCandidates(ch).filter((candidate) =>
|
|
128
|
+
type.type === VAR || (candidate.type === type.type && candidate.name === type.name));
|
|
129
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
130
|
+
const candidate = candidates[index];
|
|
125
131
|
const next = env.clone();
|
|
126
|
-
if (unify(goal.args[0], atom(ch), next) && unify(goal.args[1], candidate, next))
|
|
132
|
+
if (unify(goal.args[0], atom(ch), next) && unify(goal.args[1], candidate, next)) {
|
|
133
|
+
state.pending = index + 1 < candidates.length || code < lastCode;
|
|
134
|
+
yield next;
|
|
135
|
+
}
|
|
127
136
|
}
|
|
128
137
|
}
|
|
138
|
+
state.pending = false;
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
const shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|