eyeprolog 1.1.24 → 1.1.26
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
CHANGED
package/src/iso.js
CHANGED
|
@@ -1164,9 +1164,9 @@ function* readTermBuiltin({ solver, goal, env }) {
|
|
|
1164
1164
|
yield next;
|
|
1165
1165
|
}
|
|
1166
1166
|
function defaultTermWriteOptions(mode) {
|
|
1167
|
-
if (mode === 'writeq') return { quoted: true, ignoreOps: false, numbervars: true, variableNames: new Map() };
|
|
1168
|
-
if (mode === 'canonical') return { quoted: true, ignoreOps: true, numbervars: false, variableNames: new Map() };
|
|
1169
|
-
return { quoted: false, ignoreOps: false, numbervars: true, variableNames: new Map() };
|
|
1167
|
+
if (mode === 'writeq') return { quoted: true, ignoreOps: false, numbervars: true, variableNames: new Map(), compact: true, operatorAtomsAsArgs: true };
|
|
1168
|
+
if (mode === 'canonical') return { quoted: true, ignoreOps: true, numbervars: false, variableNames: new Map(), compact: true, operatorAtomsAsArgs: true };
|
|
1169
|
+
return { quoted: false, ignoreOps: false, numbervars: true, variableNames: new Map(), compact: true, operatorAtomsAsArgs: true };
|
|
1170
1170
|
}
|
|
1171
1171
|
|
|
1172
1172
|
function writeOptionBoolean(value, env, option) {
|
package/src/quads.js
CHANGED
|
@@ -106,6 +106,11 @@ function checkAlternative(program, quad, alternative, options) {
|
|
|
106
106
|
if (leaf.more && !leaf.hasExpectation) return { ok: true };
|
|
107
107
|
const matches = matchLeaf(quad.query, leaf, actual, position);
|
|
108
108
|
if (leaf.unexpected ? matches : !matches) return { ok: false };
|
|
109
|
+
// An unexpected error description is a negative assertion about that
|
|
110
|
+
// particular error pattern. A different exception may be present and is
|
|
111
|
+
// checked by other descriptions; do not make the final 'no error' test
|
|
112
|
+
// turn a successful negative match back into a failure.
|
|
113
|
+
if (leaf.unexpected && leaf.error != null) return { ok: true };
|
|
109
114
|
if (leaf.more) return { ok: true };
|
|
110
115
|
if (!leaf.unexpected && (leaf.false || leaf.error != null)) {
|
|
111
116
|
return { ok: position === leaves.length - 1 };
|
|
@@ -252,7 +257,7 @@ function matchLeaf(query, leaf, actual, position) {
|
|
|
252
257
|
}
|
|
253
258
|
if (leaf.error != null) {
|
|
254
259
|
return position === actual.solutions.length && actual.error != null &&
|
|
255
|
-
errorMatches(leaf.error, actual.error.term) && outputMatches(leaf.output, actual.error.output);
|
|
260
|
+
errorMatches(query, leaf.error, actual.error.term) && outputMatches(leaf.output, actual.error.output);
|
|
256
261
|
}
|
|
257
262
|
const solution = actual.solutions[position];
|
|
258
263
|
if (!solution || !outputMatches(leaf.output, solution.output)) return false;
|
|
@@ -297,12 +302,15 @@ function namedVariables(term) {
|
|
|
297
302
|
return found;
|
|
298
303
|
}
|
|
299
304
|
|
|
300
|
-
function patternVariant(
|
|
305
|
+
function patternVariant(
|
|
306
|
+
pattern, patternEnv, actual, actualEnv, pairs = new Map(), reverse = new Map(), fixedPatternNames = null,
|
|
307
|
+
) {
|
|
301
308
|
pattern = deref(pattern, patternEnv);
|
|
302
309
|
actual = deref(actual, actualEnv);
|
|
303
310
|
if (pattern.type === ATOM && pattern.name === '...') return true;
|
|
304
311
|
if (pattern.type === VAR || actual.type === VAR) {
|
|
305
312
|
if (pattern.type !== VAR || actual.type !== VAR) return false;
|
|
313
|
+
if (fixedPatternNames?.has(pattern.name)) return pattern.name === actual.name;
|
|
306
314
|
const paired = pairs.get(pattern.name);
|
|
307
315
|
const reversed = reverse.get(actual.name);
|
|
308
316
|
if (paired != null || reversed != null) return paired === actual.name && reversed === pattern.name;
|
|
@@ -312,7 +320,9 @@ function patternVariant(pattern, patternEnv, actual, actualEnv, pairs = new Map(
|
|
|
312
320
|
}
|
|
313
321
|
if (pattern.type !== actual.type || pattern.name !== actual.name || pattern.arity !== actual.arity) return false;
|
|
314
322
|
for (let index = 0; index < pattern.arity; index++) {
|
|
315
|
-
if (!patternVariant(
|
|
323
|
+
if (!patternVariant(
|
|
324
|
+
pattern.args[index], patternEnv, actual.args[index], actualEnv, pairs, reverse, fixedPatternNames,
|
|
325
|
+
)) return false;
|
|
316
326
|
}
|
|
317
327
|
return true;
|
|
318
328
|
}
|
|
@@ -334,16 +344,26 @@ function errorTerm(error) {
|
|
|
334
344
|
return compound('error', [atom('system_error'), variable('$quad_context')]);
|
|
335
345
|
}
|
|
336
346
|
|
|
337
|
-
function errorMatches(expected, actual) {
|
|
347
|
+
function errorMatches(query, expected, actual) {
|
|
348
|
+
// Variables named in the query keep their identity in answer descriptions.
|
|
349
|
+
// Seed both directions so a query variable can match only that same query
|
|
350
|
+
// variable, while variables introduced by the description (for example _X)
|
|
351
|
+
// remain fresh pattern variables. This is especially important for throw/1:
|
|
352
|
+
// ISO requires the thrown ball to be a renamed copy, so throw(g(_X)) may
|
|
353
|
+
// describe throw(g(X)), but throw(g(X)) must not.
|
|
354
|
+
const queryNames = new Set(namedVariables(query).map((item) => item.name));
|
|
355
|
+
const matches = (pattern, term) => patternVariant(
|
|
356
|
+
pattern, new Env(), term, new Env(), new Map(), new Map(), queryNames);
|
|
357
|
+
|
|
338
358
|
if (expected.type === COMPOUND && expected.name === 'throw' && expected.arity === 1 &&
|
|
339
359
|
actual.type === COMPOUND && actual.name === '$quad_thrown' && actual.arity === 1) {
|
|
340
|
-
return
|
|
360
|
+
return matches(expected.args[0], actual.args[0]);
|
|
341
361
|
}
|
|
342
362
|
if (actual.type !== COMPOUND || actual.name !== 'error' || actual.arity !== 2) return false;
|
|
343
363
|
if (expected.type === COMPOUND && expected.name === 'error' && expected.arity === 2) {
|
|
344
|
-
return
|
|
364
|
+
return matches(expected, actual);
|
|
345
365
|
}
|
|
346
|
-
return
|
|
366
|
+
return matches(expected, actual.args[0]);
|
|
347
367
|
}
|
|
348
368
|
|
|
349
369
|
function isErrorDescription(term) {
|
package/src/write.js
CHANGED
|
@@ -124,11 +124,18 @@ function chooseOperator(term, table) {
|
|
|
124
124
|
return null;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function format(term, env, options, table, maxPriority = 1200) {
|
|
127
|
+
function format(term, env, options, table, maxPriority = 1200, context = 'term') {
|
|
128
128
|
const resolved = deref(term, env);
|
|
129
129
|
if (resolved.type === VAR) return options.variableNames.get(resolved.name) ?? writeVariable(resolved.name);
|
|
130
130
|
if (resolved.type === STRING) return writeString(resolved.name);
|
|
131
|
-
if (resolved.type === ATOM)
|
|
131
|
+
if (resolved.type === ATOM) {
|
|
132
|
+
if (!options.quoted) return resolved.name;
|
|
133
|
+
// ISO 6.3.3.1 gives functional arguments and list elements a special
|
|
134
|
+
// `arg` production: an atom that is a current operator is valid there
|
|
135
|
+
// without quoting. Keep lexical exceptions such as `|` quoted.
|
|
136
|
+
if (options.operatorAtomsAsArgs && context === 'argument' && table.has(resolved.name)) return operatorName(resolved.name);
|
|
137
|
+
return writeAtom(resolved.name);
|
|
138
|
+
}
|
|
132
139
|
if (resolved.type === NUMBER) return resolved.name;
|
|
133
140
|
|
|
134
141
|
if (options.numbervars && resolved.type === COMPOUND && resolved.name === '$VAR' && resolved.arity === 1) {
|
|
@@ -146,9 +153,13 @@ function format(term, env, options, table, maxPriority = 1200) {
|
|
|
146
153
|
let cursor = resolved;
|
|
147
154
|
while (true) {
|
|
148
155
|
cursor = deref(cursor, env);
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
|
|
156
|
+
const separator = options.compact ? ',' : ', ';
|
|
157
|
+
if (isEmptyList(cursor)) return `[${parts.join(separator)}]`;
|
|
158
|
+
if (!isCons(cursor)) {
|
|
159
|
+
const tailSeparator = options.compact ? '|' : ' | ';
|
|
160
|
+
return `[${parts.join(separator)}${tailSeparator}${format(cursor, env, options, table, 999, 'argument')}]`;
|
|
161
|
+
}
|
|
162
|
+
parts.push(format(cursor.args[0], env, options, table, 999, 'argument'));
|
|
152
163
|
cursor = cursor.args[1];
|
|
153
164
|
}
|
|
154
165
|
}
|
|
@@ -183,8 +194,8 @@ function format(term, env, options, table, maxPriority = 1200) {
|
|
|
183
194
|
}
|
|
184
195
|
|
|
185
196
|
const name = options.quoted ? writeAtom(resolved.name) : resolved.name;
|
|
186
|
-
const args = resolved.args.map((arg) => format(arg, env, options, table, 999));
|
|
187
|
-
return `${name}(${args.join(', ')})`;
|
|
197
|
+
const args = resolved.args.map((arg) => format(arg, env, options, table, 999, 'argument'));
|
|
198
|
+
return `${name}(${args.join(options.compact ? ',' : ', ')})`;
|
|
188
199
|
}
|
|
189
200
|
|
|
190
201
|
export function formatTermForWrite(term, env = new Env(), options = {}) {
|
|
@@ -194,6 +205,8 @@ export function formatTermForWrite(term, env = new Env(), options = {}) {
|
|
|
194
205
|
numbervars: options.numbervars !== false,
|
|
195
206
|
doubleQuotes: options.doubleQuotes,
|
|
196
207
|
variableNames: options.variableNames instanceof Map ? options.variableNames : new Map(),
|
|
208
|
+
compact: options.compact === true,
|
|
209
|
+
operatorAtomsAsArgs: options.operatorAtomsAsArgs === true,
|
|
197
210
|
};
|
|
198
211
|
return format(term, env, normalized, operatorTable(options.operators), 1200);
|
|
199
212
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
pair(First,
|
|
1
|
+
pair(First,Second)
|
|
2
2
|
corrigendum3_write_variable_names.
|
package/test/run-regression.mjs
CHANGED
|
@@ -243,6 +243,24 @@ why(
|
|
|
243
243
|
assertEqual(result.stdout, 'quads: 12 run, 12 passed, 0 failed.\n', 'quad report');
|
|
244
244
|
},
|
|
245
245
|
},
|
|
246
|
+
{
|
|
247
|
+
name: 'runQuads distinguishes query variables from renamed throw variables',
|
|
248
|
+
run: () => {
|
|
249
|
+
const source = `?- throw(g(X)).\n` +
|
|
250
|
+
` throw(g(_X)).\n` +
|
|
251
|
+
` throw(g(X)), unexpected.\n`;
|
|
252
|
+
const result = publicApi.runQuads(Program.parseSources([{ text: source, filename: 'throw-copy-quad.pl' }]));
|
|
253
|
+
assertEqual(result.total, 1, 'quad total');
|
|
254
|
+
assertEqual(result.passed, 1, 'quad passed');
|
|
255
|
+
assertEqual(result.failed, 0, 'quad failed');
|
|
256
|
+
assertEqual(result.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'quad report');
|
|
257
|
+
|
|
258
|
+
const forbiddenFresh = publicApi.runQuads(
|
|
259
|
+
`?- throw(g(X)).\n throw(g(_X)), unexpected.\n`,
|
|
260
|
+
);
|
|
261
|
+
assertEqual(forbiddenFresh.failed, 1, 'fresh thrown variable is detected');
|
|
262
|
+
},
|
|
263
|
+
},
|
|
246
264
|
{
|
|
247
265
|
name: 'runQuads matches the corrected ISO phrase quad boundaries',
|
|
248
266
|
run: () => {
|
|
@@ -950,6 +968,23 @@ c4 ?- call((!;1)).
|
|
|
950
968
|
assertEqual(run(source, { goal: 'operator_argument(ok)' }).stdout, 'operator_argument(ok).\n', 'operator argument syntax');
|
|
951
969
|
},
|
|
952
970
|
},
|
|
971
|
+
{
|
|
972
|
+
name: 'ISO writeq preserves operator atoms in argument syntax',
|
|
973
|
+
run: () => {
|
|
974
|
+
const source = [
|
|
975
|
+
'emit_operator_arguments :-',
|
|
976
|
+
" writeq([:-,-]), put_char('|'),",
|
|
977
|
+
" writeq(f(*)), put_char('|'),",
|
|
978
|
+
" writeq(f(;,'|',';;')).",
|
|
979
|
+
'',
|
|
980
|
+
].join('\n');
|
|
981
|
+
assertEqual(
|
|
982
|
+
run(source, { goal: 'emit_operator_arguments' }).stdout,
|
|
983
|
+
"[:-,-]|f(*)|f(;,'|',';;')emit_operator_arguments.\n",
|
|
984
|
+
'operator argument output',
|
|
985
|
+
);
|
|
986
|
+
},
|
|
987
|
+
},
|
|
953
988
|
{
|
|
954
989
|
name: 'ISO query operator and quad infix extension are visible through current_op/3',
|
|
955
990
|
run: () => {
|
|
@@ -1020,7 +1055,7 @@ c4 ?- call((!;1)).
|
|
|
1020
1055
|
].join('\n');
|
|
1021
1056
|
assertEqual(
|
|
1022
1057
|
run(source, { goal: 'emit' }).stdout,
|
|
1023
|
-
"hello world|'hello world'|a + b * c|'+'(a
|
|
1058
|
+
"hello world|'hello world'|a + b * c|'+'(a,*(b,c))|hello world|'hello world'|+(a,b)|a + b|A|$VAR(0)|pair(Left,Right)emit.\n",
|
|
1024
1059
|
'stdout',
|
|
1025
1060
|
);
|
|
1026
1061
|
},
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5131,7 +5131,12 @@ parsing of subsequent text, place them before their first use. ISO argument
|
|
|
5131
5131
|
syntax also permits an atom that is currently an operator to appear directly
|
|
5132
5132
|
as a functional argument or list element, so forms such as
|
|
5133
5133
|
`current_op(Priority, Specifier, :-)` and `[:-,-]` are valid without quoting
|
|
5134
|
-
or parenthesizing those operator atoms.
|
|
5134
|
+
or parenthesizing those operator atoms. Term output observes the same `arg`
|
|
5135
|
+
rule: with `quoted(true)`, an operator atom is not quoted merely because it is
|
|
5136
|
+
an operator when it occurs as a functional argument or list element. Thus
|
|
5137
|
+
`writeq([:-,-])` emits `[:-,-]`, and `writeq(f(;,'|',';;'))` emits
|
|
5138
|
+
`f(;,'|',';;')`; the bar stays quoted because ISO treats the unquoted `|`
|
|
5139
|
+
token as a list separator rather than an atom. The ISO initial operator table also
|
|
5135
5140
|
contains `?-` at priority 1200 with specifier `fx`, so
|
|
5136
5141
|
`current_op(1200, fx, ?-)` succeeds. EyeProlog's embedded quad syntax permits
|
|
5137
5142
|
an optional label before the query marker (`Label ?- Query.`), so while quad
|
|
@@ -5171,7 +5176,10 @@ write_event(Path, Event) :-
|
|
|
5171
5176
|
|
|
5172
5177
|
The period is essential when another Prolog processor will read the result as
|
|
5173
5178
|
a term. `write/1-2` uses readable conventional syntax, `writeq/1-2` quotes
|
|
5174
|
-
where needed, and `write_canonical/1-2` exposes canonical structure.
|
|
5179
|
+
where needed, and `write_canonical/1-2` exposes canonical structure. ISO term
|
|
5180
|
+
output uses only the separator characters needed by the syntax, so functional
|
|
5181
|
+
arguments and list elements are emitted compactly; for example `writeq([a,b])`
|
|
5182
|
+
outputs `[a,b]`.
|
|
5175
5183
|
`write_term/2-3` supports `quoted/1`, `ignore_ops/1`, `numbervars/1`, and
|
|
5176
5184
|
`variable_names/1`.
|
|
5177
5185
|
|
|
@@ -6292,7 +6300,11 @@ accepted as a negative answer.
|
|
|
6292
6300
|
Answer descriptions support ordered answers separated by `;`, acceptable
|
|
6293
6301
|
alternatives separated by `|`, `true`, `false`, standard error descriptions,
|
|
6294
6302
|
and the `unexpected` annotation for an answer that must not occur (`inattendue`
|
|
6295
|
-
is its synonym).
|
|
6303
|
+
is its synonym). Variables named in the query keep their identity inside answer
|
|
6304
|
+
descriptions; variables introduced only by a description are fresh. For example,
|
|
6305
|
+
a query `throw(g(X))` is described by `throw(g(_X))`, while
|
|
6306
|
+
`throw(g(X)), unexpected` verifies that ISO `throw/1` did not retain the query
|
|
6307
|
+
variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. Multiple
|
|
6296
6308
|
indented descriptions after one query must all hold. `inputs/1` supplies and
|
|
6297
6309
|
checks consumed characters; `outputs/1` checks emitted characters. `sto` marks
|
|
6298
6310
|
an answer description that this finite-tree implementation skips. `loops` is
|