@skillstech/thunderlang 0.1.7 → 0.3.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/CHANGELOG.md +53 -0
- package/dist/core.cjs +240 -20
- package/dist/index.cjs +241 -21
- package/intent-graph.schema.json +227 -4
- package/package.json +1 -1
- package/src/cli.mjs +632 -23
- package/src/conformance.mjs +35 -0
- package/src/coverage.mjs +56 -0
- package/src/emit.mjs +3 -1
- package/src/expr.mjs +11 -7
- package/src/intent-graph.mjs +29 -0
- package/src/lift.mjs +79 -3
- package/src/mutate.mjs +46 -0
- package/src/parse.mjs +82 -3
- package/src/property.mjs +108 -0
- package/src/target-cs.mjs +98 -0
- package/src/target-java.mjs +87 -0
- package/src/target-py.mjs +90 -0
- package/src/target-ts.mjs +66 -0
- package/src/target-util.mjs +95 -0
- package/src/testing.mjs +1 -1
package/dist/index.cjs
CHANGED
|
@@ -450,6 +450,60 @@ function parseDecision(name, node2) {
|
|
|
450
450
|
}
|
|
451
451
|
return dec;
|
|
452
452
|
}
|
|
453
|
+
function parseProperty(name, node2) {
|
|
454
|
+
const prop = { name, vars: [], decide: null, expects: [], line: node2.line };
|
|
455
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
456
|
+
const k = firstWord(c.text);
|
|
457
|
+
const a = rest(c.text);
|
|
458
|
+
if (k === "forAll" || k === "forall") {
|
|
459
|
+
for (const v of c.children.filter((x) => !isNote(x))) {
|
|
460
|
+
const m = v.text.trim().match(/^([A-Za-z_]\w*)\s*:\s*(\w+)(?:\s+where\s+(.+))?$/);
|
|
461
|
+
if (!m) continue;
|
|
462
|
+
const whereChild = (v.children || []).find((x) => firstWord(x.text) === "where");
|
|
463
|
+
prop.vars.push({ name: m[1], type: m[2], where: m[3] || (whereChild ? rest(whereChild.text) : null) });
|
|
464
|
+
}
|
|
465
|
+
} else if (k === "decide") prop.decide = a || c.children[0] && c.children[0].text.trim() || null;
|
|
466
|
+
else if (k === "when") {
|
|
467
|
+
const dm = a.match(/(?:decide|execute)\s+(\w+)/);
|
|
468
|
+
if (dm) prop.decide = dm[1];
|
|
469
|
+
} else if (k === "expect") for (const e of c.children.filter((x) => !isNote(x))) prop.expects.push(e.text.trim());
|
|
470
|
+
}
|
|
471
|
+
return prop;
|
|
472
|
+
}
|
|
473
|
+
function parseEvaluation(name, node2) {
|
|
474
|
+
const ev = { name, dataset: null, requires: [], line: node2.line };
|
|
475
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
476
|
+
const k = firstWord(c.text);
|
|
477
|
+
const a = rest(c.text);
|
|
478
|
+
if (k === "dataset") ev.dataset = a || c.children[0] && c.children[0].text.trim() || null;
|
|
479
|
+
else if (k === "require") {
|
|
480
|
+
for (const r of c.children.filter((x) => !isNote(x))) {
|
|
481
|
+
const m = r.text.trim().match(/^([A-Za-z_][\w.]*)\s*(>=|<=|==|!=|>|<)\s*(-?[\d.]+)$/);
|
|
482
|
+
if (m) ev.requires.push({ metric: m[1], op: m[2], threshold: parseFloat(m[3]) });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return ev;
|
|
487
|
+
}
|
|
488
|
+
function parseScenario(name, node2) {
|
|
489
|
+
const sc = { name, given: [], when: [], then: [], never: [], eventually: [], line: node2.line };
|
|
490
|
+
const clausesOf = (c) => {
|
|
491
|
+
const items = leafItems(c);
|
|
492
|
+
return items.length ? items : rest(c.text) ? [rest(c.text)] : [];
|
|
493
|
+
};
|
|
494
|
+
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
495
|
+
const k = firstWord(c.text);
|
|
496
|
+
if (k === "given") sc.given.push(...clausesOf(c));
|
|
497
|
+
else if (k === "when") sc.when.push(...clausesOf(c));
|
|
498
|
+
else if (k === "then") sc.then.push(...clausesOf(c));
|
|
499
|
+
else if (k === "never") sc.never.push(...clausesOf(c));
|
|
500
|
+
else if (k === "eventually") {
|
|
501
|
+
const within = (rest(c.text).match(/within\s+(.+)/) || [])[1] || null;
|
|
502
|
+
sc.eventually.push({ within, clauses: leafItems(c) });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return sc;
|
|
506
|
+
}
|
|
453
507
|
function parseLifecycle(name, node2) {
|
|
454
508
|
const lc = { name, states: [], transitions: [], terminals: [], line: node2.line };
|
|
455
509
|
for (const c of node2.children.filter((x) => !isNote(x))) {
|
|
@@ -482,7 +536,13 @@ function parseInvariant(name, node2) {
|
|
|
482
536
|
for (const c of kids(node2)) {
|
|
483
537
|
const k = firstWord(c.text);
|
|
484
538
|
if (k === "verify") inv.verify.push(rest(c.text));
|
|
485
|
-
else if (k === "
|
|
539
|
+
else if (k === "id") {
|
|
540
|
+
const v = rest(c.text);
|
|
541
|
+
if (v) {
|
|
542
|
+
inv.id = v;
|
|
543
|
+
inv.stableId = v;
|
|
544
|
+
}
|
|
545
|
+
} else if (k === "because") inv.because = rest(c.text);
|
|
486
546
|
else if (k === "statement") inv.statement = rest(c.text) || leafItems(c).join(" ");
|
|
487
547
|
else if (k === "scope") inv.scope = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
488
548
|
else if (k === "severity") inv.severity = (rest(c.text) || leafItems(c).join(" ")).toLowerCase();
|
|
@@ -568,11 +628,12 @@ function parseHandler(trigger, node2) {
|
|
|
568
628
|
actions
|
|
569
629
|
};
|
|
570
630
|
}
|
|
631
|
+
var VERIFY_KINDS = /* @__PURE__ */ new Set(["assertion", "static", "runtime", "evidence", "tool", "approval", "formal"]);
|
|
571
632
|
function upsertRule(list, statement, line) {
|
|
572
633
|
const id = slug(statement);
|
|
573
634
|
let r = list.find((x) => x.id === id);
|
|
574
635
|
if (!r) {
|
|
575
|
-
r = { id, statement, because: null, verify: [], notes: [], line };
|
|
636
|
+
r = { id, statement, because: null, verify: [], verifications: [], notes: [], line };
|
|
576
637
|
list.push(r);
|
|
577
638
|
}
|
|
578
639
|
return r;
|
|
@@ -581,8 +642,28 @@ function applyDetail(rule, node2) {
|
|
|
581
642
|
for (const c of node2.children) {
|
|
582
643
|
const kw = firstWord(c.text);
|
|
583
644
|
if (kw === "because") rule.because = rest(c.text) || c.children[0] && c.children[0].text || null;
|
|
584
|
-
else if (kw === "verify")
|
|
585
|
-
|
|
645
|
+
else if (kw === "verify") {
|
|
646
|
+
const a = rest(c.text);
|
|
647
|
+
const m = a && a.match(/^by\s+(\w+)/i);
|
|
648
|
+
if (m) {
|
|
649
|
+
const kind = m[1].toLowerCase();
|
|
650
|
+
const details = (c.children || []).filter((x) => !isNote(x)).map((x) => x.text.trim()).filter(Boolean);
|
|
651
|
+
rule.verifications.push({ kind: VERIFY_KINDS.has(kind) ? kind : "unknown", declared: kind, details });
|
|
652
|
+
rule.verify.push(details.length ? `${kind}: ${details.join("; ")}` : `by ${kind}`);
|
|
653
|
+
} else {
|
|
654
|
+
const v = a || c.children[0] && c.children[0].text || "";
|
|
655
|
+
if (v) {
|
|
656
|
+
rule.verify.push(v);
|
|
657
|
+
rule.verifications.push({ kind: "unclassified", declared: null, details: [v] });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
} else if (kw === "id") {
|
|
661
|
+
const v = rest(c.text) || c.children[0] && c.children[0].text;
|
|
662
|
+
if (v) {
|
|
663
|
+
rule.id = v;
|
|
664
|
+
rule.stableId = v;
|
|
665
|
+
}
|
|
666
|
+
} else if (isNote(c)) rule.notes.push(parseNoteNode(c));
|
|
586
667
|
}
|
|
587
668
|
rule.verify = rule.verify.filter(Boolean);
|
|
588
669
|
}
|
|
@@ -647,6 +728,9 @@ function parseIntent(source) {
|
|
|
647
728
|
handlers: [],
|
|
648
729
|
// Decisions, rules, process (Gap 4)
|
|
649
730
|
decisions: [],
|
|
731
|
+
properties: [],
|
|
732
|
+
scenarios: [],
|
|
733
|
+
evaluations: [],
|
|
650
734
|
// Governance: waivers , governed exceptions to blocking diagnostics (Gap 5)
|
|
651
735
|
waivers: [],
|
|
652
736
|
// Data purpose + privacy , governed data elements (Gap 6)
|
|
@@ -875,6 +959,15 @@ function parseIntent(source) {
|
|
|
875
959
|
case "decision":
|
|
876
960
|
ast.decisions.push(parseDecision(arg, node2));
|
|
877
961
|
break;
|
|
962
|
+
case "property":
|
|
963
|
+
ast.properties.push(parseProperty(arg, node2));
|
|
964
|
+
break;
|
|
965
|
+
case "scenario":
|
|
966
|
+
ast.scenarios.push(parseScenario(arg, node2));
|
|
967
|
+
break;
|
|
968
|
+
case "evaluation":
|
|
969
|
+
ast.evaluations.push(parseEvaluation(arg, node2));
|
|
970
|
+
break;
|
|
878
971
|
// ── System profile ──
|
|
879
972
|
case "capability": {
|
|
880
973
|
const cap = { name: arg, description: null, implements: [], line: node2.line };
|
|
@@ -2194,7 +2287,7 @@ function notesSummary(ast) {
|
|
|
2194
2287
|
byLens
|
|
2195
2288
|
};
|
|
2196
2289
|
}
|
|
2197
|
-
var COMPILER_VERSION = "0.
|
|
2290
|
+
var COMPILER_VERSION = "0.3.0";
|
|
2198
2291
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
2199
2292
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
2200
2293
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -2687,13 +2780,15 @@ function buildProof(ast, { sourceFile, sourceHash, targetsRequested, targetsGene
|
|
|
2687
2780
|
id: g.id,
|
|
2688
2781
|
text: g.statement,
|
|
2689
2782
|
status: g.verify.length > 0 || verifiedText ? "planned" : "needs_verification",
|
|
2690
|
-
evidence: g.verify
|
|
2783
|
+
evidence: g.verify,
|
|
2784
|
+
verifications: g.verifications || []
|
|
2691
2785
|
})),
|
|
2692
2786
|
neverRules: ast.neverRules.map((n) => ({
|
|
2693
2787
|
id: n.id,
|
|
2694
2788
|
text: n.statement,
|
|
2695
2789
|
status: n.verify.length > 0 ? "planned" : "needs_verification",
|
|
2696
|
-
evidence: n.verify
|
|
2790
|
+
evidence: n.verify,
|
|
2791
|
+
verifications: n.verifications || []
|
|
2697
2792
|
})),
|
|
2698
2793
|
errors: (ast.errors || []).map((e) => ({ name: e.name })),
|
|
2699
2794
|
examples: (ast.examples || []).map((ex) => ({ given: ex.given, expect: ex.expect })),
|
|
@@ -3658,6 +3753,112 @@ function extractFactsRuby(source, file = "input.rb") {
|
|
|
3658
3753
|
}
|
|
3659
3754
|
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "ruby", sourceRoot: file, functions, tests, errors };
|
|
3660
3755
|
}
|
|
3756
|
+
function parseNameColonTypeParams(raw) {
|
|
3757
|
+
return splitTopLevel(raw, ",").map((p) => p.trim()).filter(Boolean).map((p) => {
|
|
3758
|
+
const cleaned = p.replace(/@\w+(\([^)]*\))?/g, "").replace(/^(?:val|var|vararg|implicit|final|lazy)\s+/, "").replace(/=.*$/, "").trim();
|
|
3759
|
+
const mm = cleaned.match(/^([A-Za-z_]\w*)\s*:\s*(.+)$/);
|
|
3760
|
+
if (mm) return { name: mm[1], type: mm[2].trim() };
|
|
3761
|
+
return { name: cleaned.replace(/[^\w].*$/, "") || cleaned, type: null };
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
function extractFactsKotlin(source, file = "input.kt") {
|
|
3765
|
+
let m;
|
|
3766
|
+
const functions = [];
|
|
3767
|
+
const tests = [];
|
|
3768
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3769
|
+
const testMethods = /* @__PURE__ */ new Set();
|
|
3770
|
+
const ta = /@Test\b[\s\S]{0,120}?\bfun\s+(?:`([^`]+)`|(\w+))\s*\(/g;
|
|
3771
|
+
while (m = ta.exec(source)) testMethods.add(m[1] || m[2]);
|
|
3772
|
+
const fnRe = /\bfun\s+(?:<[^>]*>\s*)?(?:[A-Za-z_][\w.]*\.)?(?:`([^`]+)`|([A-Za-z_]\w*))\s*\(([^)]*)\)\s*(?::\s*([^{=\n]+))?/g;
|
|
3773
|
+
while (m = fnRe.exec(source)) {
|
|
3774
|
+
const name = m[1] || m[2];
|
|
3775
|
+
if (testMethods.has(name)) {
|
|
3776
|
+
if (!tests.some((t) => t.name === name)) tests.push({ name, file, line: lineOf(source, m.index) });
|
|
3777
|
+
continue;
|
|
3778
|
+
}
|
|
3779
|
+
if (seen.has(name)) continue;
|
|
3780
|
+
seen.add(name);
|
|
3781
|
+
functions.push({ name, file, line: lineOf(source, m.index), parameters: parseNameColonTypeParams(m[3] || ""), returnType: m[4] ? m[4].trim() : null, evidence: [{ kind: "fun", file, line: lineOf(source, m.index) }] });
|
|
3782
|
+
}
|
|
3783
|
+
const errors = [];
|
|
3784
|
+
const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
|
|
3785
|
+
let mm;
|
|
3786
|
+
const ce = /class\s+(\w*(?:Exception|Error))\b/g;
|
|
3787
|
+
while (mm = ce.exec(source)) addErr(mm[1], mm.index);
|
|
3788
|
+
const th = /throw\s+(\w+)\s*\(/g;
|
|
3789
|
+
while (mm = th.exec(source)) addErr(mm[1], mm.index);
|
|
3790
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "kotlin", sourceRoot: file, functions, tests, errors };
|
|
3791
|
+
}
|
|
3792
|
+
function extractFactsScala(source, file = "input.scala") {
|
|
3793
|
+
let m;
|
|
3794
|
+
const functions = [];
|
|
3795
|
+
const tests = [];
|
|
3796
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3797
|
+
const fnRe = /\bdef\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*\(([^)]*)\)\s*(?::\s*([^={\n]+))?/g;
|
|
3798
|
+
while (m = fnRe.exec(source)) {
|
|
3799
|
+
const name = m[1];
|
|
3800
|
+
if (seen.has(name)) continue;
|
|
3801
|
+
seen.add(name);
|
|
3802
|
+
functions.push({ name, file, line: lineOf(source, m.index), parameters: parseNameColonTypeParams(m[2] || ""), returnType: m[3] ? m[3].trim() : null, evidence: [{ kind: "def", file, line: lineOf(source, m.index) }] });
|
|
3803
|
+
}
|
|
3804
|
+
const seenT = /* @__PURE__ */ new Set();
|
|
3805
|
+
const addTest = (n, idx) => {
|
|
3806
|
+
const k = String(n).toLowerCase();
|
|
3807
|
+
if (n && !seenT.has(k)) {
|
|
3808
|
+
seenT.add(k);
|
|
3809
|
+
tests.push({ name: n, file, line: lineOf(source, idx) });
|
|
3810
|
+
}
|
|
3811
|
+
};
|
|
3812
|
+
const tr = /\btest\s*\(\s*"([^"]+)"/g;
|
|
3813
|
+
while (m = tr.exec(source)) addTest(m[1], m.index);
|
|
3814
|
+
const inRe = /"([^"]+)"\s+(?:in|should|must)\b/g;
|
|
3815
|
+
while (m = inRe.exec(source)) addTest(m[1], m.index);
|
|
3816
|
+
const errors = [];
|
|
3817
|
+
const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
|
|
3818
|
+
let mm;
|
|
3819
|
+
const ce = /class\s+(\w*(?:Exception|Error))\b/g;
|
|
3820
|
+
while (mm = ce.exec(source)) addErr(mm[1], mm.index);
|
|
3821
|
+
const th = /throw\s+new\s+(\w+)\s*\(/g;
|
|
3822
|
+
while (mm = th.exec(source)) addErr(mm[1], mm.index);
|
|
3823
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "scala", sourceRoot: file, functions, tests, errors };
|
|
3824
|
+
}
|
|
3825
|
+
function extractFactsElixir(source, file = "input.ex") {
|
|
3826
|
+
let m;
|
|
3827
|
+
const functions = [];
|
|
3828
|
+
const tests = [];
|
|
3829
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3830
|
+
const seenT = /* @__PURE__ */ new Set();
|
|
3831
|
+
const addTest = (n, idx) => {
|
|
3832
|
+
const k = String(n).toLowerCase();
|
|
3833
|
+
if (n && !seenT.has(k)) {
|
|
3834
|
+
seenT.add(k);
|
|
3835
|
+
tests.push({ name: n, file, line: lineOf(source, idx) });
|
|
3836
|
+
}
|
|
3837
|
+
};
|
|
3838
|
+
const tr = /\btest\s+"([^"]+)"/g;
|
|
3839
|
+
while (m = tr.exec(source)) addTest(m[1], m.index);
|
|
3840
|
+
const fnRe = /^[ \t]*defp?\s+([a-z_]\w*[!?]?)\s*(?:\(([^)]*)\))?/gm;
|
|
3841
|
+
while (m = fnRe.exec(source)) {
|
|
3842
|
+
const name = m[1];
|
|
3843
|
+
if (seen.has(name)) continue;
|
|
3844
|
+
seen.add(name);
|
|
3845
|
+
const parameters = splitTopLevel(m[2] || "", ",").map((p) => p.trim()).filter(Boolean).map((p) => ({ name: p.split(/\\\\/)[0].trim().replace(/^%\{?/, "").replace(/[^\w].*$/, "") || p, type: null }));
|
|
3846
|
+
functions.push({ name, file, line: lineOf(source, m.index), indent: (m[0].match(/^[ \t]*/) || [""])[0].length, parameters, returnType: null, evidence: [{ kind: "def", file, line: lineOf(source, m.index) }] });
|
|
3847
|
+
}
|
|
3848
|
+
const errors = [];
|
|
3849
|
+
const seenErr = /* @__PURE__ */ new Set();
|
|
3850
|
+
const addErr = (n, idx) => {
|
|
3851
|
+
if (n && !seenErr.has(n)) {
|
|
3852
|
+
seenErr.add(n);
|
|
3853
|
+
errors.push({ name: n, file, line: lineOf(source, idx) });
|
|
3854
|
+
}
|
|
3855
|
+
};
|
|
3856
|
+
const modErr = /defmodule\s+([\w.]*(?:Error|Exception))\b/g;
|
|
3857
|
+
while (m = modErr.exec(source)) addErr(m[1].split(".").pop(), m.index);
|
|
3858
|
+
const raiseRe = /raise\s+([A-Z][\w.]*)/g;
|
|
3859
|
+
while (m = raiseRe.exec(source)) addErr(m[1].split(".").pop(), m.index);
|
|
3860
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "elixir", sourceRoot: file, functions, tests, errors };
|
|
3861
|
+
}
|
|
3661
3862
|
var ADAPTERS = {
|
|
3662
3863
|
typescript: extractFactsTypeScript,
|
|
3663
3864
|
ts: extractFactsTypeScript,
|
|
@@ -3681,10 +3882,18 @@ var ADAPTERS = {
|
|
|
3681
3882
|
cc: extractFactsCpp,
|
|
3682
3883
|
php: extractFactsPhp,
|
|
3683
3884
|
ruby: extractFactsRuby,
|
|
3684
|
-
rb: extractFactsRuby
|
|
3885
|
+
rb: extractFactsRuby,
|
|
3886
|
+
kotlin: extractFactsKotlin,
|
|
3887
|
+
kt: extractFactsKotlin,
|
|
3888
|
+
kts: extractFactsKotlin,
|
|
3889
|
+
scala: extractFactsScala,
|
|
3890
|
+
sc: extractFactsScala,
|
|
3891
|
+
elixir: extractFactsElixir,
|
|
3892
|
+
ex: extractFactsElixir,
|
|
3893
|
+
exs: extractFactsElixir
|
|
3685
3894
|
};
|
|
3686
|
-
var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl"];
|
|
3687
|
-
var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php"]);
|
|
3895
|
+
var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl", "kotlin", "scala", "elixir"];
|
|
3896
|
+
var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php", "elixir"]);
|
|
3688
3897
|
var LANG_DISPLAY = {
|
|
3689
3898
|
typescript: "TypeScript",
|
|
3690
3899
|
javascript: "JavaScript",
|
|
@@ -3696,7 +3905,10 @@ var LANG_DISPLAY = {
|
|
|
3696
3905
|
cpp: "C++",
|
|
3697
3906
|
php: "PHP",
|
|
3698
3907
|
ruby: "Ruby",
|
|
3699
|
-
perl: "Perl"
|
|
3908
|
+
perl: "Perl",
|
|
3909
|
+
kotlin: "Kotlin",
|
|
3910
|
+
scala: "Scala",
|
|
3911
|
+
elixir: "Elixir"
|
|
3700
3912
|
};
|
|
3701
3913
|
function unwrapReturn(ret) {
|
|
3702
3914
|
if (!ret) return { output: null, error: null };
|
|
@@ -3820,6 +4032,9 @@ function languageForFile(file) {
|
|
|
3820
4032
|
if (/\.(cpp|cc|cxx|hpp|hh|c|h)$/i.test(file)) return "cpp";
|
|
3821
4033
|
if (/\.php$/i.test(file)) return "php";
|
|
3822
4034
|
if (/\.rb$/i.test(file)) return "ruby";
|
|
4035
|
+
if (/\.kts?$/i.test(file)) return "kotlin";
|
|
4036
|
+
if (/\.(scala|sc)$/i.test(file)) return "scala";
|
|
4037
|
+
if (/\.exs?$/i.test(file)) return "elixir";
|
|
3823
4038
|
if (/\.(mjs|cjs|jsx?)$/i.test(file)) return "javascript";
|
|
3824
4039
|
return "typescript";
|
|
3825
4040
|
}
|
|
@@ -3827,7 +4042,7 @@ function isPublicFn(fn, language) {
|
|
|
3827
4042
|
const name = fn.name || "";
|
|
3828
4043
|
if (/(?:Internal|Impl|_impl|_helper|_test|Helper)$/.test(name)) return false;
|
|
3829
4044
|
if (language === "go" || language === "golang") return /^[A-Z]/.test(name) && name !== "Test";
|
|
3830
|
-
if (language === "python" || language === "ruby") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
|
|
4045
|
+
if (language === "python" || language === "ruby" || language === "elixir") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
|
|
3831
4046
|
return !name.startsWith("_") && name !== "init" && name !== "constructor";
|
|
3832
4047
|
}
|
|
3833
4048
|
function liftAll(source, { language = "typescript", file = "", publicOnly = true } = {}) {
|
|
@@ -3902,7 +4117,10 @@ var LANG_EXT = {
|
|
|
3902
4117
|
cpp: "cpp",
|
|
3903
4118
|
php: "php",
|
|
3904
4119
|
ruby: "rb",
|
|
3905
|
-
perl: "pl"
|
|
4120
|
+
perl: "pl",
|
|
4121
|
+
kotlin: "kt",
|
|
4122
|
+
scala: "scala",
|
|
4123
|
+
elixir: "ex"
|
|
3906
4124
|
};
|
|
3907
4125
|
function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
|
|
3908
4126
|
const key = String(language).toLowerCase();
|
|
@@ -6088,21 +6306,23 @@ function compileExpr(src) {
|
|
|
6088
6306
|
function evalExpr(src, inputs = {}) {
|
|
6089
6307
|
return compileExpr(src)(inputs);
|
|
6090
6308
|
}
|
|
6309
|
+
var C_LOGIC = { and: (a, b) => `(${a} && ${b})`, or: (a, b) => `(${a} || ${b})`, not: (a) => `!${a}`, bool: (v) => String(v) };
|
|
6091
6310
|
var DIALECTS = {
|
|
6092
|
-
js: { eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
6093
|
-
csharp: { eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
6094
|
-
java: { eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" }
|
|
6311
|
+
js: { ...C_LOGIC, eq: (a, b) => `${a} === ${b}`, neq: (a, b) => `${a} !== ${b}`, inList: (list, x) => `[${list.join(", ")}].includes(${x})`, nil: "undefined" },
|
|
6312
|
+
csharp: { ...C_LOGIC, eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `new[]{${list.join(", ")}}.Contains(${x})`, nil: "null" },
|
|
6313
|
+
java: { ...C_LOGIC, eq: (a, b) => `java.util.Objects.equals(${a}, ${b})`, neq: (a, b) => `!java.util.Objects.equals(${a}, ${b})`, inList: (list, x) => `java.util.List.of(${list.join(", ")}).contains(${x})`, nil: "null" },
|
|
6314
|
+
python: { and: (a, b) => `(${a} and ${b})`, or: (a, b) => `(${a} or ${b})`, not: (a) => `(not ${a})`, bool: (v) => v ? "True" : "False", eq: (a, b) => `${a} == ${b}`, neq: (a, b) => `${a} != ${b}`, inList: (list, x) => `${x} in [${list.join(", ")}]`, nil: "None" }
|
|
6095
6315
|
};
|
|
6096
6316
|
function renderExpr(src, inputs, D) {
|
|
6097
6317
|
const known = new Set(inputs);
|
|
6098
6318
|
const r = (n) => {
|
|
6099
6319
|
switch (n.k) {
|
|
6100
6320
|
case "or":
|
|
6101
|
-
return
|
|
6321
|
+
return D.or(r(n.a), r(n.b));
|
|
6102
6322
|
case "and":
|
|
6103
|
-
return
|
|
6323
|
+
return D.and(r(n.a), r(n.b));
|
|
6104
6324
|
case "not":
|
|
6105
|
-
return
|
|
6325
|
+
return D.not(r(n.a));
|
|
6106
6326
|
case "neg":
|
|
6107
6327
|
return `-${r(n.a)}`;
|
|
6108
6328
|
case "arith":
|
|
@@ -6116,7 +6336,7 @@ function renderExpr(src, inputs, D) {
|
|
|
6116
6336
|
case "list":
|
|
6117
6337
|
return `[${n.items.map(r).join(", ")}]`;
|
|
6118
6338
|
case "lit":
|
|
6119
|
-
return typeof n.v === "string" ? JSON.stringify(n.v) : String(n.v);
|
|
6339
|
+
return typeof n.v === "string" ? JSON.stringify(n.v) : typeof n.v === "boolean" ? D.bool(n.v) : String(n.v);
|
|
6120
6340
|
case "ref":
|
|
6121
6341
|
return known.has(n.path) || known.has(n.path.split(".")[0]) ? n.path : JSON.stringify(n.path);
|
|
6122
6342
|
default:
|
|
@@ -6237,7 +6457,7 @@ function runTests(ast) {
|
|
|
6237
6457
|
const inputs = coerce(c.given);
|
|
6238
6458
|
const run = evaluateDecision(dec, inputs);
|
|
6239
6459
|
const pass = c.expect == null || String(run.result) === String(c.expect);
|
|
6240
|
-
results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
|
|
6460
|
+
results.push({ ...label, kind: "decision", expected: c.expect, actual: run.result, matched: run.matched, pass, ...run.ok ? {} : { note: "a condition failed to evaluate" } });
|
|
6241
6461
|
} else if (lc) {
|
|
6242
6462
|
const sim = simulateLifecycle(lc, c.events || []);
|
|
6243
6463
|
const passState = c.expect == null || sim.finalState === c.expect;
|