eyeprolog 1.3.82 → 1.3.83
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 +22 -14
- package/src/repl.js +28 -12
- package/src/solver.js +39 -12
- package/test/conformance/ISO-COMPLIANCE.md +2 -2
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +2 -2
- package/test/conformance/expected/iso/exceptions_and_flags.pl +1 -1
- package/test/run-iso-strict.mjs +9 -0
- package/test/run-regression.mjs +64 -0
- package/the-art-of-eyeprolog.md +27 -6
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -804,18 +804,7 @@ function* currentPrologFlagBuiltin({ solver, goal, env }) {
|
|
|
804
804
|
}
|
|
805
805
|
|
|
806
806
|
function* setPrologFlagBuiltin({ solver, goal, env }) {
|
|
807
|
-
|
|
808
|
-
const value = deref(goal.args[1], env);
|
|
809
|
-
if (flag.type === VAR || value.type === VAR) throw new PrologError('instantiation_error');
|
|
810
|
-
if (flag.type !== ATOM) throw new PrologError('type_error(atom)', flag);
|
|
811
|
-
const definition = solver.prologFlags.get(flag.name);
|
|
812
|
-
if (!definition) throw new PrologError('domain_error(prolog_flag)', flag);
|
|
813
|
-
const expectedType = definition.valueType ?? ATOM;
|
|
814
|
-
if (value.type !== expectedType || !definition.allowed.includes(value.name)) {
|
|
815
|
-
throw new PrologError('domain_error(flag_value)', compound('+', [flag, value]));
|
|
816
|
-
}
|
|
817
|
-
if (!definition.changeable) throw new PrologError('permission_error(modify, flag)', flag);
|
|
818
|
-
definition.value = atom(value.name);
|
|
807
|
+
solver.setPrologFlag(goal.args[0], goal.args[1], env);
|
|
819
808
|
yield env;
|
|
820
809
|
}
|
|
821
810
|
|
|
@@ -1720,6 +1709,15 @@ function writeOptionBoolean(value, env, option) {
|
|
|
1720
1709
|
return value.name === 'true';
|
|
1721
1710
|
}
|
|
1722
1711
|
|
|
1712
|
+
function writeOptionSpacing(value, env, option) {
|
|
1713
|
+
value = deref(value, env);
|
|
1714
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
1715
|
+
if (value.type !== ATOM || !['standard', 'minimal'].includes(value.name)) {
|
|
1716
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1717
|
+
}
|
|
1718
|
+
return value.name;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1723
1721
|
function writeVariableNames(value, env, option) {
|
|
1724
1722
|
value = deref(value, env);
|
|
1725
1723
|
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
@@ -1748,8 +1746,10 @@ function writeVariableNames(value, env, option) {
|
|
|
1748
1746
|
return names;
|
|
1749
1747
|
}
|
|
1750
1748
|
|
|
1751
|
-
function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = null) {
|
|
1752
|
-
const result =
|
|
1749
|
+
function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = null, initial = null) {
|
|
1750
|
+
const result = initial == null
|
|
1751
|
+
? defaultTermWriteOptions(mode)
|
|
1752
|
+
: { ...initial, variableNames: new Map(initial.variableNames ?? []) };
|
|
1753
1753
|
for (const option of options) {
|
|
1754
1754
|
if (option.type !== COMPOUND || option.arity !== 1) {
|
|
1755
1755
|
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
@@ -1760,6 +1760,8 @@ function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = n
|
|
|
1760
1760
|
else if (option.name === 'variable_names') result.variableNames = writeVariableNames(option.args[0], env, option);
|
|
1761
1761
|
else if (option.name === 'double_quotes' && !solver?.isoStrict) {
|
|
1762
1762
|
result.doubleQuotes = writeOptionBoolean(option.args[0], env, option);
|
|
1763
|
+
} else if (option.name === 'spacing' && !solver?.isoStrict) {
|
|
1764
|
+
result.minimalOperatorSpacing = writeOptionSpacing(option.args[0], env, option) === 'minimal';
|
|
1763
1765
|
} else {
|
|
1764
1766
|
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1765
1767
|
}
|
|
@@ -1767,6 +1769,12 @@ function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = n
|
|
|
1767
1769
|
return result;
|
|
1768
1770
|
}
|
|
1769
1771
|
|
|
1772
|
+
export function parseTermWriteOptions(optionTerm, env, mode = 'write_term', solver = null, initial = null) {
|
|
1773
|
+
const preflight = preflightOptionList(optionTerm, env);
|
|
1774
|
+
const items = requireProperOptionList(preflight);
|
|
1775
|
+
return termWriteOptionsFromItems(items, env, mode, solver, initial);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1770
1778
|
|
|
1771
1779
|
function writeBuiltin(mode) {
|
|
1772
1780
|
return function* ({ solver, goal, env }) {
|
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, parseTermWriteOptions } from './iso.js';
|
|
7
7
|
import {
|
|
8
8
|
characterCodeConstantEnd, continuesGraphicToken, isTerminatingFullStop, quotedEscapeEnd,
|
|
9
9
|
} from './syntax-scan.js';
|
|
@@ -303,20 +303,18 @@ function makeState(engine, sources, output, options = {}, previousState = null,
|
|
|
303
303
|
const flagOverrides = new Map(previousState?.flagOverrides ?? []);
|
|
304
304
|
for (const [name, value] of flagOverrides) {
|
|
305
305
|
const definition = solver.prologFlags.get(name);
|
|
306
|
-
if (definition?.changeable
|
|
307
|
-
definition.value = engine.atom(value);
|
|
308
|
-
}
|
|
306
|
+
if (definition?.changeable) definition.value = value;
|
|
309
307
|
}
|
|
310
308
|
return { program: solver.program, solver, strictIso, flagOverrides };
|
|
311
309
|
}
|
|
312
310
|
|
|
313
311
|
function snapshotFlagValues(solver) {
|
|
314
|
-
return new Map([...solver.prologFlags].map(([name, definition]) => [name, definition.value
|
|
312
|
+
return new Map([...solver.prologFlags].map(([name, definition]) => [name, definition.value]));
|
|
315
313
|
}
|
|
316
314
|
|
|
317
315
|
function rememberFlagOverrides(state, before) {
|
|
318
316
|
for (const [name, definition] of state.solver.prologFlags) {
|
|
319
|
-
const value = definition.value
|
|
317
|
+
const value = definition.value;
|
|
320
318
|
if (before.get(name) !== value) state.flagOverrides.set(name, value);
|
|
321
319
|
}
|
|
322
320
|
}
|
|
@@ -720,6 +718,7 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
720
718
|
const valueMaxPriority = equality == null
|
|
721
719
|
? 699
|
|
722
720
|
: equality.specifier === 'xfy' ? equality.priority : equality.priority - 1;
|
|
721
|
+
const writeOptions = answerTermWriteOptions(state, env);
|
|
723
722
|
let generated = 0;
|
|
724
723
|
|
|
725
724
|
// Meta-predicate wrappers can make a query variable alias an internal fresh
|
|
@@ -734,7 +733,7 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
734
733
|
if (value.type === 'var' &&
|
|
735
734
|
(value.name === variable.name || !queryVariableNames.has(value.name))) continue;
|
|
736
735
|
bindings.push(`${variable.name} = ${engine.formatTermForWrite(value, env, {
|
|
737
|
-
|
|
736
|
+
...writeOptions,
|
|
738
737
|
operators,
|
|
739
738
|
variableNames: names,
|
|
740
739
|
maxPriority: valueMaxPriority,
|
|
@@ -743,7 +742,6 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
743
742
|
// quotes, just as writeq/1 already prints them.
|
|
744
743
|
operatorAtomsAsArgs: true,
|
|
745
744
|
dottedGraphicAtoms: true,
|
|
746
|
-
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
747
745
|
})}`);
|
|
748
746
|
}
|
|
749
747
|
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
@@ -751,12 +749,11 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
751
749
|
if (residual == null) continue;
|
|
752
750
|
collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
|
|
753
751
|
bindings.push(engine.formatTermForWrite(residual, env, {
|
|
754
|
-
|
|
752
|
+
...writeOptions,
|
|
755
753
|
operators,
|
|
756
754
|
variableNames: names,
|
|
757
755
|
operatorAtomsAsArgs: true,
|
|
758
756
|
dottedGraphicAtoms: true,
|
|
759
|
-
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
760
757
|
}));
|
|
761
758
|
}
|
|
762
759
|
const projectedAttributeRoots = new Set();
|
|
@@ -768,18 +765,37 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
768
765
|
for (const residual of state.solver.attributeResidualGoals(root, env)) {
|
|
769
766
|
collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
|
|
770
767
|
bindings.push(engine.formatTermForWrite(residual, env, {
|
|
771
|
-
|
|
768
|
+
...writeOptions,
|
|
772
769
|
operators,
|
|
773
770
|
variableNames: names,
|
|
774
771
|
operatorAtomsAsArgs: true,
|
|
775
772
|
dottedGraphicAtoms: true,
|
|
776
|
-
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
777
773
|
}));
|
|
778
774
|
}
|
|
779
775
|
}
|
|
780
776
|
return bindings.length === 0 ? 'true' : bindings.join(', ');
|
|
781
777
|
}
|
|
782
778
|
|
|
779
|
+
function answerTermWriteOptions(state, env) {
|
|
780
|
+
const doubleQuotes = state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars';
|
|
781
|
+
const initial = {
|
|
782
|
+
quoted: true,
|
|
783
|
+
ignoreOps: false,
|
|
784
|
+
numbervars: true,
|
|
785
|
+
variableNames: new Map(),
|
|
786
|
+
compact: false,
|
|
787
|
+
minimalOperatorSpacing: false,
|
|
788
|
+
operatorAtomsAsArgs: true,
|
|
789
|
+
doubleQuotes,
|
|
790
|
+
};
|
|
791
|
+
const optionTerm = state.solver.prologFlags.get('answer_write_options')?.value;
|
|
792
|
+
if (optionTerm == null) return initial;
|
|
793
|
+
const options = parseTermWriteOptions(optionTerm, env, 'write_term', state.solver, initial);
|
|
794
|
+
if (options.doubleQuotes === true) options.doubleQuotes = doubleQuotes;
|
|
795
|
+
else if (options.doubleQuotes === false) options.doubleQuotes = null;
|
|
796
|
+
return options;
|
|
797
|
+
}
|
|
798
|
+
|
|
783
799
|
function collectUnboundVariables(engine, term, env, names, nextName) {
|
|
784
800
|
const stack = [term];
|
|
785
801
|
const seen = new Set();
|
package/src/solver.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Depth-first EyeProlog solver with builtin dispatch, memoization, and guarded recursion handling.
|
|
2
2
|
// Most semantic decisions still flow through unification; optimizations only select candidates earlier.
|
|
3
3
|
import {
|
|
4
|
-
ATOM, COMPOUND, NUMBER, STRING, VAR, Env, compactListLength, compactVariableList, compound, cons, copyResolved, deref, emptyList,
|
|
4
|
+
ATOM, COMPOUND, NUMBER, STRING, VAR, Env, atom, compactListLength, compactVariableList, compound, cons, copyResolved, deref, emptyList,
|
|
5
5
|
flattenConjunction, freshTerm, isCons, isDecimalInteger, isEmptyList,
|
|
6
|
-
numberTerm, numberTextFromDouble, properListItems, termIsGround, termToString, unify, variable, variantTerms,
|
|
6
|
+
listFromItems, numberTerm, numberTextFromDouble, properListItems, termIsGround, termToString, unify, variable, variantTerms,
|
|
7
7
|
} from './term.js';
|
|
8
8
|
import { numberValueKey, sameNumberValue } from './number-value.js';
|
|
9
|
-
import { PrologError, getStrictIsoRegistry } from './iso.js';
|
|
9
|
+
import { PrologError, getStrictIsoRegistry, parseTermWriteOptions } from './iso.js';
|
|
10
10
|
import { getEyePrologRegistry } from './standard-library.js';
|
|
11
11
|
import { selectClauseCandidates, selectClauseCandidatesForValues, selectGroundClauseCandidates } from './program-indexing.js';
|
|
12
12
|
import { StreamManager } from './io.js';
|
|
@@ -144,15 +144,7 @@ export class Solver {
|
|
|
144
144
|
this.prologFlags.get('double_quotes').value = compound(program.doubleQuotes, []);
|
|
145
145
|
}
|
|
146
146
|
for (const [flag, value] of program.prologFlagDirectives ?? []) {
|
|
147
|
-
|
|
148
|
-
if (flag.type !== 'atom') throw new PrologError('type_error(atom)', flag);
|
|
149
|
-
const definition = this.prologFlags.get(flag.name);
|
|
150
|
-
if (!definition) throw new PrologError('domain_error(prolog_flag)', flag);
|
|
151
|
-
if (value.type !== 'atom' || !definition.allowed.includes(value.name)) {
|
|
152
|
-
throw new PrologError('domain_error(flag_value)', compound('+', [flag, value]));
|
|
153
|
-
}
|
|
154
|
-
if (!definition.changeable) throw new PrologError('permission_error(modify, flag)', flag);
|
|
155
|
-
definition.value = value;
|
|
147
|
+
this.setPrologFlag(flag, value, new Env());
|
|
156
148
|
}
|
|
157
149
|
}
|
|
158
150
|
if (!options.charConversions) {
|
|
@@ -206,6 +198,35 @@ export class Solver {
|
|
|
206
198
|
};
|
|
207
199
|
}
|
|
208
200
|
|
|
201
|
+
setPrologFlag(flagTerm, valueTerm, env = new Env()) {
|
|
202
|
+
const flag = deref(flagTerm, env);
|
|
203
|
+
const value = deref(valueTerm, env);
|
|
204
|
+
if (flag.type === VAR || value.type === VAR) throw new PrologError('instantiation_error');
|
|
205
|
+
if (flag.type !== ATOM) throw new PrologError('type_error(atom)', flag);
|
|
206
|
+
const definition = this.prologFlags.get(flag.name);
|
|
207
|
+
if (!definition) throw new PrologError('domain_error(prolog_flag)', flag);
|
|
208
|
+
|
|
209
|
+
if (definition.writeOptions === true) {
|
|
210
|
+
if (!termIsGround(value, env)) throw new PrologError('instantiation_error');
|
|
211
|
+
try {
|
|
212
|
+
parseTermWriteOptions(value, env, 'write_term', this);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (error instanceof PrologError && error.formal === 'instantiation_error') throw error;
|
|
215
|
+
throw new PrologError('domain_error(flag_value)', compound('+', [flag, copyResolved(value, env)]));
|
|
216
|
+
}
|
|
217
|
+
if (!definition.changeable) throw new PrologError('permission_error(modify, flag)', flag);
|
|
218
|
+
definition.value = copyResolved(value, env);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const expectedType = definition.valueType ?? ATOM;
|
|
223
|
+
if (value.type !== expectedType || !definition.allowed.includes(value.name)) {
|
|
224
|
+
throw new PrologError('domain_error(flag_value)', compound('+', [flag, value]));
|
|
225
|
+
}
|
|
226
|
+
if (!definition.changeable) throw new PrologError('permission_error(modify, flag)', flag);
|
|
227
|
+
definition.value = atom(value.name);
|
|
228
|
+
}
|
|
229
|
+
|
|
209
230
|
cloneForInnerGoal(solutionLimit = this.solutionLimit, options = {}) {
|
|
210
231
|
const solver = new Solver(this.program, {
|
|
211
232
|
registry: this.registry,
|
|
@@ -1072,6 +1093,12 @@ function defaultPrologFlags(unknown = 'error', strictIso = false) {
|
|
|
1072
1093
|
['unknown', { value: compound(unknown, []), allowed: ['error', 'fail', 'warning'], changeable: true }],
|
|
1073
1094
|
['double_quotes', { value: compound('chars', []), allowed: ['chars', 'codes', 'atom'], changeable: true }],
|
|
1074
1095
|
['occurs_check', { value: compound('true', []), allowed: ['true', 'error'], changeable: true }],
|
|
1096
|
+
['answer_write_options', {
|
|
1097
|
+
value: listFromItems([compound('spacing', [compound('standard', [])])]),
|
|
1098
|
+
allowed: [],
|
|
1099
|
+
changeable: true,
|
|
1100
|
+
writeOptions: true,
|
|
1101
|
+
}],
|
|
1075
1102
|
]);
|
|
1076
1103
|
if (strictIso) flags.delete('occurs_check');
|
|
1077
1104
|
return flags;
|
|
@@ -97,8 +97,8 @@ required permission error. `read_term/3`, `write_term/3`, `op/3`, and
|
|
|
97
97
|
EyeProlog's chosen simultaneous-error behavior stable. Section 7.12 makes that
|
|
98
98
|
choice implementation dependent when several error conditions hold at once. Strict `write_term/2-3` accepts only
|
|
99
99
|
the Part 1 plus Corrigendum 3 option surface; the normal-profile
|
|
100
|
-
`double_quotes/1` write
|
|
101
|
-
|
|
100
|
+
`double_quotes/1` and `spacing/1` write options remain explicitly documented
|
|
101
|
+
EyeProlog extensions.
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
The preparation, stream, conversion, sorting, and arithmetic audit expands the coverage into Prolog-text,
|
|
@@ -120,11 +120,11 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
120
120
|
| 5.5.5 Directives | `module/2`, `use_module/1-2`, `meta_predicate/1` and normal-profile library behavior | Rejected as implementation-specific Part 1 directives. |
|
|
121
121
|
| 5.5.6 Side effects | Normal mode adds `statistics/0-2`, cleanup/library state, and optional proof/statistics host instrumentation. | The strict registry excludes Prolog-visible statistics/cleanup/library adapters; ordinary Part 1 I/O/database/flag/operator side effects remain. Host proof/statistics collection observes execution through the embedding API rather than adding a strict Prolog goal effect. **covered** — strict registry/execution regression. |
|
|
122
122
|
| 5.5.7 Control constructs | `tnot/1` and normal-profile execution optimizations | `tnot/1` is absent; automatic tabling/recursion guards are disabled. |
|
|
123
|
-
| 5.5.8 Flags | `occurs_check` |
|
|
123
|
+
| 5.5.8 Flags | `occurs_check`; `answer_write_options`, whose normal-mode default is `[spacing(standard)]` and whose value controls REPL answer rendering | Both extension flags are absent in strict mode. |
|
|
124
124
|
| 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and interop autoloaded predicates | Strict registry contains only the Part 1 + Corrigenda core registry. |
|
|
125
125
|
| 5.5.10 Evaluable functors | Normal mode additionally accepts the EyeProlog evaluable atom `e`; the remaining arithmetic functors accepted by strict mode are the Part 1 + Corrigenda set. | Strict mode rejects `e/0` as non-evaluable and retains the Corrigendum arithmetic additions. **covered** — strict extension-boundary regression plus `src/iso-arithmetic.js`. |
|
|
126
126
|
| 5.5.11 Reserved atoms | None | None. |
|
|
127
|
-
| Cor.3 5.5.12 Options | Extra library/host options may exist outside core option lists | Normal mode additionally accepts the EyeProlog `write_term/2-3`
|
|
127
|
+
| Cor.3 5.5.12 Options | Extra library/host options may exist outside core option lists | Normal mode additionally accepts the EyeProlog `write_term/2-3` options `double_quotes(true|false)` and `spacing(standard|minimal)`. Strict core excludes those extensions and accepts only the Part 1 plus Corrigendum 3 write-option surface; unknown extension options raise `domain_error(write_option,...)`. |
|
|
128
128
|
|
|
129
129
|
Normal mode provides documented module and DCG compatibility profiles whose
|
|
130
130
|
features overlap standardized Part 2 and Part 3 facilities. They are extensions
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
answer(7, red, caught(eyeprolog), first, caught(eyeprolog)).
|
|
2
2
|
answer(7, red, caught(eyeprolog), second, caught(eyeprolog)).
|
|
3
|
-
flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true)]).
|
|
3
|
+
flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true), pair(answer_write_options, [spacing(standard)])]).
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -41,6 +41,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
41
41
|
reporter.test('exposes only ISO core Prolog flags', () => {
|
|
42
42
|
const solver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
|
|
43
43
|
equal(solver.prologFlags.has('occurs_check'), false, 'occurs_check');
|
|
44
|
+
equal(solver.prologFlags.has('answer_write_options'), false, 'answer_write_options');
|
|
44
45
|
equal(solver.prologFlags.get('unknown')?.value?.name, 'error', 'unknown default');
|
|
45
46
|
});
|
|
46
47
|
|
|
@@ -283,8 +284,16 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
283
284
|
}));
|
|
284
285
|
equal(strictError.formal, 'domain_error(write_option)', 'strict extension rejection');
|
|
285
286
|
|
|
287
|
+
const strictSpacingError = capture(() => run('', {
|
|
288
|
+
isoStrict: true,
|
|
289
|
+
goal: 'write_term(1*1,[spacing(minimal)])',
|
|
290
|
+
}));
|
|
291
|
+
equal(strictSpacingError.formal, 'domain_error(write_option)', 'strict spacing extension rejection');
|
|
292
|
+
|
|
286
293
|
const normal = run('', { goal: 'write_term("ab",[double_quotes(true)])' });
|
|
287
294
|
includes(normal.stdout, '"ab"', 'normal-profile extension remains available');
|
|
295
|
+
includes(run('', { goal: 'write_term(1*1,[spacing(standard)])' }).stdout,
|
|
296
|
+
'1 * 1', 'normal-profile spacing extension remains available');
|
|
288
297
|
});
|
|
289
298
|
|
|
290
299
|
reporter.test('follows Corrigendum 3 variable metadata traversal and write naming', () => {
|
package/test/run-regression.mjs
CHANGED
|
@@ -3881,6 +3881,70 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
3881
3881
|
assertEqual(run(source, { goal: 'emit' }).stdout, '1+2|a+ -b|a+b*c\nemit.\n', 'minimal operator spacing');
|
|
3882
3882
|
},
|
|
3883
3883
|
},
|
|
3884
|
+
{
|
|
3885
|
+
name: 'answer write options make operator spacing explicit and persistent (issue #76)',
|
|
3886
|
+
run: () => {
|
|
3887
|
+
const source = [
|
|
3888
|
+
'emit :-',
|
|
3889
|
+
' write_term(1*1, [spacing(standard)]), put_char(\'|\'),',
|
|
3890
|
+
' write_term(1*1, [spacing(minimal)]), put_char(\'|\'),',
|
|
3891
|
+
' write_term(1*1, [spacing(standard),spacing(minimal)]).',
|
|
3892
|
+
'',
|
|
3893
|
+
].join('\n');
|
|
3894
|
+
assertEqual(run(source, { goal: 'emit' }).stdout, '1 * 1|1*1|1*1emit.\n',
|
|
3895
|
+
'explicit and rightmost spacing options');
|
|
3896
|
+
assertEqual(run([
|
|
3897
|
+
':- set_prolog_flag(answer_write_options, [spacing(minimal)]).',
|
|
3898
|
+
'emit :- current_prolog_flag(answer_write_options, [spacing(minimal)]).',
|
|
3899
|
+
], { goal: 'emit' }).stdout, 'emit.\n', 'source flag directive');
|
|
3900
|
+
|
|
3901
|
+
const result = runCli([], {
|
|
3902
|
+
input:
|
|
3903
|
+
'current_prolog_flag(answer_write_options, Os).\n' +
|
|
3904
|
+
'X=1*1.\n' +
|
|
3905
|
+
'set_prolog_flag(answer_write_options,[spacing(minimal)]).\n' +
|
|
3906
|
+
'use_module(library(lists)).\n' +
|
|
3907
|
+
'X=1*1.\n' +
|
|
3908
|
+
'current_prolog_flag(answer_write_options, Os).\n' +
|
|
3909
|
+
'halt.\n',
|
|
3910
|
+
});
|
|
3911
|
+
assertEqual(result.status, 0, 'exit status');
|
|
3912
|
+
assertEqual(result.stdout,
|
|
3913
|
+
'?- Os = [spacing(standard)].\n' +
|
|
3914
|
+
'?- X = 1 * 1.\n' +
|
|
3915
|
+
'?- true.\n' +
|
|
3916
|
+
'?- true.\n' +
|
|
3917
|
+
'?- X = 1*1.\n' +
|
|
3918
|
+
'?- Os = [spacing(minimal)].\n' +
|
|
3919
|
+
'?- ',
|
|
3920
|
+
'discoverable top-level option and persistence across module reload');
|
|
3921
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
3922
|
+
|
|
3923
|
+
let writeOptionError = null;
|
|
3924
|
+
try {
|
|
3925
|
+
run('', { goal: 'write_term(a,[spacing(other)])' });
|
|
3926
|
+
} catch (error) {
|
|
3927
|
+
writeOptionError = error;
|
|
3928
|
+
}
|
|
3929
|
+
assertEqual(writeOptionError?.formal, 'domain_error(write_option)', 'spacing value domain');
|
|
3930
|
+
|
|
3931
|
+
let flagValueError = null;
|
|
3932
|
+
try {
|
|
3933
|
+
run('', { goal: 'set_prolog_flag(answer_write_options,[spacing(other)])' });
|
|
3934
|
+
} catch (error) {
|
|
3935
|
+
flagValueError = error;
|
|
3936
|
+
}
|
|
3937
|
+
assertEqual(flagValueError?.formal, 'domain_error(flag_value)', 'answer option flag domain');
|
|
3938
|
+
|
|
3939
|
+
let nongroundFlagError = null;
|
|
3940
|
+
try {
|
|
3941
|
+
run('', { goal: 'set_prolog_flag(answer_write_options,[spacing(X)])' });
|
|
3942
|
+
} catch (error) {
|
|
3943
|
+
nongroundFlagError = error;
|
|
3944
|
+
}
|
|
3945
|
+
assertEqual(nongroundFlagError?.formal, 'instantiation_error', 'answer options must be ground');
|
|
3946
|
+
},
|
|
3947
|
+
},
|
|
3884
3948
|
{
|
|
3885
3949
|
name: 'write predicates and write_term options select distinct formats',
|
|
3886
3950
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5426,8 +5426,10 @@ still retained where adjacent graphic tokens would otherwise merge, as in
|
|
|
5426
5426
|
`write_term/2-3` supports `quoted/1`, `ignore_ops/1`, `numbervars/1`, and
|
|
5427
5427
|
`variable_names/1`. Normal mode additionally accepts the EyeProlog extension
|
|
5428
5428
|
`double_quotes(true|false)`: `true` lets eligible character/code lists use the
|
|
5429
|
-
current `double_quotes` representation
|
|
5430
|
-
|
|
5429
|
+
current `double_quotes` representation. It also accepts
|
|
5430
|
+
`spacing(standard|minimal)`: `standard` places layout around operators, while
|
|
5431
|
+
`minimal` emits only the separators required to avoid lexical ambiguity.
|
|
5432
|
+
Strict ISO mode rejects both non-standard write options.
|
|
5431
5433
|
|
|
5432
5434
|
Character operations are `get_char`, `peek_char`, `put_char`, `get_code`,
|
|
5433
5435
|
`peek_code`, and `put_code`; byte streams use the corresponding byte
|
|
@@ -6000,7 +6002,7 @@ silently changing a static program.
|
|
|
6000
6002
|
| `char_conversion(+Input,+Output)` | Installs a one-character conversion. In prepared Prolog text, later **unquoted** characters are converted when `char_conversion=on`; quoted characters are unchanged. The same mapping initializes execution-time term input. Mapping a character to itself removes its custom mapping. |
|
|
6001
6003
|
| `current_char_conversion(?Input,?Output)` | Enumerates installed nonidentity conversions. |
|
|
6002
6004
|
| `current_prolog_flag(?Flag,?Value)` | Enumerates flags or retrieves one named flag. An unknown bound flag raises *domain_error(prolog_flag)*. |
|
|
6003
|
-
| `set_prolog_flag(+Flag,+Value)` | Changes a supported mutable flag after validating its
|
|
6005
|
+
| `set_prolog_flag(+Flag,+Value)` | Changes a supported mutable flag after validating its value. Read-only flags raise a permission error. |
|
|
6004
6006
|
|
|
6005
6007
|
| Flag | Default in normal EyeProlog | Allowed values | Mutable |
|
|
6006
6008
|
| --- | --- | --- | --- |
|
|
@@ -6014,6 +6016,7 @@ silently changing a static program.
|
|
|
6014
6016
|
| `unknown` | `error` | `error`, `fail`, `warning` | yes |
|
|
6015
6017
|
| `double_quotes` | `chars` | `chars`, `codes`, `atom` | yes |
|
|
6016
6018
|
| `occurs_check` | `true` | `true`, `error` | yes |
|
|
6019
|
+
| `answer_write_options` | `[spacing(standard)]` | A ground proper list of supported normal-mode `write_term/2-3` options | yes |
|
|
6017
6020
|
|
|
6018
6021
|
Because `bounded=false`, `current_prolog_flag(max_integer, _)` and
|
|
6019
6022
|
`current_prolog_flag(min_integer, _)` fail as required by ISO 7.11.1.1;
|
|
@@ -6048,6 +6051,24 @@ codes("ab"). % codes([97,98])
|
|
|
6048
6051
|
quoted_atom("ab"). % quoted_atom(ab)
|
|
6049
6052
|
```
|
|
6050
6053
|
|
|
6054
|
+
Normal mode also exposes the REPL's term-writing options through the mutable
|
|
6055
|
+
`answer_write_options` flag. Its explicit default explains the spaces in an
|
|
6056
|
+
answer such as `X = 1 * 1`; selecting minimal spacing is discoverable and
|
|
6057
|
+
reusable from Prolog:
|
|
6058
|
+
|
|
6059
|
+
```text
|
|
6060
|
+
?- current_prolog_flag(answer_write_options, Options).
|
|
6061
|
+
Options = [spacing(standard)].
|
|
6062
|
+
?- set_prolog_flag(answer_write_options, [spacing(minimal)]).
|
|
6063
|
+
true.
|
|
6064
|
+
?- X = 1*1.
|
|
6065
|
+
X = 1*1.
|
|
6066
|
+
```
|
|
6067
|
+
|
|
6068
|
+
The setting survives interactive `consult/1` and `use_module/1-2` program
|
|
6069
|
+
rebuilds. `answer_write_options` is a normal-mode host flag and is absent in
|
|
6070
|
+
strict ISO mode.
|
|
6071
|
+
|
|
6051
6072
|
### Atomic-term operations and conversions
|
|
6052
6073
|
|
|
6053
6074
|
| Predicate and principal call | Behavior |
|
|
@@ -6120,7 +6141,7 @@ the beginning. A peek does not mark the stream as past-end.
|
|
|
6120
6141
|
| `write(+Term)`, `write(+Stream,+Term)` | Writes readable operator notation without quoting atoms merely because quoting would be required for reparsing. Number variables are enabled. |
|
|
6121
6142
|
| `writeq(+Term)`, `writeq(+Stream,+Term)` | Like `write`, but quotes atoms when required for unambiguous input syntax. |
|
|
6122
6143
|
| `write_canonical(+Term)`, `write_canonical(+Stream,+Term)` | Writes quoted canonical functor notation while ignoring operators and without interpreting *$VAR/1*. |
|
|
6123
|
-
| `write_term(+Term,+Options)`, `write_term(+Stream,+Term,+Options)` | Writes with *quoted(true or false)*, *ignore_ops(true or false)*, *numbervars(true or false)*, and *variable_names([Name=Variable,...])*. |
|
|
6144
|
+
| `write_term(+Term,+Options)`, `write_term(+Stream,+Term,+Options)` | Writes with *quoted(true or false)*, *ignore_ops(true or false)*, *numbervars(true or false)*, and *variable_names([Name=Variable,...])*. Normal mode also supports *double_quotes(true or false)* and *spacing(standard or minimal)*. |
|
|
6124
6145
|
|
|
6125
6146
|
Term input uses the program's current operator table and the same ISO quoted-character
|
|
6126
6147
|
syntax as source text, including backslash-terminated octal and hexadecimal
|
|
@@ -7765,8 +7786,8 @@ Notable implementation boundaries are:
|
|
|
7765
7786
|
- `write_term/2-3` implements the Part 1 plus Corrigendum 3 `quoted/1`,
|
|
7766
7787
|
`ignore_ops/1`, `numbervars/1`, and `variable_names/1` option surface,
|
|
7767
7788
|
including option validation and traversal rules; normal mode also offers
|
|
7768
|
-
`double_quotes(true|false)`
|
|
7769
|
-
strict mode rejects;
|
|
7789
|
+
`double_quotes(true|false)` and `spacing(standard|minimal)` as explicitly
|
|
7790
|
+
non-standard extensions, which strict mode rejects;
|
|
7770
7791
|
- unification consistently performs an occurs check, rejecting rational-tree
|
|
7771
7792
|
bindings accepted as extensions by some systems.
|
|
7772
7793
|
|