eyeprolog 1.2.43 → 1.2.44

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.43",
6
+ "version": "1.2.44",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/iso.js CHANGED
@@ -1198,6 +1198,15 @@ function scopeReadTerm(term) {
1198
1198
 
1199
1199
  function parseReadTermText(text, solver) {
1200
1200
  const converted = convertedTermText(text, solver);
1201
+ // A standalone number needs no general term parser or operator tables.
1202
+ // Reuse the same bounded numeric scanner as number_chars/2 so stream reads
1203
+ // and number conversion agree on every accepted numeric token. The stream
1204
+ // scanner has already established that the final full stop is a candidate
1205
+ // terminator; trim only ISO layout immediately before that terminator.
1206
+ const numericText = converted.slice(0, -1).replace(/[\u0009-\u000d\u0020]+$/, '');
1207
+ const numericTerm = parseIsoNumber(numericText);
1208
+ if (numericTerm != null) return numericTerm;
1209
+
1201
1210
  const operatorState = createParserOperatorState(solver.program.operators.values(), false);
1202
1211
  const clauses = parseClauses(converted, {
1203
1212
  sourceMetadata: false,
@@ -692,6 +692,108 @@ c4 ?- call((!;1)).
692
692
  }
693
693
  },
694
694
  },
695
+ {
696
+ name: 'read/2 and number_chars/2 agree on bounded numeric syntax (issue #29)',
697
+ run: () => {
698
+ const registry = createDefaultRegistry();
699
+ const numberChars = registry.get('number_chars', 2).handler;
700
+ const read = registry.get('read', 2).handler;
701
+ const solver = new Solver(Program.parse(''), { registry, ioOptions: { input: '' } });
702
+ const stream = solver.io.resolve(0);
703
+ const streamTerm = compound('$stream', [numberTerm('0')]);
704
+ const alphabet = ['0', '1', '2', '7', '8', '9', 'a', 'f', 'x', 'e', 'E', '+', '-', '.', "'", '\\', ' '];
705
+ let checked = 0;
706
+ let accepted = 0;
707
+
708
+ const visit = (prefix, remaining) => {
709
+ if (remaining === 0) {
710
+ checked++;
711
+ const converted = variable('Converted');
712
+ const conversionGoal = compound('number_chars', [
713
+ converted,
714
+ listFromItems(Array.from(prefix, atom)),
715
+ ]);
716
+ let conversion;
717
+ try {
718
+ conversion = numberChars({ goal: conversionGoal, env: new Env() }).next();
719
+ } catch (_) {
720
+ return;
721
+ }
722
+ if (conversion.done) return;
723
+ accepted++;
724
+ const expected = copyResolved(converted, conversion.value);
725
+
726
+ stream.content = `${prefix}. `;
727
+ stream.position = 0;
728
+ stream.pastEnd = false;
729
+ const readValue = variable('ReadValue');
730
+ const readGoal = compound('read', [streamTerm, readValue]);
731
+ const answer = read({ solver, goal: readGoal, env: new Env() }).next();
732
+ if (answer.done) throw new Error(`read/2 rejected number_chars/2 spelling ${JSON.stringify(prefix)}`);
733
+ const actual = copyResolved(readValue, answer.value);
734
+ assertEqual(actual.type, 'number', `read/2 type for ${JSON.stringify(prefix)}`);
735
+ assertEqual(actual.name, expected.name, `read/2 value for ${JSON.stringify(prefix)}`);
736
+ return;
737
+ }
738
+ for (const character of alphabet) visit(prefix + character, remaining - 1);
739
+ };
740
+
741
+ for (let length = 1; length <= 4; length++) visit('', length);
742
+ assertEqual(checked, 88740, 'bounded numeric spellings checked');
743
+ if (accepted < 1000) throw new Error(`unexpectedly small accepted numeric corpus: ${accepted}`);
744
+ },
745
+ },
746
+ {
747
+ name: 'number_chars/2 to read/2 cross-check stays bounded under a small heap (issue #29)',
748
+ run: () => {
749
+ const engineUrl = new URL('../src/index.js', import.meta.url).href;
750
+ const script = `
751
+ import {
752
+ Program, Solver, Env, atom, compound, variable, listFromItems,
753
+ numberTerm, copyResolved, createDefaultRegistry,
754
+ } from ${JSON.stringify(engineUrl)};
755
+ const registry = createDefaultRegistry();
756
+ const numberChars = registry.get('number_chars', 2).handler;
757
+ const read = registry.get('read', 2).handler;
758
+ const solver = new Solver(Program.parse(''), { registry, ioOptions: { input: '' } });
759
+ const stream = solver.io.resolve(0);
760
+ const streamTerm = compound('$stream', [numberTerm('0')]);
761
+ for (let i = 0; i < 250000; i++) {
762
+ const text = String(i);
763
+ const converted = variable('Converted');
764
+ const conversionGoal = compound('number_chars', [
765
+ converted,
766
+ listFromItems(Array.from(text, atom)),
767
+ ]);
768
+ const conversion = numberChars({ goal: conversionGoal, env: new Env() }).next();
769
+ if (conversion.done) throw new Error('number_chars/2 failed at ' + i);
770
+ const expected = copyResolved(converted, conversion.value);
771
+
772
+ stream.content = text + '. ';
773
+ stream.position = 0;
774
+ stream.pastEnd = false;
775
+ const readValue = variable('ReadValue');
776
+ const readGoal = compound('read', [streamTerm, readValue]);
777
+ const answer = read({ solver, goal: readGoal, env: new Env() }).next();
778
+ if (answer.done) throw new Error('read/2 failed at ' + i);
779
+ const actual = copyResolved(readValue, answer.value);
780
+ if (actual.type !== 'number' || actual.name !== expected.name) {
781
+ throw new Error('number mismatch at ' + i + ': ' + actual.name + ' != ' + expected.name);
782
+ }
783
+ }
784
+ process.stdout.write('250000');
785
+ `;
786
+ const result = spawnSync(process.execPath, [
787
+ '--max-old-space-size=32',
788
+ '--input-type=module',
789
+ '--eval',
790
+ script,
791
+ ], { cwd: packageRoot, encoding: 'utf8', timeout: 30000 });
792
+ if (result.error) throw result.error;
793
+ assertEqual(result.status, 0, `bounded-heap child status; stderr=${result.stderr}`);
794
+ assertEqual(result.stdout, '250000', 'number_chars/read cross-check count');
795
+ },
796
+ },
695
797
  {
696
798
  name: 'number syntax and number_chars normalize floating-point negative zero',
697
799
  run: () => {