eyeling 1.13.7 → 1.14.1
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/HANDBOOK.md +41 -0
- package/examples/genetic-algorithm.n3 +177 -0
- package/examples/output/genetic-algorithm.n3 +3 -0
- package/eyeling-builtins.ttl +15 -0
- package/eyeling.js +170 -0
- package/lib/builtins.js +167 -0
- package/package.json +1 -1
package/HANDBOOK.md
CHANGED
|
@@ -1516,6 +1516,47 @@ A tiny `sprintf` subset:
|
|
|
1516
1516
|
- Any other specifier (`%d`, `%f`, …) causes the builtin to fail.
|
|
1517
1517
|
- Missing arguments are treated as empty strings.
|
|
1518
1518
|
|
|
1519
|
+
### Length and character utilities (Eyeling extensions)
|
|
1520
|
+
|
|
1521
|
+
Eyeling also implements a few **non-standard** `string:` helpers that are handy for string-based algorithms. These are **not** part of the SWAP builtin set, so treat them as Eyeling extensions.
|
|
1522
|
+
|
|
1523
|
+
#### `string:length`
|
|
1524
|
+
|
|
1525
|
+
**Shape:** `s string:length n`
|
|
1526
|
+
|
|
1527
|
+
Casts `s` to a string and returns its length as an integer literal token.
|
|
1528
|
+
|
|
1529
|
+
#### `string:charAt`
|
|
1530
|
+
|
|
1531
|
+
**Shape:** `( s i ) string:charAt ch`
|
|
1532
|
+
|
|
1533
|
+
- `i` is a numeric term, truncated to an integer.
|
|
1534
|
+
- Indexing is **0-based** (like JavaScript).
|
|
1535
|
+
- If `i` is out of range, `ch` is the empty string `""`.
|
|
1536
|
+
|
|
1537
|
+
#### `string:setCharAt`
|
|
1538
|
+
|
|
1539
|
+
**Shape:** `( s i ch ) string:setCharAt out`
|
|
1540
|
+
|
|
1541
|
+
Returns a copy of `s` with the character at index `i` (0-based) replaced by:
|
|
1542
|
+
|
|
1543
|
+
- the **first character** of `ch` if `ch` is non-empty, otherwise
|
|
1544
|
+
- the empty string.
|
|
1545
|
+
|
|
1546
|
+
If `i` is out of range, `out` is the original string.
|
|
1547
|
+
|
|
1548
|
+
#### `string:hammingDistance`
|
|
1549
|
+
|
|
1550
|
+
**Shape:** `( a b ) string:hammingDistance d`
|
|
1551
|
+
|
|
1552
|
+
Returns the number of differing positions between `a` and `b`. Fails if the two strings have different lengths.
|
|
1553
|
+
|
|
1554
|
+
#### `string:mutateSelectBest`
|
|
1555
|
+
|
|
1556
|
+
**Shape:** `( samples current target mutProb seedState ) string:mutateSelectBest ( best bestScore seedState2 )`
|
|
1557
|
+
|
|
1558
|
+
Generates `samples` mutated variants of `current` (mutating each character with probability `mutProb` percent), scores each candidate against `target` (Hamming distance), and returns the best candidate (lowest score). RNG is a deterministic 31-bit LCG, threaded via `seedState` → `seedState2`, so runs are reproducible.
|
|
1559
|
+
|
|
1519
1560
|
### Containment and prefix/suffix tests
|
|
1520
1561
|
|
|
1521
1562
|
- `string:contains`
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# ==========================================================================================
|
|
2
|
+
# Genetic algorithm demo
|
|
3
|
+
#
|
|
4
|
+
# Genetic algorithm (GA) in a nutshell:
|
|
5
|
+
# - Start with a random candidate string of the same length as the target.
|
|
6
|
+
# - Repeatedly create a batch of “children” by copying the current best string and randomly
|
|
7
|
+
# mutating each character with a small probability.
|
|
8
|
+
# - Score each child by how many positions differ from the target (Hamming distance).
|
|
9
|
+
# - Keep the best-scoring child as the new current string and iterate until the score is 0.
|
|
10
|
+
# This is a simple hill-climbing GA (selection + mutation, no crossover), using a seeded RNG
|
|
11
|
+
# so runs are reproducible.
|
|
12
|
+
#
|
|
13
|
+
# The GA is still expressed as backward rules (<=) for the outer evolution loop,
|
|
14
|
+
# but the expensive inner loop ("take N mutated samples and keep the best") is
|
|
15
|
+
# handled by a single string builtin to avoid huge proof trees and OOM.
|
|
16
|
+
#
|
|
17
|
+
# ==========================================================================================
|
|
18
|
+
|
|
19
|
+
@prefix : <urn:ga:>.
|
|
20
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
21
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#>.
|
|
22
|
+
@prefix list: <http://www.w3.org/2000/10/swap/list#>.
|
|
23
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#>.
|
|
24
|
+
|
|
25
|
+
:cfg
|
|
26
|
+
:mutationProbability 5;
|
|
27
|
+
:samples 80;
|
|
28
|
+
:seed 100;
|
|
29
|
+
|
|
30
|
+
# Debug knobs:
|
|
31
|
+
:traceEvery 1; # 0 disables; 1 traces every generation
|
|
32
|
+
:maxGenerations 0; # 0 means unlimited
|
|
33
|
+
|
|
34
|
+
:alphabet (" " "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
|
|
35
|
+
"N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z").
|
|
36
|
+
|
|
37
|
+
# ------------------------
|
|
38
|
+
# Small arithmetic helpers
|
|
39
|
+
# ------------------------
|
|
40
|
+
|
|
41
|
+
{ ( ?N 1 ) :dec ?N1 } <= { ( ?N 1 ) math:difference ?N1 }.
|
|
42
|
+
{ ( ?N 1 ) :inc ?N1 } <= { ( ?N 1 ) math:sum ?N1 }.
|
|
43
|
+
|
|
44
|
+
# ----------------------------------------------
|
|
45
|
+
# Seeded RNG (LCG) and JS Math.round(rnd() * N)
|
|
46
|
+
# (Used only for the initial random chromosome.)
|
|
47
|
+
# ----------------------------------------------
|
|
48
|
+
|
|
49
|
+
{ ?S0 :lcgNext ?S1 } <= {
|
|
50
|
+
( ?S0 1103515245 ) math:product ?P .
|
|
51
|
+
( ?P 12345 ) math:sum ?T .
|
|
52
|
+
( ?T 2147483648 ) math:remainder ?S1
|
|
53
|
+
}.
|
|
54
|
+
|
|
55
|
+
{ ( ?S0 ?N ) :randRound ( ?R ?S1 ) } <= {
|
|
56
|
+
?S0 :lcgNext ?S1 .
|
|
57
|
+
( ?S1 ?N ) math:product ?A .
|
|
58
|
+
( ?A ?A ) math:sum ?TwoA .
|
|
59
|
+
( ?TwoA 2147483648 ) math:sum ?Num .
|
|
60
|
+
( 2147483648 2 ) math:product ?Den .
|
|
61
|
+
( ?Num ?Den ) math:integerQuotient ?R
|
|
62
|
+
}.
|
|
63
|
+
|
|
64
|
+
{ ?S0 :randomAlpha ( ?Ch ?S1 ) } <= {
|
|
65
|
+
:cfg :alphabet ?Alphabet .
|
|
66
|
+
( ?S0 26 ) :randRound ( ?Idx ?S1 ) .
|
|
67
|
+
( ?Alphabet ?Idx ) list:memberAt ?Ch
|
|
68
|
+
}.
|
|
69
|
+
|
|
70
|
+
{ ( 0 ?S0 ) :randomText ( "" ?S0 ) } <= true.
|
|
71
|
+
|
|
72
|
+
{ ( ?Len ?S0 ) :randomText ( ?Out ?S2 ) } <= {
|
|
73
|
+
?Len math:greaterThan 0 .
|
|
74
|
+
( ?Len 1 ) :dec ?Len1 .
|
|
75
|
+
?S0 :randomAlpha ( ?Ch ?S1 ) .
|
|
76
|
+
( ?Len1 ?S1 ) :randomText ( ?Rest ?S2 ) .
|
|
77
|
+
( "%s%s" ?Ch ?Rest ) string:format ?Out
|
|
78
|
+
}.
|
|
79
|
+
|
|
80
|
+
# -------------------------
|
|
81
|
+
# tracing helper (optional)
|
|
82
|
+
# -------------------------
|
|
83
|
+
|
|
84
|
+
{ ( 0 ?Gen ?Score ?Value ) :traceMaybe true } <= true.
|
|
85
|
+
|
|
86
|
+
{ ( ?Every ?Gen ?Score ?Value ) :traceMaybe true } <= {
|
|
87
|
+
?Every math:greaterThan 0 .
|
|
88
|
+
( ?Gen ?Every ) math:remainder ?R .
|
|
89
|
+
?R math:equalTo 0 .
|
|
90
|
+
?Gen log:trace ( ?Score ?Value )
|
|
91
|
+
}.
|
|
92
|
+
|
|
93
|
+
# --------------
|
|
94
|
+
# evolution loop
|
|
95
|
+
# --------------
|
|
96
|
+
|
|
97
|
+
# Stop when score == 0
|
|
98
|
+
{ ( ?Current 0 ?Target ?MutProb ?Samples ?Gen ?S0 ?MaxGen ?TraceEvery )
|
|
99
|
+
:evolve ( ?Current ?Gen ?S0 ) } <= true.
|
|
100
|
+
|
|
101
|
+
# Stop when generation cap is reached (only if MaxGen > 0)
|
|
102
|
+
{ ( ?Current ?Score ?Target ?MutProb ?Samples ?Gen ?S0 ?MaxGen ?TraceEvery )
|
|
103
|
+
:evolve ( ?Current ?Gen ?S0 ) } <= {
|
|
104
|
+
?Score math:notEqualTo 0 .
|
|
105
|
+
?MaxGen math:greaterThan 0 .
|
|
106
|
+
?Gen math:equalTo ?MaxGen
|
|
107
|
+
}.
|
|
108
|
+
|
|
109
|
+
# Unlimited run (MaxGen == 0): keep evolving
|
|
110
|
+
{ ( ?Current ?Score ?Target ?MutProb ?Samples ?Gen ?S0 0 ?TraceEvery )
|
|
111
|
+
:evolve ( ?Final ?FinalGen ?S2 ) } <= {
|
|
112
|
+
?Score math:notEqualTo 0 .
|
|
113
|
+
( ?Samples ?Current ?Target ?MutProb ?S0 )
|
|
114
|
+
string:mutateSelectBest
|
|
115
|
+
( ?Best ?BestScore ?S1 ) .
|
|
116
|
+
( ?Gen 1 ) :inc ?Gen1 .
|
|
117
|
+
( ?TraceEvery ?Gen1 ?BestScore ?Best ) :traceMaybe true .
|
|
118
|
+
( ?Best ?BestScore ?Target ?MutProb ?Samples ?Gen1 ?S1 0 ?TraceEvery )
|
|
119
|
+
:evolve ( ?Final ?FinalGen ?S2 )
|
|
120
|
+
}.
|
|
121
|
+
|
|
122
|
+
# Bounded run (MaxGen > 0): keep evolving while Gen < MaxGen
|
|
123
|
+
{ ( ?Current ?Score ?Target ?MutProb ?Samples ?Gen ?S0 ?MaxGen ?TraceEvery )
|
|
124
|
+
:evolve ( ?Final ?FinalGen ?S2 ) } <= {
|
|
125
|
+
?Score math:notEqualTo 0 .
|
|
126
|
+
?MaxGen math:greaterThan 0 .
|
|
127
|
+
?Gen math:lessThan ?MaxGen .
|
|
128
|
+
( ?Samples ?Current ?Target ?MutProb ?S0 )
|
|
129
|
+
string:mutateSelectBest
|
|
130
|
+
( ?Best ?BestScore ?S1 ) .
|
|
131
|
+
( ?Gen 1 ) :inc ?Gen1 .
|
|
132
|
+
( ?TraceEvery ?Gen1 ?BestScore ?Best ) :traceMaybe true .
|
|
133
|
+
( ?Best ?BestScore ?Target ?MutProb ?Samples ?Gen1 ?S1 ?MaxGen ?TraceEvery )
|
|
134
|
+
:evolve ( ?Final ?FinalGen ?S2 )
|
|
135
|
+
}.
|
|
136
|
+
|
|
137
|
+
# ----------------------------------
|
|
138
|
+
# Public solver wrapper (string API)
|
|
139
|
+
# ----------------------------------
|
|
140
|
+
|
|
141
|
+
{ ?TargetString :solveResult ( ?Gen 0 ?Value ?Seed ) } <= {
|
|
142
|
+
:cfg :mutationProbability ?MutProb;
|
|
143
|
+
:samples ?Samples;
|
|
144
|
+
:seed ?Seed;
|
|
145
|
+
:traceEvery ?TraceEvery;
|
|
146
|
+
:maxGenerations ?MaxGen.
|
|
147
|
+
|
|
148
|
+
?TargetString string:length ?Len .
|
|
149
|
+
|
|
150
|
+
( ?Len ?Seed ) :randomText ( ?Current0 ?S1 ) .
|
|
151
|
+
|
|
152
|
+
# initial score using string:hammingDistance
|
|
153
|
+
( ?Current0 ?TargetString ) string:hammingDistance ?Score0 .
|
|
154
|
+
|
|
155
|
+
( ?Current0 ?Score0 ?TargetString ?MutProb ?Samples 0 ?S1 ?MaxGen ?TraceEvery )
|
|
156
|
+
:evolve
|
|
157
|
+
( ?Value ?Gen ?S2 ) .
|
|
158
|
+
|
|
159
|
+
# Succeeds only when solved:
|
|
160
|
+
( ?Value ?TargetString ) string:hammingDistance 0 .
|
|
161
|
+
?Value log:equalTo ?TargetString .
|
|
162
|
+
?S2 log:equalTo ?_FinalSeedState
|
|
163
|
+
}.
|
|
164
|
+
|
|
165
|
+
{ ?TargetString :solved true } <= {
|
|
166
|
+
?TargetString :solveResult ( ?Gen 0 ?TargetString ?Seed )
|
|
167
|
+
}.
|
|
168
|
+
|
|
169
|
+
# --------------------------------------------------------
|
|
170
|
+
# Requested output: solve('METHINKS IT IS LIKE A WEASEL').
|
|
171
|
+
# --------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
{ "METHINKS IT IS LIKE A WEASEL" :solved true .
|
|
174
|
+
( "solve('%s').\n" "METHINKS IT IS LIKE A WEASEL" ) string:format ?Line
|
|
175
|
+
}
|
|
176
|
+
log:query
|
|
177
|
+
{ 1 log:outputString ?Line }.
|
package/eyeling-builtins.ttl
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
6
6
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
7
7
|
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
8
|
+
@prefix ega: <urn:eyeling:ga:> .
|
|
8
9
|
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
9
10
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
10
11
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
@@ -333,3 +334,17 @@ string:scrape a ex:Builtin ; ex:kind ex:Function ;
|
|
|
333
334
|
string:format a ex:Builtin ; ex:kind ex:Function ;
|
|
334
335
|
rdfs:comment "Simple formatter: (fmt arg1 ... argN) -> output string. Only %s and %% are supported; other specifiers fail." .
|
|
335
336
|
|
|
337
|
+
string:length a ex:Builtin ; ex:kind ex:Function ;
|
|
338
|
+
rdfs:comment "Function: binds/unifies object with the character length of the subject after string-casting. Output is an integer literal token." .
|
|
339
|
+
|
|
340
|
+
string:charAt a ex:Builtin ; ex:kind ex:Function ;
|
|
341
|
+
rdfs:comment "Function: subject is a 2-item list (string idx). Binds/unifies object with the 1-character string at 0-based idx; out-of-range yields the empty string." .
|
|
342
|
+
|
|
343
|
+
string:setCharAt a ex:Builtin ; ex:kind ex:Function ;
|
|
344
|
+
rdfs:comment "Function: subject is a 3-item list (string idx ch). Returns a copy of the string with position idx (0-based) replaced by the first character of ch; out-of-range returns the original string." .
|
|
345
|
+
|
|
346
|
+
string:hammingDistance a ex:Builtin ; ex:kind ex:Function ;
|
|
347
|
+
rdfs:comment "Function: subject is a 2-item list (a b). Binds/unifies object with the number of differing positions (Hamming distance). Fails if lengths differ." .
|
|
348
|
+
|
|
349
|
+
string:mutateSelectBest a ex:Builtin ; ex:kind ex:Function ;
|
|
350
|
+
rdfs:comment "Shape: (samples current target mutProb seedState) string:mutateSelectBest (best bestScore seedState2). Generates samples mutated variants of current (mutating each character with probability mutProb percent), scores each candidate against target (Hamming distance), and returns the best candidate (lowest score). RNG is a deterministic 31-bit LCG, threaded via seedState → seedState2, so runs are reproducible." .
|
package/eyeling.js
CHANGED
|
@@ -3463,6 +3463,176 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3463
3463
|
return sStr.startsWith(oStr) ? [{ ...subst }] : [];
|
|
3464
3464
|
}
|
|
3465
3465
|
|
|
3466
|
+
// -----------------------------------------------------------------
|
|
3467
|
+
// 4.6.1 extended string: builtins (Eyeling extensions)
|
|
3468
|
+
// -----------------------------------------------------------------
|
|
3469
|
+
|
|
3470
|
+
// string:length
|
|
3471
|
+
// Schema: $s+ string:length $o?
|
|
3472
|
+
// $o is an integer literal (untyped numeric token).
|
|
3473
|
+
if (pv === STRING_NS + 'length') {
|
|
3474
|
+
const sStr = termToJsString(g.s);
|
|
3475
|
+
if (sStr === null) return [];
|
|
3476
|
+
const lit = internLiteral(String(sStr.length));
|
|
3477
|
+
if (g.o instanceof Var) {
|
|
3478
|
+
const s2 = { ...subst };
|
|
3479
|
+
s2[g.o.name] = lit;
|
|
3480
|
+
return [s2];
|
|
3481
|
+
}
|
|
3482
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3483
|
+
return s2 !== null ? [s2] : [];
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
// string:charAt
|
|
3487
|
+
// Schema: ( $str $idx ) string:charAt $ch
|
|
3488
|
+
// $ch is a (possibly empty) string literal.
|
|
3489
|
+
if (pv === STRING_NS + 'charAt') {
|
|
3490
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3491
|
+
const sStr = termToJsString(g.s.elems[0]);
|
|
3492
|
+
const idxNum = parseNum(g.s.elems[1]);
|
|
3493
|
+
if (sStr === null || idxNum === null) return [];
|
|
3494
|
+
const idx = Math.trunc(idxNum);
|
|
3495
|
+
const ch = idx < 0 || idx >= sStr.length ? '' : sStr.charAt(idx);
|
|
3496
|
+
const lit = makeStringLiteral(ch);
|
|
3497
|
+
if (g.o instanceof Var) {
|
|
3498
|
+
const s2 = { ...subst };
|
|
3499
|
+
s2[g.o.name] = lit;
|
|
3500
|
+
return [s2];
|
|
3501
|
+
}
|
|
3502
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3503
|
+
return s2 !== null ? [s2] : [];
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// string:setCharAt
|
|
3507
|
+
// Schema: ( $str $idx $ch ) string:setCharAt $out
|
|
3508
|
+
// If idx out of range, returns the original string.
|
|
3509
|
+
if (pv === STRING_NS + 'setCharAt') {
|
|
3510
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
3511
|
+
const sStr = termToJsString(g.s.elems[0]);
|
|
3512
|
+
const idxNum = parseNum(g.s.elems[1]);
|
|
3513
|
+
const chStr = termToJsString(g.s.elems[2]);
|
|
3514
|
+
if (sStr === null || idxNum === null || chStr === null) return [];
|
|
3515
|
+
const idx = Math.trunc(idxNum);
|
|
3516
|
+
const rep = chStr.length ? chStr[0] : '';
|
|
3517
|
+
let out = sStr;
|
|
3518
|
+
if (idx >= 0 && idx < sStr.length) out = sStr.slice(0, idx) + rep + sStr.slice(idx + 1);
|
|
3519
|
+
const lit = makeStringLiteral(out);
|
|
3520
|
+
if (g.o instanceof Var) {
|
|
3521
|
+
const s2 = { ...subst };
|
|
3522
|
+
s2[g.o.name] = lit;
|
|
3523
|
+
return [s2];
|
|
3524
|
+
}
|
|
3525
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3526
|
+
return s2 !== null ? [s2] : [];
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
// string:hammingDistance
|
|
3530
|
+
// Schema: ( $a $b ) string:hammingDistance $d
|
|
3531
|
+
// Fails if strings have different length.
|
|
3532
|
+
if (pv === STRING_NS + 'hammingDistance') {
|
|
3533
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3534
|
+
const a = termToJsString(g.s.elems[0]);
|
|
3535
|
+
const b = termToJsString(g.s.elems[1]);
|
|
3536
|
+
if (a === null || b === null) return [];
|
|
3537
|
+
if (a.length !== b.length) return [];
|
|
3538
|
+
let diffs = 0;
|
|
3539
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) diffs++;
|
|
3540
|
+
const lit = internLiteral(String(diffs));
|
|
3541
|
+
if (g.o instanceof Var) {
|
|
3542
|
+
const s2 = { ...subst };
|
|
3543
|
+
s2[g.o.name] = lit;
|
|
3544
|
+
return [s2];
|
|
3545
|
+
}
|
|
3546
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3547
|
+
return s2 !== null ? [s2] : [];
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
// string:mutateSelectBest
|
|
3551
|
+
// Schema:
|
|
3552
|
+
// ( $samples $current $target $mutProb $seedState )
|
|
3553
|
+
// string:mutateSelectBest
|
|
3554
|
+
// ( $best $bestScore $seedState2 )
|
|
3555
|
+
//
|
|
3556
|
+
// Deterministic 31-bit LCG (same as the GA demo):
|
|
3557
|
+
// state = (1103515245 * state + 12345) % 2^31
|
|
3558
|
+
// and randRound(N) = Math.round((state/2^31) * N) with state advanced once per draw.
|
|
3559
|
+
//
|
|
3560
|
+
// This builtin exists to keep GA-style "many samples × many characters" loops out of
|
|
3561
|
+
// the proof search (dramatically reducing memory use).
|
|
3562
|
+
if (pv === STRING_NS + 'mutateSelectBest') {
|
|
3563
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 5) return [];
|
|
3564
|
+
|
|
3565
|
+
const samplesNum = parseNum(g.s.elems[0]);
|
|
3566
|
+
const current = termToJsXsdStringNoLang(g.s.elems[1]);
|
|
3567
|
+
const target = termToJsXsdStringNoLang(g.s.elems[2]);
|
|
3568
|
+
const mutProbNum = parseNum(g.s.elems[3]);
|
|
3569
|
+
const seedNum = parseNum(g.s.elems[4]);
|
|
3570
|
+
|
|
3571
|
+
if (samplesNum === null || current === null || target === null || mutProbNum === null || seedNum === null)
|
|
3572
|
+
return [];
|
|
3573
|
+
if (current.length !== target.length) return [];
|
|
3574
|
+
|
|
3575
|
+
const samples = Math.max(1, Math.trunc(samplesNum));
|
|
3576
|
+
const mutProb = Math.max(0, Math.trunc(mutProbNum));
|
|
3577
|
+
const seed0 = Math.trunc(seedNum);
|
|
3578
|
+
|
|
3579
|
+
let state = BigInt(seed0);
|
|
3580
|
+
const MOD = 2147483648n; // 2^31
|
|
3581
|
+
const A = 1103515245n;
|
|
3582
|
+
const C = 12345n;
|
|
3583
|
+
|
|
3584
|
+
function rnd() {
|
|
3585
|
+
state = (A * state + C) % MOD;
|
|
3586
|
+
return Number(state) / 2147483648;
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
function randRound(N) {
|
|
3590
|
+
return Math.round(rnd() * N);
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
function randomAlpha() {
|
|
3594
|
+
const p = randRound(26);
|
|
3595
|
+
return p === 0 ? ' ' : String.fromCharCode(64 + p);
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
function mutate(str) {
|
|
3599
|
+
let out = '';
|
|
3600
|
+
for (let i = 0; i < str.length; i++) {
|
|
3601
|
+
const p = randRound(100);
|
|
3602
|
+
if (p > mutProb) out += str[i];
|
|
3603
|
+
else out += randomAlpha();
|
|
3604
|
+
}
|
|
3605
|
+
return out;
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
function score(str) {
|
|
3609
|
+
let diffs = 0;
|
|
3610
|
+
for (let i = 0; i < target.length; i++) if (str[i] !== target[i]) diffs++;
|
|
3611
|
+
return diffs;
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
let best = '';
|
|
3615
|
+
let bestScore = Infinity;
|
|
3616
|
+
|
|
3617
|
+
for (let i = 0; i < samples; i++) {
|
|
3618
|
+
const cand = mutate(current);
|
|
3619
|
+
const candScore = score(cand);
|
|
3620
|
+
if (candScore < bestScore) {
|
|
3621
|
+
best = cand;
|
|
3622
|
+
bestScore = candScore;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
const outList = new ListTerm([
|
|
3627
|
+
makeStringLiteral(best),
|
|
3628
|
+
internLiteral(String(bestScore)),
|
|
3629
|
+
internLiteral(state.toString()),
|
|
3630
|
+
]);
|
|
3631
|
+
|
|
3632
|
+
const s2 = unifyTerm(g.o, outList, subst);
|
|
3633
|
+
return s2 !== null ? [s2] : [];
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3466
3636
|
// Unknown builtin
|
|
3467
3637
|
return [];
|
|
3468
3638
|
}
|
package/lib/builtins.js
CHANGED
|
@@ -3451,6 +3451,173 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3451
3451
|
return sStr.startsWith(oStr) ? [{ ...subst }] : [];
|
|
3452
3452
|
}
|
|
3453
3453
|
|
|
3454
|
+
// -----------------------------------------------------------------
|
|
3455
|
+
// 4.6.1 extended string: builtins (Eyeling extensions)
|
|
3456
|
+
// -----------------------------------------------------------------
|
|
3457
|
+
|
|
3458
|
+
// string:length
|
|
3459
|
+
// Schema: $s+ string:length $o?
|
|
3460
|
+
// $o is an integer literal (untyped numeric token).
|
|
3461
|
+
if (pv === STRING_NS + 'length') {
|
|
3462
|
+
const sStr = termToJsString(g.s);
|
|
3463
|
+
if (sStr === null) return [];
|
|
3464
|
+
const lit = internLiteral(String(sStr.length));
|
|
3465
|
+
if (g.o instanceof Var) {
|
|
3466
|
+
const s2 = { ...subst };
|
|
3467
|
+
s2[g.o.name] = lit;
|
|
3468
|
+
return [s2];
|
|
3469
|
+
}
|
|
3470
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3471
|
+
return s2 !== null ? [s2] : [];
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
// string:charAt
|
|
3475
|
+
// Schema: ( $str $idx ) string:charAt $ch
|
|
3476
|
+
// $ch is a (possibly empty) string literal.
|
|
3477
|
+
if (pv === STRING_NS + 'charAt') {
|
|
3478
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3479
|
+
const sStr = termToJsString(g.s.elems[0]);
|
|
3480
|
+
const idxNum = parseNum(g.s.elems[1]);
|
|
3481
|
+
if (sStr === null || idxNum === null) return [];
|
|
3482
|
+
const idx = Math.trunc(idxNum);
|
|
3483
|
+
const ch = idx < 0 || idx >= sStr.length ? '' : sStr.charAt(idx);
|
|
3484
|
+
const lit = makeStringLiteral(ch);
|
|
3485
|
+
if (g.o instanceof Var) {
|
|
3486
|
+
const s2 = { ...subst };
|
|
3487
|
+
s2[g.o.name] = lit;
|
|
3488
|
+
return [s2];
|
|
3489
|
+
}
|
|
3490
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3491
|
+
return s2 !== null ? [s2] : [];
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
// string:setCharAt
|
|
3495
|
+
// Schema: ( $str $idx $ch ) string:setCharAt $out
|
|
3496
|
+
// If idx out of range, returns the original string.
|
|
3497
|
+
if (pv === STRING_NS + 'setCharAt') {
|
|
3498
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
3499
|
+
const sStr = termToJsString(g.s.elems[0]);
|
|
3500
|
+
const idxNum = parseNum(g.s.elems[1]);
|
|
3501
|
+
const chStr = termToJsString(g.s.elems[2]);
|
|
3502
|
+
if (sStr === null || idxNum === null || chStr === null) return [];
|
|
3503
|
+
const idx = Math.trunc(idxNum);
|
|
3504
|
+
const rep = chStr.length ? chStr[0] : '';
|
|
3505
|
+
let out = sStr;
|
|
3506
|
+
if (idx >= 0 && idx < sStr.length) out = sStr.slice(0, idx) + rep + sStr.slice(idx + 1);
|
|
3507
|
+
const lit = makeStringLiteral(out);
|
|
3508
|
+
if (g.o instanceof Var) {
|
|
3509
|
+
const s2 = { ...subst };
|
|
3510
|
+
s2[g.o.name] = lit;
|
|
3511
|
+
return [s2];
|
|
3512
|
+
}
|
|
3513
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3514
|
+
return s2 !== null ? [s2] : [];
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
// string:hammingDistance
|
|
3518
|
+
// Schema: ( $a $b ) string:hammingDistance $d
|
|
3519
|
+
// Fails if strings have different length.
|
|
3520
|
+
if (pv === STRING_NS + 'hammingDistance') {
|
|
3521
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3522
|
+
const a = termToJsString(g.s.elems[0]);
|
|
3523
|
+
const b = termToJsString(g.s.elems[1]);
|
|
3524
|
+
if (a === null || b === null) return [];
|
|
3525
|
+
if (a.length !== b.length) return [];
|
|
3526
|
+
let diffs = 0;
|
|
3527
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) diffs++;
|
|
3528
|
+
const lit = internLiteral(String(diffs));
|
|
3529
|
+
if (g.o instanceof Var) {
|
|
3530
|
+
const s2 = { ...subst };
|
|
3531
|
+
s2[g.o.name] = lit;
|
|
3532
|
+
return [s2];
|
|
3533
|
+
}
|
|
3534
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3535
|
+
return s2 !== null ? [s2] : [];
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
// string:mutateSelectBest
|
|
3539
|
+
// Schema:
|
|
3540
|
+
// ( $samples $current $target $mutProb $seedState )
|
|
3541
|
+
// string:mutateSelectBest
|
|
3542
|
+
// ( $best $bestScore $seedState2 )
|
|
3543
|
+
//
|
|
3544
|
+
// Deterministic 31-bit LCG (same as the GA demo):
|
|
3545
|
+
// state = (1103515245 * state + 12345) % 2^31
|
|
3546
|
+
// and randRound(N) = Math.round((state/2^31) * N) with state advanced once per draw.
|
|
3547
|
+
if (pv === STRING_NS + 'mutateSelectBest') {
|
|
3548
|
+
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 5) return [];
|
|
3549
|
+
|
|
3550
|
+
const samplesNum = parseNum(g.s.elems[0]);
|
|
3551
|
+
const current = termToJsXsdStringNoLang(g.s.elems[1]);
|
|
3552
|
+
const target = termToJsXsdStringNoLang(g.s.elems[2]);
|
|
3553
|
+
const mutProbNum = parseNum(g.s.elems[3]);
|
|
3554
|
+
const seedNum = parseNum(g.s.elems[4]);
|
|
3555
|
+
|
|
3556
|
+
if (samplesNum === null || current === null || target === null || mutProbNum === null || seedNum === null)
|
|
3557
|
+
return [];
|
|
3558
|
+
if (current.length !== target.length) return [];
|
|
3559
|
+
|
|
3560
|
+
const samples = Math.max(1, Math.trunc(samplesNum));
|
|
3561
|
+
const mutProb = Math.max(0, Math.trunc(mutProbNum));
|
|
3562
|
+
const seed0 = Math.trunc(seedNum);
|
|
3563
|
+
|
|
3564
|
+
let state = BigInt(seed0);
|
|
3565
|
+
const MOD = 2147483648n; // 2^31
|
|
3566
|
+
const A = 1103515245n;
|
|
3567
|
+
const C = 12345n;
|
|
3568
|
+
|
|
3569
|
+
function rnd() {
|
|
3570
|
+
state = (A * state + C) % MOD;
|
|
3571
|
+
return Number(state) / 2147483648;
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
function randRound(N) {
|
|
3575
|
+
return Math.round(rnd() * N);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
function randomAlpha() {
|
|
3579
|
+
const p = randRound(26);
|
|
3580
|
+
return p === 0 ? ' ' : String.fromCharCode(64 + p);
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
function mutate(str) {
|
|
3584
|
+
let out = '';
|
|
3585
|
+
for (let i = 0; i < str.length; i++) {
|
|
3586
|
+
const p = randRound(100);
|
|
3587
|
+
if (p > mutProb) out += str[i];
|
|
3588
|
+
else out += randomAlpha();
|
|
3589
|
+
}
|
|
3590
|
+
return out;
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
function score(str) {
|
|
3594
|
+
let diffs = 0;
|
|
3595
|
+
for (let i = 0; i < target.length; i++) if (str[i] !== target[i]) diffs++;
|
|
3596
|
+
return diffs;
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
let best = '';
|
|
3600
|
+
let bestScore = Infinity;
|
|
3601
|
+
|
|
3602
|
+
for (let i = 0; i < samples; i++) {
|
|
3603
|
+
const cand = mutate(current);
|
|
3604
|
+
const candScore = score(cand);
|
|
3605
|
+
if (candScore < bestScore) {
|
|
3606
|
+
best = cand;
|
|
3607
|
+
bestScore = candScore;
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
const outList = new ListTerm([
|
|
3612
|
+
makeStringLiteral(best),
|
|
3613
|
+
internLiteral(String(bestScore)),
|
|
3614
|
+
internLiteral(state.toString()),
|
|
3615
|
+
]);
|
|
3616
|
+
|
|
3617
|
+
const s2 = unifyTerm(g.o, outList, subst);
|
|
3618
|
+
return s2 !== null ? [s2] : [];
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3454
3621
|
// Unknown builtin
|
|
3455
3622
|
return [];
|
|
3456
3623
|
}
|