eyeprolog 1.2.37 → 1.2.39
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/iso.js +27 -9
- package/src/parser.js +31 -10
- package/src/repl.js +20 -7
- package/src/write.js +3 -1
- package/test/conformance/cases/iso/lexical_and_curly_terms.pl +1 -0
- package/test/run-regression.mjs +67 -1
- package/the-art-of-eyeprolog.md +8 -6
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1178,6 +1178,31 @@ function scopeReadTerm(term) {
|
|
|
1178
1178
|
return { term: copy(term), variables };
|
|
1179
1179
|
}
|
|
1180
1180
|
|
|
1181
|
+
function parseReadTermText(text, solver) {
|
|
1182
|
+
const converted = convertedTermText(text, solver);
|
|
1183
|
+
const operatorState = createParserOperatorState(solver.program.operators.values(), false);
|
|
1184
|
+
const clauses = parseClauses(converted, {
|
|
1185
|
+
sourceMetadata: false,
|
|
1186
|
+
operatorState,
|
|
1187
|
+
isoStrict: solver.isoStrict,
|
|
1188
|
+
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
1189
|
+
// The stream scanner supplies one candidate ending at this full stop.
|
|
1190
|
+
// Earlier ambiguous dots must remain available to maximal graphic tokens.
|
|
1191
|
+
readTermEnd: converted.length - 1,
|
|
1192
|
+
});
|
|
1193
|
+
if (clauses.length !== 1 || clauses[0].body.length) throw new Error('bad term');
|
|
1194
|
+
return clauses[0].head;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
export function isCompleteReadTermText(text, solver) {
|
|
1198
|
+
try {
|
|
1199
|
+
parseReadTermText(text, solver);
|
|
1200
|
+
return true;
|
|
1201
|
+
} catch (_) {
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1181
1206
|
function readTermFromStream(stream, solver) {
|
|
1182
1207
|
let requestedInteractiveTerm = false;
|
|
1183
1208
|
while (true) {
|
|
@@ -1189,16 +1214,9 @@ function readTermFromStream(stream, solver) {
|
|
|
1189
1214
|
throw new PrologError('syntax_error(read_term)');
|
|
1190
1215
|
}
|
|
1191
1216
|
try {
|
|
1192
|
-
const
|
|
1193
|
-
const clauses = parseClauses(convertedTermText(candidate.text, solver), {
|
|
1194
|
-
sourceMetadata: false,
|
|
1195
|
-
operatorState,
|
|
1196
|
-
isoStrict: solver.isoStrict,
|
|
1197
|
-
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
1198
|
-
});
|
|
1199
|
-
if (clauses.length !== 1 || clauses[0].body.length) throw new Error('bad term');
|
|
1217
|
+
const term = parseReadTermText(candidate.text, solver);
|
|
1200
1218
|
stream.position = candidate.end;
|
|
1201
|
-
return scopeReadTerm(
|
|
1219
|
+
return scopeReadTerm(term);
|
|
1202
1220
|
} catch (_) {
|
|
1203
1221
|
// A dot inside a graphic operator, such as =.., is only a possible
|
|
1204
1222
|
// terminator. Keep scanning until a complete term parses.
|
package/src/parser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Tokenizer and recursive-descent parser for the EyeProlog source language.
|
|
2
2
|
// It preserves the compact Prolog-like syntax while producing Term objects for the solver.
|
|
3
3
|
import { ATOM, COMPOUND, atom, compound, cons, emptyList, numberTerm, variable } from './term.js';
|
|
4
|
-
import { isTerminatingFullStop } from './syntax-scan.js';
|
|
4
|
+
import { continuesGraphicToken, isTerminatingFullStop } from './syntax-scan.js';
|
|
5
5
|
|
|
6
6
|
const TOK = {
|
|
7
7
|
EOF: 'eof', ATOM: 'atom', VAR: 'var', STRING: 'string', NUMBER: 'number',
|
|
@@ -196,9 +196,25 @@ class Parser {
|
|
|
196
196
|
this.infixOperators = operatorState.infixOperators;
|
|
197
197
|
this.prefixOperators = operatorState.prefixOperators;
|
|
198
198
|
this.postfixOperators = operatorState.postfixOperators;
|
|
199
|
+
this.readTermEnd = Number.isInteger(options.readTermEnd) ? options.readTermEnd : null;
|
|
199
200
|
this.previousToken = null;
|
|
200
201
|
this.token = this.nextToken();
|
|
201
202
|
}
|
|
203
|
+
terminatingFullStop(index = this.pos) {
|
|
204
|
+
// read/1 tries each possible full stop in turn. While parsing a later
|
|
205
|
+
// candidate, a preceding dot after a graphic character belongs to that
|
|
206
|
+
// maximal graphic token; the candidate's final dot is the end char. Keep
|
|
207
|
+
// ordinary program parsing context-free by enabling this only for a
|
|
208
|
+
// designated read-term candidate.
|
|
209
|
+
if (this.readTermEnd != null) {
|
|
210
|
+
if (index === this.readTermEnd) return true;
|
|
211
|
+
if (index < this.readTermEnd && continuesGraphicToken(this.source, index)) {
|
|
212
|
+
const next = this.source[index + 1] ?? '';
|
|
213
|
+
if (next === '%' || /^[\u0009-\u000d\u0020]$/.test(next)) return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return isTerminatingFullStop(this.source, index);
|
|
217
|
+
}
|
|
202
218
|
defineOperator(priority, specifier, name) {
|
|
203
219
|
defineParserOperator(this, priority, specifier, name);
|
|
204
220
|
}
|
|
@@ -366,15 +382,16 @@ class Parser {
|
|
|
366
382
|
this.pos += 3;
|
|
367
383
|
return { type: TOK.ATOM, text: '...', line };
|
|
368
384
|
}
|
|
369
|
-
if (ch === '?' && this.peek(1) === '-'
|
|
385
|
+
if (ch === '?' && this.peek(1) === '-' &&
|
|
386
|
+
!(isGraphicAtomCode(this.peek(2).charCodeAt(0)) && !this.terminatingFullStop(this.pos + 2))) {
|
|
370
387
|
this.pos += 2;
|
|
371
388
|
return { type: TOK.ATOM, text: '?-', line };
|
|
372
389
|
}
|
|
373
|
-
if (ch === '.' && !
|
|
390
|
+
if (ch === '.' && !this.terminatingFullStop()) {
|
|
374
391
|
const start = this.pos;
|
|
375
392
|
this.take();
|
|
376
393
|
while (isGraphicAtomCode(this.peek().charCodeAt(0)) &&
|
|
377
|
-
!
|
|
394
|
+
!this.terminatingFullStop()) this.take();
|
|
378
395
|
return { type: TOK.ATOM, text: this.source.slice(start, this.pos), line };
|
|
379
396
|
}
|
|
380
397
|
if (ch === '!') {
|
|
@@ -394,11 +411,13 @@ class Parser {
|
|
|
394
411
|
this.take();
|
|
395
412
|
return { type: punct[ch], text: ch, line, precededByLayout };
|
|
396
413
|
}
|
|
397
|
-
if (ch === ':' && this.peek(1) === '-'
|
|
414
|
+
if (ch === ':' && this.peek(1) === '-' &&
|
|
415
|
+
!(isGraphicAtomCode(this.peek(2).charCodeAt(0)) && !this.terminatingFullStop(this.pos + 2))) {
|
|
398
416
|
this.pos += 2;
|
|
399
417
|
return { type: TOK.IF, text: ':-', line };
|
|
400
418
|
}
|
|
401
|
-
if (ch === ':'
|
|
419
|
+
if (ch === ':' &&
|
|
420
|
+
!(isGraphicAtomCode(this.peek(1).charCodeAt(0)) && !this.terminatingFullStop(this.pos + 1))) {
|
|
402
421
|
this.take();
|
|
403
422
|
return { type: TOK.ATOM, text: ':', line };
|
|
404
423
|
}
|
|
@@ -544,7 +563,7 @@ class Parser {
|
|
|
544
563
|
const start = this.pos;
|
|
545
564
|
this.take();
|
|
546
565
|
while (isGraphicAtomCode(this.peek().charCodeAt(0)) &&
|
|
547
|
-
!
|
|
566
|
+
!this.terminatingFullStop()) this.take();
|
|
548
567
|
return { type: TOK.ATOM, text: this.source.slice(start, this.pos), line };
|
|
549
568
|
}
|
|
550
569
|
|
|
@@ -606,7 +625,9 @@ class Parser {
|
|
|
606
625
|
this.advance();
|
|
607
626
|
return atom('{}');
|
|
608
627
|
}
|
|
609
|
-
|
|
628
|
+
// As with a parenthesized term, a current operator atom may be the entire
|
|
629
|
+
// curly-bracket content: `{*}` denotes {}(*), not an incomplete infix use.
|
|
630
|
+
const term = this.parseTerm(0, true, true, true);
|
|
610
631
|
this.expect(TOK.RBRACE, '}');
|
|
611
632
|
this.advance();
|
|
612
633
|
return compound('{}', [term]);
|
|
@@ -732,7 +753,7 @@ class Parser {
|
|
|
732
753
|
// an argument delimiter there is no operand for prefix syntax, so keep
|
|
733
754
|
// the operator as an atom instead of reporting a misleading bad-term
|
|
734
755
|
// error.
|
|
735
|
-
if ([TOK.COMMA, TOK.RPAREN, TOK.RBRACKET, TOK.BAR, TOK.DOT].includes(this.token.type)) {
|
|
756
|
+
if ([TOK.COMMA, TOK.RPAREN, TOK.RBRACKET, TOK.RBRACE, TOK.BAR, TOK.DOT].includes(this.token.type)) {
|
|
736
757
|
if (!allowOperatorAtom && this.token.type !== TOK.DOT) {
|
|
737
758
|
throw new Error(`parse line ${this.token.line}: operator atom ${op} requires argument context or parentheses`);
|
|
738
759
|
}
|
|
@@ -1032,7 +1053,7 @@ export function parseClauses(source, options = {}) {
|
|
|
1032
1053
|
const parserOptions = ownsParserFlagState
|
|
1033
1054
|
? { ...options, parserFlagState: { doubleQuotes: initialDoubleQuotes } }
|
|
1034
1055
|
: options;
|
|
1035
|
-
if (options.sourceMetadata === false) {
|
|
1056
|
+
if (options.sourceMetadata === false && options.readTermEnd == null) {
|
|
1036
1057
|
const clauses = parseClausesFastNoSource(source, null, null, parserOptions);
|
|
1037
1058
|
if (clauses) return clauses;
|
|
1038
1059
|
if (ownsParserFlagState) parserOptions.parserFlagState.doubleQuotes = initialDoubleQuotes;
|
package/src/repl.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import { readSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { createInterface } from 'node:readline';
|
|
6
|
-
import { formalErrorTerm } from './iso.js';
|
|
6
|
+
import { formalErrorTerm, isCompleteReadTermText } from './iso.js';
|
|
7
7
|
import {
|
|
8
8
|
characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
|
|
9
9
|
} from './syntax-scan.js';
|
|
@@ -162,7 +162,7 @@ class LineReader {
|
|
|
162
162
|
return this.terminal && Number.isInteger(this.input.fd);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
readInteractiveTermSync() {
|
|
165
|
+
readInteractiveTermSync(solver = null) {
|
|
166
166
|
if (!this.canReadTermSynchronously()) return null;
|
|
167
167
|
let source = '';
|
|
168
168
|
let prompt = '|: ';
|
|
@@ -172,7 +172,9 @@ class LineReader {
|
|
|
172
172
|
if (line == null) return source.trim() ? source : null;
|
|
173
173
|
source += `${line}\n`;
|
|
174
174
|
const end = terminalFullStop(source);
|
|
175
|
-
if (end >= 0
|
|
175
|
+
if (end >= 0 && acceptsReadTermBoundary(source, end, solver)) {
|
|
176
|
+
return source.slice(0, end + 1) + '\n';
|
|
177
|
+
}
|
|
176
178
|
prompt = '| ';
|
|
177
179
|
}
|
|
178
180
|
}
|
|
@@ -256,7 +258,7 @@ function makeState(engine, sources, output, options = {}, previousState = null,
|
|
|
256
258
|
// let ISO term input request a complete terminal term exactly when read/1-2
|
|
257
259
|
// or read_term/2-3 actually executes. This also works inside conjunctions
|
|
258
260
|
// and user predicates instead of only when read/* is the whole REPL goal.
|
|
259
|
-
userInput.interactiveReadTerm = () => reader.readInteractiveTermSync();
|
|
261
|
+
userInput.interactiveReadTerm = () => reader.readInteractiveTermSync(solver);
|
|
260
262
|
}
|
|
261
263
|
const flagOverrides = new Map(previousState?.flagOverrides ?? []);
|
|
262
264
|
for (const [name, value] of flagOverrides) {
|
|
@@ -300,7 +302,7 @@ async function prepareInteractiveTermInput(state, goal, reader) {
|
|
|
300
302
|
const stream = interactiveTermInputStream(state, goal);
|
|
301
303
|
if (stream == null || terminalFullStop(String(stream.content).slice(stream.position)) >= 0) return;
|
|
302
304
|
|
|
303
|
-
const text = await readInteractiveTerm(reader);
|
|
305
|
+
const text = await readInteractiveTerm(reader, state.solver);
|
|
304
306
|
if (text == null) return;
|
|
305
307
|
stream.content += text;
|
|
306
308
|
stream.pastEnd = false;
|
|
@@ -334,7 +336,7 @@ function explicitInputReference(term) {
|
|
|
334
336
|
return null;
|
|
335
337
|
}
|
|
336
338
|
|
|
337
|
-
async function readInteractiveTerm(reader) {
|
|
339
|
+
async function readInteractiveTerm(reader, solver = null) {
|
|
338
340
|
let source = '';
|
|
339
341
|
let prompt = '|: ';
|
|
340
342
|
while (true) {
|
|
@@ -342,11 +344,22 @@ async function readInteractiveTerm(reader) {
|
|
|
342
344
|
if (line == null) return source.trim() ? source : null;
|
|
343
345
|
source += `${line}\n`;
|
|
344
346
|
const end = terminalFullStop(source);
|
|
345
|
-
if (end >= 0
|
|
347
|
+
if (end >= 0 && acceptsReadTermBoundary(source, end, solver)) {
|
|
348
|
+
return source.slice(0, end + 1) + '\n';
|
|
349
|
+
}
|
|
346
350
|
prompt = '| ';
|
|
347
351
|
}
|
|
348
352
|
}
|
|
349
353
|
|
|
354
|
+
function acceptsReadTermBoundary(source, end, solver) {
|
|
355
|
+
// A dot after a graphic character has two possible readings. Stop at once
|
|
356
|
+
// when the candidate is already a complete term (for example `./*.`);
|
|
357
|
+
// otherwise keep reading so a later end char can make it part of an atom
|
|
358
|
+
// (for example the first dot in `!,*.\n.`).
|
|
359
|
+
return !continuesGraphicToken(source, end) || solver == null ||
|
|
360
|
+
isCompleteReadTermText(source.slice(0, end + 1), solver);
|
|
361
|
+
}
|
|
362
|
+
|
|
350
363
|
function terminalFullStop(source) {
|
|
351
364
|
let quote = null;
|
|
352
365
|
let lineComment = false;
|
package/src/write.js
CHANGED
|
@@ -227,7 +227,9 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
if (!options.ignoreOps && resolved.name === '{}' && resolved.arity === 1) {
|
|
230
|
-
|
|
230
|
+
// A current operator atom is valid as the complete curly-bracket content,
|
|
231
|
+
// just as it is in a functional argument or list element.
|
|
232
|
+
return `{${format(resolved.args[0], env, options, table, 1200, 'argument')}}`;
|
|
231
233
|
}
|
|
232
234
|
|
|
233
235
|
if (!options.ignoreOps) {
|
|
@@ -9,6 +9,7 @@ answer(Binary, Octal, Hex, Character, Escaped, Curly, EmptyCurly, IntegerPart, F
|
|
|
9
9
|
=(Character, 0'\n),
|
|
10
10
|
=(Escaped, 'A\x42\\101\'),
|
|
11
11
|
=(Curly, {pair(a, b)}),
|
|
12
|
+
{*} = {}(*),
|
|
12
13
|
=(EmptyCurly, {}),
|
|
13
14
|
IntegerPart is float_integer_part(-3.75),
|
|
14
15
|
FractionalPart is float_fractional_part(-3.75).
|
package/test/run-regression.mjs
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
variantTerms,
|
|
39
39
|
parseProgramText,
|
|
40
40
|
} from '../src/index.js';
|
|
41
|
-
import { parseGoalText, parseNumberTokenText } from '../src/parser.js';
|
|
41
|
+
import { ISO_OPERATOR_DEFINITIONS, parseGoalText, parseNumberTokenText } from '../src/parser.js';
|
|
42
42
|
import { compareTerms } from '../src/term.js';
|
|
43
43
|
import { formatTermForWrite } from '../src/write.js';
|
|
44
44
|
import { selectClauseCandidates } from '../src/program.js';
|
|
@@ -752,6 +752,31 @@ c4 ?- call((!;1)).
|
|
|
752
752
|
});
|
|
753
753
|
assertEqual(consecutive.stdout, "answer('./*.', ok).\n", 'following read starts after the complete term');
|
|
754
754
|
|
|
755
|
+
// A possible full stop can fail to complete the term while still
|
|
756
|
+
// extending a current graphic operator into an ordinary atom. The
|
|
757
|
+
// later standalone full stop then completes the read term. Exercise
|
|
758
|
+
// every predefined graphic operator, including the tokenizer's
|
|
759
|
+
// special :- and ?- cases, so they cannot diverge from * again.
|
|
760
|
+
const graphicOperator = /^[#$&*+\-./<=>?@^~\\:]+$/;
|
|
761
|
+
const graphicOperators = [...new Set(ISO_OPERATOR_DEFINITIONS.map(([, , name]) => name))]
|
|
762
|
+
.filter((name) => graphicOperator.test(name));
|
|
763
|
+
for (const name of graphicOperators) {
|
|
764
|
+
const program = Program.parse('');
|
|
765
|
+
const solver = new Solver(program, {
|
|
766
|
+
registry: getEyePrologRegistry(),
|
|
767
|
+
ioOptions: { input: `!,${name}.\n.\n` },
|
|
768
|
+
});
|
|
769
|
+
const readGoal = parseGoalText('read(T)', {
|
|
770
|
+
operatorDefinitions: [...program.operators.values()],
|
|
771
|
+
});
|
|
772
|
+
const answers = [...solver.solve([readGoal], new Env(), 0)];
|
|
773
|
+
assertEqual(answers.length, 1, `graphic operator read answer for ${name}`);
|
|
774
|
+
const term = copyResolved(readGoal.args[0], answers[0]);
|
|
775
|
+
assertEqual(term.name, ',', `graphic operator conjunction for ${name}`);
|
|
776
|
+
assertEqual(term.args[0].name, '!', `graphic operator left operand for ${name}`);
|
|
777
|
+
assertEqual(term.args[1].name, `${name}.`, `graphic operator atom for ${name}`);
|
|
778
|
+
}
|
|
779
|
+
|
|
755
780
|
let error = null;
|
|
756
781
|
try {
|
|
757
782
|
runEyeProlog('', { goal: 'read(T)', ioOptions: { input: '!.!.' } });
|
|
@@ -769,6 +794,15 @@ c4 ?- call((!;1)).
|
|
|
769
794
|
assertIncludes(repl.stdout, 'T = ok.', 'REPL following read answer');
|
|
770
795
|
assertIncludes(repl.stdout, 'error(syntax_error(read_term), eyeprolog)', 'REPL syntax error');
|
|
771
796
|
assertEqual(repl.stderr, '', 'REPL stderr');
|
|
797
|
+
|
|
798
|
+
const continuedGraphic = runCli([], {
|
|
799
|
+
input: 'read(T).\n!,*.\n.\nread(T).\na\n.\nhalt.\n',
|
|
800
|
+
});
|
|
801
|
+
assertEqual(continuedGraphic.status, 0, 'continued graphic operator REPL status');
|
|
802
|
+
assertIncludes(continuedGraphic.stdout, 'T = (!, *.).', 'continued graphic operator answer');
|
|
803
|
+
assertIncludes(continuedGraphic.stdout, 'T = a.', 'read after continued graphic operator');
|
|
804
|
+
assertNotIncludes(continuedGraphic.stdout, 'syntax_error', 'continued graphic operator syntax');
|
|
805
|
+
assertEqual(continuedGraphic.stderr, '', 'continued graphic operator REPL stderr');
|
|
772
806
|
},
|
|
773
807
|
},
|
|
774
808
|
{
|
|
@@ -782,6 +816,38 @@ c4 ?- call((!;1)).
|
|
|
782
816
|
assertNotIncludes(result.stdout, "'?-'", 'writeq(?-) has no quotes');
|
|
783
817
|
},
|
|
784
818
|
},
|
|
819
|
+
{
|
|
820
|
+
name: 'curly brackets accept ISO and custom operator atoms (issue #41)',
|
|
821
|
+
run: () => {
|
|
822
|
+
const operatorNames = [...new Set(ISO_OPERATOR_DEFINITIONS.map(([, , name]) => name))]
|
|
823
|
+
.filter((name) => name !== ',');
|
|
824
|
+
for (const name of operatorNames) {
|
|
825
|
+
const holder = parseGoalText(`holder({${name}})`);
|
|
826
|
+
const curly = holder.args[0];
|
|
827
|
+
assertEqual(curly.name, '{}', `curly functor for ${name}`);
|
|
828
|
+
assertEqual(curly.args[0].name, name, `curly operator atom ${name}`);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const customProgram = parseProgramText([
|
|
832
|
+
':- op(100, fx, pre).',
|
|
833
|
+
':- op(100, xf, post).',
|
|
834
|
+
':- op(100, xfx, infix).',
|
|
835
|
+
'custom({pre}, {post}, {infix}).',
|
|
836
|
+
'',
|
|
837
|
+
].join('\n'));
|
|
838
|
+
const custom = customProgram.find((clause) => clause.head.name === 'custom').head;
|
|
839
|
+
assertEqual(
|
|
840
|
+
custom.args.map((curly) => curly.args[0].name).join(','),
|
|
841
|
+
'pre,post,infix',
|
|
842
|
+
'custom prefix, postfix, and infix operator atoms',
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
const repl = runCli([], { input: 'read(T).\n{*}.\nhalt.\n' });
|
|
846
|
+
assertEqual(repl.status, 0, 'curly operator REPL status');
|
|
847
|
+
assertIncludes(repl.stdout, 'T = {*}.', 'curly operator REPL answer');
|
|
848
|
+
assertNotIncludes(repl.stdout, '{(*)}', 'curly operator has no unnecessary parentheses');
|
|
849
|
+
},
|
|
850
|
+
},
|
|
785
851
|
{
|
|
786
852
|
name: 'normal parser rejects non-conforming bare operator operands',
|
|
787
853
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5197,12 +5197,14 @@ parsing of subsequent text, place them before their first use. ISO argument
|
|
|
5197
5197
|
syntax also permits an atom that is currently an operator to appear directly
|
|
5198
5198
|
as a functional argument or list element, so forms such as
|
|
5199
5199
|
`current_op(Priority, Specifier, :-)` and `[:-,-]` are valid without quoting
|
|
5200
|
-
or parenthesizing those operator atoms.
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5200
|
+
or parenthesizing those operator atoms. A current operator atom may likewise
|
|
5201
|
+
be the complete content of parentheses or curly brackets: `(+)` denotes the
|
|
5202
|
+
atom `+`, and `{*}` denotes the curly term `{}(*)`. Term output observes the
|
|
5203
|
+
same context rules: with `quoted(true)`, an operator atom is not quoted merely
|
|
5204
|
+
because it occurs as a functional argument, list element, or sole curly-bracket
|
|
5205
|
+
content. Thus `writeq({*})` emits `{*}`, `writeq([:-,-])` emits `[:-,-]`, and
|
|
5206
|
+
`writeq(f(;,'|',';;'))` emits `f(;,'|',';;')`; the bar stays quoted because
|
|
5207
|
+
ISO treats the unquoted `|` token as a list separator rather than an atom. The ISO initial operator table also
|
|
5206
5208
|
contains `?-` at priority 1200 with specifier `fx`, so
|
|
5207
5209
|
`current_op(1200, fx, ?-)` succeeds. EyeProlog's embedded quad syntax permits
|
|
5208
5210
|
an optional label before the query marker (`Label ?- Query.`), so while quad
|