eyeling 1.34.2 → 1.34.4
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/README.md +7 -10
- package/docs/eyelang-guide.md +8 -30
- package/docs/eyelang-language-reference.md +32 -6
- package/examples/eyelang/basic-monadic.pl +16 -2
- package/examples/eyelang/bayes-therapy.pl +4 -4
- package/examples/eyelang/output/basic-monadic.pl +1314 -1314
- package/examples/eyelang/output/path-discovery.pl +3 -3
- package/examples/eyelang/output/reusable-builtins.pl +5 -0
- package/examples/eyelang/output/term-tools.pl +6 -0
- package/examples/eyelang/path-discovery.pl +22 -7
- package/examples/eyelang/reusable-builtins.pl +32 -0
- package/examples/eyelang/term-tools.pl +23 -0
- package/lib/eyelang/builtins/arithmetic.js +19 -6
- package/lib/eyelang/builtins/control.js +12 -0
- package/lib/eyelang/builtins/lists.js +146 -7
- package/lib/eyelang/builtins/registry.js +2 -4
- package/lib/eyelang/builtins/strings.js +165 -1
- package/lib/eyelang/builtins/terms.js +66 -0
- package/package.json +1 -1
- package/test/eyelang/conformance/README.md +1 -1
- package/test/eyelang/conformance/cases/extension/036_reusable_numeric_builtins.pl +10 -0
- package/test/eyelang/conformance/cases/extension/037_reusable_list_builtins.pl +11 -0
- package/test/eyelang/conformance/cases/extension/038_reusable_string_builtins.pl +12 -0
- package/test/eyelang/conformance/cases/extension/039_reusable_term_control_builtins.pl +11 -0
- package/test/eyelang/conformance/expected/extension/036_reusable_numeric_builtins.out +8 -0
- package/test/eyelang/conformance/expected/extension/037_reusable_list_builtins.out +9 -0
- package/test/eyelang/conformance/expected/extension/038_reusable_string_builtins.out +10 -0
- package/test/eyelang/conformance/expected/extension/039_reusable_term_control_builtins.out +6 -0
- package/examples/eyelang/collatz-1000.pl +0 -14
- package/examples/eyelang/complex-matrix-stability.pl +0 -45
- package/examples/eyelang/dense-hamiltonian-cycle.pl +0 -92
- package/examples/eyelang/gcd-bezout-identity.pl +0 -48
- package/examples/eyelang/goldbach-1000.pl +0 -185
- package/examples/eyelang/hamiltonian-cycle.pl +0 -55
- package/examples/eyelang/kaprekar.pl +0 -32
- package/examples/eyelang/matrix.pl +0 -296
- package/examples/eyelang/n-queens.pl +0 -23
- package/examples/eyelang/output/collatz-1000.pl +0 -1000
- package/examples/eyelang/output/complex-matrix-stability.pl +0 -5
- package/examples/eyelang/output/dense-hamiltonian-cycle.pl +0 -4
- package/examples/eyelang/output/gcd-bezout-identity.pl +0 -36
- package/examples/eyelang/output/goldbach-1000.pl +0 -667
- package/examples/eyelang/output/hamiltonian-cycle.pl +0 -4
- package/examples/eyelang/output/kaprekar.pl +0 -8
- package/examples/eyelang/output/matrix.pl +0 -10
- package/examples/eyelang/output/n-queens.pl +0 -93
- package/examples/eyelang/output/quine-mccluskey.pl +0 -3
- package/examples/eyelang/output/sat-dpll.pl +0 -5
- package/examples/eyelang/output/traveling-salesman.pl +0 -1
- package/examples/eyelang/quine-mccluskey.pl +0 -143
- package/examples/eyelang/sat-dpll.pl +0 -80
- package/examples/eyelang/traveling-salesman.pl +0 -64
- package/lib/eyelang/builtins/matrix.js +0 -226
- package/lib/eyelang/builtins/number-theory.js +0 -114
- package/lib/eyelang/builtins/search.js +0 -519
- package/test/eyelang/conformance/cases/extension/036_extended_gcd.pl +0 -3
- package/test/eyelang/conformance/cases/extension/037_collatz_trajectory.pl +0 -3
- package/test/eyelang/conformance/cases/extension/038_kaprekar_steps.pl +0 -3
- package/test/eyelang/conformance/cases/extension/039_goldbach_pair.pl +0 -3
- package/test/eyelang/conformance/cases/extension/040_matrix_operations.pl +0 -5
- package/test/eyelang/conformance/cases/extension/042_n_queens_small.pl +0 -3
- package/test/eyelang/conformance/cases/extension/043_cnf_model.pl +0 -3
- package/test/eyelang/conformance/expected/extension/036_extended_gcd.out +0 -1
- package/test/eyelang/conformance/expected/extension/037_collatz_trajectory.out +0 -1
- package/test/eyelang/conformance/expected/extension/038_kaprekar_steps.out +0 -1
- package/test/eyelang/conformance/expected/extension/039_goldbach_pair.out +0 -2
- package/test/eyelang/conformance/expected/extension/040_matrix_operations.out +0 -3
- package/test/eyelang/conformance/expected/extension/042_n_queens_small.out +0 -2
- package/test/eyelang/conformance/expected/extension/043_cnf_model.out +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
airroute(
|
|
2
|
-
airroute(
|
|
3
|
-
airroute(
|
|
1
|
+
airroute("Ostend-Bruges International Airport", "Václav Havel Airport Prague", 2, "Ostend-Bruges International Airport -> Liège Airport -> Heraklion International Nikos Kazantzakis Airport -> Václav Havel Airport Prague").
|
|
2
|
+
airroute("Ostend-Bruges International Airport", "Václav Havel Airport Prague", 2, "Ostend-Bruges International Airport -> Liège Airport -> Diagoras Airport -> Václav Havel Airport Prague").
|
|
3
|
+
airroute("Ostend-Bruges International Airport", "Václav Havel Airport Prague", 2, "Ostend-Bruges International Airport -> Liège Airport -> Palma De Mallorca Airport -> Václav Havel Airport Prague").
|
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
% Generic path discovery over the air-routes graph.
|
|
2
|
-
%
|
|
2
|
+
% Add route_request(FromLabel, ToLabel, MaxStopOvers) facts to answer other routes.
|
|
3
|
+
% MaxStopOvers is converted to a leg limit, and the recursive search keeps a
|
|
4
|
+
% visited list so it works without the removed finite-search helper builtins.
|
|
5
|
+
|
|
3
6
|
% Output declarations: materialize/2 selects the relations written to this example's golden output.
|
|
4
|
-
materialize(airroute,
|
|
7
|
+
materialize(airroute, 4).
|
|
5
8
|
|
|
6
9
|
% Program structure: facts set up the scenario, and rules derive the materialized conclusions.
|
|
7
10
|
route_request("Ostend-Bruges International Airport", "Václav Havel Airport Prague", 2).
|
|
8
11
|
|
|
9
12
|
% Derivation rules: each rule below contributes one logical step toward the displayed results.
|
|
10
|
-
airroute(
|
|
11
|
-
route_request(
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
airroute(FromLabel, ToLabel, MaxStopOvers, RouteText) :-
|
|
14
|
+
route_request(FromLabel, ToLabel, MaxStopOvers),
|
|
15
|
+
route_between(FromLabel, ToLabel, MaxStopOvers, RouteText).
|
|
16
|
+
|
|
17
|
+
route_between(FromLabel, ToLabel, MaxStopOvers, RouteText) :-
|
|
18
|
+
airport(Source, FromLabel),
|
|
19
|
+
airport(Destination, ToLabel),
|
|
14
20
|
add(MaxStopOvers, 1, MaxLegs),
|
|
15
|
-
|
|
21
|
+
simple_path(Source, Destination, MaxLegs, [Source], ReversePath),
|
|
22
|
+
reverse(ReversePath, Path),
|
|
16
23
|
route_text(Path, RouteText).
|
|
17
24
|
|
|
25
|
+
simple_path(Node, Node, _RemainingLegs, Visited, Visited).
|
|
26
|
+
simple_path(Node, Goal, RemainingLegs, Visited, Path) :-
|
|
27
|
+
gt(RemainingLegs, 0),
|
|
28
|
+
flight(Node, Next),
|
|
29
|
+
not_member(Next, Visited),
|
|
30
|
+
sub(RemainingLegs, 1, NextRemainingLegs),
|
|
31
|
+
simple_path(Next, Goal, NextRemainingLegs, [Next|Visited], Path).
|
|
32
|
+
|
|
18
33
|
route_text([Node], Text) :-
|
|
19
34
|
airport(Node, Text).
|
|
20
35
|
route_text([Node|Rest], Text) :-
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
% Reusable builtin tour: normalize text, summarize lists, and compute numeric values.
|
|
2
|
+
materialize(report, 2).
|
|
3
|
+
|
|
4
|
+
name_raw(" Ada Lovelace ").
|
|
5
|
+
tag_csv("logic,math,logic,programming").
|
|
6
|
+
scores([8, 13, 21]).
|
|
7
|
+
|
|
8
|
+
report(normalized_name, Name) :-
|
|
9
|
+
name_raw(Raw),
|
|
10
|
+
trim(Raw, Trimmed),
|
|
11
|
+
lowercase(Trimmed, Name).
|
|
12
|
+
|
|
13
|
+
report(unique_tags, Tags) :-
|
|
14
|
+
tag_csv(Csv),
|
|
15
|
+
split(Csv, ",", Parts),
|
|
16
|
+
list_to_set(Parts, Tags).
|
|
17
|
+
|
|
18
|
+
report(tag_label, Label) :-
|
|
19
|
+
tag_csv(Csv),
|
|
20
|
+
split(Csv, ",", Parts),
|
|
21
|
+
list_to_set(Parts, Tags),
|
|
22
|
+
join(Tags, " / ", Label).
|
|
23
|
+
|
|
24
|
+
report(score_summary, summary(Total, Peak, RootTotal)) :-
|
|
25
|
+
scores(Scores),
|
|
26
|
+
sum_list(Scores, Total),
|
|
27
|
+
max_list(Scores, Peak),
|
|
28
|
+
sqrt(Total, RootTotal).
|
|
29
|
+
|
|
30
|
+
report(window, Slice) :-
|
|
31
|
+
scores(Scores),
|
|
32
|
+
slice(1, 2, Scores, Slice).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
% Term tools: inspect and build structured terms, then validate all facts with forall/2.
|
|
2
|
+
materialize(report, 2).
|
|
3
|
+
|
|
4
|
+
edge(a, b, 3).
|
|
5
|
+
edge(b, c, 4).
|
|
6
|
+
|
|
7
|
+
report(shape, shape(Name, Arity)) :-
|
|
8
|
+
functor(edge(a, b, 3), Name, Arity).
|
|
9
|
+
|
|
10
|
+
report(second_argument, Node) :-
|
|
11
|
+
arg(2, edge(a, b, 3), Node).
|
|
12
|
+
|
|
13
|
+
report(parts, parts(Name, Args)) :-
|
|
14
|
+
compound_name_arguments(edge(a, b, 3), Name, Args).
|
|
15
|
+
|
|
16
|
+
report(rebuilt, Term) :-
|
|
17
|
+
compound_name_arguments(Term, edge, [c, d, 5]).
|
|
18
|
+
|
|
19
|
+
report(rendered, Text) :-
|
|
20
|
+
term_string(edge(a, [b, c]), Text).
|
|
21
|
+
|
|
22
|
+
report(all_weights_positive, yes) :-
|
|
23
|
+
forall(edge(_From, _To, Weight), gt(Weight, 0)).
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// The code keeps BigInt paths where possible so large eyelang integers remain exact.
|
|
3
3
|
import { compareIntegerText, deref, isDecimalInteger, lexicalValue, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.js';
|
|
4
4
|
|
|
5
|
-
const unaryNames = ['neg', 'abs', 'sin', 'cos', 'asin', 'acos', 'rounded', 'log'];
|
|
6
|
-
const binaryNames = ['add', 'sub', 'mul', 'div', 'mod', 'min', 'pow'];
|
|
5
|
+
const unaryNames = ['neg', 'abs', 'sin', 'cos', 'tan', 'asin', 'acos', 'sqrt', 'floor', 'ceiling', 'trunc', 'rounded', 'exp', 'log'];
|
|
6
|
+
const binaryNames = ['add', 'sub', 'mul', 'div', 'mod', 'min', 'max', 'pow', 'atan2'];
|
|
7
7
|
const compareNames = ['lt', 'gt', 'le', 'ge'];
|
|
8
8
|
|
|
9
9
|
export const arithmeticBuiltins = {
|
|
@@ -22,9 +22,11 @@ function unary(name) {
|
|
|
22
22
|
const text = lexicalValue(goal.args[0], env);
|
|
23
23
|
if (text == null) return;
|
|
24
24
|
let out = null;
|
|
25
|
-
if ((name === 'neg' || name === 'abs') && isDecimalInteger(text)) {
|
|
25
|
+
if ((name === 'neg' || name === 'abs' || name === 'floor' || name === 'ceiling' || name === 'trunc' || name === 'rounded') && isDecimalInteger(text)) {
|
|
26
26
|
const value = BigInt(text);
|
|
27
|
-
|
|
27
|
+
if (name === 'neg') out = (-value).toString();
|
|
28
|
+
else if (name === 'abs') out = (value < 0n ? -value : value).toString();
|
|
29
|
+
else out = value.toString();
|
|
28
30
|
} else {
|
|
29
31
|
const input = parseFiniteNumber(text);
|
|
30
32
|
if (input == null) return;
|
|
@@ -33,14 +35,22 @@ function unary(name) {
|
|
|
33
35
|
else if (name === 'abs') value = Math.abs(input);
|
|
34
36
|
else if (name === 'sin') value = Math.sin(input);
|
|
35
37
|
else if (name === 'cos') value = Math.cos(input);
|
|
38
|
+
else if (name === 'tan') value = Math.tan(input);
|
|
36
39
|
else if (name === 'asin') value = Math.asin(input);
|
|
37
40
|
else if (name === 'acos') value = Math.acos(input);
|
|
41
|
+
else if (name === 'sqrt') { if (input < 0) return; value = Math.sqrt(input); }
|
|
42
|
+
else if (name === 'floor') value = Math.floor(input);
|
|
43
|
+
else if (name === 'ceiling') value = Math.ceil(input);
|
|
44
|
+
else if (name === 'trunc') value = Math.trunc(input);
|
|
38
45
|
else if (name === 'rounded') value = Math.round(input);
|
|
46
|
+
else if (name === 'exp') value = Math.exp(input);
|
|
39
47
|
else if (name === 'log') {
|
|
40
48
|
if (input <= 0) return;
|
|
41
49
|
value = logCompat(input);
|
|
42
50
|
}
|
|
43
|
-
out = name === '
|
|
51
|
+
out = (name === 'floor' || name === 'ceiling' || name === 'trunc' || name === 'rounded')
|
|
52
|
+
? String(Math.trunc(value))
|
|
53
|
+
: numberTextFromDouble(value);
|
|
44
54
|
}
|
|
45
55
|
const next = env.clone();
|
|
46
56
|
if (out != null && unify(goal.args[1], numberTerm(out), next)) yield next;
|
|
@@ -53,7 +63,7 @@ function binary(name) {
|
|
|
53
63
|
const rightText = lexicalValue(goal.args[1], env);
|
|
54
64
|
if (leftText == null || rightText == null) return;
|
|
55
65
|
let out = null;
|
|
56
|
-
if (isDecimalInteger(leftText) && isDecimalInteger(rightText) && name !== 'mod') {
|
|
66
|
+
if (isDecimalInteger(leftText) && isDecimalInteger(rightText) && name !== 'mod' && name !== 'atan2') {
|
|
57
67
|
const a = BigInt(leftText);
|
|
58
68
|
const b = BigInt(rightText);
|
|
59
69
|
if (name === 'add') out = (a + b).toString();
|
|
@@ -61,6 +71,7 @@ function binary(name) {
|
|
|
61
71
|
else if (name === 'mul') out = (a * b).toString();
|
|
62
72
|
else if (name === 'div') { if (b === 0n) return; out = (a / b).toString(); }
|
|
63
73
|
else if (name === 'min') out = (a <= b ? a : b).toString();
|
|
74
|
+
else if (name === 'max') out = (a >= b ? a : b).toString();
|
|
64
75
|
else if (name === 'pow') { if (b < 0n) return; out = (a ** b).toString(); }
|
|
65
76
|
} else if (name === 'mod') {
|
|
66
77
|
if (!isDecimalInteger(leftText) || !isDecimalInteger(rightText)) return;
|
|
@@ -77,6 +88,8 @@ function binary(name) {
|
|
|
77
88
|
else if (name === 'div') { if (b === 0) return; value = a / b; }
|
|
78
89
|
else if (name === 'pow') value = Math.pow(a, b);
|
|
79
90
|
else if (name === 'min') value = Math.min(a, b);
|
|
91
|
+
else if (name === 'max') value = Math.max(a, b);
|
|
92
|
+
else if (name === 'atan2') value = Math.atan2(a, b);
|
|
80
93
|
out = numberTextFromDouble(value);
|
|
81
94
|
}
|
|
82
95
|
const next = env.clone();
|
|
@@ -3,6 +3,7 @@ export const controlBuiltins = {
|
|
|
3
3
|
register(registry) {
|
|
4
4
|
registry.add('not', 1, notBuiltin);
|
|
5
5
|
registry.add('once', 1, onceBuiltin);
|
|
6
|
+
registry.add('forall', 2, forallBuiltin);
|
|
6
7
|
}
|
|
7
8
|
};
|
|
8
9
|
|
|
@@ -20,3 +21,14 @@ function* onceBuiltin({ solver, goal, env }) {
|
|
|
20
21
|
break;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
function* forallBuiltin({ solver, goal, env }) {
|
|
26
|
+
const generator = solver.cloneForInnerGoal(10000000);
|
|
27
|
+
for (const answerEnv of generator.solve([goal.args[0]], env.clone(), 0)) {
|
|
28
|
+
const checker = solver.cloneForInnerGoal(1);
|
|
29
|
+
let ok = false;
|
|
30
|
+
for (const _ of checker.solve([goal.args[1]], answerEnv.clone(), 0)) { ok = true; break; }
|
|
31
|
+
if (!ok) return;
|
|
32
|
+
}
|
|
33
|
+
yield env;
|
|
34
|
+
}
|
|
@@ -1,23 +1,62 @@
|
|
|
1
|
-
// List builtins for proper lists, selection, membership, sorting, and
|
|
1
|
+
// List builtins for proper lists, selection, membership, sorting, indexing, slicing, and summaries.
|
|
2
2
|
// Several predicates support both checking and generation, so the argument modes are handled explicitly.
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
compareTerms,
|
|
5
|
+
copyResolved,
|
|
6
|
+
deref,
|
|
7
|
+
isDecimalInteger,
|
|
8
|
+
isCons,
|
|
9
|
+
lexicalValue,
|
|
10
|
+
listFromItems,
|
|
11
|
+
numberTerm,
|
|
12
|
+
numberTextFromDouble,
|
|
13
|
+
parseFiniteNumber,
|
|
14
|
+
properListItems,
|
|
15
|
+
unify,
|
|
16
|
+
} from '../term.js';
|
|
4
17
|
|
|
5
18
|
export const listBuiltins = {
|
|
6
19
|
register(registry) {
|
|
7
20
|
registry.add('append', 3, append);
|
|
8
21
|
registry.add('nth0', 3, nth0);
|
|
9
22
|
registry.add('set_nth0', 4, setNth0, { deterministic: true });
|
|
10
|
-
registry.add('
|
|
23
|
+
registry.add('head', 2, head, { deterministic: true, fallbackWhenNotReady: true, ready: firstConsReady });
|
|
24
|
+
registry.add('rest', 2, rest, { deterministic: true, fallbackWhenNotReady: true, ready: firstConsReady });
|
|
25
|
+
registry.add('last', 2, last, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
|
|
26
|
+
registry.add('take', 3, take, { deterministic: true, fallbackWhenNotReady: true, ready: countAndListReady });
|
|
27
|
+
registry.add('drop', 3, drop, { deterministic: true, fallbackWhenNotReady: true, ready: countAndListReady });
|
|
28
|
+
registry.add('slice', 4, slice, { deterministic: true, fallbackWhenNotReady: true, ready: sliceReady });
|
|
11
29
|
registry.add('member', 2, member);
|
|
12
30
|
registry.add('select', 3, select);
|
|
13
31
|
registry.add('not_member', 2, notMember, { deterministic: true });
|
|
14
32
|
registry.add('reverse', 2, reverse, { deterministic: true });
|
|
15
33
|
registry.add('length', 2, lengthBuiltin, { deterministic: true });
|
|
34
|
+
registry.add('sum_list', 2, sumList, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
|
|
35
|
+
registry.add('min_list', 2, minList, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
|
|
36
|
+
registry.add('max_list', 2, maxList, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
|
|
37
|
+
registry.add('list_to_set', 2, listToSet, { deterministic: true, fallbackWhenNotReady: true, ready: firstProperListReady });
|
|
16
38
|
registry.add('sort', 2, sortBuiltin, { deterministic: true });
|
|
17
39
|
}
|
|
18
40
|
};
|
|
19
41
|
|
|
20
42
|
|
|
43
|
+
|
|
44
|
+
function firstConsReady(goal, env) {
|
|
45
|
+
return isCons(deref(goal.args[0], env));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function firstProperListReady(goal, env) {
|
|
49
|
+
return properListItems(goal.args[0], env) !== null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function countAndListReady(goal, env) {
|
|
53
|
+
return safeIndex(goal.args[0], env) !== null && properListItems(goal.args[1], env) !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sliceReady(goal, env) {
|
|
57
|
+
return safeIndex(goal.args[0], env) !== null && safeIndex(goal.args[1], env) !== null && properListItems(goal.args[2], env) !== null;
|
|
58
|
+
}
|
|
59
|
+
|
|
21
60
|
function listFromItemsExcept(items, skip) {
|
|
22
61
|
const copy = new Array(items.length - 1);
|
|
23
62
|
for (let i = 0, j = 0; i < items.length; i++) if (i !== skip) copy[j++] = items[i];
|
|
@@ -62,10 +101,8 @@ function* nth0({ goal, env }) {
|
|
|
62
101
|
}
|
|
63
102
|
|
|
64
103
|
function* setNth0({ goal, env }) {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
const index = Number(indexText);
|
|
68
|
-
if (!Number.isSafeInteger(index) || index < 0) return;
|
|
104
|
+
const index = safeIndex(goal.args[0], env);
|
|
105
|
+
if (index == null) return;
|
|
69
106
|
const items = properListItems(goal.args[1], env);
|
|
70
107
|
if (!items || index >= items.length) return;
|
|
71
108
|
const out = items.slice();
|
|
@@ -74,6 +111,13 @@ function* setNth0({ goal, env }) {
|
|
|
74
111
|
if (unify(goal.args[3], listFromItems(out), next)) yield next;
|
|
75
112
|
}
|
|
76
113
|
|
|
114
|
+
function* head({ goal, env }) {
|
|
115
|
+
const list = deref(goal.args[0], env);
|
|
116
|
+
if (!isCons(list)) return;
|
|
117
|
+
const next = env.clone();
|
|
118
|
+
if (unify(goal.args[1], list.args[0], next)) yield next;
|
|
119
|
+
}
|
|
120
|
+
|
|
77
121
|
function* rest({ goal, env }) {
|
|
78
122
|
const list = deref(goal.args[0], env);
|
|
79
123
|
if (!isCons(list)) return;
|
|
@@ -81,6 +125,41 @@ function* rest({ goal, env }) {
|
|
|
81
125
|
if (unify(goal.args[1], list.args[1], next)) yield next;
|
|
82
126
|
}
|
|
83
127
|
|
|
128
|
+
function* last({ goal, env }) {
|
|
129
|
+
const items = properListItems(goal.args[0], env);
|
|
130
|
+
if (!items || items.length === 0) return;
|
|
131
|
+
const next = env.clone();
|
|
132
|
+
if (unify(goal.args[1], items[items.length - 1], next)) yield next;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function* take({ goal, env }) {
|
|
136
|
+
const count = safeIndex(goal.args[0], env);
|
|
137
|
+
if (count == null) return;
|
|
138
|
+
const items = properListItems(goal.args[1], env);
|
|
139
|
+
if (!items || count > items.length) return;
|
|
140
|
+
const next = env.clone();
|
|
141
|
+
if (unify(goal.args[2], listFromItems(items, 0, count), next)) yield next;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function* drop({ goal, env }) {
|
|
145
|
+
const count = safeIndex(goal.args[0], env);
|
|
146
|
+
if (count == null) return;
|
|
147
|
+
const items = properListItems(goal.args[1], env);
|
|
148
|
+
if (!items || count > items.length) return;
|
|
149
|
+
const next = env.clone();
|
|
150
|
+
if (unify(goal.args[2], listFromItems(items, count, items.length), next)) yield next;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function* slice({ goal, env }) {
|
|
154
|
+
const start = safeIndex(goal.args[0], env);
|
|
155
|
+
const count = safeIndex(goal.args[1], env);
|
|
156
|
+
if (start == null || count == null) return;
|
|
157
|
+
const items = properListItems(goal.args[2], env);
|
|
158
|
+
if (!items || start + count > items.length) return;
|
|
159
|
+
const next = env.clone();
|
|
160
|
+
if (unify(goal.args[3], listFromItems(items, start, start + count), next)) yield next;
|
|
161
|
+
}
|
|
162
|
+
|
|
84
163
|
function* member({ goal, env }) {
|
|
85
164
|
const items = properListItems(goal.args[1], env);
|
|
86
165
|
if (!items) return;
|
|
@@ -132,6 +211,59 @@ function* lengthBuiltin({ goal, env }) {
|
|
|
132
211
|
if (unify(goal.args[1], numberTerm(items.length), next)) yield next;
|
|
133
212
|
}
|
|
134
213
|
|
|
214
|
+
function* sumList({ goal, env }) {
|
|
215
|
+
const items = properListItems(goal.args[0], env);
|
|
216
|
+
if (!items) return;
|
|
217
|
+
let intSum = 0n;
|
|
218
|
+
let floatMode = false;
|
|
219
|
+
let floatSum = 0;
|
|
220
|
+
for (const item of items) {
|
|
221
|
+
const text = lexicalValue(item, env);
|
|
222
|
+
if (text == null) return;
|
|
223
|
+
if (!floatMode && isDecimalInteger(text)) intSum += BigInt(text);
|
|
224
|
+
else {
|
|
225
|
+
const value = parseFiniteNumber(text);
|
|
226
|
+
if (value == null) return;
|
|
227
|
+
if (!floatMode) { floatSum = Number(intSum); floatMode = true; }
|
|
228
|
+
floatSum += value;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const out = floatMode ? numberTextFromDouble(floatSum) : intSum.toString();
|
|
232
|
+
const next = env.clone();
|
|
233
|
+
if (unify(goal.args[1], numberTerm(out), next)) yield next;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function* minList({ goal, env }) {
|
|
237
|
+
yield* minMaxList(goal, env, true);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function* maxList({ goal, env }) {
|
|
241
|
+
yield* minMaxList(goal, env, false);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function* minMaxList(goal, env, wantMin) {
|
|
245
|
+
const items = properListItems(goal.args[0], env);
|
|
246
|
+
if (!items || items.length === 0) return;
|
|
247
|
+
let best = copyResolved(items[0], env);
|
|
248
|
+
for (let i = 1; i < items.length; i++) {
|
|
249
|
+
const item = copyResolved(items[i], env);
|
|
250
|
+
const cmp = compareTerms(item, best);
|
|
251
|
+
if ((wantMin && cmp < 0) || (!wantMin && cmp > 0)) best = item;
|
|
252
|
+
}
|
|
253
|
+
const next = env.clone();
|
|
254
|
+
if (unify(goal.args[1], best, next)) yield next;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function* listToSet({ goal, env }) {
|
|
258
|
+
const items = properListItems(goal.args[0], env);
|
|
259
|
+
if (!items) return;
|
|
260
|
+
const unique = [];
|
|
261
|
+
for (const item of items.map((entry) => copyResolved(entry, env))) {
|
|
262
|
+
if (!unique.some((seen) => compareTerms(seen, item) === 0)) unique.push(item);
|
|
263
|
+
}
|
|
264
|
+
const next = env.clone();
|
|
265
|
+
if (unify(goal.args[1], listFromItems(unique), next)) yield next;
|
|
266
|
+
}
|
|
135
267
|
|
|
136
268
|
function* sortBuiltin({ goal, env }) {
|
|
137
269
|
const items = properListItems(goal.args[0], env);
|
|
@@ -142,3 +274,10 @@ function* sortBuiltin({ goal, env }) {
|
|
|
142
274
|
const next = env.clone();
|
|
143
275
|
if (unify(goal.args[1], listFromItems(unique), next)) yield next;
|
|
144
276
|
}
|
|
277
|
+
|
|
278
|
+
function safeIndex(term, env) {
|
|
279
|
+
const text = lexicalValue(term, env);
|
|
280
|
+
if (!/^-?\d+$/.test(text ?? '')) return null;
|
|
281
|
+
const index = Number(text);
|
|
282
|
+
return Number.isSafeInteger(index) && index >= 0 ? index : null;
|
|
283
|
+
}
|
|
@@ -7,9 +7,7 @@ import { listBuiltins } from './lists.js';
|
|
|
7
7
|
import { aggregationBuiltins } from './aggregation.js';
|
|
8
8
|
import { contextBuiltins } from './context.js';
|
|
9
9
|
import { controlBuiltins } from './control.js';
|
|
10
|
-
import {
|
|
11
|
-
import { numberTheoryBuiltins } from './number-theory.js';
|
|
12
|
-
import { matrixBuiltins } from './matrix.js';
|
|
10
|
+
import { termBuiltins } from './terms.js';
|
|
13
11
|
|
|
14
12
|
export class BuiltinRegistry {
|
|
15
13
|
constructor() {
|
|
@@ -36,7 +34,7 @@ export class BuiltinRegistry {
|
|
|
36
34
|
|
|
37
35
|
export function createDefaultRegistry() {
|
|
38
36
|
const registry = new BuiltinRegistry();
|
|
39
|
-
for (const mod of [coreBuiltins, arithmeticBuiltins, stringBuiltins, listBuiltins, aggregationBuiltins, contextBuiltins, controlBuiltins,
|
|
37
|
+
for (const mod of [coreBuiltins, arithmeticBuiltins, stringBuiltins, listBuiltins, aggregationBuiltins, contextBuiltins, controlBuiltins, termBuiltins]) {
|
|
40
38
|
mod.register(registry);
|
|
41
39
|
}
|
|
42
40
|
return registry;
|
|
@@ -1,16 +1,76 @@
|
|
|
1
1
|
// String builtins.
|
|
2
2
|
// They mostly project from already-ground terms to avoid guessing string domains.
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
atom,
|
|
5
|
+
compound,
|
|
6
|
+
deref,
|
|
7
|
+
isDecimalInteger,
|
|
8
|
+
lexicalValue,
|
|
9
|
+
listFromItems,
|
|
10
|
+
numberTerm,
|
|
11
|
+
parseFiniteNumber,
|
|
12
|
+
properListItems,
|
|
13
|
+
stringTerm,
|
|
14
|
+
termToString,
|
|
15
|
+
unify,
|
|
16
|
+
} from '../term.js';
|
|
4
17
|
|
|
5
18
|
export const stringBuiltins = {
|
|
6
19
|
register(registry) {
|
|
7
20
|
registry.add('str_concat', 3, concat, { deterministic: true });
|
|
8
21
|
for (const name of ['contains', 'matches', 'not_matches']) registry.add(name, 2, contains(name), { deterministic: true });
|
|
9
22
|
registry.add('matches', 3, matchCaptures, { deterministic: true });
|
|
23
|
+
registry.add('split', 3, split, { deterministic: true, fallbackWhenNotReady: true, ready: firstTwoLexicalReady });
|
|
24
|
+
registry.add('join', 3, join, { deterministic: true, fallbackWhenNotReady: true, ready: listAndSecondLexicalReady });
|
|
25
|
+
registry.add('substring', 4, substring, { deterministic: true, fallbackWhenNotReady: true, ready: substringReady });
|
|
26
|
+
registry.add('replace', 4, replace, { deterministic: true, fallbackWhenNotReady: true, ready: firstThreeLexicalReady });
|
|
27
|
+
registry.add('lowercase', 2, caseFold('lower'), { deterministic: true, fallbackWhenNotReady: true, ready: firstLexicalReady });
|
|
28
|
+
registry.add('uppercase', 2, caseFold('upper'), { deterministic: true, fallbackWhenNotReady: true, ready: firstLexicalReady });
|
|
29
|
+
registry.add('trim', 2, trim, { deterministic: true, fallbackWhenNotReady: true, ready: firstLexicalReady });
|
|
30
|
+
registry.add('number_string', 2, numberString, { deterministic: true, fallbackWhenNotReady: true, ready: numberStringReady });
|
|
31
|
+
registry.add('atom_string', 2, atomString, { deterministic: true, fallbackWhenNotReady: true, ready: atomStringReady });
|
|
32
|
+
registry.add('term_string', 2, termString, { deterministic: true, fallbackWhenNotReady: true, ready: firstNonvarReady });
|
|
10
33
|
}
|
|
11
34
|
};
|
|
12
35
|
|
|
13
36
|
|
|
37
|
+
|
|
38
|
+
function firstLexicalReady(goal, env) {
|
|
39
|
+
return lexicalValue(goal.args[0], env) !== null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function firstTwoLexicalReady(goal, env) {
|
|
43
|
+
return lexicalValue(goal.args[0], env) !== null && lexicalValue(goal.args[1], env) !== null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function firstThreeLexicalReady(goal, env) {
|
|
47
|
+
return firstTwoLexicalReady(goal, env) && lexicalValue(goal.args[2], env) !== null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function listAndSecondLexicalReady(goal, env) {
|
|
51
|
+
return properListItems(goal.args[0], env) !== null && lexicalValue(goal.args[1], env) !== null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function substringReady(goal, env) {
|
|
55
|
+
return lexicalValue(goal.args[0], env) !== null && safeIndex(goal.args[1], env) !== null && safeIndex(goal.args[2], env) !== null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function numberStringReady(goal, env) {
|
|
59
|
+
const left = deref(goal.args[0], env);
|
|
60
|
+
const right = deref(goal.args[1], env);
|
|
61
|
+
return left.type === 'number' || right.type === 'string' || right.type === 'atom';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function atomStringReady(goal, env) {
|
|
65
|
+
const left = deref(goal.args[0], env);
|
|
66
|
+
const right = deref(goal.args[1], env);
|
|
67
|
+
return left.type === 'atom' || right.type === 'string' || right.type === 'atom' || right.type === 'number';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function firstNonvarReady(goal, env) {
|
|
71
|
+
return deref(goal.args[0], env).type !== 'var';
|
|
72
|
+
}
|
|
73
|
+
|
|
14
74
|
function* concat({ goal, env }) {
|
|
15
75
|
const left = lexicalValue(goal.args[0], env);
|
|
16
76
|
const right = lexicalValue(goal.args[1], env);
|
|
@@ -53,6 +113,99 @@ function* matchCaptures({ goal, env }) {
|
|
|
53
113
|
if (unify(goal.args[2], context, next)) yield next;
|
|
54
114
|
}
|
|
55
115
|
|
|
116
|
+
function* split({ goal, env }) {
|
|
117
|
+
const text = lexicalValue(goal.args[0], env);
|
|
118
|
+
const separator = lexicalValue(goal.args[1], env);
|
|
119
|
+
if (text == null || separator == null) return;
|
|
120
|
+
const parts = text.split(separator).map(stringTerm);
|
|
121
|
+
const next = env.clone();
|
|
122
|
+
if (unify(goal.args[2], listFromItems(parts), next)) yield next;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function* join({ goal, env }) {
|
|
126
|
+
const items = properListItems(goal.args[0], env);
|
|
127
|
+
const separator = lexicalValue(goal.args[1], env);
|
|
128
|
+
if (!items || separator == null) return;
|
|
129
|
+
const strings = [];
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
const value = lexicalValue(item, env);
|
|
132
|
+
if (value == null) return;
|
|
133
|
+
strings.push(value);
|
|
134
|
+
}
|
|
135
|
+
const next = env.clone();
|
|
136
|
+
if (unify(goal.args[2], stringTerm(strings.join(separator)), next)) yield next;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function* substring({ goal, env }) {
|
|
140
|
+
const text = lexicalValue(goal.args[0], env);
|
|
141
|
+
const start = safeIndex(goal.args[1], env);
|
|
142
|
+
const count = safeIndex(goal.args[2], env);
|
|
143
|
+
if (text == null || start == null || count == null || start + count > text.length) return;
|
|
144
|
+
const next = env.clone();
|
|
145
|
+
if (unify(goal.args[3], stringTerm(text.slice(start, start + count)), next)) yield next;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function* replace({ goal, env }) {
|
|
149
|
+
const text = lexicalValue(goal.args[0], env);
|
|
150
|
+
const search = lexicalValue(goal.args[1], env);
|
|
151
|
+
const replacement = lexicalValue(goal.args[2], env);
|
|
152
|
+
if (text == null || search == null || replacement == null) return;
|
|
153
|
+
const out = search === '' ? text : text.split(search).join(replacement);
|
|
154
|
+
const next = env.clone();
|
|
155
|
+
if (unify(goal.args[3], stringTerm(out), next)) yield next;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function caseFold(direction) {
|
|
159
|
+
return function* ({ goal, env }) {
|
|
160
|
+
const text = lexicalValue(goal.args[0], env);
|
|
161
|
+
if (text == null) return;
|
|
162
|
+
const next = env.clone();
|
|
163
|
+
const out = direction === 'lower' ? text.toLowerCase() : text.toUpperCase();
|
|
164
|
+
if (unify(goal.args[1], stringTerm(out), next)) yield next;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function* trim({ goal, env }) {
|
|
169
|
+
const text = lexicalValue(goal.args[0], env);
|
|
170
|
+
if (text == null) return;
|
|
171
|
+
const next = env.clone();
|
|
172
|
+
if (unify(goal.args[1], stringTerm(text.trim()), next)) yield next;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function* numberString({ goal, env }) {
|
|
176
|
+
const left = deref(goal.args[0], env);
|
|
177
|
+
const right = deref(goal.args[1], env);
|
|
178
|
+
const next = env.clone();
|
|
179
|
+
if (left.type === 'number') {
|
|
180
|
+
if (unify(goal.args[1], stringTerm(left.name), next)) yield next;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (right.type === 'string' || right.type === 'atom') {
|
|
184
|
+
if (!numericText(right.name)) return;
|
|
185
|
+
if (unify(goal.args[0], numberTerm(right.name), next)) yield next;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function* atomString({ goal, env }) {
|
|
190
|
+
const left = deref(goal.args[0], env);
|
|
191
|
+
const right = deref(goal.args[1], env);
|
|
192
|
+
const next = env.clone();
|
|
193
|
+
if (left.type === 'atom') {
|
|
194
|
+
if (unify(goal.args[1], stringTerm(left.name), next)) yield next;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (right.type === 'string' || right.type === 'atom' || right.type === 'number') {
|
|
198
|
+
if (unify(goal.args[0], atom(right.name), next)) yield next;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function* termString({ goal, env }) {
|
|
203
|
+
const term = deref(goal.args[0], env);
|
|
204
|
+
if (term.type === 'var') return;
|
|
205
|
+
const next = env.clone();
|
|
206
|
+
if (unify(goal.args[1], stringTerm(termToString(term, env, true)), next)) yield next;
|
|
207
|
+
}
|
|
208
|
+
|
|
56
209
|
function contextFromGroups(groups) {
|
|
57
210
|
const terms = [];
|
|
58
211
|
for (const [name, value] of Object.entries(groups)) {
|
|
@@ -68,3 +221,14 @@ function contextFromGroups(groups) {
|
|
|
68
221
|
function simpleAlternationMatch(haystack, pattern) {
|
|
69
222
|
return pattern.split('|').some((part) => part === '' || haystack.includes(part));
|
|
70
223
|
}
|
|
224
|
+
|
|
225
|
+
function safeIndex(term, env) {
|
|
226
|
+
const text = lexicalValue(term, env);
|
|
227
|
+
if (!/^-?\d+$/.test(text ?? '')) return null;
|
|
228
|
+
const index = Number(text);
|
|
229
|
+
return Number.isSafeInteger(index) && index >= 0 ? index : null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function numericText(text) {
|
|
233
|
+
return isDecimalInteger(text) || parseFiniteNumber(text) != null;
|
|
234
|
+
}
|