eyeprolog 1.1.28 → 1.2.0

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/README.md CHANGED
@@ -79,6 +79,27 @@ member_test ?- member(X, [prolog, logic]).
79
79
  ; X = logic.
80
80
  ```
81
81
 
82
+ ## Strict ISO/IEC 13211-1 core
83
+
84
+ For portability and conformance work, run the Part 1 core with Technical
85
+ Corrigenda 1–3 in strict mode:
86
+
87
+ ```sh
88
+ eyeprolog --iso-strict
89
+ eyeprolog --iso-strict --goal 'p(X)' program.pl
90
+ ```
91
+
92
+ The equivalent JavaScript option is `isoStrict: true`. Strict mode rejects
93
+ EyeProlog language extensions, Part 2 module directives, and Part 3 grammar-rule
94
+ expansion/`phrase/2-3`; it also removes the EyeProlog `occurs_check` flag and
95
+ disables automatic tabling. Normal mode is unchanged and continues to support
96
+ modules, DCGs, quads, libraries, proofs, and the other documented extensions.
97
+
98
+ The auditable processor-requirement checklist lives in
99
+ [`test/conformance/ISO-COMPLIANCE.md`](test/conformance/ISO-COMPLIANCE.md).
100
+ EyeProlog does not yet claim independent certification or closure of every
101
+ normative Part 1 requirement.
102
+
82
103
  ## ISO modules and definite clause grammars
83
104
 
84
105
  EyeProlog implements ISO/IEC 13211-2 modules and the grammar rules and
@@ -11,7 +11,7 @@ This report summarizes the file-based conformance corpus under `test/conformance
11
11
  | builtins | 11 | 0 | 0 | 0 | 11 |
12
12
  | context | 11 | 0 | 0 | 0 | 11 |
13
13
  | control | 15 | 0 | 0 | 0 | 15 |
14
- | iso | 166 | 209 | 0 | 0 | 375 |
14
+ | iso | 167 | 209 | 0 | 0 | 376 |
15
15
  | lists | 52 | 3 | 0 | 0 | 55 |
16
16
  | modules | 2 | 0 | 0 | 0 | 2 |
17
17
  | negation | 8 | 0 | 19 | 0 | 27 |
@@ -23,4 +23,4 @@ This report summarizes the file-based conformance corpus under `test/conformance
23
23
  | terms | 26 | 3 | 0 | 0 | 29 |
24
24
  | unification | 18 | 0 | 0 | 0 | 18 |
25
25
  | variables | 16 | 9 | 0 | 0 | 25 |
26
- | **Total** | **481** | **260** | **19** | **21** | **781** |
26
+ | **Total** | **482** | **260** | **19** | **21** | **782** |
package/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export interface EyePrologRunOptions {
17
17
  sourceMetadata?: boolean;
18
18
  strictNegation?: boolean;
19
19
  analyzeNegation?: boolean;
20
+ /** Restrict parsing and execution to ISO/IEC 13211-1:1995 plus Corrigenda 1-3. */
21
+ isoStrict?: boolean;
20
22
  /** Initial ISO interpretation of double-quoted list notation. Defaults to chars. */
21
23
  doubleQuotes?: 'chars' | 'codes' | 'atom';
22
24
  ioOptions?: {
@@ -120,6 +122,7 @@ export class Program {
120
122
  moduleImports: Map<string, Map<string, string>>;
121
123
  quads: EyePrologQuad[];
122
124
  doubleQuotes: 'chars' | 'codes' | 'atom';
125
+ strictIso: boolean;
123
126
  negationDependencies: Array<{ from: string; to: string; negative: boolean }>;
124
127
  negationStratificationErrors: Array<{ from: string; to: string }>;
125
128
  stratifiedNegation: boolean;
@@ -155,12 +158,14 @@ export class BuiltinRegistry {
155
158
  eyePrologLibrary?: boolean;
156
159
  add(name: string, arity: number, handler: BuiltinHandler, options?: Partial<BuiltinDefinition>): this;
157
160
  get(name: string, arity: number): BuiltinDefinition | null;
161
+ remove(name: string, arity: number): this;
158
162
  }
159
163
 
160
164
  export class Solver {
161
165
  constructor(program: Program, options?: EyePrologRunOptions);
162
166
  program: Program;
163
167
  registry: BuiltinRegistry;
168
+ isoStrict: boolean;
164
169
  maxDepth: number;
165
170
  depthLimitExceeded: boolean;
166
171
  maxInferences: number;
@@ -219,8 +224,10 @@ export function parseClauses(source: string, options?: EyePrologRunOptions): Arr
219
224
  export function parseProgramText(source: string, options?: EyePrologRunOptions): Array<EyePrologClause | EyePrologQuad>;
220
225
  export function parseGoalText(source: string, options?: EyePrologRunOptions): EyePrologTerm;
221
226
  export function createDefaultRegistry(): BuiltinRegistry;
227
+ export function createStrictIsoRegistry(): BuiltinRegistry;
222
228
  export function createEyePrologRegistry(): BuiltinRegistry;
223
229
  export function getDefaultRegistry(): BuiltinRegistry;
230
+ export function getStrictIsoRegistry(): BuiltinRegistry;
224
231
  export function getEyePrologRegistry(): BuiltinRegistry;
225
232
  export const standardLibrarySources: ReadonlyMap<string, { filename: string; source: string }>;
226
233
  export const eyePrologLibraryIndicators: readonly string[];
@@ -291,8 +298,10 @@ declare const eyeprolog: {
291
298
  parseGoalText: typeof parseGoalText;
292
299
  parseProgramText: typeof parseProgramText;
293
300
  createDefaultRegistry: typeof createDefaultRegistry;
301
+ createStrictIsoRegistry: typeof createStrictIsoRegistry;
294
302
  createEyePrologRegistry: typeof createEyePrologRegistry;
295
303
  getDefaultRegistry: typeof getDefaultRegistry;
304
+ getStrictIsoRegistry: typeof getStrictIsoRegistry;
296
305
  getEyePrologRegistry: typeof getEyePrologRegistry;
297
306
  standardLibrarySources: typeof standardLibrarySources;
298
307
  eyePrologLibraryIndicators: typeof eyePrologLibraryIndicators;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.28",
6
+ "version": "1.2.0",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -49,6 +49,7 @@
49
49
  "generate": "node tools/extract-book-examples.mjs",
50
50
  "test:eyeprolog": "node test/run-all.mjs",
51
51
  "test:conformance": "node test/run-conformance.mjs",
52
+ "test:iso-strict": "node test/run-iso-strict.mjs",
52
53
  "test:examples": "node test/run-examples.mjs",
53
54
  "test:regression": "node test/run-regression.mjs",
54
55
  "test:playground": "node test/run-playground.mjs",
package/src/cli.js CHANGED
@@ -26,6 +26,7 @@ export async function main(argv) {
26
26
  proof: false,
27
27
  quads: false,
28
28
  stats: false,
29
+ isoStrict: false,
29
30
  version: false,
30
31
  warnings: false,
31
32
  goals: [],
@@ -47,6 +48,8 @@ export async function main(argv) {
47
48
  options.quads = true;
48
49
  } else if (!endOptions && (arg === '--stats' || arg === '-s')) {
49
50
  options.stats = true;
51
+ } else if (!endOptions && arg === '--iso-strict') {
52
+ options.isoStrict = true;
50
53
  } else if (!endOptions && (arg === '--version' || arg === '-v')) {
51
54
  options.version = true;
52
55
  } else if (!endOptions && (arg === '--warnings' || arg === '-w')) {
@@ -81,6 +84,24 @@ export async function main(argv) {
81
84
  return;
82
85
  }
83
86
 
87
+ if (options.isoStrict && options.quads) {
88
+ throw new Error('--iso-strict cannot be combined with --quads');
89
+ }
90
+
91
+ if (options.isoStrict && options.files.length === 0 && options.goals.length === 0 &&
92
+ !options.proof && !options.stats && !options.warnings) {
93
+ const engine = await loadEngine();
94
+ const { runRepl } = await import('./repl.js');
95
+ const exitCode = await runRepl(engine, {
96
+ input: process.stdin,
97
+ output: process.stdout,
98
+ errorOutput: process.stderr,
99
+ isoStrict: true,
100
+ });
101
+ if (exitCode !== 0) process.exitCode = exitCode;
102
+ return;
103
+ }
104
+
84
105
  if (options.files.length === 0) {
85
106
  options.files.push('-');
86
107
  }
@@ -121,7 +142,10 @@ export async function main(argv) {
121
142
  }
122
143
 
123
144
  const engine = await loadEngine();
124
- let program = engine.Program.parseSources(sourceParts, { sourceMetadata: options.proof });
145
+ let program = engine.Program.parseSources(sourceParts, {
146
+ sourceMetadata: options.proof || options.isoStrict,
147
+ isoStrict: options.isoStrict,
148
+ });
125
149
 
126
150
  if (options.warnings) printWarnings(program);
127
151
 
@@ -156,15 +180,18 @@ async function loadExplanation() {
156
180
  }
157
181
 
158
182
  async function runDefault(engine, program, options) {
159
- const registry = engine.getEyePrologRegistry();
183
+ const registry = options.isoStrict ? engine.getStrictIsoRegistry() : engine.getEyePrologRegistry();
160
184
  const solver = new engine.Solver(program, {
161
185
  registry,
186
+ isoStrict: options.isoStrict,
162
187
  ioOptions: { write: (text) => process.stdout.write(String(text)) },
163
188
  });
164
189
  program = solver.program;
165
190
  const goals = options.goals.map((text) => {
166
191
  const goal = engine.parseGoalText(text, {
167
192
  doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
193
+ operatorDefinitions: [...program.operators.values()],
194
+ isoStrict: options.isoStrict,
168
195
  });
169
196
  if (goal.type === 'var') throw new engine.PrologError('instantiation_error');
170
197
  if (goal.type !== 'atom' && goal.type !== 'compound') throw new engine.PrologError('type_error(callable)', goal);
@@ -233,6 +260,8 @@ Options:
233
260
  -p, --proof Enable proof explanations.
234
261
  -q, --quads Run embedded quad tests and fail if any do not hold.
235
262
  -s, --stats Print solver statistics to stderr after execution.
263
+ --iso-strict Use ISO/IEC 13211-1 core + Corrigenda 1-3 only;
264
+ reject EyeProlog language extensions and disable automatic tabling.
236
265
  -v, --version Show the package version and exit.
237
266
  -w, --warnings Print non-fatal portability warnings to stderr.
238
267
  -g, --goal goal Solve goal and print its ground answers; may be repeated.
package/src/index.js CHANGED
@@ -7,7 +7,9 @@ export * from './term.js';
7
7
  export {
8
8
  BuiltinRegistry,
9
9
  createDefaultRegistry,
10
+ createStrictIsoRegistry,
10
11
  getDefaultRegistry,
12
+ getStrictIsoRegistry,
11
13
  HaltSignal,
12
14
  PrologError,
13
15
  } from './iso.js';
@@ -26,16 +28,23 @@ import { ATOM, COMPOUND, VAR, Env, copyResolved, termIsGround } from './term.js'
26
28
  import { Program } from './program.js';
27
29
  import { Solver } from './solver.js';
28
30
  import { whyNoProof, whyProof } from './explain.js';
29
- import { HaltSignal, PrologError } from './iso.js';
31
+ import { HaltSignal, PrologError, getStrictIsoRegistry } from './iso.js';
30
32
  import { getEyePrologRegistry } from './standard-library.js';
31
33
  import { parseGoalText } from './parser.js';
32
34
  import { formatTermForWrite } from './write.js';
33
35
 
34
36
  export function run(source, options = {}) {
35
37
  const includeWhy = options.proof === true || options.why === true || options.explain === true;
36
- const parseOptions = { ...options, sourceMetadata: includeWhy };
38
+ const requestedStrictIso = options.isoStrict === true;
39
+ if (source instanceof Program && requestedStrictIso && source.strictIso !== true) {
40
+ throw new Error('strict ISO mode requires a Program parsed with isoStrict: true');
41
+ }
42
+ const parseOptions = { ...options, sourceMetadata: includeWhy || requestedStrictIso };
37
43
  let program = source instanceof Program ? source : Program.parse(source, parseOptions);
38
- const runOptions = options.registry ? options : { ...options, registry: getEyePrologRegistry() };
44
+ const strictIso = requestedStrictIso || program.strictIso === true;
45
+ const runOptions = strictIso
46
+ ? { ...options, isoStrict: true, registry: getStrictIsoRegistry() }
47
+ : options.registry ? options : { ...options, registry: getEyePrologRegistry() };
39
48
  const output = [];
40
49
  const solver = new Solver(program, {
41
50
  ...runOptions,
@@ -91,6 +100,8 @@ function normalizeGoals(options, solver) {
91
100
  const goal = typeof requestedGoal === 'string'
92
101
  ? parseGoalText(requestedGoal, {
93
102
  doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
103
+ operatorDefinitions: [...solver.program.operators.values()],
104
+ isoStrict: solver.isoStrict,
94
105
  })
95
106
  : requestedGoal;
96
107
  if (goal.type === VAR) throw new PrologError('instantiation_error');
package/src/iso.js CHANGED
@@ -508,11 +508,18 @@ function* clauseBuiltin({ solver, goal, env }) {
508
508
  if (head.type !== ATOM && head.type !== COMPOUND) throw new PrologError('type_error(callable)', head);
509
509
  callableOrVariable(goal.args[1], env);
510
510
  const indicator = compound('/', [atom(head.name), numberTerm(head.arity)]);
511
- if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head)) {
511
+ if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head)) {
512
512
  throw new PrologError('permission_error(access, private_procedure)', indicator);
513
513
  }
514
514
  const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
515
515
  if (!group) return;
516
+ // ISO 7.5.3 makes dynamic procedures public and static user-defined
517
+ // procedures private by default. EyeProlog's normal profile keeps static
518
+ // clauses inspectable for proof tooling; strict core mode restores the ISO
519
+ // access rule used by clause/2.
520
+ if (solver.isoStrict && !group.dynamic) {
521
+ throw new PrologError('permission_error(access, private_procedure)', indicator);
522
+ }
516
523
  for (const clause of group.clauses) {
517
524
  const pair = compound('$clause', [clause.head, clauseBodyTerm(clause.body)]);
518
525
  const copied = freshCopy(pair, new Env());
@@ -547,13 +554,13 @@ function procedureIndicator(head) {
547
554
  return compound('/', [atom(head.name), numberTerm(head.arity)]);
548
555
  }
549
556
 
550
- function isGrammarRuleProcedure(head) {
551
- return head.name === '-->' && head.arity === 2;
557
+ function isGrammarRuleProcedure(solver, head) {
558
+ return !solver.isoStrict && head.name === '-->' && head.arity === 2;
552
559
  }
553
560
 
554
561
  function assertModifiable(solver, head, module = 'user') {
555
562
  const group = solver.program.findGroup(head.name, head.arity, head.module ?? module);
556
- if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head) || (group && !group.dynamic)) {
563
+ if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head) || (group && !group.dynamic)) {
557
564
  throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
558
565
  }
559
566
  }
@@ -580,7 +587,7 @@ function* retractBuiltin({ solver, goal, env }) {
580
587
  const parts = clauseParts(goal.args[0], env);
581
588
  requireClauseHead(parts.head);
582
589
  const group = solver.program.findGroup(parts.head.name, parts.head.arity, parts.head.module ?? goal.module ?? 'user');
583
- if (solver.registry.get(parts.head.name, parts.head.arity) || isGrammarRuleProcedure(parts.head) || (group && !group.dynamic)) {
590
+ if (solver.registry.get(parts.head.name, parts.head.arity) || isGrammarRuleProcedure(solver, parts.head) || (group && !group.dynamic)) {
584
591
  throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(parts.head));
585
592
  }
586
593
  if (!group) return;
@@ -603,7 +610,7 @@ function* retractAllBuiltin({ solver, goal, env }) {
603
610
  const head = deref(goal.args[0], env);
604
611
  requireClauseHead(head);
605
612
  const group = solver.program.findGroup(head.name, head.arity, head.module ?? goal.module ?? 'user');
606
- if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(head) || (group && !group.dynamic)) {
613
+ if (solver.registry.get(head.name, head.arity) || isGrammarRuleProcedure(solver, head) || (group && !group.dynamic)) {
607
614
  throw new PrologError('permission_error(modify, static_procedure)', procedureIndicator(head));
608
615
  }
609
616
  if (group) {
@@ -637,7 +644,7 @@ function* abolishBuiltin({ solver, goal, env }) {
637
644
  const target = predicateIndicatorParts(goal.args[0], env);
638
645
  const module = goal.module ?? 'user';
639
646
  const group = solver.program.findGroup(target.name, target.arity, module);
640
- if (solver.registry.get(target.name, target.arity) || isGrammarRuleProcedure(target) || (group && !group.dynamic)) {
647
+ if (solver.registry.get(target.name, target.arity) || isGrammarRuleProcedure(solver, target) || (group && !group.dynamic)) {
641
648
  throw new PrologError('permission_error(modify, static_procedure)', target.indicator);
642
649
  }
643
650
  solver.program.abolishDynamicGroup(target.name, target.arity, module);
@@ -2057,6 +2064,11 @@ export class BuiltinRegistry {
2057
2064
  get(name, arity) {
2058
2065
  return this.defs.get(`${name}/${arity}`) ?? null;
2059
2066
  }
2067
+
2068
+ remove(name, arity) {
2069
+ this.defs.delete(`${name}/${arity}`);
2070
+ return this;
2071
+ }
2060
2072
  }
2061
2073
 
2062
2074
  export function createDefaultRegistry() {
@@ -2065,9 +2077,24 @@ export function createDefaultRegistry() {
2065
2077
  return registry;
2066
2078
  }
2067
2079
 
2080
+ // ISO/IEC 13211-1:1995 + Corrigenda 1-3 only. phrase/2-3 and grammar-rule
2081
+ // expansion belong to the separate grammar-rule specification, while the
2082
+ // EyeProlog standard-library/CLP(Z) adapters are registered elsewhere.
2083
+ export function createStrictIsoRegistry() {
2084
+ return createDefaultRegistry()
2085
+ .remove('phrase', 2)
2086
+ .remove('phrase', 3);
2087
+ }
2088
+
2068
2089
  let defaultRegistry = null;
2090
+ let strictIsoRegistry = null;
2069
2091
 
2070
2092
  export function getDefaultRegistry() {
2071
2093
  if (defaultRegistry == null) defaultRegistry = createDefaultRegistry();
2072
2094
  return defaultRegistry;
2073
2095
  }
2096
+
2097
+ export function getStrictIsoRegistry() {
2098
+ if (strictIsoRegistry == null) strictIsoRegistry = createStrictIsoRegistry();
2099
+ return strictIsoRegistry;
2100
+ }
package/src/parser.js CHANGED
@@ -139,12 +139,16 @@ function defineParserOperator(state, priority, specifier, name) {
139
139
  }
140
140
  }
141
141
 
142
- export function createParserOperatorState(definitions = [], includeDefaults = true) {
142
+ export function createParserOperatorState(definitions = [], includeDefaults = true, options = {}) {
143
143
  const state = {
144
144
  infixOperators: includeDefaults ? new Map(INFIX_OPERATORS) : new Map(),
145
145
  prefixOperators: includeDefaults ? new Map(PREFIX_OPERATORS) : new Map(),
146
146
  postfixOperators: new Map(),
147
147
  };
148
+ // The infix ?-/2 form is an EyeProlog quad extension. ISO 13211-1
149
+ // predefines only the 1200 fx ?- operator; strict core mode starts from that
150
+ // table and still permits an explicit op/3 directive to add an infix form.
151
+ if (options.isoStrict === true) state.infixOperators.delete('?-');
148
152
  for (const definition of definitions) {
149
153
  const [priority, specifier, name] = Array.isArray(definition)
150
154
  ? definition
@@ -162,6 +166,7 @@ class Parser {
162
166
  this.line = 1;
163
167
  this.anonymous = 0;
164
168
  this.sourceMetadata = options.sourceMetadata !== false;
169
+ this.strictIso = options.isoStrict === true;
165
170
  this.parserFlagState = options.parserFlagState ?? {
166
171
  doubleQuotes: options.doubleQuotes ?? 'chars',
167
172
  };
@@ -171,6 +176,7 @@ class Parser {
171
176
  const operatorState = options.operatorState ?? createParserOperatorState(
172
177
  options.operatorDefinitions ?? [],
173
178
  options.includeDefaultOperators !== false,
179
+ { isoStrict: this.strictIso },
174
180
  );
175
181
  this.infixOperators = operatorState.infixOperators;
176
182
  this.prefixOperators = operatorState.prefixOperators;
@@ -685,6 +691,19 @@ class Parser {
685
691
  while (this.token.type !== TOK.EOF) {
686
692
  const line = this.token.line;
687
693
  if (this.operatorTokenName() === '?-') {
694
+ if (this.strictIso) {
695
+ // In Part 1, ?- is the predefined 1200 fx operator. A strict-core
696
+ // source therefore reads it as an ordinary term rather than giving
697
+ // it EyeProlog's top-level quad meaning.
698
+ const head = this.parseTerm(0, true);
699
+ this.expect(TOK.DOT, '.');
700
+ this.advance();
701
+ const clause = { head, body: [] };
702
+ clauseNumber++;
703
+ if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: clauseNumber };
704
+ accept(clause);
705
+ continue;
706
+ }
688
707
  this.advance();
689
708
  this.parseQuad(null, line, accept);
690
709
  continue;
@@ -692,13 +711,19 @@ class Parser {
692
711
  if (this.token.type === TOK.IF) {
693
712
  this.advance();
694
713
  const directive = this.parseTerm();
695
- const supportedDirective = directive.type === 'compound' && (
696
- (['dynamic', 'multifile', 'discontiguous', 'initialization', 'include', 'ensure_loaded',
697
- 'use_module', 'meta_predicate'].includes(directive.name) && directive.arity === 1) ||
698
- (['char_conversion', 'set_prolog_flag', 'module', 'use_module'].includes(directive.name) && directive.arity === 2)
714
+ const coreDirective = directive.type === 'compound' && (
715
+ (['dynamic', 'multifile', 'discontiguous', 'initialization', 'include', 'ensure_loaded'].includes(directive.name) && directive.arity === 1) ||
716
+ (['char_conversion', 'set_prolog_flag'].includes(directive.name) && directive.arity === 2)
699
717
  );
718
+ const extensionDirective = directive.type === 'compound' && (
719
+ (['use_module', 'meta_predicate'].includes(directive.name) && directive.arity === 1) ||
720
+ (['module', 'use_module'].includes(directive.name) && directive.arity === 2)
721
+ );
722
+ if (this.strictIso && extensionDirective) {
723
+ throw new Error(`parse line ${line}: implementation-specific directive ${directive.name}/${directive.arity} is not available in strict ISO core mode`);
724
+ }
700
725
  const operator = this.applyOperatorDirective(directive, line);
701
- if (!supportedDirective && !operator) {
726
+ if (!coreDirective && !extensionDirective && !operator) {
702
727
  throw new Error(`parse line ${line}: bad term`);
703
728
  }
704
729
  this.expect(TOK.DOT, '.');
@@ -716,6 +741,22 @@ class Parser {
716
741
  }
717
742
  let head = this.parseTerm(3);
718
743
  if (this.operatorTokenName() === '?-') {
744
+ if (this.strictIso) {
745
+ // There is no predefined infix ?-/2 in strict core mode. If a
746
+ // conforming source explicitly introduced one with op/3, read it as
747
+ // an ordinary operator term rather than as a quad label.
748
+ const info = this.infixOperators.get('?-');
749
+ if (!info) throw new Error(`parse line ${line}: expected ., got ?-`);
750
+ this.advance();
751
+ const right = this.parseTerm(info.associativity === 'right' ? info.precedence : info.precedence + 1, true);
752
+ const clause = { head: compound('?-', [head, right]), body: [] };
753
+ this.expect(TOK.DOT, '.');
754
+ this.advance();
755
+ clauseNumber++;
756
+ if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: clauseNumber };
757
+ accept(clause);
758
+ continue;
759
+ }
719
760
  this.advance();
720
761
  this.parseQuad(head, line, accept);
721
762
  continue;
package/src/program.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  parseClausesInto,
11
11
  tryParseClausesFastInto,
12
12
  } from './parser.js';
13
- import { PrologError } from './iso.js';
13
+ import { PrologError, getStrictIsoRegistry } from './iso.js';
14
14
  import { currentWorkingDirectory, fs, path } from './platform.js';
15
15
  import { standardLibrarySources } from './standard-library.js';
16
16
  import { expandDcgRuleClause } from './dcg.js';
@@ -101,8 +101,12 @@ export class Program {
101
101
  this.moduleImports = new Map();
102
102
  this.moduleMetaPredicates = new Map();
103
103
  this.dynamicPredicates = new Set();
104
+ this.strictIso = options.isoStrict === true;
104
105
  this.operators = new Map();
105
- for (const definitions of [ISO_OPERATOR_DEFINITIONS, QUAD_OPERATOR_DEFINITIONS]) {
106
+ const predefinedOperatorSets = this.strictIso
107
+ ? [ISO_OPERATOR_DEFINITIONS]
108
+ : [ISO_OPERATOR_DEFINITIONS, QUAD_OPERATOR_DEFINITIONS];
109
+ for (const definitions of predefinedOperatorSets) {
106
110
  for (const [priority, specifier, name] of definitions) {
107
111
  this.defineOperator(priority, specifier, name);
108
112
  }
@@ -162,7 +166,7 @@ export class Program {
162
166
  }
163
167
  _indexClause(clause, initialBuild) {
164
168
  const head = clause.head;
165
- if (!initialBuild) assertHeadIsDefinable(head);
169
+ if (!initialBuild) assertHeadIsDefinable(head, this.strictIso);
166
170
  if (head.type !== ATOM && head.type !== COMPOUND) return;
167
171
  const module = clause.module ?? 'user';
168
172
  const key = modulePredicateKey(module, head.name, head.arity);
@@ -229,7 +233,7 @@ export class Program {
229
233
  if (group) group.metaArgumentPositions = positions;
230
234
  }
231
235
  ensureDynamicGroup(name, arity, module = 'user') {
232
- assertPredicateIsDefinable(name, arity);
236
+ assertPredicateIsDefinable(name, arity, this.strictIso);
233
237
  const key = modulePredicateKey(module, name, arity);
234
238
  let group = this.groups.get(key);
235
239
  if (!group) {
@@ -280,7 +284,7 @@ export class Program {
280
284
  noteMutation(reanalyze = false) {
281
285
  this._revisionState.value++;
282
286
  this._negationAnalysis = null;
283
- if (reanalyze) this.markRecursivePredicates();
287
+ if (reanalyze && !this.strictIso) this.markRecursivePredicates();
284
288
  }
285
289
  markRecursivePredicates() {
286
290
  // Recursion analysis drives automatic tabling and is always part of program setup.
@@ -467,7 +471,7 @@ export class Program {
467
471
  class ProgramBuilder {
468
472
  constructor(options = {}, program = null) {
469
473
  this.options = options;
470
- this.program = program ?? new Program([], { [DEFER_PROGRAM_BUILD]: true });
474
+ this.program = program ?? new Program([], { ...options, [DEFER_PROGRAM_BUILD]: true });
471
475
  this.declaredDynamicIndicators = new Map();
472
476
  this.lastGroupKey = null;
473
477
  this.lastGroup = null;
@@ -491,7 +495,7 @@ class ProgramBuilder {
491
495
  program.clauses.push(clause);
492
496
 
493
497
  if (isCompactBinaryClause(clause)) {
494
- assertPredicateIsDefinable(clause.headName, 2);
498
+ assertPredicateIsDefinable(clause.headName, 2, program.strictIso);
495
499
  const module = clause.module ?? 'user';
496
500
  const key = modulePredicateKey(module, clause.headName, 2);
497
501
  let group = key === lastGroupKey ? lastGroup : program.groups.get(key);
@@ -512,7 +516,7 @@ class ProgramBuilder {
512
516
  }
513
517
 
514
518
  if (!isDirectiveClause(clause)) {
515
- assertHeadIsDefinable(clause.head);
519
+ assertHeadIsDefinable(clause.head, program.strictIso);
516
520
  const head = clause.head;
517
521
  if (head.type !== ATOM && head.type !== COMPOUND) continue;
518
522
  const module = clause.module ?? 'user';
@@ -546,7 +550,7 @@ class ProgramBuilder {
546
550
  const program = this.program;
547
551
  const module = clause.module ?? 'user';
548
552
  for (const indicator of dynamicDirectiveIndicators(clause)) {
549
- assertDynamicIndicatorIsDefinable(indicator);
553
+ assertDynamicIndicatorIsDefinable(indicator, program.strictIso);
550
554
  const key = modulePredicateKey(module, indicator.name, indicator.arity);
551
555
  program.dynamicPredicates.add(key);
552
556
  this.declaredDynamicIndicators.set(key, { ...indicator, key, module });
@@ -597,7 +601,11 @@ class ProgramBuilder {
597
601
 
598
602
  // Static indexes are built while clauses stream into the builder. Dynamic
599
603
  // updates still rebuild only the affected predicate group.
600
- program.markRecursivePredicates();
604
+ // Strict ISO core mode follows ordinary ISO clause selection rather than
605
+ // EyeProlog's automatic recursion guards, numeric recursion shortcuts, or
606
+ // tabled fixed points. Leaving the recursion-planning fields at their
607
+ // neutral defaults preserves the standard depth-first execution model.
608
+ if (!program.strictIso) program.markRecursivePredicates();
601
609
  if (this.options.analyzeNegation === true || this.options.strictNegation === true) {
602
610
  program.analyzeNegationStratification();
603
611
  }
@@ -629,7 +637,7 @@ function buildProgramFromSources(sources, options) {
629
637
  function loadSourcesIntoBuilder(builder, sources, options, fast) {
630
638
  const ensured = new Set();
631
639
  const loadedModules = new Set();
632
- const operatorState = createParserOperatorState();
640
+ const operatorState = createParserOperatorState([], true, { isoStrict: options.isoStrict === true });
633
641
  const parserFlagState = { doubleQuotes: options.doubleQuotes ?? 'chars' };
634
642
  const prepared = sources.map((source) => ({
635
643
  source,
@@ -686,7 +694,10 @@ function loadSourceIntoBuilder(builder, source, options, ensured, loadedModules,
686
694
  builder.addClauses([clause]);
687
695
  return;
688
696
  }
689
- const grammarClause = expandDcgRuleClause(clause, context.module);
697
+ // Grammar-rule expansion belongs to ISO/IEC TS 13211-3 rather than the
698
+ // Part 1 strict-core language. In strict core mode -->/2 remains the
699
+ // ordinary predefined operator term from Table 7 and is not rewritten.
700
+ const grammarClause = builder.program.strictIso ? null : expandDcgRuleClause(clause, context.module);
690
701
  if (grammarClause) clause = grammarClause;
691
702
  const moduleDeclaration = moduleDirective(clause);
692
703
  if (moduleDeclaration) {
@@ -911,16 +922,23 @@ function operatorDirective(clause) {
911
922
  };
912
923
  }
913
924
 
914
- function assertHeadIsDefinable(head) {
915
- if (head.type === ATOM) assertPredicateIsDefinable(head.name, head.arity);
925
+ function assertHeadIsDefinable(head, strictIso = false) {
926
+ if (head.type === ATOM || head.type === COMPOUND) {
927
+ assertPredicateIsDefinable(head.name, head.arity, strictIso);
928
+ }
916
929
  }
917
930
 
918
- function assertDynamicIndicatorIsDefinable(indicator) {
919
- assertPredicateIsDefinable(indicator.name, indicator.arity);
931
+ function assertDynamicIndicatorIsDefinable(indicator, strictIso = false) {
932
+ assertPredicateIsDefinable(indicator.name, indicator.arity, strictIso);
920
933
  }
921
934
 
922
- function assertPredicateIsDefinable(name, arity) {
923
- if (name === 'false' && arity === 0) {
935
+ function assertPredicateIsDefinable(name, arity, strictIso = false) {
936
+ // false/0 is standardized as a static built-in by Corrigendum 2 and cannot
937
+ // be redefined in either profile. Strict core mode extends the same ISO
938
+ // rule to every Part-1 built-in/control construct; the normal EyeProlog
939
+ // profile keeps its historical source-compatibility behavior.
940
+ if ((name === 'false' && arity === 0) ||
941
+ (strictIso && (getStrictIsoRegistry().get(name, arity) || (name === ',' && arity === 2)))) {
924
942
  throw staticProcedureModificationError(name, arity);
925
943
  }
926
944
  }