eyelang 1.1.13 → 1.1.15
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/docs/guide.md +8 -3
- package/package.json +1 -1
- package/playground.html +313 -152
- package/test/run-regression.mjs +23 -0
package/docs/guide.md
CHANGED
|
@@ -302,10 +302,10 @@ Use `holds/2` when you want to match the member term directly, for example `name
|
|
|
302
302
|
|
|
303
303
|
## Example catalog
|
|
304
304
|
|
|
305
|
-
|
|
305
|
+
Each example has a checked golden output in `examples/output`.
|
|
306
306
|
|
|
307
|
-
|
|
|
308
|
-
|
|
307
|
+
| Example | What it demonstrates | Golden output |
|
|
308
|
+
|---|---|---|
|
|
309
309
|
| [`access-control-policy.pl`](../examples/access-control-policy.pl) | Evaluates role and condition based access decisions. | [`output/access-control-policy.pl`](../examples/output/access-control-policy.pl) |
|
|
310
310
|
| [`ackermann.pl`](../examples/ackermann.pl) | Computes Ackermann-style hyperoperation values. | [`output/ackermann.pl`](../examples/output/ackermann.pl) |
|
|
311
311
|
| [`age.pl`](../examples/age.pl) | Checks whether people meet age thresholds. | [`output/age.pl`](../examples/output/age.pl) |
|
|
@@ -391,6 +391,8 @@ The repository includes examples for recursion, graph reachability, finite searc
|
|
|
391
391
|
| [`heron-theorem.pl`](../examples/heron-theorem.pl) | Computes triangle area by Heron's theorem. | [`output/heron-theorem.pl`](../examples/output/heron-theorem.pl) |
|
|
392
392
|
| [`ideal-gas-law.pl`](../examples/ideal-gas-law.pl) | Applies the ideal gas law. | [`output/ideal-gas-law.pl`](../examples/output/ideal-gas-law.pl) |
|
|
393
393
|
| [`illegitimate-reasoning.pl`](../examples/illegitimate-reasoning.pl) | Detects suspect reasoning patterns. | [`output/illegitimate-reasoning.pl`](../examples/output/illegitimate-reasoning.pl) |
|
|
394
|
+
| [`job-shop-scheduling.pl`](../examples/job-shop-scheduling.pl) | Searches a small job-shop schedule and minimizes makespan. | [`output/job-shop-scheduling.pl`](../examples/output/job-shop-scheduling.pl) |
|
|
395
|
+
| [`knapsack-optimization.pl`](../examples/knapsack-optimization.pl) | Optimizes a finite 0/1 knapsack pack with aggregation. | [`output/knapsack-optimization.pl`](../examples/output/knapsack-optimization.pl) |
|
|
394
396
|
| [`knowledge-engineering-alignment-flow.pl`](../examples/knowledge-engineering-alignment-flow.pl) | Specializes reusable alignment rules into a target-shaped flow view. | [`output/knowledge-engineering-alignment-flow.pl`](../examples/output/knowledge-engineering-alignment-flow.pl) |
|
|
395
397
|
| [`law-of-cosines.pl`](../examples/law-of-cosines.pl) | Computes a triangle side by cosine law. | [`output/law-of-cosines.pl`](../examples/output/law-of-cosines.pl) |
|
|
396
398
|
| [`least-squares-regression.pl`](../examples/least-squares-regression.pl) | Fits a least-squares regression line. | [`output/least-squares-regression.pl`](../examples/output/least-squares-regression.pl) |
|
|
@@ -399,6 +401,7 @@ The repository includes examples for recursion, graph reachability, finite searc
|
|
|
399
401
|
| [`manufacturing-quality-control.pl`](../examples/manufacturing-quality-control.pl) | Evaluates process capability and quality. | [`output/manufacturing-quality-control.pl`](../examples/output/manufacturing-quality-control.pl) |
|
|
400
402
|
| [`microgrid-dispatch.pl`](../examples/microgrid-dispatch.pl) | Plans microgrid dispatch and reserve. | [`output/microgrid-dispatch.pl`](../examples/output/microgrid-dispatch.pl) |
|
|
401
403
|
| [`monkey-bananas.pl`](../examples/monkey-bananas.pl) | Solves the monkey-and-bananas puzzle. | [`output/monkey-bananas.pl`](../examples/output/monkey-bananas.pl) |
|
|
404
|
+
| [`n-queens-8.pl`](../examples/n-queens-8.pl) | Solves the 8-queens search problem with diagonal constraints. | [`output/n-queens-8.pl`](../examples/output/n-queens-8.pl) |
|
|
402
405
|
| [`network-sla.pl`](../examples/network-sla.pl) | Checks network path SLA compliance. | [`output/network-sla.pl`](../examples/output/network-sla.pl) |
|
|
403
406
|
| [`newton-raphson.pl`](../examples/newton-raphson.pl) | Finds roots by Newton-Raphson iteration. | [`output/newton-raphson.pl`](../examples/output/newton-raphson.pl) |
|
|
404
407
|
| [`nixon-diamond.pl`](../examples/nixon-diamond.pl) | Reports the classic Nixon-diamond conflict. | [`output/nixon-diamond.pl`](../examples/output/nixon-diamond.pl) |
|
|
@@ -418,12 +421,14 @@ The repository includes examples for recursion, graph reachability, finite searc
|
|
|
418
421
|
| [`reusable-builtins.pl`](../examples/reusable-builtins.pl) | Tours reusable numeric, list, and string builtins. | [`output/reusable-builtins.pl`](../examples/output/reusable-builtins.pl) |
|
|
419
422
|
| [`riemann-hypothesis.pl`](../examples/riemann-hypothesis.pl) | Checks a finite catalogue of non-trivial zeta zeros against the Riemann-hypothesis condition. | [`output/riemann-hypothesis.pl`](../examples/output/riemann-hypothesis.pl) |
|
|
420
423
|
| [`security-incident-correlation.pl`](../examples/security-incident-correlation.pl) | Correlates security incidents across signals. | [`output/security-incident-correlation.pl`](../examples/output/security-incident-correlation.pl) |
|
|
424
|
+
| [`send-more-money.pl`](../examples/send-more-money.pl) | Solves the SEND + MORE = MONEY cryptarithm. | [`output/send-more-money.pl`](../examples/output/send-more-money.pl) |
|
|
421
425
|
| [`service-impact.pl`](../examples/service-impact.pl) | Analyzes service impact over cyclic dependencies. | [`output/service-impact.pl`](../examples/output/service-impact.pl) |
|
|
422
426
|
| [`sieve.pl`](../examples/sieve.pl) | Enumerates primes with a sieve-style program. | [`output/sieve.pl`](../examples/output/sieve.pl) |
|
|
423
427
|
| [`skolem-functions.pl`](../examples/skolem-functions.pl) | Generates deterministic functional terms. | [`output/skolem-functions.pl`](../examples/output/skolem-functions.pl) |
|
|
424
428
|
| [`socket-age.pl`](../examples/socket-age.pl) | Shows socket-declared age reasoning inputs and plugs. | [`output/socket-age.pl`](../examples/output/socket-age.pl) |
|
|
425
429
|
| [`socket-family.pl`](../examples/socket-family.pl) | Shows socket-declared family-source inputs and ancestry rules. | [`output/socket-family.pl`](../examples/output/socket-family.pl) |
|
|
426
430
|
| [`socrates.pl`](../examples/socrates.pl) | Derives that Socrates is mortal. | [`output/socrates.pl`](../examples/output/socrates.pl) |
|
|
431
|
+
| [`stable-marriage.pl`](../examples/stable-marriage.pl) | Finds stable matchings by excluding blocking pairs. | [`output/stable-marriage.pl`](../examples/output/stable-marriage.pl) |
|
|
427
432
|
| [`statistics-summary.pl`](../examples/statistics-summary.pl) | Computes population statistics for a sample. | [`output/statistics-summary.pl`](../examples/output/statistics-summary.pl) |
|
|
428
433
|
| [`superdense-coding.pl`](../examples/superdense-coding.pl) | Models superdense-coding bit transmission. | [`output/superdense-coding.pl`](../examples/output/superdense-coding.pl) |
|
|
429
434
|
| [`term-tools.pl`](../examples/term-tools.pl) | Inspects, builds, renders, and validates terms with reusable term/control builtins. | [`output/term-tools.pl`](../examples/output/term-tools.pl) |
|
package/package.json
CHANGED
package/playground.html
CHANGED
|
@@ -408,6 +408,7 @@
|
|
|
408
408
|
<button id="copy-source" type="button">Copy source</button>
|
|
409
409
|
<button id="copy-output" type="button">Copy output</button>
|
|
410
410
|
<button id="share" type="button">Copy share link</button>
|
|
411
|
+
<button id="create-gist" type="button">Create Gist share</button>
|
|
411
412
|
</div>
|
|
412
413
|
</section>
|
|
413
414
|
|
|
@@ -433,139 +434,146 @@
|
|
|
433
434
|
|
|
434
435
|
<script type="module">
|
|
435
436
|
const EXAMPLES = [
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
437
|
+
"access-control-policy",
|
|
438
|
+
"ackermann",
|
|
439
|
+
"age",
|
|
440
|
+
"aliases-and-namespaces",
|
|
441
|
+
"alignment-demo",
|
|
442
|
+
"allen-interval-calculus",
|
|
443
|
+
"ancestor",
|
|
444
|
+
"animal",
|
|
445
|
+
"annotation",
|
|
446
|
+
"auroracare",
|
|
447
|
+
"backward",
|
|
448
|
+
"basic-monadic",
|
|
449
|
+
"bayes-diagnosis",
|
|
450
|
+
"bayes-therapy",
|
|
451
|
+
"beam-deflection",
|
|
452
|
+
"blocks-world-planning",
|
|
453
|
+
"bmi",
|
|
454
|
+
"braking-safety-worlds",
|
|
455
|
+
"buck-converter-design",
|
|
456
|
+
"cache-performance",
|
|
457
|
+
"canary-release",
|
|
458
|
+
"cat-koko",
|
|
459
|
+
"clinical-trial-screening",
|
|
460
|
+
"collatz-1000",
|
|
461
|
+
"combinatorics-findall-sort",
|
|
462
|
+
"competitive-enzyme-kinetics",
|
|
463
|
+
"complex",
|
|
464
|
+
"composition-of-injective-functions-is-injective",
|
|
465
|
+
"context-association",
|
|
466
|
+
"context-schema-audit",
|
|
467
|
+
"control-system",
|
|
468
|
+
"cyclic-path",
|
|
469
|
+
"d3-group",
|
|
470
|
+
"dairy-energy-balance",
|
|
471
|
+
"data-negotiation",
|
|
472
|
+
"deep-taxonomy-10",
|
|
473
|
+
"deep-taxonomy-100",
|
|
474
|
+
"deep-taxonomy-1000",
|
|
475
|
+
"deep-taxonomy-10000",
|
|
476
|
+
"deep-taxonomy-100000",
|
|
477
|
+
"delfour",
|
|
478
|
+
"deontic-logic",
|
|
479
|
+
"derived-backward-rule",
|
|
480
|
+
"derived-rule",
|
|
481
|
+
"diamond-property",
|
|
482
|
+
"dijkstra",
|
|
483
|
+
"dijkstra-findall-sort",
|
|
484
|
+
"dijkstra-risk-path",
|
|
485
|
+
"dining-philosophers",
|
|
486
|
+
"dog",
|
|
487
|
+
"dpv-odrl-purpose-mapping",
|
|
488
|
+
"drone-corridor-planner",
|
|
489
|
+
"easter-computus",
|
|
490
|
+
"electrical-rc-filter",
|
|
491
|
+
"epidemic-policy",
|
|
492
|
+
"equivalence-classes-overlap-implies-same-class",
|
|
493
|
+
"eulerian-path",
|
|
494
|
+
"ev-range-worlds",
|
|
495
|
+
"existential-rule",
|
|
496
|
+
"exoplanet-validation-worlds",
|
|
497
|
+
"expression-eval",
|
|
498
|
+
"family-cousins",
|
|
499
|
+
"fastpow",
|
|
500
|
+
"fft8-numeric",
|
|
501
|
+
"fibonacci",
|
|
502
|
+
"field-nitrogen-balance",
|
|
503
|
+
"flandor",
|
|
504
|
+
"floating-point",
|
|
505
|
+
"four-color-map",
|
|
506
|
+
"fundamental-theorem-arithmetic",
|
|
507
|
+
"gd-step-certified",
|
|
508
|
+
"gdpr-compliance",
|
|
509
|
+
"good-cobbler",
|
|
510
|
+
"gps",
|
|
511
|
+
"graph-reachability",
|
|
512
|
+
"gray-code-counter",
|
|
513
|
+
"greatest-lower-bound-uniqueness",
|
|
514
|
+
"group-inverse-uniqueness",
|
|
515
|
+
"hamiltonian-path",
|
|
516
|
+
"hamming-code",
|
|
517
|
+
"hanoi",
|
|
518
|
+
"heat-loss",
|
|
519
|
+
"heron-theorem",
|
|
520
|
+
"ideal-gas-law",
|
|
521
|
+
"illegitimate-reasoning",
|
|
522
|
+
"job-shop-scheduling",
|
|
523
|
+
"knapsack-optimization",
|
|
524
|
+
"knowledge-engineering-alignment-flow",
|
|
525
|
+
"law-of-cosines",
|
|
526
|
+
"least-squares-regression",
|
|
527
|
+
"list-collection",
|
|
528
|
+
"lldm",
|
|
529
|
+
"manufacturing-quality-control",
|
|
530
|
+
"microgrid-dispatch",
|
|
531
|
+
"monkey-bananas",
|
|
532
|
+
"n-queens-8",
|
|
533
|
+
"network-sla",
|
|
534
|
+
"newton-raphson",
|
|
535
|
+
"nixon-diamond",
|
|
536
|
+
"observability-log-correlation",
|
|
537
|
+
"odrl-dpv-fpv-trust-flow",
|
|
538
|
+
"odrl-dpv-healthcare-risk-ranked",
|
|
539
|
+
"odrl-dpv-risk-ranked",
|
|
540
|
+
"orbital-transfer-design",
|
|
541
|
+
"path-discovery",
|
|
542
|
+
"peano-arithmetic",
|
|
543
|
+
"peasant",
|
|
544
|
+
"pendulum-period",
|
|
545
|
+
"polynomial",
|
|
546
|
+
"proof-contrapositive",
|
|
547
|
+
"quadratic-formula",
|
|
548
|
+
"radioactive-decay",
|
|
549
|
+
"reusable-builtins",
|
|
550
|
+
"riemann-hypothesis",
|
|
551
|
+
"security-incident-correlation",
|
|
552
|
+
"send-more-money",
|
|
553
|
+
"service-impact",
|
|
554
|
+
"sieve",
|
|
555
|
+
"skolem-functions",
|
|
556
|
+
"socket-age",
|
|
557
|
+
"socket-family",
|
|
558
|
+
"socrates",
|
|
559
|
+
"stable-marriage",
|
|
560
|
+
"statistics-summary",
|
|
561
|
+
"superdense-coding",
|
|
562
|
+
"term-tools",
|
|
563
|
+
"trust-flow-provenance-threshold",
|
|
564
|
+
"turing",
|
|
565
|
+
"vector-similarity",
|
|
566
|
+
"vulnerability-impact",
|
|
567
|
+
"witch",
|
|
568
|
+
"wolf-goat-cabbage",
|
|
569
|
+
"zebra"
|
|
570
|
+
];
|
|
565
571
|
const FALLBACK_SOURCE = `materialize(answer, 1).
|
|
566
572
|
answer(ok) :- eq(ok, ok).
|
|
567
573
|
`;
|
|
568
574
|
const HIGHLIGHT_LIMIT = 200000;
|
|
575
|
+
const MAX_SHARE_URL_LENGTH = 1900;
|
|
576
|
+
const GIST_STATE_FILENAME = 'eyelang-playground-state.json';
|
|
569
577
|
const KEYWORDS = new Set(['materialize', 'memoize']);
|
|
570
578
|
const BUILTINS = new Set([
|
|
571
579
|
"abs",
|
|
@@ -671,12 +679,17 @@ answer(ok) :- eq(ok, ok).
|
|
|
671
679
|
let activeWorkerUrl = null;
|
|
672
680
|
let renderToken = 0;
|
|
673
681
|
let syntaxErrorLine = null;
|
|
682
|
+
let sourceReference = { kind: 'custom' };
|
|
683
|
+
let sourceDirty = false;
|
|
684
|
+
let backgroundReference = null;
|
|
674
685
|
|
|
675
686
|
populateExamples(EXAMPLES);
|
|
676
|
-
|
|
687
|
+
await restoreFromHashOrLoadDefault();
|
|
677
688
|
loadVersion();
|
|
678
689
|
|
|
679
690
|
source.addEventListener('input', () => {
|
|
691
|
+
sourceDirty = true;
|
|
692
|
+
sourceReference = { kind: 'custom' };
|
|
680
693
|
clearSyntaxError();
|
|
681
694
|
render();
|
|
682
695
|
});
|
|
@@ -690,6 +703,7 @@ answer(ok) :- eq(ok, ok).
|
|
|
690
703
|
document.querySelector('#copy-source').addEventListener('click', () => copyText(source.value, 'Source copied.'));
|
|
691
704
|
document.querySelector('#copy-output').addEventListener('click', () => copyText(output.textContent, 'Output copied.'));
|
|
692
705
|
document.querySelector('#share').addEventListener('click', copyShareLink);
|
|
706
|
+
document.querySelector('#create-gist').addEventListener('click', createGistShare);
|
|
693
707
|
|
|
694
708
|
function populateExamples(names) {
|
|
695
709
|
const current = exampleSelect.value;
|
|
@@ -716,7 +730,7 @@ answer(ok) :- eq(ok, ok).
|
|
|
716
730
|
const exampleUrl = new URL(`./examples/${name}.pl`, location.href);
|
|
717
731
|
const response = await fetch(exampleUrl, { cache: 'no-store' });
|
|
718
732
|
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
719
|
-
setSource(await response.text(), `examples/${name}.pl
|
|
733
|
+
setSource(await response.text(), `examples/${name}.pl`, { kind: 'example', name });
|
|
720
734
|
setStatus(`Loaded examples/${name}.pl.`);
|
|
721
735
|
} catch (error) {
|
|
722
736
|
setSource(FALLBACK_SOURCE, 'fallback program');
|
|
@@ -735,10 +749,11 @@ answer(ok) :- eq(ok, ok).
|
|
|
735
749
|
if (loadBackground.checked) {
|
|
736
750
|
backgroundSource = text;
|
|
737
751
|
backgroundName = url;
|
|
752
|
+
backgroundReference = { kind: 'url', url };
|
|
738
753
|
updateBackgroundStatus();
|
|
739
754
|
setStatus(`Loaded background knowledge from ${url}.`);
|
|
740
755
|
} else {
|
|
741
|
-
setSource(text, url);
|
|
756
|
+
setSource(text, url, { kind: 'url', url });
|
|
742
757
|
setStatus(`Loaded ${url}.`);
|
|
743
758
|
}
|
|
744
759
|
} catch (error) {
|
|
@@ -749,6 +764,7 @@ answer(ok) :- eq(ok, ok).
|
|
|
749
764
|
function clearBackground() {
|
|
750
765
|
backgroundSource = '';
|
|
751
766
|
backgroundName = '';
|
|
767
|
+
backgroundReference = null;
|
|
752
768
|
updateBackgroundStatus();
|
|
753
769
|
setStatus('Background knowledge cleared.');
|
|
754
770
|
}
|
|
@@ -759,9 +775,11 @@ answer(ok) :- eq(ok, ok).
|
|
|
759
775
|
: 'No background knowledge loaded.';
|
|
760
776
|
}
|
|
761
777
|
|
|
762
|
-
function setSource(text, name) {
|
|
778
|
+
function setSource(text, name, reference = { kind: 'custom' }) {
|
|
763
779
|
source.value = text;
|
|
764
780
|
sourceName.textContent = name;
|
|
781
|
+
sourceReference = reference;
|
|
782
|
+
sourceDirty = false;
|
|
765
783
|
clearSyntaxError();
|
|
766
784
|
render();
|
|
767
785
|
}
|
|
@@ -1001,39 +1019,182 @@ answer(ok) :- eq(ok, ok).
|
|
|
1001
1019
|
}
|
|
1002
1020
|
|
|
1003
1021
|
async function copyShareLink() {
|
|
1004
|
-
const
|
|
1022
|
+
const link = buildShareLink();
|
|
1023
|
+
if (link != null) {
|
|
1024
|
+
await copyText(link, 'Share link copied.');
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
await createGistShare({
|
|
1028
|
+
reason: 'This program is too large for an inline URL, so Copy share link will create a Gist share instead.',
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function buildShareLink() {
|
|
1033
|
+
const referenced = buildReferenceShareLink();
|
|
1034
|
+
if (referenced && referenced.length <= MAX_SHARE_URL_LENGTH) return referenced;
|
|
1035
|
+
const embedded = `${basePlaygroundUrl()}#state=${encodeState(currentShareState())}`;
|
|
1036
|
+
return embedded.length <= MAX_SHARE_URL_LENGTH ? embedded : null;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function buildReferenceShareLink() {
|
|
1040
|
+
const params = new URLSearchParams();
|
|
1041
|
+
if (proof.checked) params.set('proof', '1');
|
|
1042
|
+
if (stats.checked) params.set('stats', '1');
|
|
1043
|
+
if (backgroundSource.trim()) {
|
|
1044
|
+
if (backgroundReference?.kind !== 'url') return null;
|
|
1045
|
+
params.set('background-url', backgroundReference.url);
|
|
1046
|
+
}
|
|
1047
|
+
if (!sourceDirty && sourceReference.kind === 'example') params.set('example', sourceReference.name);
|
|
1048
|
+
else if (!sourceDirty && sourceReference.kind === 'url') params.set('url', sourceReference.url);
|
|
1049
|
+
else return null;
|
|
1050
|
+
return `${basePlaygroundUrl()}#${params.toString()}`;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
async function createGistShare(options = {}) {
|
|
1054
|
+
const stateText = JSON.stringify(currentShareState(), null, 2);
|
|
1055
|
+
if (options.reason) setStatus(options.reason);
|
|
1056
|
+
const token = prompt(`${options.reason ? `${options.reason}\n\n` : ''}Optional GitHub token with gist scope. It is only sent to api.github.com and is not stored. Leave blank to copy Gist-ready state and open gist.github.com.`);
|
|
1057
|
+
if (token === null) {
|
|
1058
|
+
setStatus('Gist share cancelled.');
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
if (!token.trim()) {
|
|
1062
|
+
await copyText(stateText, `Gist-ready state copied. Create a Gist file named ${GIST_STATE_FILENAME}, paste it, then copy its raw URL into a playground #state-url link.`);
|
|
1063
|
+
window.open('https://gist.github.com/', '_blank', 'noopener');
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
try {
|
|
1068
|
+
setStatus('Creating GitHub Gist…');
|
|
1069
|
+
const files = {
|
|
1070
|
+
[GIST_STATE_FILENAME]: { content: stateText },
|
|
1071
|
+
'program.pl': { content: source.value },
|
|
1072
|
+
};
|
|
1073
|
+
if (backgroundSource.trim()) files['background.pl'] = { content: backgroundSource };
|
|
1074
|
+
const response = await fetch('https://api.github.com/gists', {
|
|
1075
|
+
method: 'POST',
|
|
1076
|
+
headers: {
|
|
1077
|
+
Accept: 'application/vnd.github+json',
|
|
1078
|
+
Authorization: `Bearer ${token.trim()}`,
|
|
1079
|
+
'Content-Type': 'application/json',
|
|
1080
|
+
},
|
|
1081
|
+
body: JSON.stringify({
|
|
1082
|
+
description: 'Eyelang playground share',
|
|
1083
|
+
public: false,
|
|
1084
|
+
files,
|
|
1085
|
+
}),
|
|
1086
|
+
});
|
|
1087
|
+
const gist = await response.json().catch(() => ({}));
|
|
1088
|
+
if (!response.ok) throw new Error(gist.message || `${response.status} ${response.statusText}`);
|
|
1089
|
+
const rawUrl = gist.files?.[GIST_STATE_FILENAME]?.raw_url;
|
|
1090
|
+
if (!rawUrl) throw new Error('Gist response did not include a raw state URL.');
|
|
1091
|
+
await copyText(`${basePlaygroundUrl()}#state-url=${encodeURIComponent(rawUrl)}`, 'Gist share link copied.');
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
await copyText(stateText, `Could not create Gist: ${formatError(error)}. Gist-ready state copied instead.`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function currentShareState() {
|
|
1098
|
+
return {
|
|
1005
1099
|
source: source.value,
|
|
1006
1100
|
proof: proof.checked,
|
|
1007
1101
|
stats: stats.checked,
|
|
1008
1102
|
backgroundSource,
|
|
1009
1103
|
backgroundName,
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function encodeState(payload) {
|
|
1108
|
+
const data = new TextEncoder().encode(JSON.stringify(payload));
|
|
1012
1109
|
let binary = '';
|
|
1013
1110
|
for (const byte of data) binary += String.fromCharCode(byte);
|
|
1014
|
-
|
|
1015
|
-
await copyText(`${location.href.split('#')[0]}#state=${encoded}`, 'Share link copied.');
|
|
1111
|
+
return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
|
|
1016
1112
|
}
|
|
1017
1113
|
|
|
1018
|
-
function
|
|
1019
|
-
|
|
1114
|
+
function decodeState(encoded) {
|
|
1115
|
+
const base64 = encoded.replaceAll('-', '+').replaceAll('_', '/');
|
|
1116
|
+
const padded = base64 + '='.repeat((4 - base64.length % 4) % 4);
|
|
1117
|
+
const bytes = Uint8Array.from(atob(padded), (ch) => ch.charCodeAt(0));
|
|
1118
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function basePlaygroundUrl() {
|
|
1122
|
+
return location.href.split('#')[0];
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
async function restoreFromHashOrLoadDefault() {
|
|
1126
|
+
if (!(await restoreFromHash())) await loadExample('ancestor');
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
async function restoreFromHash() {
|
|
1130
|
+
if (!location.hash || location.hash === '#') return false;
|
|
1131
|
+
const params = new URLSearchParams(location.hash.slice(1));
|
|
1020
1132
|
try {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1133
|
+
if (params.has('state')) {
|
|
1134
|
+
applySharedState(decodeState(params.get('state')), 'shared program');
|
|
1135
|
+
setStatus('Loaded shared program.');
|
|
1136
|
+
return true;
|
|
1137
|
+
}
|
|
1138
|
+
if (params.has('state-url')) {
|
|
1139
|
+
const stateUrl = params.get('state-url');
|
|
1140
|
+
const response = await fetch(stateUrl);
|
|
1141
|
+
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
1142
|
+
applySharedState(await response.json(), `shared state from ${stateUrl}`);
|
|
1143
|
+
setStatus('Loaded shared Gist state.');
|
|
1144
|
+
return true;
|
|
1145
|
+
}
|
|
1146
|
+
await restoreBackgroundUrl(params);
|
|
1147
|
+
applyOptionParams(params);
|
|
1148
|
+
if (params.has('example')) {
|
|
1149
|
+
await loadExample(params.get('example'));
|
|
1150
|
+
applyOptionParams(params);
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
if (params.has('url')) {
|
|
1154
|
+
await loadSourceFromUrl(params.get('url'), false);
|
|
1155
|
+
applyOptionParams(params);
|
|
1156
|
+
return true;
|
|
1157
|
+
}
|
|
1033
1158
|
} catch (error) {
|
|
1034
1159
|
setStatus(`Could not read shared state: ${formatError(error)}`, true);
|
|
1035
1160
|
return false;
|
|
1036
1161
|
}
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
async function restoreBackgroundUrl(params) {
|
|
1166
|
+
const url = params.get('background-url');
|
|
1167
|
+
if (!url) return;
|
|
1168
|
+
await loadSourceFromUrl(url, true);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
async function loadSourceFromUrl(url, asBackground) {
|
|
1172
|
+
const response = await fetch(url);
|
|
1173
|
+
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
1174
|
+
const text = await response.text();
|
|
1175
|
+
if (asBackground) {
|
|
1176
|
+
backgroundSource = text;
|
|
1177
|
+
backgroundName = url;
|
|
1178
|
+
backgroundReference = { kind: 'url', url };
|
|
1179
|
+
updateBackgroundStatus();
|
|
1180
|
+
} else {
|
|
1181
|
+
setSource(text, url, { kind: 'url', url });
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function applySharedState(payload, label) {
|
|
1186
|
+
setSource(String(payload.source || ''), label, { kind: 'custom' });
|
|
1187
|
+
proof.checked = Boolean(payload.proof);
|
|
1188
|
+
stats.checked = Boolean(payload.stats);
|
|
1189
|
+
backgroundSource = String(payload.backgroundSource || '');
|
|
1190
|
+
backgroundName = String(payload.backgroundName || 'shared background');
|
|
1191
|
+
backgroundReference = null;
|
|
1192
|
+
updateBackgroundStatus();
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function applyOptionParams(params) {
|
|
1196
|
+
proof.checked = params.get('proof') === '1' || params.get('proof') === 'true';
|
|
1197
|
+
stats.checked = params.get('stats') === '1' || params.get('stats') === 'true';
|
|
1037
1198
|
}
|
|
1038
1199
|
|
|
1039
1200
|
async function loadVersion() {
|
package/test/run-regression.mjs
CHANGED
|
@@ -524,6 +524,17 @@ function whiteBoxCases() {
|
|
|
524
524
|
assertEqual(termToString(candidates.primary[0].head, new Env(), true), 'edge(a, b)', 'primary head');
|
|
525
525
|
},
|
|
526
526
|
},
|
|
527
|
+
{
|
|
528
|
+
name: 'n-queens example keeps diagonal checks memoized',
|
|
529
|
+
run: () => {
|
|
530
|
+
const text = fs.readFileSync(path.join(packageRoot, 'examples', 'n-queens-8.pl'), 'utf8');
|
|
531
|
+
const program = Program.parseSources([{ text, filename: 'n-queens-8.pl' }]);
|
|
532
|
+
const group = program.findGroup('no_diagonal_attack', 3);
|
|
533
|
+
assertEqual(Boolean(group), true, 'no_diagonal_attack/3 group exists');
|
|
534
|
+
assertEqual(group.memoized, true, 'no_diagonal_attack/3 memoized');
|
|
535
|
+
assertEqual(group.recursive, true, 'no_diagonal_attack/3 recursive');
|
|
536
|
+
},
|
|
537
|
+
},
|
|
527
538
|
{
|
|
528
539
|
name: 'collatz example keeps recursive trajectory predicate memoized',
|
|
529
540
|
run: () => {
|
|
@@ -678,6 +689,18 @@ function playgroundStaticIssues() {
|
|
|
678
689
|
if (!html.includes('id="line-numbers"') || !html.includes('updateLineNumbers') || !html.includes('lineNumbersInner.style.transform') || !html.includes('--line-number-bg')) {
|
|
679
690
|
issues.push('playground editor must include synced line numbers');
|
|
680
691
|
}
|
|
692
|
+
if (!html.includes('MAX_SHARE_URL_LENGTH') || !html.includes('buildReferenceShareLink') || !html.includes("params.set('example'") || !html.includes("params.set('url'")) {
|
|
693
|
+
issues.push('playground share links must avoid embedding large example or URL-loaded sources');
|
|
694
|
+
}
|
|
695
|
+
if (!html.includes('id="create-gist"') || !html.includes('createGistShare') || !html.includes('GIST_STATE_FILENAME') || !html.includes("fetch('https://api.github.com/gists'")) {
|
|
696
|
+
issues.push('playground must support Gist-backed sharing for large programs');
|
|
697
|
+
}
|
|
698
|
+
if (!html.includes('await createGistShare({') || html.includes('Use “Create Gist share” instead')) {
|
|
699
|
+
issues.push('playground Copy share link must automatically fall back to Gist sharing for large programs');
|
|
700
|
+
}
|
|
701
|
+
if (!html.includes("params.has('state-url')") || !html.includes('#state-url=')) {
|
|
702
|
+
issues.push('playground must restore state from raw Gist state URLs');
|
|
703
|
+
}
|
|
681
704
|
if (!html.includes('id="example-search"') || !html.includes('id="examples"')) issues.push('playground must include searchable examples');
|
|
682
705
|
const scriptMatch = html.match(new RegExp('<script type="module">\\n([\\s\\S]*?)\\n <\\/script>'));
|
|
683
706
|
if (scriptMatch == null) {
|