eyeprolog 1.1.12 → 1.1.13

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.1.12",
6
+ "version": "1.1.13",
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
@@ -5,7 +5,7 @@ import {
5
5
  isDecimalInteger, listFromItems, numberTerm, numberTextFromDouble,
6
6
  properListItems, termIsGround, termToString, unify, variable, variantTerms,
7
7
  } from './term.js';
8
- import { createParserOperatorState, parseClauses } from './parser.js';
8
+ import { createParserOperatorState, parseClauses, parseGoalText } from './parser.js';
9
9
  import { formatTermForWrite } from './write.js';
10
10
  import { emptyTerminalSequence, expandDcgBody, isListOrPartialList, validateDcgEmbeddedGoals } from './dcg.js';
11
11
 
@@ -1406,12 +1406,129 @@ function* charCodeBuiltin({ goal, env }) {
1406
1406
  } else if (unify(goal.args[0], atom(String.fromCodePoint(Number(code.name))), next)) yield next;
1407
1407
  }
1408
1408
 
1409
+ function skipNumberLayout(text, start) {
1410
+ let position = start;
1411
+ while (true) {
1412
+ while (position < text.length && /[\u0009-\u000d\u0020]/.test(text[position])) {
1413
+ position++;
1414
+ }
1415
+ if (text[position] === '%') {
1416
+ const newline = text.indexOf('\n', position + 1);
1417
+ if (newline < 0) return text.length;
1418
+ position = newline + 1;
1419
+ continue;
1420
+ }
1421
+ if (text.startsWith('/*', position)) {
1422
+ const end = text.indexOf('*/', position + 2);
1423
+ if (end < 0) return text.length;
1424
+ position = end + 2;
1425
+ continue;
1426
+ }
1427
+ return position;
1428
+ }
1429
+ }
1430
+
1431
+ function quotedNumberSign(text, start) {
1432
+ if (text[start] !== "'") return null;
1433
+ let position = start + 1;
1434
+ let value = '';
1435
+ while (position < text.length) {
1436
+ let character = text[position++];
1437
+ if (character === "'") {
1438
+ if (text[position] === "'") {
1439
+ position++;
1440
+ value += "'";
1441
+ continue;
1442
+ }
1443
+ return { value, position };
1444
+ }
1445
+ if (character !== '\\') {
1446
+ value += character;
1447
+ continue;
1448
+ }
1449
+ if (position >= text.length) return null;
1450
+ character = text[position++];
1451
+ if (character === '\n') continue;
1452
+ const controls = { a: '\x07', b: '\b', r: '\r', f: '\f', t: '\t', n: '\n', v: '\v' };
1453
+ if (controls[character] != null) {
1454
+ value += controls[character];
1455
+ continue;
1456
+ }
1457
+ value += character;
1458
+ }
1459
+ return null;
1460
+ }
1461
+
1409
1462
  function parseIsoNumber(text) {
1410
- const trimmed = text.replace(/^[\u0009-\u000d\u0020]+/, '');
1411
- if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(trimmed)) return null;
1412
- if (/^[+-]?\d+$/.test(trimmed)) return numberTerm(BigInt(trimmed).toString());
1413
- const value = Number(trimmed);
1414
- return Number.isFinite(value) ? numberTerm(numberTextFromDouble(value)) : null;
1463
+ // number_chars/2 accepts leading layout, but its character list must end
1464
+ // with the numeric token itself rather than trailing layout text.
1465
+ if (text.length === 0 || /[\u0009-\u000d\u0020]$/.test(text)) return null;
1466
+ let position = skipNumberLayout(text, 0);
1467
+ let sign = '';
1468
+
1469
+ if (text[position] === '-') {
1470
+ if (/[\u0009-\u000d\u0020]/.test(text[position + 1] ?? '')) {
1471
+ position = skipNumberLayout(text, position + 1);
1472
+ sign = '-';
1473
+ }
1474
+ } else {
1475
+ const quoted = quotedNumberSign(text, position);
1476
+ if (quoted?.value === '-') {
1477
+ position = skipNumberLayout(text, quoted.position);
1478
+ sign = '-';
1479
+ }
1480
+ }
1481
+
1482
+ const numericText = `${sign}${text.slice(position)}`;
1483
+ // ISO floating-point syntax requires a decimal fraction before an exponent.
1484
+ if (/^-?\d+[eE][+-]?\d+$/.test(numericText)) return null;
1485
+ try {
1486
+ const parsed = parseGoalText(`number_chars_value(${numericText})`);
1487
+ if (parsed.type !== COMPOUND || parsed.name !== 'number_chars_value' ||
1488
+ parsed.arity !== 1 || parsed.args[0].type !== NUMBER) return null;
1489
+ const value = parsed.args[0];
1490
+ if (isDecimalInteger(value.name)) return numberTerm(BigInt(value.name).toString());
1491
+ const finite = Number(value.name);
1492
+ if (!Number.isFinite(finite)) return null;
1493
+ return numberTerm(numberTextFromDouble(finite));
1494
+ } catch (_) {
1495
+ return null;
1496
+ }
1497
+ }
1498
+
1499
+ function sameNumber(left, right) {
1500
+ const leftInteger = isDecimalInteger(left.name);
1501
+ const rightInteger = isDecimalInteger(right.name);
1502
+ if (leftInteger || rightInteger) {
1503
+ return leftInteger && rightInteger && BigInt(left.name) === BigInt(right.name);
1504
+ }
1505
+ const leftValue = Number(left.name);
1506
+ const rightValue = Number(right.name);
1507
+ return Number.isFinite(leftValue) && Number.isFinite(rightValue) && leftValue === rightValue;
1508
+ }
1509
+
1510
+ function numberListText(list, env, kind, valueIsBound) {
1511
+ const whole = deref(list, env);
1512
+ const { items, tail } = listElements(list, env);
1513
+ const proper = tail.type === ATOM && tail.name === '[]';
1514
+ if (tail.type !== VAR && !proper) throw new PrologError('type_error(list)', whole);
1515
+
1516
+ const invalid = items.find((item) => item.type !== VAR &&
1517
+ (kind === 'chars' ? !oneChar(item) : !validCharacterCode(item)));
1518
+ if (invalid) {
1519
+ if (kind === 'chars') throw new PrologError('type_error(character)', invalid);
1520
+ if (invalid.type !== NUMBER || !isDecimalInteger(invalid.name)) {
1521
+ throw new PrologError('type_error(integer)', invalid);
1522
+ }
1523
+ throw new PrologError('representation_error(character_code)');
1524
+ }
1525
+
1526
+ const hasVariable = tail.type === VAR || items.some((item) => item.type === VAR);
1527
+ if (!valueIsBound && hasVariable) throw new PrologError('instantiation_error');
1528
+ if (hasVariable) return null;
1529
+ return items.map((item) => kind === 'chars'
1530
+ ? item.name
1531
+ : String.fromCodePoint(Number(item.name))).join('');
1415
1532
  }
1416
1533
 
1417
1534
  function numberListBuiltin(kind) {
@@ -1420,14 +1537,21 @@ function numberListBuiltin(kind) {
1420
1537
  if (value.type !== VAR && value.type !== NUMBER) throw new PrologError('type_error(number)', value);
1421
1538
  const list = deref(goal.args[1], env);
1422
1539
  if (value.type === VAR && list.type === VAR) throw new PrologError('instantiation_error');
1540
+ const text = numberListText(list, env, kind, value.type === NUMBER);
1423
1541
  const next = env.clone();
1424
1542
  if (value.type === NUMBER) {
1543
+ if (text != null) {
1544
+ const parsed = parseIsoNumber(text);
1545
+ if (parsed == null) throw new PrologError('syntax_error(number)');
1546
+ if (sameNumber(value, parsed)) yield next;
1547
+ return;
1548
+ }
1425
1549
  const items = characters(value.name).map((ch) =>
1426
1550
  kind === 'chars' ? atom(ch) : numberTerm(ch.codePointAt(0)));
1427
1551
  if (unify(goal.args[1], listFromItems(items), next)) yield next;
1428
1552
  return;
1429
1553
  }
1430
- const parsed = parseIsoNumber(listToAtomInput(list, env, kind));
1554
+ const parsed = parseIsoNumber(text);
1431
1555
  if (parsed == null) throw new PrologError('syntax_error(number)');
1432
1556
  if (unify(goal.args[0], parsed, next)) yield next;
1433
1557
  };
@@ -622,7 +622,21 @@ number_string(Number, Text) :-
622
622
  number_string(Number, Text) :-
623
623
  var(Number),
624
624
  eyeprolog__text_chars(Text, Chars),
625
- catch(number_chars(Number, Chars), _, fail).
625
+ catch(number_chars(Number, Chars), _,
626
+ catch(( eyeprolog__integer_exponent_chars(Chars, Expanded),
627
+ number_chars(Number, Expanded)
628
+ ), _, fail)).
629
+
630
+ eyeprolog__integer_exponent_chars(Chars, Expanded) :-
631
+ eyeprolog__split_exponent(Chars, Mantissa, Marker, Exponent),
632
+ number_chars(Integer, Mantissa),
633
+ integer(Integer),
634
+ eyeprolog__append(Mantissa, ['.','0',Marker|Exponent], Expanded).
635
+
636
+ eyeprolog__split_exponent([e|Exponent], [], e, Exponent).
637
+ eyeprolog__split_exponent(['E'|Exponent], [], 'E', Exponent).
638
+ eyeprolog__split_exponent([C|Chars], [C|Mantissa], Marker, Exponent) :-
639
+ eyeprolog__split_exponent(Chars, Mantissa, Marker, Exponent).
626
640
 
627
641
  atom_string(Atom, Text) :-
628
642
  atom(Atom),
@@ -0,0 +1,235 @@
1
+ % ids refer to https://www.complang.tuwien.ac.at/ulrich/iso-prolog/number_chars_cont
2
+
3
+ 1 ?- number_chars(1.2,"1.2").
4
+ true.
5
+
6
+ 55 ?- number_chars(1.2,"1.20").
7
+ true.
8
+
9
+ 2 ?- number_chars(1.0e9,"1.0E9").
10
+ true.
11
+
12
+ 56 ?- number_chars(1.0e9,"1.0e9").
13
+ true.
14
+
15
+ 64 ?- number_chars(0.0,"-0.0").
16
+ true.
17
+
18
+ 3 ?- number_chars(1,"01").
19
+ true.
20
+
21
+ 65 ?- number_chars(10,"010").
22
+ true.
23
+
24
+ 66 ?- number_chars(N,"010").
25
+ N=10.
26
+
27
+ 67 ?- number_chars(N,"08").
28
+ N=8.
29
+
30
+ 68 ?- number_chars(N,"0b11").
31
+ N=3.
32
+
33
+ 69 ?- number_chars(N,"0o11").
34
+ N=9.
35
+
36
+ 70 ?- number_chars(N,"0x11").
37
+ N=17.
38
+
39
+ 4 ?- number_chars(1,"a").
40
+ syntax_error(...).
41
+
42
+ 5 ?- number_chars(1,[]).
43
+ syntax_error(...).
44
+
45
+ 6 ?- number_chars(1,[[]]).
46
+ type_error(character,[]).
47
+
48
+ 7 ?- number_chars(1,[' ',[]]).
49
+ type_error(character,[]).
50
+
51
+ 8 ?- number_chars(1,[0]).
52
+ type_error(character,0).
53
+
54
+ 9 ?- number_chars(1,[_,[]]).
55
+ type_error(character,[]).
56
+
57
+ 10 ?- number_chars(N,[X]).
58
+ instantiation_error.
59
+
60
+ 11 ?- number_chars(N,['0'|_]).
61
+ instantiation_error.
62
+
63
+ 12 ?- number_chars(N,'1').
64
+ type_error(list,'1').
65
+
66
+ 13 ?- number_chars(N,[a|a]).
67
+ type_error(list,[a|a]).
68
+
69
+ 14 ?- number_chars(N,[49]).
70
+ type_error(character,49).
71
+
72
+ 15 ?- number_chars(N,[]).
73
+ syntax_error(...).
74
+
75
+ 16 ?- number_chars(N,"3 ").
76
+ syntax_error(...).
77
+
78
+ 17 ?- number_chars(N,"3.").
79
+ syntax_error(...).
80
+
81
+ 18 ?- number_chars(N," 1").
82
+ N = 1.
83
+
84
+ 19 ?- number_chars(N,"\n1").
85
+ N = 1.
86
+
87
+ 20 ?- number_chars(N," 0'a").
88
+ N = 0'a. N = 97.
89
+
90
+ 58 ?- number_chars(N,"0'").
91
+ syntax_error(...).
92
+
93
+ 59 ?- number_chars(N,"0'\n").
94
+ syntax_error(...).
95
+
96
+ 60 ?- number_chars(N,"0'\\n").
97
+ N=0'\n. N=10.
98
+
99
+ 61 ?- number_chars(N,"0'\\7\\").
100
+ N=7.
101
+
102
+ 71 ?- number_chars(N,"0'\\7").
103
+ syntax_error(...).
104
+
105
+ 62 ?- number_chars(N,"0'.").
106
+ N=0'. . N = 46.
107
+
108
+ 21 ?- number_chars(N,"- 1").
109
+ N = -1.
110
+
111
+ 54 ?- number_chars(N,"'-'1").
112
+ N = -1.
113
+
114
+ 22 ?- number_chars(N,"/**/1").
115
+ N = 1.
116
+
117
+ 23 ?- number_chars(N,"%\n1").
118
+ N = 1.
119
+
120
+ 57 ?- number_chars(N,"- /**/1").
121
+ N = -1.
122
+
123
+ 24 ?- number_chars(N,"-/**/1").
124
+ syntax_error(...).
125
+
126
+ 63 ?- number_chars(N,"'\\\n-' 3").
127
+ N= -3.
128
+
129
+ 25 ?- number_chars(N,"1e1").
130
+ syntax_error(...).
131
+
132
+ 26 ?- number_chars(N,"1.0e").
133
+ syntax_error(...).
134
+
135
+ 27 ?- number_chars(N,"1.0ee").
136
+ syntax_error(...).
137
+
138
+ 28 ?- number_chars(N,"0x1").
139
+ N = 1.
140
+
141
+ 29 ?- number_chars(N,"0X1").
142
+ syntax_error(...).
143
+
144
+ 30 ?- number_chars(N,"1E1").
145
+ syntax_error(...).
146
+
147
+ 47 ?- number_chars(1,['.'|_]).
148
+ false.
149
+
150
+ 48 ?- number_chars(N,"+1").
151
+ syntax_error(...).
152
+
153
+ 49 ?- number_chars(N,"+ 1").
154
+ syntax_error(...).
155
+
156
+ 50 ?- number_chars(N,"'+'1").
157
+ syntax_error(...).
158
+
159
+
160
+ 51 ?- number_chars(N,['11']).
161
+ type_error(character,'11').
162
+
163
+ 52 ?- number_chars(N,['1.1']).
164
+ type_error(character,'1.1').
165
+
166
+ 53 ?- number_chars(1+1,"2").
167
+ type_error(number,1+1).
168
+
169
+
170
+ 31 ?- number_chars(1,[C]).
171
+ C = '1'.
172
+
173
+ 32 ?- number_chars(1,[C,D]).
174
+ false.
175
+
176
+
177
+ 33 ?- number_chars(1,[C,C]).
178
+ false.
179
+
180
+ 34 ?- number_chars(0,[C,C]).
181
+ false.
182
+
183
+ 35 ?- number_chars(10,[C,D]).
184
+ C = '1', D = '0'.
185
+
186
+ 36 ?- number_chars(100,[C,D]).
187
+ false.
188
+
189
+ 37 ?- number_chars(N,[X|2]).
190
+ type_error(list,[_|2])
191
+ | instantiation_error.
192
+
193
+ 38 ?- number_chars(N,[1|_]).
194
+ instantiation_error
195
+ | type_error(character,1).
196
+
197
+ 39 ?- number_chars(V,[1|2]).
198
+ type_error(list,[1|2])
199
+ | type_error(character,1).
200
+
201
+ 40 ?- number_chars([],1).
202
+ type_error(number,[])
203
+ | type_error(list,1).
204
+
205
+ 41 ?- number_chars(1,1).
206
+ type_error(list,1).
207
+
208
+ 42 ?- number_chars(1,[a|2]).
209
+ type_error(list,[a|2]).
210
+
211
+ 43 ?- number_chars(1,[_|2]).
212
+ type_error(list,[_|2]).
213
+
214
+ 44 ?- number_chars(1,[[]|_]).
215
+ type_error(character,[]).
216
+
217
+ 45 ?- number_chars(1,[[]|2]).
218
+ type_error(character,[])
219
+ | type_error(list,[[]|2]).
220
+
221
+ 72 ?- number_chars(V,[1,[],X|2]).
222
+ type_error(list,[1,[],_A|2])
223
+ | type_error(character,1)
224
+ | type_error(character,[])
225
+ | instantiation_error.
226
+
227
+ 46 ?- L=['1'|L], number_chars(N,L).
228
+ sto, ... ; ... .
229
+ ( sto, type_error(list,['1'|...]) % rational trees
230
+ | sto, false % occurs-check
231
+ | sto, representation_error(term)
232
+ | sto, instantiation_error % literal substitutions
233
+ | sto, resource_error(...)
234
+ | sto, loops
235
+ ).
@@ -322,6 +322,20 @@ c4 ?- call((!;1)).
322
322
  assertEqual(result.stdout, 'quads: 58 run, 58 passed, 0 failed.\n', 'quad report');
323
323
  },
324
324
  },
325
+ {
326
+ name: 'runQuads passes the complete vendored number_chars continuation corpus',
327
+ run: () => {
328
+ const filename = path.join(testRoot, 'fixtures', 'number_chars_cont_quad.pl');
329
+ const source = fs.readFileSync(filename, 'utf8');
330
+ const result = publicApi.runQuads(Program.parseSources([{
331
+ text: source,
332
+ filename,
333
+ }]));
334
+ assertEqual(result.total, 72, 'quad total');
335
+ assertEqual(result.passed, 72, 'quad passed');
336
+ assertEqual(result.stdout, 'quads: 72 run, 72 passed, 0 failed.\n', 'quad report');
337
+ },
338
+ },
325
339
  {
326
340
  name: 'runQuads passes the complete vendored Prolog Prologue quad corpus',
327
341
  run: () => {
@@ -5636,7 +5636,7 @@ quoted_atom("ab"). % quoted_atom(ab)
5636
5636
  | `sub_atom(+Atom,?Before,?Length,?After,?SubAtom)` | Enumerates substrings and their Unicode-code-point offsets. Supplied counts must be nonnegative integers. |
5637
5637
  | `atom_chars(?Atom,?Chars)`, `atom_codes(?Atom,?Codes)` | Convert between an atom and a proper list of one-character atoms or Unicode scalar codes. At least one side must be instantiated. |
5638
5638
  | `char_code(?Character,?Code)` | Converts one character atom and one Unicode scalar code. Surrogates and values outside `0..0x10ffff` raise a representation error. |
5639
- | `number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)` | Convert finite numbers to canonical text or parse a proper character/code list. At least one side must be instantiated; malformed numeric input raises *syntax_error(number)*. |
5639
+ | `number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)` | Convert finite numbers to canonical text or parse a proper character/code list using ISO numeric-token syntax, including radix integers, character-code constants, and leading layout. At least one side must be instantiated; malformed numeric input raises *syntax_error(number)*. |
5640
5640
 
5641
5641
  Conversions accept partial output lists when the atomic input is known, but
5642
5642
  constructing an atom or number requires a complete proper list with no unbound