eyelang 1.5.4 → 1.5.6
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/conformance/README.md +3 -3
- package/conformance/cases/core/021_repeated_variable_head.pl +7 -0
- package/conformance/cases/core/022_rule_head_structure.pl +5 -0
- package/conformance/cases/core/023_quoted_escapes_readback.pl +5 -0
- package/conformance/cases/core/024_numeric_literal_readback.pl +6 -0
- package/conformance/cases/core/025_body_parentheses_with_formula_data.pl +5 -0
- package/conformance/cases/core/026_underscore_named_variable_reuse.pl +5 -0
- package/conformance/cases/extension/035_date_difference.pl +4 -0
- package/conformance/cases/extension/036_extended_gcd.pl +3 -0
- package/conformance/cases/extension/037_collatz_trajectory.pl +3 -0
- package/conformance/cases/extension/038_kaprekar_steps.pl +3 -0
- package/conformance/cases/extension/039_goldbach_pair.pl +3 -0
- package/conformance/cases/extension/040_matrix_operations.pl +5 -0
- package/conformance/cases/extension/041_atom_range_generators.pl +5 -0
- package/conformance/cases/extension/042_n_queens_small.pl +3 -0
- package/conformance/cases/extension/043_cnf_model.pl +3 -0
- package/conformance/cases/extension/044_cover9_filter.pl +6 -0
- package/conformance/cases/extension/045_alphametic_sum_small.pl +3 -0
- package/conformance/cases/extension/046_bounded_subset.pl +4 -0
- package/conformance/expected/core/021_repeated_variable_head.out +2 -0
- package/conformance/expected/core/022_rule_head_structure.out +2 -0
- package/conformance/expected/core/023_quoted_escapes_readback.out +2 -0
- package/conformance/expected/core/024_numeric_literal_readback.out +3 -0
- package/conformance/expected/core/025_body_parentheses_with_formula_data.out +1 -0
- package/conformance/expected/core/026_underscore_named_variable_reuse.out +1 -0
- package/conformance/expected/extension/035_date_difference.out +2 -0
- package/conformance/expected/extension/036_extended_gcd.out +1 -0
- package/conformance/expected/extension/037_collatz_trajectory.out +1 -0
- package/conformance/expected/extension/038_kaprekar_steps.out +1 -0
- package/conformance/expected/extension/039_goldbach_pair.out +2 -0
- package/conformance/expected/extension/040_matrix_operations.out +3 -0
- package/conformance/expected/extension/041_atom_range_generators.out +8 -0
- package/conformance/expected/extension/042_n_queens_small.out +2 -0
- package/conformance/expected/extension/043_cnf_model.out +1 -0
- package/conformance/expected/extension/044_cover9_filter.out +2 -0
- package/conformance/expected/extension/045_alphametic_sum_small.out +4 -0
- package/conformance/expected/extension/046_bounded_subset.out +5 -0
- package/package.json +1 -1
- package/src/index.js +2 -2
- package/src/parser.js +37 -16
package/conformance/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The suite is intentionally file-based so another implementation can run the same
|
|
|
7
7
|
- `conformance/cases/<profile>/<name>.pl` — input program;
|
|
8
8
|
- `conformance/expected/<profile>/<name>.out` — exact expected standard output.
|
|
9
9
|
|
|
10
|
-
The current runner compares standard output
|
|
10
|
+
The current runner compares standard output from normal execution. Proof explanations are opt-in in the CLI and are not part of these conformance goldens. Standard error, performance, and resource limits are outside this suite.
|
|
11
11
|
|
|
12
12
|
## Running the suite
|
|
13
13
|
|
|
@@ -34,9 +34,9 @@ The runner executes materialized programs in-process through the public JavaScri
|
|
|
34
34
|
|
|
35
35
|
## Profiles
|
|
36
36
|
|
|
37
|
-
`core` covers the portable core language profile from `../SPEC.md`: lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, unification through user predicates, left-to-right goal-directed proof search, and
|
|
37
|
+
`core` covers the portable core language profile from `../SPEC.md`: lexical syntax, facts, definite clauses, first-order terms, lists, conjunction, structured unification through user predicates, left-to-right goal-directed proof search, materialized output, and read-back printing.
|
|
38
38
|
|
|
39
|
-
`extension` covers the standard built-in and host behavior exercised by the current reference implementation: arithmetic, comparison, strings, list relations, aggregation, formula-term helpers, `memoize/2`, `materialize/2`, and default derived output.
|
|
39
|
+
`extension` covers the standard built-in and host behavior exercised by the current reference implementation: arithmetic, comparison, strings, list relations, aggregation, formula-term helpers, number-theory helpers, finite-search helpers, matrix helpers, `memoize/2`, `materialize/2`, and default derived output.
|
|
40
40
|
|
|
41
41
|
The profile name `extension` is a test-suite grouping name. It does not mean that these cases are outside the eyelang specification; most of them correspond to the standard built-in profile and standard host profile in `../SPEC.md`.
|
|
42
42
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
% SPEC 9.2: matrix helpers handle small ground numeric matrices.
|
|
2
|
+
answer(sum, M) :- matrix_sum([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], M).
|
|
3
|
+
answer(product, M) :- matrix_multiply([[[1, 2], [3, 4]], [[2, 0], [1, 2]]], M).
|
|
4
|
+
answer(determinant, D) :- determinant([[4, 2], [2, 3]], D).
|
|
5
|
+
materialize(answer, 2).
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
% SPEC 9.5: bounded_subset/7 enumerates subsets within budget and risk caps.
|
|
2
|
+
projects([p(a, 5, 3, 1), p(b, 4, 2, 2), p(c, 7, 5, 3)]).
|
|
3
|
+
answer(selection, result(Names, Value, Cost, Risk)) :- projects(Ps), bounded_subset(Ps, 5, 3, Names, Value, Cost, Risk).
|
|
4
|
+
materialize(answer, 2).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(ok, (left(a), right(b))).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(shared, a).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(gcd, result(2, -9, 47)).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(path, [6, 3, 10, 5, 16, 8, 4, 2, 1]).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(steps, 3).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(model, [value(a, false), value(b, true)]).
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -24,8 +24,8 @@ export function run(source, options = {}) {
|
|
|
24
24
|
const facts = program.sourceFactLines(materializedKeys);
|
|
25
25
|
const seen = new Set();
|
|
26
26
|
for (const goal of goals) {
|
|
27
|
-
|
|
28
|
-
for (const env of
|
|
27
|
+
solver.solutionsSeen = 0;
|
|
28
|
+
for (const env of solver.solve([goal], new Env(), 0)) {
|
|
29
29
|
const resolved = copyResolved(goal, env);
|
|
30
30
|
if (!termIsGround(resolved)) continue;
|
|
31
31
|
const line = `${termToString(resolved, new Env(), true)}.\n`;
|
package/src/parser.js
CHANGED
|
@@ -287,12 +287,14 @@ function isSimpleName(text) {
|
|
|
287
287
|
|
|
288
288
|
const SIMPLE_NUMBER = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
289
289
|
const SIMPLE_ARG_FORBIDDEN = /[\s()[\]|"']/;
|
|
290
|
+
const FAST_BINARY_FACT = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
|
|
291
|
+
const FAST_BINARY_RULE = /^([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\s*:-\s*([a-z][A-Za-z0-9_]*)\(\s*([^,\s()[\]|"']+)\s*,\s*([^,\s()[\]|"']+)\s*\)\.$/;
|
|
290
292
|
|
|
291
293
|
function parseClausesFastNoSource(source) {
|
|
292
294
|
source = String(source ?? '');
|
|
293
|
-
const atomCache = new Map();
|
|
294
295
|
const numberCache = new Map();
|
|
295
296
|
const stringCache = new Map();
|
|
297
|
+
const variableCache = new Map();
|
|
296
298
|
const clauses = [];
|
|
297
299
|
let anonymous = 0;
|
|
298
300
|
let chunk = '';
|
|
@@ -304,22 +306,23 @@ function parseClausesFastNoSource(source) {
|
|
|
304
306
|
cache.set(key, value);
|
|
305
307
|
return value;
|
|
306
308
|
};
|
|
307
|
-
const
|
|
308
|
-
text = text.trim();
|
|
309
|
+
const scalarOrVariableFast = (text) => {
|
|
309
310
|
if (!text) throw new Error('empty simple term');
|
|
311
|
+
const first = text.charCodeAt(0);
|
|
310
312
|
if (text === '_') return variable(`__anon${anonymous++}`);
|
|
311
|
-
if (
|
|
312
|
-
const existing =
|
|
313
|
+
if (first === 95 || (first >= 65 && first <= 90)) {
|
|
314
|
+
const existing = variableCache.get(text);
|
|
313
315
|
if (existing) return existing;
|
|
314
316
|
const value = variable(text);
|
|
315
|
-
|
|
317
|
+
variableCache.set(text, value);
|
|
316
318
|
return value;
|
|
317
319
|
}
|
|
318
|
-
if (SIMPLE_NUMBER.test(text)) return cached(numberCache, text, numberTerm);
|
|
319
|
-
if (
|
|
320
|
-
return
|
|
320
|
+
if ((first === 45 || isDigitCode(first)) && SIMPLE_NUMBER.test(text)) return cached(numberCache, text, numberTerm);
|
|
321
|
+
if (first === 34 && text.endsWith('"')) return cached(stringCache, text.slice(1, -1), stringTerm);
|
|
322
|
+
return atom(text);
|
|
321
323
|
};
|
|
322
|
-
const
|
|
324
|
+
const scalarOrVariable = (text) => scalarOrVariableFast(text.trim());
|
|
325
|
+
const parseBinaryCompound = (text) => {
|
|
323
326
|
text = text.trim();
|
|
324
327
|
const open = text.indexOf('(');
|
|
325
328
|
if (open <= 0 || text[text.length - 1] !== ')') return null;
|
|
@@ -332,19 +335,37 @@ function parseClausesFastNoSource(source) {
|
|
|
332
335
|
const left = inner.slice(0, comma).trim();
|
|
333
336
|
const right = inner.slice(comma + 1).trim();
|
|
334
337
|
if (!left || !right || SIMPLE_ARG_FORBIDDEN.test(left) || SIMPLE_ARG_FORBIDDEN.test(right)) return null;
|
|
335
|
-
return compound(name, [scalarOrVariable(left
|
|
338
|
+
return compound(name, [scalarOrVariable(left), scalarOrVariable(right)]);
|
|
339
|
+
};
|
|
340
|
+
const parseFastBinaryMatch = (match) => {
|
|
341
|
+
return compound(match[1], [scalarOrVariableFast(match[2]), scalarOrVariableFast(match[3])]);
|
|
342
|
+
};
|
|
343
|
+
const parseFastBinaryRuleMatch = (match) => {
|
|
344
|
+
return {
|
|
345
|
+
head: compound(match[1], [scalarOrVariableFast(match[2]), scalarOrVariableFast(match[3])]),
|
|
346
|
+
body: [compound(match[4], [scalarOrVariableFast(match[5]), scalarOrVariableFast(match[6])])],
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
const parseFastLine = (text) => {
|
|
350
|
+
if (!text.endsWith('.')) return null;
|
|
351
|
+
const ruleMatch = FAST_BINARY_RULE.exec(text);
|
|
352
|
+
if (ruleMatch) return parseFastBinaryRuleMatch(ruleMatch);
|
|
353
|
+
const factMatch = FAST_BINARY_FACT.exec(text);
|
|
354
|
+
if (factMatch) return { head: parseFastBinaryMatch(factMatch), body: [] };
|
|
355
|
+
return null;
|
|
336
356
|
};
|
|
337
357
|
const parseSimple = (text) => {
|
|
338
358
|
if (!text.endsWith('.') || text.includes('\n')) return null;
|
|
359
|
+
const fast = parseFastLine(text);
|
|
360
|
+
if (fast) return fast;
|
|
339
361
|
text = text.slice(0, -1);
|
|
340
|
-
const variables = new Map();
|
|
341
362
|
const rule = text.indexOf(':-');
|
|
342
363
|
if (rule < 0) {
|
|
343
|
-
const head = parseBinaryCompound(text
|
|
364
|
+
const head = parseBinaryCompound(text);
|
|
344
365
|
return head ? { head, body: [] } : null;
|
|
345
366
|
}
|
|
346
|
-
const head = parseBinaryCompound(text.slice(0, rule)
|
|
347
|
-
const bodyGoal = parseBinaryCompound(text.slice(rule + 2)
|
|
367
|
+
const head = parseBinaryCompound(text.slice(0, rule));
|
|
368
|
+
const bodyGoal = parseBinaryCompound(text.slice(rule + 2));
|
|
348
369
|
return head && bodyGoal ? { head, body: [bodyGoal] } : null;
|
|
349
370
|
};
|
|
350
371
|
|
|
@@ -375,7 +396,7 @@ function parseClausesFastNoSource(source) {
|
|
|
375
396
|
const trimmed = line.trim();
|
|
376
397
|
if (trimmed && !trimmed.startsWith('%')) {
|
|
377
398
|
if (!chunk && trimmed.endsWith('.')) {
|
|
378
|
-
const simple = parseSimple(trimmed);
|
|
399
|
+
const simple = parseFastLine(trimmed) ?? parseSimple(trimmed);
|
|
379
400
|
if (simple) clauses.push(simple);
|
|
380
401
|
else {
|
|
381
402
|
chunk = line + '\n';
|