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 +1 -1
- package/src/iso.js +9 -0
- package/test/run-regression.mjs +102 -0
package/package.json
CHANGED
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,
|
package/test/run-regression.mjs
CHANGED
|
@@ -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: () => {
|