eyeling 1.13.7 → 1.14.0

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 CHANGED
@@ -1516,6 +1516,41 @@ 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
+
1519
1554
  ### Containment and prefix/suffix tests
1520
1555
 
1521
1556
  - `string:contains`
@@ -1941,6 +1976,24 @@ References:
1941
1976
 
1942
1977
  If you are running untrusted inputs, consider `--super-restricted` to disable all builtins except implication.
1943
1978
 
1979
+ ### Eyeling fast-path builtins
1980
+
1981
+ Eyeling ships one optional fast-path builtins builtin e.g. used by the genetic-algorithm example:
1982
+
1983
+ #### `urn:eyeling:ga:solveString`
1984
+
1985
+ **Shape:** `( target mutationProbability samples seed traceEvery maxGenerations ) urn:eyeling:ga:solveString ( generation score value seed )`
1986
+
1987
+ - `target` and `value` are `xsd:string` literals.
1988
+ - `mutationProbability` is interpreted as a percentage per character (0–100).
1989
+ - `traceEvery` controls debug tracing:
1990
+ - `0` disables tracing,
1991
+ - `1` traces every generation,
1992
+ - `N` traces every `N` generations.
1993
+ - `maxGenerations` is a safety cap:
1994
+ - `0` means unlimited,
1995
+ - otherwise the builtin stops once `generation >= maxGenerations` (even if not solved).
1996
+
1944
1997
  ### A.6 Skolemization and `log:skolem`
1945
1998
 
1946
1999
  When forward rule heads contain blank nodes (existentials), Eyeling replaces them with generated Skolem IRIs so derived facts are ground.
@@ -0,0 +1,63 @@
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
+ # Uses Eyeling's builtin:
14
+ # urn:eyeling:ga:solveString
15
+ #
16
+ # Configure:
17
+ # :mutationProbability 5 # percent per character
18
+ # :samples 80
19
+ # :seed 100
20
+ # :traceEvery 0 # 0=off, 1=every generation, 100=every 100 generations
21
+ # :maxGenerations 0 # 0=unlimited
22
+ # ==========================================================================================
23
+
24
+ @prefix : <http://example.org/ga-solve#>.
25
+ @prefix ega: <urn:eyeling:ga:>.
26
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
27
+ @prefix string: <http://www.w3.org/2000/10/swap/string#>.
28
+
29
+ :cfg
30
+ :mutationProbability 5;
31
+ :samples 80;
32
+ :seed 100;
33
+ :traceEvery 1;
34
+ :maxGenerations 0.
35
+
36
+ # solveResult(TargetString) = (generation score value seed)
37
+ { ?TargetString :solveResult ( ?Gen ?Score ?Value ?Seed ) } <= {
38
+ :cfg :mutationProbability ?MutProb;
39
+ :samples ?Samples;
40
+ :seed ?Seed;
41
+ :traceEvery ?TraceEvery;
42
+ :maxGenerations ?MaxG.
43
+
44
+ ( ?TargetString ?MutProb ?Samples ?Seed ?TraceEvery ?MaxG )
45
+ ega:solveString
46
+ ( ?Gen ?Score ?Value ?SeedOut ) .
47
+
48
+ # By design, the 4th output is the (input) seed.
49
+ ?SeedOut log:equalTo ?Seed
50
+ }.
51
+
52
+ # Boolean-ish API for querying success
53
+ { ?TargetString :solved true } <= {
54
+ ?TargetString :solveResult ( ?Gen 0 ?TargetString ?Seed )
55
+ }.
56
+
57
+ # --------------------------------------------------------
58
+ # Requested output: solve('METHINKS IT IS LIKE A WEASEL').
59
+ # --------------------------------------------------------
60
+
61
+ { "METHINKS IT IS LIKE A WEASEL" :solved true .
62
+ ( "solve('%s').\n" "METHINKS IT IS LIKE A WEASEL" ) string:format ?Line
63
+ } => { 1 log:outputString ?Line }.
@@ -0,0 +1,3 @@
1
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
2
+
3
+ 1 log:outputString "solve('METHINKS IT IS LIKE A WEASEL').\n" .
@@ -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,19 @@ 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
+ # --- eyeling fast path builtins ------------------------------
350
+
351
+ ega:solveString a ex:Builtin ; ex:kind ex:Function ;
352
+ rdfs:comment "Eyeling extension: GA solver on strings. Subject list: (target mutationProbability samples seed traceEvery maxGenerations). Object list: (generation score value seed). traceEvery=0 disables tracing; maxGenerations=0 means unlimited." .
package/eyeling.js CHANGED
@@ -3463,6 +3463,209 @@ 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
+ // -----------------------------------------------------------------
3551
+ // 4.6.2 urn:eyeling:ga: builtins (fast GA on *strings*)
3552
+ // -----------------------------------------------------------------
3553
+
3554
+ // urn:eyeling:ga:solveString
3555
+ // Schema: ( $target $mutProb $samples $seed $traceEvery $maxGenerations ) :solveString ( $gen $score $value $seed )
3556
+ // Side effect: if traceEvery > 0, prints: <gen> TRACE (<score> "<value>")
3557
+ if (pv === 'urn:eyeling:ga:solveString') {
3558
+ if (!(g.s instanceof ListTerm) || g.s.elems.length != 6) return [];
3559
+
3560
+ const target = termToJsXsdStringNoLang(g.s.elems[0]);
3561
+ const mutProbNum = parseNum(g.s.elems[1]);
3562
+ const samplesNum = parseNum(g.s.elems[2]);
3563
+ const seedNum = parseNum(g.s.elems[3]);
3564
+ const traceEveryNum = parseNum(g.s.elems[4]);
3565
+ const maxGenNum = parseNum(g.s.elems[5]);
3566
+
3567
+ if (target === null || mutProbNum === null || samplesNum === null || seedNum === null) return [];
3568
+ if (traceEveryNum === null || maxGenNum === null) return [];
3569
+
3570
+ const mutProb = Math.max(0, Math.trunc(mutProbNum));
3571
+ const samples = Math.max(1, Math.trunc(samplesNum));
3572
+ const seed0 = Math.trunc(seedNum);
3573
+
3574
+ const traceEvery = Math.max(0, Math.trunc(traceEveryNum));
3575
+ const maxGenerations = Math.max(0, Math.trunc(maxGenNum));
3576
+
3577
+ // Deterministic 31-bit LCG using BigInt arithmetic (matches the N3 version)
3578
+ let state = BigInt(seed0);
3579
+ const MOD = 2147483648n; // 2^31
3580
+ const A = 1103515245n;
3581
+ const C = 12345n;
3582
+
3583
+ function rnd() {
3584
+ state = (A * state + C) % MOD;
3585
+ return Number(state) / 2147483648;
3586
+ }
3587
+
3588
+ function randRound(N) {
3589
+ return Math.round(rnd() * N);
3590
+ }
3591
+
3592
+ function randomAlpha() {
3593
+ const p = randRound(26);
3594
+ return p === 0 ? ' ' : String.fromCharCode(64 + p);
3595
+ }
3596
+
3597
+ function randomText(len) {
3598
+ let out = '';
3599
+ for (let i = 0; i < len; i++) out += randomAlpha();
3600
+ return out;
3601
+ }
3602
+
3603
+ function mutate(str) {
3604
+ let out = '';
3605
+ for (let i = 0; i < str.length; i++) {
3606
+ const p = randRound(100);
3607
+ if (p > mutProb) out += str[i];
3608
+ else out += randomAlpha();
3609
+ }
3610
+ return out;
3611
+ }
3612
+
3613
+ function score(str) {
3614
+ let diffs = 0;
3615
+ for (let i = 0; i < target.length; i++) if (str[i] !== target[i]) diffs++;
3616
+ return diffs;
3617
+ }
3618
+
3619
+ const pref = trace.getTracePrefixes() || PrefixEnv.newDefault();
3620
+ function doTrace(gen, sc, val) {
3621
+ const genLit = internLiteral(String(gen));
3622
+ const scLit = internLiteral(String(sc));
3623
+ const valLit = makeStringLiteral(val);
3624
+ const obj = new ListTerm([scLit, valLit]);
3625
+ const xStr = termToN3(genLit, pref);
3626
+ const yStr = termToN3(obj, pref);
3627
+ trace.writeTraceLine(`${xStr} TRACE ${yStr}`);
3628
+ }
3629
+
3630
+ let generation = 0;
3631
+ let current = randomText(target.length);
3632
+ let currentScore = score(current);
3633
+
3634
+ if (traceEvery > 0 && generation % traceEvery === 0) doTrace(generation, currentScore, current);
3635
+
3636
+ while (currentScore !== 0) {
3637
+ if (maxGenerations > 0 && generation >= maxGenerations) break;
3638
+
3639
+ let best = '';
3640
+ let bestScore = Infinity;
3641
+
3642
+ for (let i = 0; i < samples; i++) {
3643
+ const cand = mutate(current);
3644
+ const candScore = score(cand);
3645
+ if (candScore < bestScore) {
3646
+ best = cand;
3647
+ bestScore = candScore;
3648
+ }
3649
+ }
3650
+
3651
+ generation += 1;
3652
+ current = best;
3653
+ currentScore = bestScore;
3654
+
3655
+ if (traceEvery > 0 && generation % traceEvery === 0) doTrace(generation, currentScore, current);
3656
+ }
3657
+
3658
+ const outList = new ListTerm([
3659
+ internLiteral(String(generation)),
3660
+ internLiteral(String(currentScore)),
3661
+ makeStringLiteral(current),
3662
+ internLiteral(String(seed0)),
3663
+ ]);
3664
+
3665
+ const s2 = unifyTerm(g.o, outList, subst);
3666
+ return s2 !== null ? [s2] : [];
3667
+ }
3668
+
3466
3669
  // Unknown builtin
3467
3670
  return [];
3468
3671
  }
@@ -3483,6 +3686,11 @@ function isBuiltinPred(p) {
3483
3686
  return true;
3484
3687
  }
3485
3688
 
3689
+ // Eyeling extension: GA demo builtins
3690
+ if (v === 'urn:eyeling:ga:solveString') {
3691
+ return true;
3692
+ }
3693
+
3486
3694
  return (
3487
3695
  v.startsWith(CRYPTO_NS) ||
3488
3696
  v.startsWith(MATH_NS) ||
package/lib/builtins.js CHANGED
@@ -3451,6 +3451,209 @@ 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
+ // -----------------------------------------------------------------
3539
+ // 4.6.2 urn:eyeling:ga: builtins (fast GA on *strings*)
3540
+ // -----------------------------------------------------------------
3541
+
3542
+ // urn:eyeling:ga:solveString
3543
+ // Schema: ( $target $mutProb $samples $seed $traceEvery $maxGenerations ) :solveString ( $gen $score $value $seed )
3544
+ // Side effect: if traceEvery > 0, prints: <gen> TRACE (<score> "<value>")
3545
+ if (pv === 'urn:eyeling:ga:solveString') {
3546
+ if (!(g.s instanceof ListTerm) || g.s.elems.length != 6) return [];
3547
+
3548
+ const target = termToJsXsdStringNoLang(g.s.elems[0]);
3549
+ const mutProbNum = parseNum(g.s.elems[1]);
3550
+ const samplesNum = parseNum(g.s.elems[2]);
3551
+ const seedNum = parseNum(g.s.elems[3]);
3552
+ const traceEveryNum = parseNum(g.s.elems[4]);
3553
+ const maxGenNum = parseNum(g.s.elems[5]);
3554
+
3555
+ if (target === null || mutProbNum === null || samplesNum === null || seedNum === null) return [];
3556
+ if (traceEveryNum === null || maxGenNum === null) return [];
3557
+
3558
+ const mutProb = Math.max(0, Math.trunc(mutProbNum));
3559
+ const samples = Math.max(1, Math.trunc(samplesNum));
3560
+ const seed0 = Math.trunc(seedNum);
3561
+
3562
+ const traceEvery = Math.max(0, Math.trunc(traceEveryNum));
3563
+ const maxGenerations = Math.max(0, Math.trunc(maxGenNum));
3564
+
3565
+ // Deterministic 31-bit LCG using BigInt arithmetic (matches the N3 version)
3566
+ let state = BigInt(seed0);
3567
+ const MOD = 2147483648n; // 2^31
3568
+ const A = 1103515245n;
3569
+ const C = 12345n;
3570
+
3571
+ function rnd() {
3572
+ state = (A * state + C) % MOD;
3573
+ return Number(state) / 2147483648;
3574
+ }
3575
+
3576
+ function randRound(N) {
3577
+ return Math.round(rnd() * N);
3578
+ }
3579
+
3580
+ function randomAlpha() {
3581
+ const p = randRound(26);
3582
+ return p === 0 ? ' ' : String.fromCharCode(64 + p);
3583
+ }
3584
+
3585
+ function randomText(len) {
3586
+ let out = '';
3587
+ for (let i = 0; i < len; i++) out += randomAlpha();
3588
+ return out;
3589
+ }
3590
+
3591
+ function mutate(str) {
3592
+ let out = '';
3593
+ for (let i = 0; i < str.length; i++) {
3594
+ const p = randRound(100);
3595
+ if (p > mutProb) out += str[i];
3596
+ else out += randomAlpha();
3597
+ }
3598
+ return out;
3599
+ }
3600
+
3601
+ function score(str) {
3602
+ let diffs = 0;
3603
+ for (let i = 0; i < target.length; i++) if (str[i] !== target[i]) diffs++;
3604
+ return diffs;
3605
+ }
3606
+
3607
+ const pref = trace.getTracePrefixes() || PrefixEnv.newDefault();
3608
+ function doTrace(gen, sc, val) {
3609
+ const genLit = internLiteral(String(gen));
3610
+ const scLit = internLiteral(String(sc));
3611
+ const valLit = makeStringLiteral(val);
3612
+ const obj = new ListTerm([scLit, valLit]);
3613
+ const xStr = termToN3(genLit, pref);
3614
+ const yStr = termToN3(obj, pref);
3615
+ trace.writeTraceLine(`${xStr} TRACE ${yStr}`);
3616
+ }
3617
+
3618
+ let generation = 0;
3619
+ let current = randomText(target.length);
3620
+ let currentScore = score(current);
3621
+
3622
+ if (traceEvery > 0 && generation % traceEvery === 0) doTrace(generation, currentScore, current);
3623
+
3624
+ while (currentScore !== 0) {
3625
+ if (maxGenerations > 0 && generation >= maxGenerations) break;
3626
+
3627
+ let best = '';
3628
+ let bestScore = Infinity;
3629
+
3630
+ for (let i = 0; i < samples; i++) {
3631
+ const cand = mutate(current);
3632
+ const candScore = score(cand);
3633
+ if (candScore < bestScore) {
3634
+ best = cand;
3635
+ bestScore = candScore;
3636
+ }
3637
+ }
3638
+
3639
+ generation += 1;
3640
+ current = best;
3641
+ currentScore = bestScore;
3642
+
3643
+ if (traceEvery > 0 && generation % traceEvery === 0) doTrace(generation, currentScore, current);
3644
+ }
3645
+
3646
+ const outList = new ListTerm([
3647
+ internLiteral(String(generation)),
3648
+ internLiteral(String(currentScore)),
3649
+ makeStringLiteral(current),
3650
+ internLiteral(String(seed0)),
3651
+ ]);
3652
+
3653
+ const s2 = unifyTerm(g.o, outList, subst);
3654
+ return s2 !== null ? [s2] : [];
3655
+ }
3656
+
3454
3657
  // Unknown builtin
3455
3658
  return [];
3456
3659
  }
@@ -3471,6 +3674,11 @@ function isBuiltinPred(p) {
3471
3674
  return true;
3472
3675
  }
3473
3676
 
3677
+ // Eyeling extension: GA demo builtins
3678
+ if (v === 'urn:eyeling:ga:solveString') {
3679
+ return true;
3680
+ }
3681
+
3474
3682
  return (
3475
3683
  v.startsWith(CRYPTO_NS) ||
3476
3684
  v.startsWith(MATH_NS) ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.13.7",
3
+ "version": "1.14.0",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [