eyeprolog 1.5.11 → 1.5.12
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/package.json +1 -1
- package/src/execute.js +1 -0
- package/src/iso.js +2 -0
- package/src/parser.js +31 -3
- package/src/repl.js +2 -0
- package/src/term.js +17 -5
- package/src/write.js +19 -5
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/conformance/expected/iso/scryer_lexical_terms.pl +1 -1
- package/test/conformance/expected/lists/extra_append_to_improper_tail.pl +1 -1
- package/test/conformance/expected/lists/improper_list_readback.pl +1 -1
- package/test/conformance/expected/terms/003_terms_and_readback.pl +1 -1
- package/test/run-regression.mjs +39 -1
- package/the-art-of-eyeprolog.md +15 -3
package/package.json
CHANGED
package/src/execute.js
CHANGED
|
@@ -51,6 +51,7 @@ export function executeGoals(program, solver, goals, { onAnswer = () => {} } = {
|
|
|
51
51
|
function currentWriteOptions(program, solver) {
|
|
52
52
|
return {
|
|
53
53
|
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
54
|
+
doubleBar: !solver.isoStrict,
|
|
54
55
|
operators: [...program.operators.values()],
|
|
55
56
|
quoted: true,
|
|
56
57
|
};
|
package/src/iso.js
CHANGED
|
@@ -1988,6 +1988,7 @@ function writeBuiltin(mode) {
|
|
|
1988
1988
|
generateVariableNames: true,
|
|
1989
1989
|
variableNameState: solver.writeVariableState,
|
|
1990
1990
|
operators: solver.program.operators.values(),
|
|
1991
|
+
doubleBar: !solver.isoStrict,
|
|
1991
1992
|
}));
|
|
1992
1993
|
yield env;
|
|
1993
1994
|
};
|
|
@@ -2019,6 +2020,7 @@ function* writeTermBuiltin({ solver, goal, env }) {
|
|
|
2019
2020
|
generateVariableNames: true,
|
|
2020
2021
|
variableNameState: solver.writeVariableState,
|
|
2021
2022
|
operators: solver.program.operators.values(),
|
|
2023
|
+
doubleBar: !solver.isoStrict,
|
|
2022
2024
|
}));
|
|
2023
2025
|
yield env;
|
|
2024
2026
|
}
|
package/src/parser.js
CHANGED
|
@@ -937,9 +937,37 @@ class Parser {
|
|
|
937
937
|
this.parserFlagState.doubleQuotes === 'chars'
|
|
938
938
|
? atom(character)
|
|
939
939
|
: numberTerm(character.codePointAt(0)));
|
|
940
|
-
let
|
|
941
|
-
|
|
942
|
-
|
|
940
|
+
let tail = emptyList();
|
|
941
|
+
|
|
942
|
+
// Trealla-compatible double-bar right splice (issue #88): in normal
|
|
943
|
+
// mode, a double-quoted chars/codes prefix may be followed by `||Tail`.
|
|
944
|
+
// It is equivalent to the corresponding list prefix with Tail as its
|
|
945
|
+
// final list tail, e.g. "ab"||T is [a,b|T] with double_quotes(chars).
|
|
946
|
+
// The extension binds at priority 1 (tighter than ordinary operators)
|
|
947
|
+
// and is deliberately absent from the strict ISO profile.
|
|
948
|
+
if (!this.strictIso && this.token.type === TOK.BAR) {
|
|
949
|
+
const state = {
|
|
950
|
+
pos: this.pos,
|
|
951
|
+
line: this.line,
|
|
952
|
+
previousToken: this.previousToken,
|
|
953
|
+
token: this.token,
|
|
954
|
+
};
|
|
955
|
+
this.advance();
|
|
956
|
+
if (this.token.type === TOK.BAR) {
|
|
957
|
+
this.advance();
|
|
958
|
+
tail = this.parseTerm(operatorStrength(1), false, allowBar, false);
|
|
959
|
+
} else {
|
|
960
|
+
// A single bar remains ordinary bar syntax. We had to advance once
|
|
961
|
+
// to distinguish it from `||`, so restore the tokenizer state.
|
|
962
|
+
this.pos = state.pos;
|
|
963
|
+
this.line = state.line;
|
|
964
|
+
this.previousToken = state.previousToken;
|
|
965
|
+
this.token = state.token;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
for (let i = items.length - 1; i >= 0; i--) tail = cons(items[i], tail);
|
|
970
|
+
return tail;
|
|
943
971
|
}
|
|
944
972
|
if (this.token.type === TOK.NUMBER) {
|
|
945
973
|
const value = this.token.text;
|
package/src/repl.js
CHANGED
|
@@ -746,6 +746,7 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
746
746
|
operatorAtomsAsArgs: true,
|
|
747
747
|
dottedGraphicAtoms: true,
|
|
748
748
|
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
749
|
+
doubleBar: !state.strictIso,
|
|
749
750
|
};
|
|
750
751
|
let generated = 0;
|
|
751
752
|
|
|
@@ -844,6 +845,7 @@ function formatError(engine, state, error) {
|
|
|
844
845
|
quoted: true,
|
|
845
846
|
operators: [...state.program.operators.values()],
|
|
846
847
|
variableNames,
|
|
848
|
+
doubleBar: !state.strictIso,
|
|
847
849
|
})}.`;
|
|
848
850
|
}
|
|
849
851
|
const message = error?.message ?? String(error);
|
package/src/term.js
CHANGED
|
@@ -1067,14 +1067,18 @@ function writeString(value, quoteStrings) {
|
|
|
1067
1067
|
return out + '"';
|
|
1068
1068
|
}
|
|
1069
1069
|
|
|
1070
|
-
function
|
|
1070
|
+
function quotedListSplice(term, env, doubleQuotes) {
|
|
1071
1071
|
if (doubleQuotes !== 'chars' && doubleQuotes !== 'codes') return null;
|
|
1072
1072
|
const characters = [];
|
|
1073
1073
|
let cursor = term;
|
|
1074
1074
|
while (true) {
|
|
1075
1075
|
cursor = deref(cursor, env);
|
|
1076
|
-
if (isEmptyList(cursor))
|
|
1077
|
-
|
|
1076
|
+
if (isEmptyList(cursor)) {
|
|
1077
|
+
return characters.length === 0 ? null : { text: characters.join(''), tail: null };
|
|
1078
|
+
}
|
|
1079
|
+
if (!isCons(cursor)) {
|
|
1080
|
+
return characters.length === 0 ? null : { text: characters.join(''), tail: cursor };
|
|
1081
|
+
}
|
|
1078
1082
|
const item = deref(cursor.args[0], env);
|
|
1079
1083
|
if (doubleQuotes === 'chars') {
|
|
1080
1084
|
if (item.type !== ATOM || Array.from(item.name).length !== 1) return null;
|
|
@@ -1090,8 +1094,12 @@ function quotedListText(term, env, doubleQuotes) {
|
|
|
1090
1094
|
}
|
|
1091
1095
|
|
|
1092
1096
|
function writeList(term, env, options) {
|
|
1093
|
-
const
|
|
1094
|
-
if (
|
|
1097
|
+
const quotedSplice = quotedListSplice(term, env, options.doubleQuotes);
|
|
1098
|
+
if (quotedSplice != null && (quotedSplice.tail == null || options.doubleBar === true)) {
|
|
1099
|
+
const prefix = writeString(quotedSplice.text, true);
|
|
1100
|
+
if (quotedSplice.tail == null) return prefix;
|
|
1101
|
+
return `${prefix}||${termToString(quotedSplice.tail, env, true, options)}`;
|
|
1102
|
+
}
|
|
1095
1103
|
const parts = [];
|
|
1096
1104
|
let cursor = term;
|
|
1097
1105
|
while (true) {
|
|
@@ -1110,6 +1118,10 @@ export function termToString(term, env = new Env(), quoteStrings = true, options
|
|
|
1110
1118
|
options = {
|
|
1111
1119
|
...options,
|
|
1112
1120
|
doubleQuotes: options.doubleQuotes ?? 'chars',
|
|
1121
|
+
// termToString is also used for context-free processor-error messages.
|
|
1122
|
+
// Keep the normal-profile `||` extension opt-in here so strict errors do
|
|
1123
|
+
// not accidentally emit syntax that the strict parser rejects.
|
|
1124
|
+
doubleBar: options.doubleBar === true,
|
|
1113
1125
|
readVariableNames: options.readVariableNames instanceof Map ? options.readVariableNames : new Map(),
|
|
1114
1126
|
usedReadVariableNames: options.usedReadVariableNames instanceof Set ? options.usedReadVariableNames : new Set(),
|
|
1115
1127
|
};
|
package/src/write.js
CHANGED
|
@@ -152,14 +152,18 @@ function writeString(value) {
|
|
|
152
152
|
return out + '"';
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
function
|
|
155
|
+
function quotedListSplice(term, env, doubleQuotes) {
|
|
156
156
|
if (doubleQuotes !== 'chars' && doubleQuotes !== 'codes') return null;
|
|
157
157
|
const characters = [];
|
|
158
158
|
let cursor = term;
|
|
159
159
|
while (true) {
|
|
160
160
|
cursor = deref(cursor, env);
|
|
161
|
-
if (isEmptyList(cursor))
|
|
162
|
-
|
|
161
|
+
if (isEmptyList(cursor)) {
|
|
162
|
+
return characters.length === 0 ? null : { text: characters.join(''), tail: null };
|
|
163
|
+
}
|
|
164
|
+
if (!isCons(cursor)) {
|
|
165
|
+
return characters.length === 0 ? null : { text: characters.join(''), tail: cursor };
|
|
166
|
+
}
|
|
163
167
|
const item = deref(cursor.args[0], env);
|
|
164
168
|
if (doubleQuotes === 'chars') {
|
|
165
169
|
if (item.type !== ATOM || Array.from(item.name).length !== 1) return null;
|
|
@@ -331,8 +335,15 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
331
335
|
}
|
|
332
336
|
|
|
333
337
|
if (!options.ignoreOps && isCons(resolved)) {
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
338
|
+
const quotedSplice = quotedListSplice(resolved, env, options.doubleQuotes);
|
|
339
|
+
if (quotedSplice != null && (quotedSplice.tail == null || options.doubleBar)) {
|
|
340
|
+
const prefix = writeString(quotedSplice.text);
|
|
341
|
+
if (quotedSplice.tail == null) return prefix;
|
|
342
|
+
// `||` is a normal-profile syntax extension with priority 1, so a
|
|
343
|
+
// looser operator in the right operand must be parenthesized to keep the
|
|
344
|
+
// emitted text readable as the same term.
|
|
345
|
+
return `${prefix}||${format(quotedSplice.tail, env, options, table, 1, 'term')}`;
|
|
346
|
+
}
|
|
336
347
|
const parts = [];
|
|
337
348
|
let cursor = resolved;
|
|
338
349
|
while (true) {
|
|
@@ -442,6 +453,9 @@ export function formatTermForWrite(term, env = new Env(), options = {}) {
|
|
|
442
453
|
ignoreOps: options.ignoreOps === true,
|
|
443
454
|
numbervars: options.numbervars !== false,
|
|
444
455
|
doubleQuotes: options.doubleQuotes,
|
|
456
|
+
// `||` is a normal-profile syntax extension. Public normal-mode writers
|
|
457
|
+
// enable it by default; strict callers explicitly turn it off.
|
|
458
|
+
doubleBar: options.doubleBar !== false,
|
|
445
459
|
variableNames: options.generateVariableNames === true
|
|
446
460
|
? printableGeneratedVariableNames(term, env, explicitVariableNames, options.variableNameState)
|
|
447
461
|
: printableReadVariableNames(term, env, explicitVariableNames),
|
|
@@ -113,7 +113,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
113
113
|
|
|
114
114
|
| Part 1 extension hook | EyeProlog normal-profile feature | Strict-core disposition |
|
|
115
115
|
| --- | --- | --- |
|
|
116
|
-
| 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion,
|
|
116
|
+
| 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion, embedded quad syntax, and Trealla-compatible `"text"||Tail` right-splicing for double-quoted `chars`/`codes` lists | Module directives are rejected; grammar rules remain ordinary `-->/2` terms rather than being expanded; quad syntax and double-bar right-splicing are rejected. Unicode PCS membership/classification is implementation defined and therefore shared with normal mode rather than treated as an extension. |
|
|
117
117
|
| 5.5.2 Predefined operators | Part 3 `|` and EyeProlog's labelable infix `(?-)/2`; CLP(Z) operators when that library is imported | Only the Part 1 operator table is predefined; a conforming `op/3` may still add permitted operators. |
|
|
118
118
|
| 5.5.3 Character-conversion mapping | No non-identity initial `Convc` extension | Identity initial mapping. |
|
|
119
119
|
| 5.5.4 Types | The normal JavaScript API exposes an implementation-specific string term `stringTerm(Text)`. It is disjoint from the five Part 1 term types; normal term order places it after atoms and before compound terms, and normal `atomic/1` treats it as atomic. It has no Prolog source token syntax (double-quoted source still follows `double_quotes`), is non-callable for term-to-clause conversion, is not evaluable as an arithmetic expression, and writes as a double-quoted host string. | Strict mode rejects a programmatic string term at program/goal entry with `representation_error(term)`, so the additional type cannot enter the Part 1 execution domain. **defined** — `src/term.js`, `src/program.js`, `src/solver.js`; strict API-boundary regression. |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
answer(append_to_improper_tail,
|
|
1
|
+
answer(append_to_improper_tail, "ab"||tail).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
answer(
|
|
1
|
+
answer("ab"||tail).
|
package/test/run-regression.mjs
CHANGED
|
@@ -2441,7 +2441,7 @@ c4 ?- call((!;1)).
|
|
|
2441
2441
|
'?- Xs = [].\n' +
|
|
2442
2442
|
'?- true.\n' +
|
|
2443
2443
|
'?- true.\n' +
|
|
2444
|
-
'?- Xs0 =
|
|
2444
|
+
'?- Xs0 = "a"||Xs.\n' +
|
|
2445
2445
|
'?- ',
|
|
2446
2446
|
'stdout');
|
|
2447
2447
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -6947,6 +6947,44 @@ function whiteBoxCases() {
|
|
|
6947
6947
|
assertEqual(`${quotedAtom.type}:${quotedAtom.name}`, 'atom:aλ', 'atom');
|
|
6948
6948
|
},
|
|
6949
6949
|
},
|
|
6950
|
+
{
|
|
6951
|
+
name: 'double-bar syntax splices double-quoted list prefixes (issue #88)',
|
|
6952
|
+
run: () => {
|
|
6953
|
+
const chars = parseGoalText('p("ab"||Tail)').args[0];
|
|
6954
|
+
assertEqual(chars.args[0].name, 'a', 'chars first element');
|
|
6955
|
+
assertEqual(chars.args[1].args[0].name, 'b', 'chars second element');
|
|
6956
|
+
assertEqual(chars.args[1].args[1].type, 'var', 'chars partial tail type');
|
|
6957
|
+
assertEqual(chars.args[1].args[1].name, 'Tail', 'chars partial tail name');
|
|
6958
|
+
assertEqual(termToString(chars, new Env(), true, { doubleBar: true }), '"ab"||Tail', 'chars readback');
|
|
6959
|
+
|
|
6960
|
+
const codes = parseGoalText('p("aλ" || Tail)', { doubleQuotes: 'codes' }).args[0];
|
|
6961
|
+
assertEqual(codes.args[0].name, '97', 'codes first element');
|
|
6962
|
+
assertEqual(codes.args[1].args[0].name, '955', 'codes second element');
|
|
6963
|
+
assertEqual(termToString(codes, new Env(), true, { doubleQuotes: 'codes', doubleBar: true }), '"aλ"||Tail', 'codes readback');
|
|
6964
|
+
|
|
6965
|
+
const nested = parseGoalText('p(["ab"|Tail])');
|
|
6966
|
+
assertEqual(termToString(nested), 'p(["ab" | Tail])', 'single bar remains list syntax');
|
|
6967
|
+
|
|
6968
|
+
for (const options of [{ isoStrict: true }, { doubleQuotes: 'atom' }]) {
|
|
6969
|
+
let threw = false;
|
|
6970
|
+
try { parseGoalText('p("ab"||Tail)', options); } catch (_) { threw = true; }
|
|
6971
|
+
assertEqual(threw, true, `double-bar rejected for ${JSON.stringify(options)}`);
|
|
6972
|
+
}
|
|
6973
|
+
},
|
|
6974
|
+
},
|
|
6975
|
+
{
|
|
6976
|
+
name: 'REPL prints partial character lists with double-bar syntax (issue #88)',
|
|
6977
|
+
run: () => {
|
|
6978
|
+
const result = runCli([], {
|
|
6979
|
+
input: 'phrase("Think of this text, and much more.", S0,S).\nhalt.\n',
|
|
6980
|
+
});
|
|
6981
|
+
assertEqual(result.status, 0, 'REPL exit status');
|
|
6982
|
+
assertIncludes(result.stdout,
|
|
6983
|
+
'S0 = "Think of this text, and much more."||S.',
|
|
6984
|
+
'Trealla-compatible partial string answer');
|
|
6985
|
+
assertNotIncludes(result.stdout, "S0 = ['T'", 'legacy expanded list answer');
|
|
6986
|
+
},
|
|
6987
|
+
},
|
|
6950
6988
|
{
|
|
6951
6989
|
name: 'double_quotes directives affect subsequent source text',
|
|
6952
6990
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -474,6 +474,12 @@ Prolog programs accepted by EyeProlog are built from terms:
|
|
|
474
474
|
- compound terms: `point(3, 4)`, `reading(temp, 91)`;
|
|
475
475
|
- lists: `[]`, `[red, green, blue]`, `[Head | Tail]`.
|
|
476
476
|
|
|
477
|
+
In normal mode, double-quoted character/code lists also support Trealla's
|
|
478
|
+
right-splice notation. With `double_quotes(chars)`, `"ab"||Tail` is shorthand
|
|
479
|
+
for `[a,b|Tail]`; with `double_quotes(codes)`, it denotes `[97,98|Tail]`. The
|
|
480
|
+
splice is not available when `double_quotes(atom)` is active, and
|
|
481
|
+
`--iso-strict` rejects it as an implementation-specific syntax extension.
|
|
482
|
+
|
|
477
483
|
Plain atom constants begin with a lowercase ASCII letter. Variables begin with
|
|
478
484
|
an uppercase letter or underscore. The bare `_` is anonymous and every
|
|
479
485
|
occurrence is fresh. `_Name` is a named variable; repeated occurrences refer to
|
|
@@ -5492,8 +5498,10 @@ still retained where adjacent graphic tokens would otherwise merge, as in
|
|
|
5492
5498
|
`write_term/2-3` supports `quoted/1`, `ignore_ops/1`, `numbervars/1`, and
|
|
5493
5499
|
`variable_names/1`. Normal mode additionally accepts the EyeProlog extension
|
|
5494
5500
|
`double_quotes(true|false)`: `true` lets eligible character/code lists use the
|
|
5495
|
-
current `double_quotes` representation
|
|
5496
|
-
|
|
5501
|
+
current `double_quotes` representation. Proper lists can therefore be written
|
|
5502
|
+
as `"text"`, while a partial list such as `[a,b|Tail]` is written as
|
|
5503
|
+
`"ab"||Tail`. Strict ISO mode rejects this implementation-specific write
|
|
5504
|
+
option. Normal mode also accepts the
|
|
5497
5505
|
implementation-specific boolean `spacing(true|false)` option: `false` emits
|
|
5498
5506
|
only separators required to avoid lexical ambiguity, while `true` adds
|
|
5499
5507
|
conventional layout around operators. For example,
|
|
@@ -5588,6 +5596,8 @@ lowercase ASCII letter. Variables begin with uppercase or underscore. The bare
|
|
|
5588
5596
|
`_` is fresh each time. Single quotes delimit quoted atoms; double quotes use
|
|
5589
5597
|
ISO double-quoted-list notation. Integers, decimals, scientific notation,
|
|
5590
5598
|
binary/octal/hexadecimal integers, and character-code constants are accepted.
|
|
5599
|
+
Normal mode additionally accepts the Trealla-compatible `"text"||Tail`
|
|
5600
|
+
right-splice for double-quoted `chars`/`codes` lists; strict ISO mode does not.
|
|
5591
5601
|
|
|
5592
5602
|
The processor character set is shared by normal and `--iso-strict` modes because
|
|
5593
5603
|
Part 1 makes it implementation defined rather than an extension boundary.
|
|
@@ -8017,7 +8027,9 @@ Notable implementation boundaries are:
|
|
|
8017
8027
|
- module and ISO/IEC TS 13211-3-oriented DCG compatibility profiles are supported in normal mode, without a complete Part 2/Part 3 certification claim;
|
|
8018
8028
|
- variables cannot occupy functor or predicate position;
|
|
8019
8029
|
- double-quoted text follows `double_quotes` exactly; the default `chars` value
|
|
8020
|
-
matches Trealla and Scryer and may be changed to `codes` or `atom`;
|
|
8030
|
+
matches Trealla and Scryer and may be changed to `codes` or `atom`; normal
|
|
8031
|
+
mode additionally accepts Trealla-compatible `"text"||Tail` right-splicing
|
|
8032
|
+
for the `chars` and `codes` values, while strict mode rejects that syntax;
|
|
8021
8033
|
- `write_term/2-3` implements the Part 1 plus Corrigendum 3 `quoted/1`,
|
|
8022
8034
|
`ignore_ops/1`, `numbervars/1`, and `variable_names/1` option surface,
|
|
8023
8035
|
including option validation and traversal rules; normal mode also offers
|