eyeprolog 1.3.1 → 1.3.3
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 +2 -5
- package/src/parser.js +14 -0
- package/src/repl.js +61 -11
- package/src/standard-library.js +1 -1
- package/test/run-regression.mjs +169 -0
- package/the-art-of-eyeprolog.md +12 -4
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
properListItems, termIsGround, termToString, unify, variable, variantTerms,
|
|
7
7
|
} from './term.js';
|
|
8
8
|
import { sameNumberValue } from './number-value.js';
|
|
9
|
-
import { createParserOperatorState,
|
|
9
|
+
import { createParserOperatorState, parseGoalText, parseNumberTokenText, parseTermText } from './parser.js';
|
|
10
10
|
import { formatTermForWrite } from './write.js';
|
|
11
11
|
import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
|
|
12
12
|
import {
|
|
@@ -1199,8 +1199,7 @@ function scopeReadTerm(term) {
|
|
|
1199
1199
|
function parseReadTermText(text, solver) {
|
|
1200
1200
|
const converted = convertedTermText(text, solver);
|
|
1201
1201
|
const operatorState = createParserOperatorState(solver.program.operators.values(), false);
|
|
1202
|
-
|
|
1203
|
-
sourceMetadata: false,
|
|
1202
|
+
return parseTermText(converted, {
|
|
1204
1203
|
operatorState,
|
|
1205
1204
|
isoStrict: solver.isoStrict,
|
|
1206
1205
|
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
@@ -1208,8 +1207,6 @@ function parseReadTermText(text, solver) {
|
|
|
1208
1207
|
// Earlier ambiguous dots must remain available to maximal graphic tokens.
|
|
1209
1208
|
readTermEnd: converted.length - 1,
|
|
1210
1209
|
});
|
|
1211
|
-
if (clauses.length !== 1 || clauses[0].body.length) throw new Error('bad term');
|
|
1212
|
-
return clauses[0].head;
|
|
1213
1210
|
}
|
|
1214
1211
|
|
|
1215
1212
|
export function isCompleteReadTermText(text, solver) {
|
package/src/parser.js
CHANGED
|
@@ -819,6 +819,16 @@ class Parser {
|
|
|
819
819
|
}
|
|
820
820
|
throw new Error(`parse line ${this.token.line}: bad term`);
|
|
821
821
|
}
|
|
822
|
+
parseStandaloneTerm() {
|
|
823
|
+
// read/1 and read_term/* consume one ordinary Prolog term, not a program
|
|
824
|
+
// clause. In particular, commas and operators such as :- and ?- belong to
|
|
825
|
+
// the term itself and must not be reinterpreted by parseProgram().
|
|
826
|
+
const term = this.parseTerm(0, true, true, true);
|
|
827
|
+
this.expect(TOK.DOT, '.');
|
|
828
|
+
this.advance();
|
|
829
|
+
this.expect(TOK.EOF, 'end of input');
|
|
830
|
+
return term;
|
|
831
|
+
}
|
|
822
832
|
sourceLineIsIndented(line) {
|
|
823
833
|
let start = 0;
|
|
824
834
|
for (let current = 1; current < line; current++) {
|
|
@@ -1504,6 +1514,10 @@ export function parseNumberTokenText(text) {
|
|
|
1504
1514
|
return numberTerm(source);
|
|
1505
1515
|
}
|
|
1506
1516
|
|
|
1517
|
+
export function parseTermText(text, options = {}) {
|
|
1518
|
+
return new Parser(text, options).parseStandaloneTerm();
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1507
1521
|
export function parseGoalText(text, options = {}) {
|
|
1508
1522
|
const clauses = parseClauses(`zz_goal((${text})).`, options);
|
|
1509
1523
|
const head = clauses[0]?.head;
|
package/src/repl.js
CHANGED
|
@@ -55,7 +55,10 @@ export async function runRepl(engine, options = {}) {
|
|
|
55
55
|
}
|
|
56
56
|
const consultFiles = options.isoStrict ? null : consultDesignations(engine, goal);
|
|
57
57
|
if (consultFiles != null) {
|
|
58
|
-
for (const filename of consultFiles)
|
|
58
|
+
for (const filename of consultFiles) {
|
|
59
|
+
const source = await readSource(filename);
|
|
60
|
+
replaceConsultedSource(sources, source);
|
|
61
|
+
}
|
|
59
62
|
state = makeState(engine, sources, output, options, state, reader);
|
|
60
63
|
runWithTerminalSignals(reader, () => state.solver.runInitializations());
|
|
61
64
|
output.write(' true.\n');
|
|
@@ -447,10 +450,32 @@ function isHaltGoal(goal) {
|
|
|
447
450
|
}
|
|
448
451
|
|
|
449
452
|
function consultDesignations(engine, goal) {
|
|
453
|
+
// The traditional [file]. top-level shorthand and explicit consult/1 use
|
|
454
|
+
// the same resolver and modern reconsult semantics. Accept reconsult/1 as
|
|
455
|
+
// a compatibility alias as well; contemporary consult/1 already replaces
|
|
456
|
+
// clauses previously loaded from the same source.
|
|
450
457
|
if (goal.type === 'atom' && goal.name === '[]') return [];
|
|
451
|
-
if (goal.type
|
|
452
|
-
|
|
453
|
-
|
|
458
|
+
if (goal.type === 'compound' && goal.name === '.' && goal.arity === 2) {
|
|
459
|
+
return consultListDesignations(engine, goal);
|
|
460
|
+
}
|
|
461
|
+
if (goal.type === 'compound' && ['consult', 'reconsult'].includes(goal.name) && goal.arity === 1) {
|
|
462
|
+
return consultArgumentDesignations(engine, goal.args[0]);
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function consultArgumentDesignations(engine, term) {
|
|
468
|
+
if (term.type === 'var') throw new engine.PrologError('instantiation_error');
|
|
469
|
+
if (term.type === 'atom') return term.name === '[]' ? [] : [term.name];
|
|
470
|
+
if (term.type === 'compound' && term.name === '.' && term.arity === 2) {
|
|
471
|
+
return consultListDesignations(engine, term);
|
|
472
|
+
}
|
|
473
|
+
throw new engine.PrologError('type_error(atom)', term);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function consultListDesignations(engine, list) {
|
|
477
|
+
const items = engine.properListItems(list, new engine.Env());
|
|
478
|
+
if (items == null) throw new engine.PrologError('type_error(list)', list);
|
|
454
479
|
return items.map((item) => {
|
|
455
480
|
if (item.type === 'var') throw new engine.PrologError('instantiation_error');
|
|
456
481
|
if (item.type !== 'atom') throw new engine.PrologError('type_error(atom)', item);
|
|
@@ -458,18 +483,43 @@ function consultDesignations(engine, goal) {
|
|
|
458
483
|
});
|
|
459
484
|
}
|
|
460
485
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
486
|
+
function replaceConsultedSource(sources, source) {
|
|
487
|
+
const index = sources.findIndex((existing) =>
|
|
488
|
+
source.consultPath != null && existing.consultPath === source.consultPath);
|
|
489
|
+
if (index >= 0) sources[index] = source;
|
|
490
|
+
else sources.push(source);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function missingSource(error) {
|
|
494
|
+
return error?.code === 'ENOENT' || error?.code === 'ENOTDIR';
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function resolvedConsultFilename(designation) {
|
|
498
|
+
const requested = path.resolve(designation);
|
|
499
|
+
// Long-standing Prolog consult convention: for an extensionless name, try
|
|
500
|
+
// the .pl source first and use the unsuffixed path only as a fallback.
|
|
501
|
+
if (!path.extname(requested)) {
|
|
502
|
+
const prologFile = `${requested}.pl`;
|
|
503
|
+
try {
|
|
504
|
+
await fs.access(prologFile);
|
|
505
|
+
return await fs.realpath(prologFile);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
if (!missingSource(error)) throw error;
|
|
508
|
+
}
|
|
468
509
|
}
|
|
510
|
+
await fs.access(requested);
|
|
511
|
+
return fs.realpath(requested);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function readSource(designation) {
|
|
515
|
+
const filename = await resolvedConsultFilename(designation);
|
|
469
516
|
return {
|
|
470
517
|
text: await fs.readFile(filename, 'utf8'),
|
|
471
518
|
filename: path.basename(filename),
|
|
472
519
|
baseDir: path.dirname(filename),
|
|
520
|
+
// Keep host-only provenance so consulting the same resolved file again
|
|
521
|
+
// replaces its previous source instead of accumulating stale clauses.
|
|
522
|
+
consultPath: filename,
|
|
473
523
|
};
|
|
474
524
|
}
|
|
475
525
|
|
package/src/standard-library.js
CHANGED
|
@@ -154,7 +154,7 @@ function* statisticsValueBuiltin({ solver, goal, env }) {
|
|
|
154
154
|
|
|
155
155
|
if (entries == null) {
|
|
156
156
|
if (key.type !== ATOM) throw new PrologError('type_error(atom)', key);
|
|
157
|
-
|
|
157
|
+
throw new PrologError('domain_error(statistics_key)', key);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
for (const [name, value] of entries) {
|
package/test/run-regression.mjs
CHANGED
|
@@ -819,6 +819,27 @@ c4 ?- call((!;1)).
|
|
|
819
819
|
});
|
|
820
820
|
assertEqual(consecutive.stdout, "answer(./*., ok).\n", 'following read starts after the complete term');
|
|
821
821
|
|
|
822
|
+
// Issue #41 follow-up: read/1 consumes an ordinary term, not a program
|
|
823
|
+
// clause head. A comma chain therefore has no program-level two-comma
|
|
824
|
+
// limit, and source operators such as :- and ?- remain term data.
|
|
825
|
+
const commaChain = runEyeProlog('', {
|
|
826
|
+
goal: 'read(T)',
|
|
827
|
+
ioOptions: { input: '!,!,! .\n' },
|
|
828
|
+
});
|
|
829
|
+
assertEqual(commaChain.stdout, 'read((!, !, !)).\n', 'three-element comma term');
|
|
830
|
+
|
|
831
|
+
const ruleAsData = runEyeProlog('', {
|
|
832
|
+
goal: 'read(T)',
|
|
833
|
+
ioOptions: { input: 'a :- b.\n' },
|
|
834
|
+
});
|
|
835
|
+
assertEqual(ruleAsData.stdout, 'read((a :- b)).\n', 'rule operator remains term data');
|
|
836
|
+
|
|
837
|
+
const queryAsData = runEyeProlog('', {
|
|
838
|
+
goal: 'read(T)',
|
|
839
|
+
ioOptions: { input: '?- foo.\n' },
|
|
840
|
+
});
|
|
841
|
+
assertEqual(queryAsData.stdout, 'read((?- foo)).\n', 'query operator remains term data');
|
|
842
|
+
|
|
822
843
|
// A possible full stop can fail to complete the term while still
|
|
823
844
|
// extending a current graphic operator into an ordinary atom. The
|
|
824
845
|
// later standalone full stop then completes the read term. Exercise
|
|
@@ -1712,6 +1733,143 @@ c4 ?- call((!;1)).
|
|
|
1712
1733
|
assertEqual(result.stderr, '', 'stderr');
|
|
1713
1734
|
},
|
|
1714
1735
|
},
|
|
1736
|
+
{
|
|
1737
|
+
name: 'REPL consult prefers .pl over an unsuffixed file (issue #47)',
|
|
1738
|
+
run: () => {
|
|
1739
|
+
const stem = path.join(tmp, `repl-consult-order-${++tmpCounter}`);
|
|
1740
|
+
fs.writeFileSync(stem, 'chosen(bare).\n');
|
|
1741
|
+
fs.writeFileSync(`${stem}.pl`, 'chosen(pl).\n');
|
|
1742
|
+
const result = runCli([], {
|
|
1743
|
+
input: `[${sourceAtom(stem)}].\nchosen(X).\nhalt.\n`,
|
|
1744
|
+
});
|
|
1745
|
+
assertEqual(result.status, 0, 'exit status');
|
|
1746
|
+
assertIncludes(result.stdout, 'X = pl.', 'consulted .pl source');
|
|
1747
|
+
assertNotIncludes(result.stdout, 'X = bare', 'unsuffixed source is fallback only');
|
|
1748
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
1749
|
+
|
|
1750
|
+
const explicit = runCli([], {
|
|
1751
|
+
input: `consult(${sourceAtom(stem)}).\nchosen(X).\nhalt.\n`,
|
|
1752
|
+
});
|
|
1753
|
+
assertEqual(explicit.status, 0, 'consult/1 exit status');
|
|
1754
|
+
assertIncludes(explicit.stdout, 'X = pl.', 'consult/1 prefers .pl source');
|
|
1755
|
+
assertNotIncludes(explicit.stdout, 'X = bare', 'consult/1 does not prefer unsuffixed source');
|
|
1756
|
+
assertEqual(explicit.stderr, '', 'consult/1 stderr');
|
|
1757
|
+
|
|
1758
|
+
fs.rmSync(`${stem}.pl`);
|
|
1759
|
+
const fallback = runCli([], {
|
|
1760
|
+
input: `[${sourceAtom(stem)}].\nchosen(X).\nhalt.\n`,
|
|
1761
|
+
});
|
|
1762
|
+
assertEqual(fallback.status, 0, 'fallback exit status');
|
|
1763
|
+
assertIncludes(fallback.stdout, 'X = bare.', 'unsuffixed fallback source');
|
|
1764
|
+
assertEqual(fallback.stderr, '', 'fallback stderr');
|
|
1765
|
+
},
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
name: 'REPL consultation replaces earlier clauses from the same file (issue #46)',
|
|
1769
|
+
run: () => {
|
|
1770
|
+
const filename = path.join(tmp, `repl-reconsult-${++tmpCounter}.pl`);
|
|
1771
|
+
fs.writeFileSync(filename, 'factum(f).\n');
|
|
1772
|
+
const harness = path.join(tmp, `repl-reconsult-harness-${++tmpCounter}.mjs`);
|
|
1773
|
+
const consultedAtom = sourceAtom(filename);
|
|
1774
|
+
fs.writeFileSync(harness, `
|
|
1775
|
+
import fs from 'node:fs';
|
|
1776
|
+
import process from 'node:process';
|
|
1777
|
+
import { spawn } from 'node:child_process';
|
|
1778
|
+
|
|
1779
|
+
const child = spawn(process.execPath, [${JSON.stringify(bin)}], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1780
|
+
let stdout = '';
|
|
1781
|
+
let stderr = '';
|
|
1782
|
+
let advanced = false;
|
|
1783
|
+
let failed = false;
|
|
1784
|
+
const timer = setTimeout(() => {
|
|
1785
|
+
failed = true;
|
|
1786
|
+
child.kill();
|
|
1787
|
+
}, 5000);
|
|
1788
|
+
|
|
1789
|
+
child.stdout.setEncoding('utf8');
|
|
1790
|
+
child.stderr.setEncoding('utf8');
|
|
1791
|
+
child.stdout.on('data', (chunk) => {
|
|
1792
|
+
stdout += chunk;
|
|
1793
|
+
if (!advanced && stdout.includes('?- true.\\n?- ')) {
|
|
1794
|
+
advanced = true;
|
|
1795
|
+
fs.writeFileSync(${JSON.stringify(filename)}, 'factum(g).\\n');
|
|
1796
|
+
child.stdin.write(\`[${consultedAtom}].\\nfindall(F,factum(F),Fs).\\nhalt.\\n\`);
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
1800
|
+
child.on('close', (code) => {
|
|
1801
|
+
clearTimeout(timer);
|
|
1802
|
+
process.stdout.write(stdout);
|
|
1803
|
+
process.stderr.write(stderr);
|
|
1804
|
+
process.exitCode = failed ? 99 : (code ?? 98);
|
|
1805
|
+
});
|
|
1806
|
+
child.stdin.write(\`[${consultedAtom}].\\n\`);
|
|
1807
|
+
`);
|
|
1808
|
+
const result = spawnSync(process.execPath, [harness], {
|
|
1809
|
+
cwd: packageRoot,
|
|
1810
|
+
encoding: 'utf8',
|
|
1811
|
+
timeout: 7000,
|
|
1812
|
+
});
|
|
1813
|
+
assertEqual(result.error?.code, undefined, 'reconsult harness timeout');
|
|
1814
|
+
assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
|
|
1815
|
+
assertIncludes(result.stdout, 'Fs = \"g\".', 'reconsulted clauses');
|
|
1816
|
+
assertNotIncludes(result.stdout, 'f', 'stale clause removed');
|
|
1817
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
1818
|
+
},
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
name: 'REPL consult/1 has reconsult semantics',
|
|
1822
|
+
run: () => {
|
|
1823
|
+
const filename = path.join(tmp, `repl-consult-predicate-${++tmpCounter}.pl`);
|
|
1824
|
+
fs.writeFileSync(filename, 'factum(f).\n');
|
|
1825
|
+
const harness = path.join(tmp, `repl-consult-predicate-harness-${++tmpCounter}.mjs`);
|
|
1826
|
+
const consultedAtom = sourceAtom(filename);
|
|
1827
|
+
fs.writeFileSync(harness, `
|
|
1828
|
+
import fs from 'node:fs';
|
|
1829
|
+
import process from 'node:process';
|
|
1830
|
+
import { spawn } from 'node:child_process';
|
|
1831
|
+
|
|
1832
|
+
const child = spawn(process.execPath, [${JSON.stringify(bin)}], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
1833
|
+
let stdout = '';
|
|
1834
|
+
let stderr = '';
|
|
1835
|
+
let advanced = false;
|
|
1836
|
+
let failed = false;
|
|
1837
|
+
const timer = setTimeout(() => {
|
|
1838
|
+
failed = true;
|
|
1839
|
+
child.kill();
|
|
1840
|
+
}, 5000);
|
|
1841
|
+
|
|
1842
|
+
child.stdout.setEncoding('utf8');
|
|
1843
|
+
child.stderr.setEncoding('utf8');
|
|
1844
|
+
child.stdout.on('data', (chunk) => {
|
|
1845
|
+
stdout += chunk;
|
|
1846
|
+
if (!advanced && stdout.includes('?- true.\\n?- ')) {
|
|
1847
|
+
advanced = true;
|
|
1848
|
+
fs.writeFileSync(${JSON.stringify(filename)}, 'factum(g).\\n');
|
|
1849
|
+
child.stdin.write(\`consult(${consultedAtom}).\\nfindall(F,factum(F),Fs).\\nhalt.\\n\`);
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1852
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
1853
|
+
child.on('close', (code) => {
|
|
1854
|
+
clearTimeout(timer);
|
|
1855
|
+
process.stdout.write(stdout);
|
|
1856
|
+
process.stderr.write(stderr);
|
|
1857
|
+
process.exitCode = failed ? 99 : (code ?? 98);
|
|
1858
|
+
});
|
|
1859
|
+
child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
1860
|
+
`);
|
|
1861
|
+
const result = spawnSync(process.execPath, [harness], {
|
|
1862
|
+
cwd: packageRoot,
|
|
1863
|
+
encoding: 'utf8',
|
|
1864
|
+
timeout: 7000,
|
|
1865
|
+
});
|
|
1866
|
+
assertEqual(result.error?.code, undefined, 'consult/1 reconsult harness timeout');
|
|
1867
|
+
assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
|
|
1868
|
+
assertIncludes(result.stdout, 'Fs = "g".', 'consult/1 replaced earlier clauses');
|
|
1869
|
+
assertNotIncludes(result.stdout, 'f', 'consult/1 removed stale clause');
|
|
1870
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
1871
|
+
},
|
|
1872
|
+
},
|
|
1715
1873
|
{
|
|
1716
1874
|
name: 'REPL preserves runtime unknown flag across consultation',
|
|
1717
1875
|
run: () => {
|
|
@@ -2002,6 +2160,17 @@ c4 ?- call((!;1)).
|
|
|
2002
2160
|
assertEqual(result.stderr, '', 'stderr');
|
|
2003
2161
|
},
|
|
2004
2162
|
},
|
|
2163
|
+
{
|
|
2164
|
+
name: 'statistics/2 rejects unknown keys instead of silently failing (issue #45)',
|
|
2165
|
+
run: () => {
|
|
2166
|
+
const result = runCli([], { input: 'statistics(nonsense, Value).\nhalt.\n' });
|
|
2167
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2168
|
+
assertIncludes(result.stdout,
|
|
2169
|
+
'error(domain_error(statistics_key, nonsense), eyeprolog).',
|
|
2170
|
+
'statistics key error');
|
|
2171
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2172
|
+
},
|
|
2173
|
+
},
|
|
2005
2174
|
{
|
|
2006
2175
|
name: 'statistics predicates are excluded from strict ISO mode',
|
|
2007
2176
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -4646,8 +4646,10 @@ where a live snapshot is useful. For example, a long-running loop can include
|
|
|
4646
4646
|
immediately to the current output stream. `statistics/2` makes an individual
|
|
4647
4647
|
value available to the program, for example
|
|
4648
4648
|
`statistics(memory_guard_used_bytes, Used)`. With an unbound first argument it
|
|
4649
|
-
enumerates the available statistic keys and values.
|
|
4650
|
-
|
|
4649
|
+
enumerates the available statistic keys and values. An atom that is not an
|
|
4650
|
+
available key raises `domain_error(statistics_key, Key)` rather than silently
|
|
4651
|
+
failing. These predicates are EyeProlog observability extensions and are not
|
|
4652
|
+
available under `--iso-strict`.
|
|
4651
4653
|
|
|
4652
4654
|
Compare statistics only between runs with the same query, data, and observable
|
|
4653
4655
|
answer contract. A faster program that silently loses answers is not an
|
|
@@ -6399,8 +6401,14 @@ not be a valid right operand of the displayed `=/2`, EyeProlog adds parentheses,
|
|
|
6399
6401
|
for example `T = (a = b).` rather than the invalid `T = a = b.`. When an answer
|
|
6400
6402
|
ends in a graphic token, the top level inserts layout before its terminating
|
|
6401
6403
|
full stop so the two tokens cannot merge; for example `?- X = .* .` displays
|
|
6402
|
-
`X = .* .`, not `X = .*.`. Use `[file]
|
|
6403
|
-
consult local source
|
|
6404
|
+
`X = .* .`, not `X = .*.`. Use `[file].`, `['file.pl'].`, or
|
|
6405
|
+
`consult(file).` to consult local source; `reconsult(file).` is accepted as a
|
|
6406
|
+
compatibility alias. Use `halt.` or `halt(Status).` to leave the top level.
|
|
6407
|
+
For an extensionless designation such as `[file].` or `consult(file).`, the
|
|
6408
|
+
top level tries `file.pl` before the unsuffixed `file`. Both the shorthand and
|
|
6409
|
+
`consult/1` have modern reconsult semantics: consulting the same resolved file
|
|
6410
|
+
again replaces its previous source, so clauses removed from the file do not
|
|
6411
|
+
remain active.
|
|
6404
6412
|
When `read/1-2` or `read_term/2-3` actually reaches interactive
|
|
6405
6413
|
`user_input`, the top level requests the next full-stop-terminated Prolog term
|
|
6406
6414
|
with a `|: ` input prompt instead of treating the terminal stream as already
|