eyeprolog 1.2.20 → 1.2.22

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.2.20",
6
+ "version": "1.2.22",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/parser.js CHANGED
@@ -436,7 +436,16 @@ class Parser {
436
436
  if (!value || (value !== ' ' && isWhitespaceCode(value.charCodeAt(0)))) {
437
437
  throw new Error(`parse line ${line}: bad character code constant`);
438
438
  }
439
- if (value === '\\') value = this.readEscape(line, { allowContinuation: false });
439
+ if (value === "'") {
440
+ // In the single-quoted-character notation used after 0', an
441
+ // apostrophe is doubled just as it is inside a quoted atom. Thus
442
+ // 0''' is one numeric token denoting character code 39, while the
443
+ // undoubled 0'' is not a complete single quoted character.
444
+ if (this.peek() !== "'") throw new Error(`parse line ${line}: bad character code constant`);
445
+ this.take();
446
+ } else if (value === '\\') {
447
+ value = this.readEscape(line, { allowContinuation: false });
448
+ }
440
449
  const code = value.codePointAt(0);
441
450
  return { type: TOK.NUMBER, text: String(negative ? -code : code), line };
442
451
  }
package/src/repl.js CHANGED
@@ -31,13 +31,23 @@ export async function runRepl(engine, options = {}) {
31
31
  if (text == null) break;
32
32
  if (!text.trim()) continue;
33
33
 
34
+ let resultIndent = ' ';
34
35
  try {
35
36
  const goal = parseGoal(engine, state, text);
37
+ // A complete, successfully parsed query has left the top-level reader
38
+ // and is about to execute. Show two spaces immediately so a terminal
39
+ // user can distinguish that state from a reader waiting for more text;
40
+ // the final result adds the third indentation space below. A direct
41
+ // halt has no result and exits immediately, so it needs no marker.
42
+ if (!isHaltGoal(goal)) {
43
+ output.write(' ');
44
+ resultIndent = ' ';
45
+ }
36
46
  if (!options.isoStrict && isUseModuleGoal(goal)) {
37
47
  sources.push({ text: `:- ${text}.\n`, filename: '<repl>' });
38
48
  state = makeState(engine, sources, output, options, state, reader);
39
49
  runWithTerminalSignals(reader, () => state.solver.runInitializations());
40
- output.write(' true.\n');
50
+ output.write(' true.\n');
41
51
  continue;
42
52
  }
43
53
  const consultFiles = options.isoStrict ? null : consultDesignations(engine, goal);
@@ -45,7 +55,7 @@ export async function runRepl(engine, options = {}) {
45
55
  for (const filename of consultFiles) sources.push(await readSource(filename));
46
56
  state = makeState(engine, sources, output, options, state, reader);
47
57
  runWithTerminalSignals(reader, () => state.solver.runInitializations());
48
- output.write(' true.\n');
58
+ output.write(' true.\n');
49
59
  continue;
50
60
  }
51
61
 
@@ -65,7 +75,7 @@ export async function runRepl(engine, options = {}) {
65
75
  exitCode = error.code;
66
76
  break;
67
77
  }
68
- output.write(` ${formatError(engine, state, error)}\n`);
78
+ output.write(`${resultIndent}${formatError(engine, state, error)}\n`);
69
79
  }
70
80
  }
71
81
  } catch (error) {
@@ -442,6 +452,11 @@ function isUseModuleGoal(goal) {
442
452
  return goal.type === 'compound' && goal.name === 'use_module' && [1, 2].includes(goal.arity);
443
453
  }
444
454
 
455
+ function isHaltGoal(goal) {
456
+ return (goal.type === 'atom' && goal.name === 'halt') ||
457
+ (goal.type === 'compound' && goal.name === 'halt' && goal.arity === 1);
458
+ }
459
+
445
460
  function consultDesignations(engine, goal) {
446
461
  if (goal.type === 'atom' && goal.name === '[]') return [];
447
462
  if (goal.type !== 'compound' || goal.name !== '.' || goal.arity !== 2) return null;
@@ -481,7 +496,7 @@ async function solveQuery(engine, state, goal, reader, output) {
481
496
  }
482
497
 
483
498
  if (current.result.done) {
484
- output.write(' false.\n');
499
+ output.write(' false.\n');
485
500
  return null;
486
501
  }
487
502
 
@@ -498,7 +513,7 @@ async function solveQuery(engine, state, goal, reader, output) {
498
513
  if (formattingAfterAdvance) output.write(' ');
499
514
  formattingAfterAdvance = false;
500
515
  output.write(current.output);
501
- output.write(`${firstAnswer ? ' ' : ''}${formatAnswer(engine, state, variables, current.result.value)}`);
516
+ output.write(`${firstAnswer ? ' ' : ''}${formatAnswer(engine, state, variables, current.result.value)}`);
502
517
  answersShown++;
503
518
  firstAnswer = false;
504
519
  if (!next.error && next.result.done) {
@@ -538,6 +538,35 @@ c4 ?- call((!;1)).
538
538
  }
539
539
  },
540
540
  },
541
+ {
542
+ name: 'apostrophe character-code constants parse in source and number conversion',
543
+ run: () => {
544
+ const source = String.raw`
545
+ ?- N = 0'''.
546
+ N = 39.
547
+ ?- number_chars(N,"0'''").
548
+ N = 39.
549
+ ?- number_chars(N,"0'\\'").
550
+ N = 39.
551
+ ?- number_codes(N,[48,39,39,39]).
552
+ N = 39.
553
+ `;
554
+ const result = publicApi.runQuads(source);
555
+ assertEqual(result.total, 4, 'quad total');
556
+ assertEqual(result.passed, 4, 'quad passed');
557
+ assertEqual(result.stdout, 'quads: 4 run, 4 passed, 0 failed.\n', 'quad report');
558
+
559
+ for (const goal of ["N = 0''", 'number_chars(N,"0\'\'")']) {
560
+ let caught = null;
561
+ try {
562
+ publicApi.run('', { goal });
563
+ } catch (error) {
564
+ caught = error;
565
+ }
566
+ if (caught == null) throw new Error(`${goal} should reject an undoubled apostrophe`);
567
+ }
568
+ },
569
+ },
541
570
  {
542
571
  name: 'number conversion rejects parenthesized numeric terms',
543
572
  run: () => {
@@ -791,7 +820,7 @@ c4 ?- call((!;1)).
791
820
  },
792
821
  },
793
822
  {
794
- name: 'REPL answer prompt distinguishes waiting from computation',
823
+ name: 'REPL query and answer prompts distinguish waiting from computation',
795
824
  run: () => {
796
825
  const helper = `
797
826
  import { spawn } from 'node:child_process';
@@ -804,9 +833,11 @@ c4 ?- call((!;1)).
804
833
  child.stderr.setEncoding('utf8');
805
834
  let stdout = '';
806
835
  let stderr = '';
836
+ let sawQueryComputingPrompt = false;
807
837
  let sawComputingPrompt = false;
808
838
  child.stdout.on('data', (text) => {
809
839
  stdout += text;
840
+ if (stdout.endsWith('?- ')) sawQueryComputingPrompt = true;
810
841
  if (stdout.endsWith('\\n; ')) sawComputingPrompt = true;
811
842
  });
812
843
  child.stderr.on('data', (text) => { stderr += text; });
@@ -823,6 +854,10 @@ c4 ?- call((!;1)).
823
854
 
824
855
  child.stdin.write('use_module(library(prologue)).\\n');
825
856
  await waitFor(() => stdout.includes(' true.\\n?- '), 'module import');
857
+ sawQueryComputingPrompt = false;
858
+ child.stdin.write('between(0,0xff,I),I<0.\\n');
859
+ await waitFor(() => sawQueryComputingPrompt, 'query computing prompt');
860
+ await waitFor(() => stdout.endsWith(' false.\\n?- '), 'query result');
826
861
  child.stdin.write('(N = 0; N = 1; (call_nth(repeat, 100000), N = 2)).\\n');
827
862
  await waitFor(() => stdout.endsWith(' N = 0\\n;'), 'waiting prompt');
828
863
  child.stdin.write(';\\n');
@@ -833,7 +868,7 @@ c4 ?- call((!;1)).
833
868
  child.stdin.write('halt.\\n');
834
869
  const status = await new Promise((resolve) => child.once('exit', resolve));
835
870
  if (status !== 0) throw new Error('child status ' + status + '; stderr=' + stderr);
836
- process.stdout.write('waiting;computing;formatting');
871
+ process.stdout.write('query-computing;waiting;computing;formatting');
837
872
  `;
838
873
  const result = spawnSync(process.execPath, [
839
874
  '--input-type=module',
@@ -842,7 +877,7 @@ c4 ?- call((!;1)).
842
877
  ], { cwd: packageRoot, encoding: 'utf8', timeout: 10000 });
843
878
  if (result.error) throw result.error;
844
879
  assertEqual(result.status, 0, `prompt helper status; stderr=${result.stderr}`);
845
- assertEqual(result.stdout, 'waiting;computing;formatting', 'prompt state sequence');
880
+ assertEqual(result.stdout, 'query-computing;waiting;computing;formatting', 'prompt state sequence');
846
881
  },
847
882
  },
848
883
  {
@@ -912,9 +947,9 @@ c4 ?- call((!;1)).
912
947
  });
913
948
  assertEqual(result.status, 0, 'exit status');
914
949
  assertEqual(result.stdout,
915
- "?- '\\a' true.\n" +
916
- "?- '\\a' true.\n" +
917
- "?- '\\a' true.\n" +
950
+ "?- '\\a' true.\n" +
951
+ "?- '\\a' true.\n" +
952
+ "?- '\\a' true.\n" +
918
953
  '?- ',
919
954
  'stdout');
920
955
  assertEqual(result.stderr, '', 'stderr');
@@ -927,7 +962,7 @@ c4 ?- call((!;1)).
927
962
  input: "writeq('\\0\\').\nhalt.\n",
928
963
  });
929
964
  assertEqual(result.status, 0, 'exit status');
930
- assertEqual(result.stdout, "?- '\\0\\' true.\n?- ", 'stdout');
965
+ assertEqual(result.stdout, "?- '\\0\\' true.\n?- ", 'stdout');
931
966
  assertEqual(result.stderr, '', 'stderr');
932
967
  },
933
968
  },
@@ -1062,7 +1097,7 @@ c4 ?- call((!;1)).
1062
1097
  input: "writeq('\\033\\').\nhalt.\n",
1063
1098
  });
1064
1099
  assertEqual(result.status, 0, 'exit status');
1065
- assertEqual(result.stdout, "?- '\\33\\' true.\n?- ", 'stdout');
1100
+ assertEqual(result.stdout, "?- '\\33\\' true.\n?- ", 'stdout');
1066
1101
  assertEqual(result.stderr, '', 'stderr');
1067
1102
  },
1068
1103
  },
@@ -1081,9 +1116,9 @@ c4 ?- call((!;1)).
1081
1116
  });
1082
1117
  assertEqual(result.status, 0, 'exit status');
1083
1118
  assertEqual(result.stdout,
1084
- '?- |: X = foo.\n' +
1085
- "?- |: Y = '\\a'.\n" +
1086
- '?- |: Z = bar.\n' +
1119
+ '?- |: X = foo.\n' +
1120
+ "?- |: Y = '\\a'.\n" +
1121
+ '?- |: Z = bar.\n' +
1087
1122
  '?- ',
1088
1123
  'stdout');
1089
1124
  assertEqual(result.stderr, '', 'stderr');
@@ -5776,8 +5776,9 @@ minus token and the following numeric token. A single-line `%...` comment may
5776
5776
  therefore follow `-` directly because `%` cannot continue a graphic token; an
5777
5777
  adjacent bracketed comment in `-/**/1` remains a syntax error under the eager
5778
5778
  token-consumer rule. Decimal fractions and decimal exponents are supported;
5779
- trailing material and non-finite values are rejected. The regression gate
5780
- vendors all 74 numbered cases from Ulrich Neumerkel's contemporary
5779
+ the apostrophe character code is written with a doubled apostrophe as `0'''`
5780
+ and has value 39. Trailing material and non-finite values are rejected. The
5781
+ regression gate vendors all 74 numbered cases from Ulrich Neumerkel's contemporary
5781
5782
  `number_chars/2` comparison, including the Cor.2 error-precedence cases;
5782
5783
  `number_codes/2` shares the same numeric parser and has mirrored coverage for
5783
5784
  the recent numeric-syntax regressions.
@@ -6284,10 +6285,13 @@ When another answer exists in an interactive terminal, press `;`, Space, or
6284
6285
  enumeration, `a` enumerates all remaining answers, and `f` advances to the
6285
6286
  next five-answer boundary (5, 10, 15, ... displayed leaf answers), regardless
6286
6287
  of how many answers were stepped through individually beforehand. `h` displays
6287
- the answer-control help. The answer prompt is `;` with no trailing space while
6288
- it waits for input; after an advance command, one space marks active search and
6289
- a second marks an answer ready for formatting. While a query is actively
6290
- computing, EyeProlog releases readline's terminal signal handling: `Ctrl-C`
6288
+ the answer-control help. Once the top-level reader has accepted a complete
6289
+ query, the following line begins with two spaces to mark active execution; a
6290
+ third space appears when its result is ready for formatting. The answer prompt
6291
+ is `;` with no trailing space while it waits for input; after an advance
6292
+ command, one space marks active search and a second marks an answer ready for
6293
+ formatting. While a query is actively computing, EyeProlog releases readline's
6294
+ terminal signal handling: `Ctrl-C`
6291
6295
  therefore terminates the current EyeProlog process immediately, and on POSIX
6292
6296
  terminals `Ctrl-Z` suspends it in the usual shell-managed way. This remains a
6293
6297
  host top-level convention rather than an ISO/IEC 13211-1 language feature. A