eyeprolog 1.1.7 → 1.1.8
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/dcg.js +51 -3
- package/src/iso.js +24 -9
- package/src/parser.js +13 -1
- package/src/quads.js +10 -1
- package/src/solver.js +6 -0
- package/test/conformance/THIRD_PARTY.md +2 -2
- package/test/conformance/expected-errors/iso/dcg_phrase_bad_input.txt +1 -1
- package/test/conformance/expected-errors/iso/dcg_phrase_bad_output.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_bad_input_precedence.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_bad_output_precedence.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_embedded_noncallable.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_improper_semicontext.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_improper_terminal_rule.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_invalid_semicontext.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_nested_noncallable.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_noncallable_after_failure.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_noncallable_after_success.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_phrase_improper_input.txt +1 -1
- package/test/conformance/expected-errors/iso/logtalk_dcg_phrase_improper_output.txt +1 -1
- package/test/conformance/expected-errors/syntax/extra_double_period_rejected.txt +1 -1
- package/test/run-regression.mjs +88 -0
- package/the-art-of-eyeprolog.md +6 -2
package/package.json
CHANGED
package/src/dcg.js
CHANGED
|
@@ -37,14 +37,14 @@ function terminalItems(term, env = new Env()) {
|
|
|
37
37
|
const seen = new Set();
|
|
38
38
|
let cursor = deref(term, env);
|
|
39
39
|
while (cursor.type === COMPOUND && cursor.name === '.' && cursor.arity === 2) {
|
|
40
|
-
if (seen.has(cursor)) throw new PrologError('type_error(
|
|
40
|
+
if (seen.has(cursor)) throw new PrologError('type_error(list)', original);
|
|
41
41
|
seen.add(cursor);
|
|
42
42
|
items.push(cursor.args[0]);
|
|
43
43
|
cursor = deref(cursor.args[1], env);
|
|
44
44
|
}
|
|
45
45
|
if (cursor.type === ATOM && cursor.name === '[]') return items;
|
|
46
46
|
if (cursor.type === VAR) throw new PrologError('instantiation_error');
|
|
47
|
-
throw new PrologError('type_error(
|
|
47
|
+
throw new PrologError('type_error(list)', original);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function terminalsGoal(terminals, input, output, env) {
|
|
@@ -155,8 +155,12 @@ export function expandDcgBody(body, input, output, options = {}) {
|
|
|
155
155
|
// definitions and documents these choices in its conformance profile.
|
|
156
156
|
if (body.type === COMPOUND && body.name === '\\+' && body.arity === 1) {
|
|
157
157
|
const ignored = freshDcgVariable('negated');
|
|
158
|
+
const negated = body.args[0];
|
|
159
|
+
const expandedNegated = negated.type === ATOM || negated.type === COMPOUND || negated.type === VAR
|
|
160
|
+
? expandDcgBody(negated, input, ignored, options)
|
|
161
|
+
: negated;
|
|
158
162
|
return conjunction(
|
|
159
|
-
compound('\\+', [
|
|
163
|
+
compound('\\+', [expandedNegated]),
|
|
160
164
|
equality(input, output),
|
|
161
165
|
);
|
|
162
166
|
}
|
|
@@ -175,6 +179,50 @@ export function expandDcgBody(body, input, output, options = {}) {
|
|
|
175
179
|
return appendStateArguments(body, input, output, module);
|
|
176
180
|
}
|
|
177
181
|
|
|
182
|
+
// Embedded goals are validated before the translated grammar is executed.
|
|
183
|
+
// This keeps a non-callable goal visible even when an earlier terminal or
|
|
184
|
+
// branch would otherwise prevent the host goal from being reached.
|
|
185
|
+
export function validateDcgEmbeddedGoals(body, input, output) {
|
|
186
|
+
const invalidBody = invalidDcgControl(body);
|
|
187
|
+
if (invalidBody != null) throw new PrologError('type_error(callable)', invalidBody);
|
|
188
|
+
|
|
189
|
+
const visit = (term) => {
|
|
190
|
+
if (term.type !== COMPOUND) return;
|
|
191
|
+
if (term.name === '{}' && term.arity === 1) {
|
|
192
|
+
if (invalidControlGoal(term.args[0])) {
|
|
193
|
+
// Report the translated host-language conjunction, matching the
|
|
194
|
+
// convention used by Trealla and the ISO Part 3 quad corpus.
|
|
195
|
+
throw new PrologError('type_error(callable)', conjunction(term.args[0], equality(input, output)));
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if ([',', ';', '|', '->', '\\+'].includes(term.name)) {
|
|
200
|
+
for (const argument of term.args) visit(argument);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
visit(body);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function invalidDcgControl(term) {
|
|
207
|
+
if (term.type !== ATOM && term.type !== COMPOUND && term.type !== VAR) return term;
|
|
208
|
+
if (term.type !== COMPOUND || ![',', ';', '|', '->'].includes(term.name)) return null;
|
|
209
|
+
for (const argument of term.args) {
|
|
210
|
+
if (argument.type === COMPOUND && argument.name === '{}' && argument.arity === 1) continue;
|
|
211
|
+
const invalid = invalidDcgControl(argument);
|
|
212
|
+
if (invalid != null) return invalid;
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function invalidControlGoal(goal) {
|
|
218
|
+
if (goal.type === VAR) return false;
|
|
219
|
+
if (goal.type !== ATOM && goal.type !== COMPOUND) return true;
|
|
220
|
+
if (goal.type === COMPOUND && [',', ';', '->'].includes(goal.name) && goal.arity === 2) {
|
|
221
|
+
return goal.args.some(invalidControlGoal);
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
178
226
|
function splitGrammarHead(head) {
|
|
179
227
|
let terminals = null;
|
|
180
228
|
if (head.type === COMPOUND && head.name === ',' && head.arity === 2) {
|
package/src/iso.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from './term.js';
|
|
8
8
|
import { createParserOperatorState, parseClauses } from './parser.js';
|
|
9
9
|
import { formatTermForWrite } from './write.js';
|
|
10
|
-
import { emptyTerminalSequence, expandDcgBody, isListOrPartialList } from './dcg.js';
|
|
10
|
+
import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
|
|
11
11
|
|
|
12
12
|
let isoFresh = 0;
|
|
13
13
|
|
|
@@ -490,7 +490,7 @@ function* clauseBuiltin({ solver, goal, env }) {
|
|
|
490
490
|
if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
|
|
491
491
|
callableOrVariable(goal.args[1], env);
|
|
492
492
|
const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
|
|
493
|
-
if (solver.registry.get(head.name, head.arity)) {
|
|
493
|
+
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head)) {
|
|
494
494
|
throw new PrologError('permission_error(access, private_procedure)', indicator);
|
|
495
495
|
}
|
|
496
496
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
@@ -529,9 +529,13 @@ function procedureIndicator(head) {
|
|
|
529
529
|
return compound('/', [atom(head.name), numberTerm(head.arity)]);
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
function isGrammarRuleProcedure(head) {
|
|
533
|
+
return head.name === '-->' && head.arity === 2;
|
|
534
|
+
}
|
|
535
|
+
|
|
532
536
|
function assertModifiable(solver, head, module = 'user') {
|
|
533
537
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? module);
|
|
534
|
-
if (solver.registry.get(head.name, head.arity) || (group && !group.dynamic)) {
|
|
538
|
+
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head) || (group && !group.dynamic)) {
|
|
535
539
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
|
|
536
540
|
}
|
|
537
541
|
}
|
|
@@ -558,7 +562,7 @@ function* retractBuiltin({ solver, goal, env }) {
|
|
|
558
562
|
const parts = clauseParts(goal.args[0], env);
|
|
559
563
|
requireClauseHead(parts.head);
|
|
560
564
|
const group = solver.program.findGroup(parts.head.name, parts.head.arity, parts.head.module ?? goal.module ?? 'user');
|
|
561
|
-
if (solver.registry.get(parts.head.name, parts.head.arity) || (group && !group.dynamic)) {
|
|
565
|
+
if (solver.registry.get(parts.head.name, parts.head.arity) || isGrammarRuleProcedure(parts.head) || (group && !group.dynamic)) {
|
|
562
566
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(parts.head));
|
|
563
567
|
}
|
|
564
568
|
if (!group) return;
|
|
@@ -581,7 +585,7 @@ function* retractAllBuiltin({ solver, goal, env }) {
|
|
|
581
585
|
const head = deref(goal.args[0], env);
|
|
582
586
|
requireClauseHead(head);
|
|
583
587
|
const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
|
|
584
|
-
if (solver.registry.get(head.name, head.arity) || (group && !group.dynamic)) {
|
|
588
|
+
if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head) || (group && !group.dynamic)) {
|
|
585
589
|
throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
|
|
586
590
|
}
|
|
587
591
|
if (group) {
|
|
@@ -615,7 +619,7 @@ function* abolishBuiltin({ solver, goal, env }) {
|
|
|
615
619
|
const target = predicateIndicatorParts(goal.args[0], env);
|
|
616
620
|
const module = goal.module ?? 'user';
|
|
617
621
|
const group = solver.program.findGroup(target.name, target.arity, module);
|
|
618
|
-
if (solver.registry.get(target.name, target.arity) || (group && !group.dynamic)) {
|
|
622
|
+
if (solver.registry.get(target.name, target.arity) || isGrammarRuleProcedure(target) || (group && !group.dynamic)) {
|
|
619
623
|
throw new PrologError('permission_error(modify, static_procedure)', target.indicator);
|
|
620
624
|
}
|
|
621
625
|
solver.program.abolishDynamicGroup(target.name, target.arity, module);
|
|
@@ -1519,8 +1523,19 @@ function callable(term, env) {
|
|
|
1519
1523
|
term = resolveCallable(term, env);
|
|
1520
1524
|
if (term.type === VAR) throw new PrologError('instantiation_error');
|
|
1521
1525
|
if (term.type !== ATOM && term.type !== COMPOUND) throw new PrologError('type_error(callable)', term);
|
|
1526
|
+
validateControlCallable(term, term);
|
|
1522
1527
|
return term;
|
|
1523
1528
|
}
|
|
1529
|
+
function validateControlCallable(term, culprit) {
|
|
1530
|
+
if (term.type !== COMPOUND || ![',', ';', '->'].includes(term.name) || term.arity !== 2) return;
|
|
1531
|
+
for (const argument of term.args) {
|
|
1532
|
+
if (argument.type === VAR) throw new PrologError('instantiation_error');
|
|
1533
|
+
if (argument.type !== ATOM && argument.type !== COMPOUND) {
|
|
1534
|
+
throw new PrologError('type_error(callable)', culprit);
|
|
1535
|
+
}
|
|
1536
|
+
validateControlCallable(argument, culprit);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1524
1539
|
function resolveCallable(term, env) {
|
|
1525
1540
|
const resolved = deref(term, env);
|
|
1526
1541
|
if (resolved.type !== COMPOUND) return resolved;
|
|
@@ -1555,14 +1570,14 @@ function* phraseBuiltin({ solver, goal, env }) {
|
|
|
1555
1570
|
if (grammarBody.type !== ATOM && grammarBody.type !== COMPOUND) {
|
|
1556
1571
|
throw new PrologError('type_error(callable)', grammarBody);
|
|
1557
1572
|
}
|
|
1558
|
-
|
|
1559
1573
|
const input = goal.args[1];
|
|
1560
1574
|
const requestedOutput = goal.arity === 2 ? emptyTerminalSequence() : goal.args[2];
|
|
1575
|
+
validateDcgEmbeddedGoals(grammarBody, input, requestedOutput);
|
|
1561
1576
|
if (!isListOrPartialList(input, env)) {
|
|
1562
|
-
throw new PrologError('type_error(
|
|
1577
|
+
throw new PrologError('type_error(list)', deref(input, env));
|
|
1563
1578
|
}
|
|
1564
1579
|
if (!isListOrPartialList(requestedOutput, env)) {
|
|
1565
|
-
throw new PrologError('type_error(
|
|
1580
|
+
throw new PrologError('type_error(list)', deref(requestedOutput, env));
|
|
1566
1581
|
}
|
|
1567
1582
|
|
|
1568
1583
|
// Delay the final output unification to keep phrase/3 steadfast in its
|
package/src/parser.js
CHANGED
|
@@ -33,7 +33,7 @@ function isPlainAtomStartCode(code) {
|
|
|
33
33
|
return code >= 97 && code <= 122;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const graphicAtomChars = '
|
|
36
|
+
const graphicAtomChars = '#$&*+-./<=>@^~\\:';
|
|
37
37
|
|
|
38
38
|
// ISO operator syntax is lowered to the same ordinary compound terms used by
|
|
39
39
|
// canonical notation. Commas remain separators except inside parentheses.
|
|
@@ -285,10 +285,22 @@ class Parser {
|
|
|
285
285
|
this.pos += 2;
|
|
286
286
|
return { type: TOK.ATOM, text: '?-', line };
|
|
287
287
|
}
|
|
288
|
+
if (ch === '.' && this.peek(1) &&
|
|
289
|
+
!isWhitespaceCode(this.peek(1).charCodeAt(0)) &&
|
|
290
|
+
this.peek(1) !== '%' && !(this.peek(1) === '/' && this.peek(2) === '*')) {
|
|
291
|
+
const start = this.pos;
|
|
292
|
+
this.take();
|
|
293
|
+
while (isGraphicAtomCode(this.peek().charCodeAt(0))) this.take();
|
|
294
|
+
return { type: TOK.ATOM, text: this.source.slice(start, this.pos), line };
|
|
295
|
+
}
|
|
288
296
|
if (ch === '!') {
|
|
289
297
|
this.take();
|
|
290
298
|
return { type: TOK.ATOM, text: '!', line };
|
|
291
299
|
}
|
|
300
|
+
if (ch === ';') {
|
|
301
|
+
this.take();
|
|
302
|
+
return { type: TOK.ATOM, text: ';', line };
|
|
303
|
+
}
|
|
292
304
|
|
|
293
305
|
const punct = {
|
|
294
306
|
'(': TOK.LPAREN, ')': TOK.RPAREN, '[': TOK.LBRACKET, ']': TOK.RBRACKET,
|
package/src/quads.js
CHANGED
|
@@ -192,12 +192,21 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
192
192
|
const solver = new Solver(program, {
|
|
193
193
|
...options,
|
|
194
194
|
registry: options.registry ?? getEyePrologRegistry(),
|
|
195
|
-
|
|
195
|
+
// The solver's counter also observes completed nested searches (for
|
|
196
|
+
// example each arm of a DCG disjunction). Bound the public iterator here
|
|
197
|
+
// instead of letting those internal completions consume the quad's answer
|
|
198
|
+
// allowance.
|
|
199
|
+
solutionLimit: Math.max(maxSolutions, options.solutionLimit ?? 10000000),
|
|
196
200
|
ioOptions: {
|
|
197
201
|
input,
|
|
198
202
|
write: (text) => { pendingOutput += String(text); },
|
|
199
203
|
},
|
|
200
204
|
});
|
|
205
|
+
// Undefined predicates are test failures rather than silent negative
|
|
206
|
+
// answers unless the source explicitly selected another unknown policy.
|
|
207
|
+
if (!(program.prologFlagDirectives ?? []).some(([flag]) => flag.type === ATOM && flag.name === 'unknown')) {
|
|
208
|
+
solver.prologFlags.get('unknown').value = atom('error');
|
|
209
|
+
}
|
|
201
210
|
const solutions = [];
|
|
202
211
|
let error = null;
|
|
203
212
|
let tailOutput = '';
|
package/src/solver.js
CHANGED
|
@@ -281,6 +281,12 @@ export class Solver {
|
|
|
281
281
|
this.stats.solve_one_goal_calls++;
|
|
282
282
|
const group = this.program.findGroup(goal.name, goal.arity, goal.module ?? 'user');
|
|
283
283
|
if (!group) {
|
|
284
|
+
if (goal.name === '-->' && goal.arity === 2) {
|
|
285
|
+
throw new PrologError(
|
|
286
|
+
'existence_error(procedure)',
|
|
287
|
+
compound('/', [compound('-->', []), numberTerm(2)]),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
284
290
|
if (this.prologFlags.get('unknown')?.value?.name === 'error') {
|
|
285
291
|
throw new PrologError(
|
|
286
292
|
'existence_error(procedure)',
|
|
@@ -13,8 +13,8 @@ Part 3 grammar cases are additionally adapted from Logtalk's
|
|
|
13
13
|
`tests/logtalk/methods/phrase_2_3/tests.lgt` and
|
|
14
14
|
`tests/logtalk/dcgs/tests.lgt` suites. Object and unit-test scaffolding was
|
|
15
15
|
removed, translator-only assertions were converted to executable grammar
|
|
16
|
-
behavior where possible, and expected list errors
|
|
17
|
-
`
|
|
16
|
+
behavior where possible, and expected list errors use the portable ISO
|
|
17
|
+
`type_error(list)` term.
|
|
18
18
|
|
|
19
19
|
Copyright 1998-2026 Paulo Moura <pmoura@logtalk.org>
|
|
20
20
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), not_a_terminal_sequence)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), not_a_terminal_sequence)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), not_a_terminal_sequence)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), not_a_terminal_sequence)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(callable), 1)
|
|
1
|
+
error(type_error(callable), (1, =([], [])))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), [a | b])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), [a | b])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), b)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(callable), 1)
|
|
1
|
+
error(type_error(callable), (1, =([__anon1], [])))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(callable), 1)
|
|
1
|
+
error(type_error(callable), (1, =("y", [])))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(callable), 1)
|
|
1
|
+
error(type_error(callable), (1, =([__anon1], [])))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), [a | b])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
error(type_error(
|
|
1
|
+
error(type_error(list), [a | b])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
parse line 1:
|
|
1
|
+
parse line 1: expected ., got ..
|
package/test/run-regression.mjs
CHANGED
|
@@ -198,6 +198,19 @@ why(
|
|
|
198
198
|
assertEqual(Boolean(program.findGroup('?-', 2)), false, 'quad is inert');
|
|
199
199
|
},
|
|
200
200
|
},
|
|
201
|
+
{
|
|
202
|
+
name: 'parser separates compact ISO solo tokens and atom dots',
|
|
203
|
+
run: () => {
|
|
204
|
+
const program = Program.parse(
|
|
205
|
+
`compact ?- call((!;\\+1)).\n true.\n\n` +
|
|
206
|
+
`dot ?- functor([_],.,2).\n true.\n`,
|
|
207
|
+
);
|
|
208
|
+
assertEqual(program.quads.length, 2, 'quad count');
|
|
209
|
+
assertEqual(program.quads[0].query.args[0].name, ';', 'disjunction');
|
|
210
|
+
assertEqual(program.quads[0].query.args[0].args[1].name, '\\+', 'negation');
|
|
211
|
+
assertEqual(program.quads[1].query.args[1].name, '.', 'dot atom');
|
|
212
|
+
},
|
|
213
|
+
},
|
|
201
214
|
{
|
|
202
215
|
name: 'runQuads checks portable answer descriptions',
|
|
203
216
|
run: () => {
|
|
@@ -221,6 +234,81 @@ why(
|
|
|
221
234
|
assertEqual(result.stdout, 'quads: 12 run, 12 passed, 0 failed.\n', 'quad report');
|
|
222
235
|
},
|
|
223
236
|
},
|
|
237
|
+
{
|
|
238
|
+
name: 'runQuads matches the corrected ISO phrase quad boundaries',
|
|
239
|
+
run: () => {
|
|
240
|
+
const source = String.raw`c2 ?- call((1,fail)).
|
|
241
|
+
type_error(callable,(1,fail)).
|
|
242
|
+
|
|
243
|
+
c3 ?- call((fail,1)).
|
|
244
|
+
type_error(callable,(fail,1)).
|
|
245
|
+
|
|
246
|
+
c4 ?- call((!;1)).
|
|
247
|
+
type_error(callable,(!;1)).
|
|
248
|
+
|
|
249
|
+
24 ?- asserta((a-->b)).
|
|
250
|
+
permission_error(modify,static_procedure,(-->)/2).
|
|
251
|
+
|
|
252
|
+
25 ?- clause((a-->b),B).
|
|
253
|
+
permission_error(access,private_procedure,(-->)/2).
|
|
254
|
+
|
|
255
|
+
26 ?- (X-->Y).
|
|
256
|
+
existence_error(procedure,(-->)/2).
|
|
257
|
+
|
|
258
|
+
5 ?- phrase([a|b],L).
|
|
259
|
+
type_error(list,[a|b]).
|
|
260
|
+
|
|
261
|
+
10 ?- phrase(([a],{1}),[]).
|
|
262
|
+
type_error(callable,(...,...)).
|
|
263
|
+
|
|
264
|
+
37 ?- phrase((!,[a],{1}),[]).
|
|
265
|
+
type_error(callable,(...,...)).
|
|
266
|
+
|
|
267
|
+
12 ?- phrase('|'([],[a]),[a]).
|
|
268
|
+
true.
|
|
269
|
+
|
|
270
|
+
14 ?- phrase(([a];[]),L).
|
|
271
|
+
L=[a] ; L=[].
|
|
272
|
+
|
|
273
|
+
15 ?- phrase({fail,1},L).
|
|
274
|
+
type_error(callable,((fail,1),...)).
|
|
275
|
+
|
|
276
|
+
29 ?- phrase(([a],\+1),[]).
|
|
277
|
+
false.
|
|
278
|
+
|
|
279
|
+
30 ?- phrase(([a],\+1;[]),[]).
|
|
280
|
+
true.
|
|
281
|
+
|
|
282
|
+
31 ?- phrase(phrase(phrase,[]),L).
|
|
283
|
+
existence_error(procedure,phrase/4).
|
|
284
|
+
|
|
285
|
+
32 ?- phrase(call([]),[]).
|
|
286
|
+
existence_error(procedure,[]/2).
|
|
287
|
+
|
|
288
|
+
41 ?- phrase([],non_list).
|
|
289
|
+
type_error(list,non_list).
|
|
290
|
+
|
|
291
|
+
42 ?- phrase([],[a|non_list]).
|
|
292
|
+
type_error(list,[a|non_list]).
|
|
293
|
+
|
|
294
|
+
43 ?- phrase([],L,non_list).
|
|
295
|
+
type_error(list,non_list).
|
|
296
|
+
|
|
297
|
+
44 ?- phrase([],L,[a|non_list]).
|
|
298
|
+
type_error(list,[a|non_list]).
|
|
299
|
+
|
|
300
|
+
46 ?- phrase((1,{2}),[]).
|
|
301
|
+
type_error(callable,1).
|
|
302
|
+
|
|
303
|
+
47 ?- phrase(({2},1),[]).
|
|
304
|
+
type_error(callable,1).
|
|
305
|
+
`;
|
|
306
|
+
const result = publicApi.runQuads(source);
|
|
307
|
+
assertEqual(result.total, 22, 'quad total');
|
|
308
|
+
assertEqual(result.passed, 22, 'quad passed');
|
|
309
|
+
assertEqual(result.stdout, 'quads: 22 run, 22 passed, 0 failed.\n', 'quad report');
|
|
310
|
+
},
|
|
311
|
+
},
|
|
224
312
|
{
|
|
225
313
|
name: 'runQuads rejects malformed answer substitutions',
|
|
226
314
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5341,8 +5341,8 @@ look_ahead(X), [X] --> [X].
|
|
|
5341
5341
|
`phrase(+Body,?Sequence)` accepts or generates a complete sequence.
|
|
5342
5342
|
`phrase(+Body,?Sequence,?Rest)` leaves `Rest` unconsumed and is steadfast in
|
|
5343
5343
|
that argument. A variable body raises `instantiation_error`; a non-callable
|
|
5344
|
-
body raises `type_error(callable)`. EyeProlog performs
|
|
5345
|
-
|
|
5344
|
+
body raises `type_error(callable)`. EyeProlog performs terminal-sequence checks
|
|
5345
|
+
and reports the portable ISO `type_error(list)` error term.
|
|
5346
5346
|
|
|
5347
5347
|
Part 3 leaves `\+//1` and standalone `->//2` implementation dependent.
|
|
5348
5348
|
EyeProlog uses non-consuming negation (`\+ Body` tests from the current state)
|
|
@@ -6138,6 +6138,10 @@ records its quads; it does not execute them or add their queries and answers as
|
|
|
6138
6138
|
program clauses. A quad run prints a summary and exits with status `1` when any
|
|
6139
6139
|
description fails.
|
|
6140
6140
|
|
|
6141
|
+
Unless the source explicitly selects another `unknown` flag, quad execution
|
|
6142
|
+
uses `unknown=error`, so an undefined predicate is reported rather than being
|
|
6143
|
+
accepted as a negative answer.
|
|
6144
|
+
|
|
6141
6145
|
Answer descriptions support ordered answers separated by `;`, acceptable
|
|
6142
6146
|
alternatives separated by `|`, `true`, `false`, standard error descriptions,
|
|
6143
6147
|
and the `unexpected` annotation for an answer that must not occur (`inattendue`
|