@skillstech/thunderlang 0.2.0 → 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 +13 -0
- package/dist/core.cjs +130 -7
- package/dist/index.cjs +130 -7
- package/package.json +1 -1
- package/src/cli.mjs +9 -3
- package/src/emit.mjs +1 -1
- package/src/lift.mjs +79 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
All notable changes to `@skillstech/thunderlang`. Pre-1.0: the language and the
|
|
4
4
|
`intent-graph-v1` schema version independently and may still change.
|
|
5
5
|
|
|
6
|
+
## 0.3.0
|
|
7
|
+
|
|
8
|
+
The 14-language lift release. Additive; no breaking changes. Restores the OpenThunder -> ThunderLang
|
|
9
|
+
recover-intent loop for every language OpenThunder's archgraph core can now discover.
|
|
10
|
+
|
|
11
|
+
- **Kotlin, Scala, and Elixir lift adapters.** `thunder lift` now extracts candidate intent from
|
|
12
|
+
`.kt`/`.kts`, `.scala`/`.sc`, and `.ex`/`.exs`, bringing the supported-language count to 14. A new
|
|
13
|
+
`name: Type` parameter parser handles the JVM signature shape; Elixir joins the dynamic-language set.
|
|
14
|
+
- **Fix: repo-walk language coverage.** `LIFT_EXTS` was stale and silently skipped Python, Java, C#,
|
|
15
|
+
Go, C++, PHP, and Ruby during repo/`--all` lifts; it now mirrors the full adapter set.
|
|
16
|
+
- **Fix: single-file lift language detection.** `thunder lift <file>` now auto-detects the language by
|
|
17
|
+
extension instead of defaulting to TypeScript, consistent with `--all`/repo modes.
|
|
18
|
+
|
|
6
19
|
## 0.2.0
|
|
7
20
|
|
|
8
21
|
The cross-language target execution release. Additive; no breaking changes.
|
package/dist/core.cjs
CHANGED
|
@@ -4288,7 +4288,7 @@ function notesSummary(ast) {
|
|
|
4288
4288
|
byLens
|
|
4289
4289
|
};
|
|
4290
4290
|
}
|
|
4291
|
-
var COMPILER_VERSION = "0.
|
|
4291
|
+
var COMPILER_VERSION = "0.3.0";
|
|
4292
4292
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
4293
4293
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
4294
4294
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -5378,6 +5378,112 @@ function extractFactsRuby(source, file = "input.rb") {
|
|
|
5378
5378
|
}
|
|
5379
5379
|
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "ruby", sourceRoot: file, functions, tests, errors };
|
|
5380
5380
|
}
|
|
5381
|
+
function parseNameColonTypeParams(raw) {
|
|
5382
|
+
return splitTopLevel(raw, ",").map((p) => p.trim()).filter(Boolean).map((p) => {
|
|
5383
|
+
const cleaned = p.replace(/@\w+(\([^)]*\))?/g, "").replace(/^(?:val|var|vararg|implicit|final|lazy)\s+/, "").replace(/=.*$/, "").trim();
|
|
5384
|
+
const mm = cleaned.match(/^([A-Za-z_]\w*)\s*:\s*(.+)$/);
|
|
5385
|
+
if (mm) return { name: mm[1], type: mm[2].trim() };
|
|
5386
|
+
return { name: cleaned.replace(/[^\w].*$/, "") || cleaned, type: null };
|
|
5387
|
+
});
|
|
5388
|
+
}
|
|
5389
|
+
function extractFactsKotlin(source, file = "input.kt") {
|
|
5390
|
+
let m;
|
|
5391
|
+
const functions = [];
|
|
5392
|
+
const tests = [];
|
|
5393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5394
|
+
const testMethods = /* @__PURE__ */ new Set();
|
|
5395
|
+
const ta = /@Test\b[\s\S]{0,120}?\bfun\s+(?:`([^`]+)`|(\w+))\s*\(/g;
|
|
5396
|
+
while (m = ta.exec(source)) testMethods.add(m[1] || m[2]);
|
|
5397
|
+
const fnRe = /\bfun\s+(?:<[^>]*>\s*)?(?:[A-Za-z_][\w.]*\.)?(?:`([^`]+)`|([A-Za-z_]\w*))\s*\(([^)]*)\)\s*(?::\s*([^{=\n]+))?/g;
|
|
5398
|
+
while (m = fnRe.exec(source)) {
|
|
5399
|
+
const name = m[1] || m[2];
|
|
5400
|
+
if (testMethods.has(name)) {
|
|
5401
|
+
if (!tests.some((t) => t.name === name)) tests.push({ name, file, line: lineOf(source, m.index) });
|
|
5402
|
+
continue;
|
|
5403
|
+
}
|
|
5404
|
+
if (seen.has(name)) continue;
|
|
5405
|
+
seen.add(name);
|
|
5406
|
+
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) }] });
|
|
5407
|
+
}
|
|
5408
|
+
const errors = [];
|
|
5409
|
+
const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
|
|
5410
|
+
let mm;
|
|
5411
|
+
const ce = /class\s+(\w*(?:Exception|Error))\b/g;
|
|
5412
|
+
while (mm = ce.exec(source)) addErr(mm[1], mm.index);
|
|
5413
|
+
const th = /throw\s+(\w+)\s*\(/g;
|
|
5414
|
+
while (mm = th.exec(source)) addErr(mm[1], mm.index);
|
|
5415
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "kotlin", sourceRoot: file, functions, tests, errors };
|
|
5416
|
+
}
|
|
5417
|
+
function extractFactsScala(source, file = "input.scala") {
|
|
5418
|
+
let m;
|
|
5419
|
+
const functions = [];
|
|
5420
|
+
const tests = [];
|
|
5421
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5422
|
+
const fnRe = /\bdef\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*\(([^)]*)\)\s*(?::\s*([^={\n]+))?/g;
|
|
5423
|
+
while (m = fnRe.exec(source)) {
|
|
5424
|
+
const name = m[1];
|
|
5425
|
+
if (seen.has(name)) continue;
|
|
5426
|
+
seen.add(name);
|
|
5427
|
+
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) }] });
|
|
5428
|
+
}
|
|
5429
|
+
const seenT = /* @__PURE__ */ new Set();
|
|
5430
|
+
const addTest = (n, idx) => {
|
|
5431
|
+
const k = String(n).toLowerCase();
|
|
5432
|
+
if (n && !seenT.has(k)) {
|
|
5433
|
+
seenT.add(k);
|
|
5434
|
+
tests.push({ name: n, file, line: lineOf(source, idx) });
|
|
5435
|
+
}
|
|
5436
|
+
};
|
|
5437
|
+
const tr = /\btest\s*\(\s*"([^"]+)"/g;
|
|
5438
|
+
while (m = tr.exec(source)) addTest(m[1], m.index);
|
|
5439
|
+
const inRe = /"([^"]+)"\s+(?:in|should|must)\b/g;
|
|
5440
|
+
while (m = inRe.exec(source)) addTest(m[1], m.index);
|
|
5441
|
+
const errors = [];
|
|
5442
|
+
const addErr = addErrOf(errors, /* @__PURE__ */ new Set(), { _src: source, _file: file });
|
|
5443
|
+
let mm;
|
|
5444
|
+
const ce = /class\s+(\w*(?:Exception|Error))\b/g;
|
|
5445
|
+
while (mm = ce.exec(source)) addErr(mm[1], mm.index);
|
|
5446
|
+
const th = /throw\s+new\s+(\w+)\s*\(/g;
|
|
5447
|
+
while (mm = th.exec(source)) addErr(mm[1], mm.index);
|
|
5448
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "scala", sourceRoot: file, functions, tests, errors };
|
|
5449
|
+
}
|
|
5450
|
+
function extractFactsElixir(source, file = "input.ex") {
|
|
5451
|
+
let m;
|
|
5452
|
+
const functions = [];
|
|
5453
|
+
const tests = [];
|
|
5454
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5455
|
+
const seenT = /* @__PURE__ */ new Set();
|
|
5456
|
+
const addTest = (n, idx) => {
|
|
5457
|
+
const k = String(n).toLowerCase();
|
|
5458
|
+
if (n && !seenT.has(k)) {
|
|
5459
|
+
seenT.add(k);
|
|
5460
|
+
tests.push({ name: n, file, line: lineOf(source, idx) });
|
|
5461
|
+
}
|
|
5462
|
+
};
|
|
5463
|
+
const tr = /\btest\s+"([^"]+)"/g;
|
|
5464
|
+
while (m = tr.exec(source)) addTest(m[1], m.index);
|
|
5465
|
+
const fnRe = /^[ \t]*defp?\s+([a-z_]\w*[!?]?)\s*(?:\(([^)]*)\))?/gm;
|
|
5466
|
+
while (m = fnRe.exec(source)) {
|
|
5467
|
+
const name = m[1];
|
|
5468
|
+
if (seen.has(name)) continue;
|
|
5469
|
+
seen.add(name);
|
|
5470
|
+
const parameters = splitTopLevel(m[2] || "", ",").map((p) => p.trim()).filter(Boolean).map((p) => ({ name: p.split(/\\\\/)[0].trim().replace(/^%\{?/, "").replace(/[^\w].*$/, "") || p, type: null }));
|
|
5471
|
+
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) }] });
|
|
5472
|
+
}
|
|
5473
|
+
const errors = [];
|
|
5474
|
+
const seenErr = /* @__PURE__ */ new Set();
|
|
5475
|
+
const addErr = (n, idx) => {
|
|
5476
|
+
if (n && !seenErr.has(n)) {
|
|
5477
|
+
seenErr.add(n);
|
|
5478
|
+
errors.push({ name: n, file, line: lineOf(source, idx) });
|
|
5479
|
+
}
|
|
5480
|
+
};
|
|
5481
|
+
const modErr = /defmodule\s+([\w.]*(?:Error|Exception))\b/g;
|
|
5482
|
+
while (m = modErr.exec(source)) addErr(m[1].split(".").pop(), m.index);
|
|
5483
|
+
const raiseRe = /raise\s+([A-Z][\w.]*)/g;
|
|
5484
|
+
while (m = raiseRe.exec(source)) addErr(m[1].split(".").pop(), m.index);
|
|
5485
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "elixir", sourceRoot: file, functions, tests, errors };
|
|
5486
|
+
}
|
|
5381
5487
|
var ADAPTERS = {
|
|
5382
5488
|
typescript: extractFactsTypeScript,
|
|
5383
5489
|
ts: extractFactsTypeScript,
|
|
@@ -5401,10 +5507,18 @@ var ADAPTERS = {
|
|
|
5401
5507
|
cc: extractFactsCpp,
|
|
5402
5508
|
php: extractFactsPhp,
|
|
5403
5509
|
ruby: extractFactsRuby,
|
|
5404
|
-
rb: extractFactsRuby
|
|
5510
|
+
rb: extractFactsRuby,
|
|
5511
|
+
kotlin: extractFactsKotlin,
|
|
5512
|
+
kt: extractFactsKotlin,
|
|
5513
|
+
kts: extractFactsKotlin,
|
|
5514
|
+
scala: extractFactsScala,
|
|
5515
|
+
sc: extractFactsScala,
|
|
5516
|
+
elixir: extractFactsElixir,
|
|
5517
|
+
ex: extractFactsElixir,
|
|
5518
|
+
exs: extractFactsElixir
|
|
5405
5519
|
};
|
|
5406
|
-
var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl"];
|
|
5407
|
-
var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php"]);
|
|
5520
|
+
var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl", "kotlin", "scala", "elixir"];
|
|
5521
|
+
var DYNAMIC_LANGUAGES = /* @__PURE__ */ new Set(["perl", "javascript", "python", "ruby", "php", "elixir"]);
|
|
5408
5522
|
var LANG_DISPLAY = {
|
|
5409
5523
|
typescript: "TypeScript",
|
|
5410
5524
|
javascript: "JavaScript",
|
|
@@ -5416,7 +5530,10 @@ var LANG_DISPLAY = {
|
|
|
5416
5530
|
cpp: "C++",
|
|
5417
5531
|
php: "PHP",
|
|
5418
5532
|
ruby: "Ruby",
|
|
5419
|
-
perl: "Perl"
|
|
5533
|
+
perl: "Perl",
|
|
5534
|
+
kotlin: "Kotlin",
|
|
5535
|
+
scala: "Scala",
|
|
5536
|
+
elixir: "Elixir"
|
|
5420
5537
|
};
|
|
5421
5538
|
function unwrapReturn(ret) {
|
|
5422
5539
|
if (!ret) return { output: null, error: null };
|
|
@@ -5540,6 +5657,9 @@ function languageForFile(file) {
|
|
|
5540
5657
|
if (/\.(cpp|cc|cxx|hpp|hh|c|h)$/i.test(file)) return "cpp";
|
|
5541
5658
|
if (/\.php$/i.test(file)) return "php";
|
|
5542
5659
|
if (/\.rb$/i.test(file)) return "ruby";
|
|
5660
|
+
if (/\.kts?$/i.test(file)) return "kotlin";
|
|
5661
|
+
if (/\.(scala|sc)$/i.test(file)) return "scala";
|
|
5662
|
+
if (/\.exs?$/i.test(file)) return "elixir";
|
|
5543
5663
|
if (/\.(mjs|cjs|jsx?)$/i.test(file)) return "javascript";
|
|
5544
5664
|
return "typescript";
|
|
5545
5665
|
}
|
|
@@ -5547,7 +5667,7 @@ function isPublicFn(fn, language) {
|
|
|
5547
5667
|
const name = fn.name || "";
|
|
5548
5668
|
if (/(?:Internal|Impl|_impl|_helper|_test|Helper)$/.test(name)) return false;
|
|
5549
5669
|
if (language === "go" || language === "golang") return /^[A-Z]/.test(name) && name !== "Test";
|
|
5550
|
-
if (language === "python" || language === "ruby") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
|
|
5670
|
+
if (language === "python" || language === "ruby" || language === "elixir") return !name.startsWith("_") && (fn.indent == null || fn.indent <= 4);
|
|
5551
5671
|
return !name.startsWith("_") && name !== "init" && name !== "constructor";
|
|
5552
5672
|
}
|
|
5553
5673
|
function liftAll(source, { language = "typescript", file = "", publicOnly = true } = {}) {
|
|
@@ -5622,7 +5742,10 @@ var LANG_EXT = {
|
|
|
5622
5742
|
cpp: "cpp",
|
|
5623
5743
|
php: "php",
|
|
5624
5744
|
ruby: "rb",
|
|
5625
|
-
perl: "pl"
|
|
5745
|
+
perl: "pl",
|
|
5746
|
+
kotlin: "kt",
|
|
5747
|
+
scala: "scala",
|
|
5748
|
+
elixir: "ex"
|
|
5626
5749
|
};
|
|
5627
5750
|
function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
|
|
5628
5751
|
const key = String(language).toLowerCase();
|
package/dist/index.cjs
CHANGED
|
@@ -2287,7 +2287,7 @@ function notesSummary(ast) {
|
|
|
2287
2287
|
byLens
|
|
2288
2288
|
};
|
|
2289
2289
|
}
|
|
2290
|
-
var COMPILER_VERSION = "0.
|
|
2290
|
+
var COMPILER_VERSION = "0.3.0";
|
|
2291
2291
|
var PROOF_SCHEMA_VERSION = "0.1.0";
|
|
2292
2292
|
var SOURCE_PRODUCT = "skillstech-compiler";
|
|
2293
2293
|
function buildContractGraph(ast, generatedAt) {
|
|
@@ -3753,6 +3753,112 @@ function extractFactsRuby(source, file = "input.rb") {
|
|
|
3753
3753
|
}
|
|
3754
3754
|
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: "ruby", sourceRoot: file, functions, tests, errors };
|
|
3755
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
|
+
}
|
|
3756
3862
|
var ADAPTERS = {
|
|
3757
3863
|
typescript: extractFactsTypeScript,
|
|
3758
3864
|
ts: extractFactsTypeScript,
|
|
@@ -3776,10 +3882,18 @@ var ADAPTERS = {
|
|
|
3776
3882
|
cc: extractFactsCpp,
|
|
3777
3883
|
php: extractFactsPhp,
|
|
3778
3884
|
ruby: extractFactsRuby,
|
|
3779
|
-
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
|
|
3780
3894
|
};
|
|
3781
|
-
var SUPPORTED_LANGUAGES = ["typescript", "javascript", "python", "java", "csharp", "go", "rust", "cpp", "php", "ruby", "perl"];
|
|
3782
|
-
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"]);
|
|
3783
3897
|
var LANG_DISPLAY = {
|
|
3784
3898
|
typescript: "TypeScript",
|
|
3785
3899
|
javascript: "JavaScript",
|
|
@@ -3791,7 +3905,10 @@ var LANG_DISPLAY = {
|
|
|
3791
3905
|
cpp: "C++",
|
|
3792
3906
|
php: "PHP",
|
|
3793
3907
|
ruby: "Ruby",
|
|
3794
|
-
perl: "Perl"
|
|
3908
|
+
perl: "Perl",
|
|
3909
|
+
kotlin: "Kotlin",
|
|
3910
|
+
scala: "Scala",
|
|
3911
|
+
elixir: "Elixir"
|
|
3795
3912
|
};
|
|
3796
3913
|
function unwrapReturn(ret) {
|
|
3797
3914
|
if (!ret) return { output: null, error: null };
|
|
@@ -3915,6 +4032,9 @@ function languageForFile(file) {
|
|
|
3915
4032
|
if (/\.(cpp|cc|cxx|hpp|hh|c|h)$/i.test(file)) return "cpp";
|
|
3916
4033
|
if (/\.php$/i.test(file)) return "php";
|
|
3917
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";
|
|
3918
4038
|
if (/\.(mjs|cjs|jsx?)$/i.test(file)) return "javascript";
|
|
3919
4039
|
return "typescript";
|
|
3920
4040
|
}
|
|
@@ -3922,7 +4042,7 @@ function isPublicFn(fn, language) {
|
|
|
3922
4042
|
const name = fn.name || "";
|
|
3923
4043
|
if (/(?:Internal|Impl|_impl|_helper|_test|Helper)$/.test(name)) return false;
|
|
3924
4044
|
if (language === "go" || language === "golang") return /^[A-Z]/.test(name) && name !== "Test";
|
|
3925
|
-
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);
|
|
3926
4046
|
return !name.startsWith("_") && name !== "init" && name !== "constructor";
|
|
3927
4047
|
}
|
|
3928
4048
|
function liftAll(source, { language = "typescript", file = "", publicOnly = true } = {}) {
|
|
@@ -3997,7 +4117,10 @@ var LANG_EXT = {
|
|
|
3997
4117
|
cpp: "cpp",
|
|
3998
4118
|
php: "php",
|
|
3999
4119
|
ruby: "rb",
|
|
4000
|
-
perl: "pl"
|
|
4120
|
+
perl: "pl",
|
|
4121
|
+
kotlin: "kt",
|
|
4122
|
+
scala: "scala",
|
|
4123
|
+
elixir: "ex"
|
|
4001
4124
|
};
|
|
4002
4125
|
function liftSource(source, { language = "typescript", file = "", seeds = void 0 } = {}) {
|
|
4003
4126
|
const key = String(language).toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillstech/thunderlang",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Deterministic ThunderLang compiler, CLI, and Language Server. No AI required. Turns .thunder files into the canonical Intent Graph, docs, and a proof, and EXECUTES the intent: run decisions, simulate lifecycles, and check outcome contracts. Interops with DMN, BPMN, JSON Schema, and OpenAPI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
package/src/cli.mjs
CHANGED
|
@@ -98,7 +98,13 @@ import {
|
|
|
98
98
|
import { parseEventLog, serializeEventLog, recordEvent, timeline } from './ai-events.mjs';
|
|
99
99
|
|
|
100
100
|
// Recursively collect supported source files, skipping vendored / build dirs.
|
|
101
|
-
|
|
101
|
+
// Kept in sync with lift.mjs languageForFile(): every language the lift adapters support,
|
|
102
|
+
// so repo-mode discovery matches the advertised multi-language lift surface.
|
|
103
|
+
const LIFT_EXTS = [
|
|
104
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.rs', '.pl', '.pm', '.t',
|
|
105
|
+
'.py', '.pyi', '.java', '.cs', '.go', '.cpp', '.cc', '.cxx', '.hpp', '.hh', '.c', '.h',
|
|
106
|
+
'.php', '.rb', '.kt', '.kts', '.scala', '.sc', '.ex', '.exs',
|
|
107
|
+
];
|
|
102
108
|
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.next', 'build', '.intent', 'coverage', '.vercel']);
|
|
103
109
|
// ThunderLang source files. `.thunder` is the canonical public extension; `.tl` is an
|
|
104
110
|
// accepted shorthand; `.intent` stays supported so legacy IntentLang sources keep working.
|
|
@@ -846,9 +852,9 @@ test Example
|
|
|
846
852
|
return;
|
|
847
853
|
}
|
|
848
854
|
|
|
849
|
-
// Single-file mode.
|
|
855
|
+
// Single-file mode. Auto-detect language by extension (consistent with --all / repo modes).
|
|
850
856
|
const src = readFileSync(file, 'utf8');
|
|
851
|
-
const res = liftSource(src, { language: args.from ||
|
|
857
|
+
const res = liftSource(src, { language: args.from || languageForFile(file), file: basename(file) });
|
|
852
858
|
if (!res.ok) { console.error(res.error); process.exit(1); }
|
|
853
859
|
if (args.json) { console.log(JSON.stringify(res.summary, null, 2)); return; }
|
|
854
860
|
if (args.out) {
|
package/src/emit.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export function notesSummary(ast) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export const COMPILER_VERSION = '0.
|
|
33
|
+
export const COMPILER_VERSION = '0.3.0';
|
|
34
34
|
export const PROOF_SCHEMA_VERSION = '0.1.0';
|
|
35
35
|
// Identifies which ecosystem product emitted this proof. Consumed by SkillsTech
|
|
36
36
|
// Certified to key cert proofs to the compiler. Stable slug per the coordination bus.
|
package/src/lift.mjs
CHANGED
|
@@ -393,6 +393,74 @@ export function extractFactsRuby(source, file = 'input.rb') {
|
|
|
393
393
|
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: 'ruby', sourceRoot: file, functions, tests, errors };
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
+
// Params written "name: Type" (Kotlin, Scala). Top-level comma split so generics/tuples
|
|
397
|
+
// like Map<String, Int> or (A, B) don't split mid-type. Strips val/var/vararg/implicit and defaults.
|
|
398
|
+
function parseNameColonTypeParams(raw) {
|
|
399
|
+
return splitTopLevel(raw, ',').map((p) => p.trim()).filter(Boolean).map((p) => {
|
|
400
|
+
const cleaned = p.replace(/@\w+(\([^)]*\))?/g, '').replace(/^(?:val|var|vararg|implicit|final|lazy)\s+/, '').replace(/=.*$/, '').trim();
|
|
401
|
+
const mm = cleaned.match(/^([A-Za-z_]\w*)\s*:\s*(.+)$/);
|
|
402
|
+
if (mm) return { name: mm[1], type: mm[2].trim() };
|
|
403
|
+
return { name: cleaned.replace(/[^\w].*$/, '') || cleaned, type: null };
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ── Kotlin adapter (JVM: fun name(p: Type): Ret, @Test, *Exception) ──────────
|
|
408
|
+
export function extractFactsKotlin(source, file = 'input.kt') {
|
|
409
|
+
let m; const functions = []; const tests = []; const seen = new Set();
|
|
410
|
+
const testMethods = new Set(); const ta = /@Test\b[\s\S]{0,120}?\bfun\s+(?:`([^`]+)`|(\w+))\s*\(/g;
|
|
411
|
+
while ((m = ta.exec(source))) testMethods.add(m[1] || m[2]);
|
|
412
|
+
const fnRe = /\bfun\s+(?:<[^>]*>\s*)?(?:[A-Za-z_][\w.]*\.)?(?:`([^`]+)`|([A-Za-z_]\w*))\s*\(([^)]*)\)\s*(?::\s*([^{=\n]+))?/g;
|
|
413
|
+
while ((m = fnRe.exec(source))) {
|
|
414
|
+
const name = m[1] || m[2];
|
|
415
|
+
if (testMethods.has(name)) { if (!tests.some((t) => t.name === name)) tests.push({ name, file, line: lineOf(source, m.index) }); continue; }
|
|
416
|
+
if (seen.has(name)) continue; seen.add(name);
|
|
417
|
+
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) }] });
|
|
418
|
+
}
|
|
419
|
+
const errors = []; const addErr = addErrOf(errors, new Set(), { _src: source, _file: file });
|
|
420
|
+
let mm; const ce = /class\s+(\w*(?:Exception|Error))\b/g; while ((mm = ce.exec(source))) addErr(mm[1], mm.index);
|
|
421
|
+
const th = /throw\s+(\w+)\s*\(/g; while ((mm = th.exec(source))) addErr(mm[1], mm.index); // Kotlin: no `new`
|
|
422
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: 'kotlin', sourceRoot: file, functions, tests, errors };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ── Scala adapter (def name(p: Type): Ret, ScalaTest, *Exception) ─────────────
|
|
426
|
+
export function extractFactsScala(source, file = 'input.scala') {
|
|
427
|
+
let m; const functions = []; const tests = []; const seen = new Set();
|
|
428
|
+
const fnRe = /\bdef\s+([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*\(([^)]*)\)\s*(?::\s*([^={\n]+))?/g;
|
|
429
|
+
while ((m = fnRe.exec(source))) {
|
|
430
|
+
const name = m[1];
|
|
431
|
+
if (seen.has(name)) continue; seen.add(name);
|
|
432
|
+
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) }] });
|
|
433
|
+
}
|
|
434
|
+
// ScalaTest: `test("desc")` (FunSuite) and `"desc" in { }` / `"desc" should`/`must` (WordSpec/FlatSpec).
|
|
435
|
+
const seenT = new Set(); const addTest = (n, idx) => { const k = String(n).toLowerCase(); if (n && !seenT.has(k)) { seenT.add(k); tests.push({ name: n, file, line: lineOf(source, idx) }); } };
|
|
436
|
+
const tr = /\btest\s*\(\s*"([^"]+)"/g; while ((m = tr.exec(source))) addTest(m[1], m.index);
|
|
437
|
+
const inRe = /"([^"]+)"\s+(?:in|should|must)\b/g; while ((m = inRe.exec(source))) addTest(m[1], m.index);
|
|
438
|
+
const errors = []; const addErr = addErrOf(errors, new Set(), { _src: source, _file: file });
|
|
439
|
+
let mm; const ce = /class\s+(\w*(?:Exception|Error))\b/g; while ((mm = ce.exec(source))) addErr(mm[1], mm.index);
|
|
440
|
+
const th = /throw\s+new\s+(\w+)\s*\(/g; while ((mm = th.exec(source))) addErr(mm[1], mm.index);
|
|
441
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: 'scala', sourceRoot: file, functions, tests, errors };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ── Elixir adapter (dynamic: def/defp name(args), ExUnit test, raise/defexception)
|
|
445
|
+
export function extractFactsElixir(source, file = 'input.ex') {
|
|
446
|
+
let m; const functions = []; const tests = []; const seen = new Set();
|
|
447
|
+
const seenT = new Set(); const addTest = (n, idx) => { const k = String(n).toLowerCase(); if (n && !seenT.has(k)) { seenT.add(k); tests.push({ name: n, file, line: lineOf(source, idx) }); } };
|
|
448
|
+
const tr = /\btest\s+"([^"]+)"/g; while ((m = tr.exec(source))) addTest(m[1], m.index); // ExUnit: test "desc" do
|
|
449
|
+
const fnRe = /^[ \t]*defp?\s+([a-z_]\w*[!?]?)\s*(?:\(([^)]*)\))?/gm;
|
|
450
|
+
while ((m = fnRe.exec(source))) {
|
|
451
|
+
const name = m[1];
|
|
452
|
+
if (seen.has(name)) continue; seen.add(name);
|
|
453
|
+
// Elixir params are pattern matches (conn, %{id: id}, x \\ default); take the leading binding name.
|
|
454
|
+
const parameters = splitTopLevel(m[2] || '', ',').map((p) => p.trim()).filter(Boolean)
|
|
455
|
+
.map((p) => ({ name: p.split(/\\\\/)[0].trim().replace(/^%\{?/, '').replace(/[^\w].*$/, '') || p, type: null }));
|
|
456
|
+
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) }] });
|
|
457
|
+
}
|
|
458
|
+
const errors = []; const seenErr = new Set(); const addErr = (n, idx) => { if (n && !seenErr.has(n)) { seenErr.add(n); errors.push({ name: n, file, line: lineOf(source, idx) }); } };
|
|
459
|
+
const modErr = /defmodule\s+([\w.]*(?:Error|Exception))\b/g; while ((m = modErr.exec(source))) addErr(m[1].split('.').pop(), m.index);
|
|
460
|
+
const raiseRe = /raise\s+([A-Z][\w.]*)/g; while ((m = raiseRe.exec(source))) addErr(m[1].split('.').pop(), m.index);
|
|
461
|
+
return { schemaVersion: IR_SCHEMA_VERSION, sourceLanguage: 'elixir', sourceRoot: file, functions, tests, errors };
|
|
462
|
+
}
|
|
463
|
+
|
|
396
464
|
const ADAPTERS = {
|
|
397
465
|
typescript: extractFactsTypeScript, ts: extractFactsTypeScript,
|
|
398
466
|
javascript: extractFactsTypeScript, js: extractFactsTypeScript,
|
|
@@ -405,13 +473,17 @@ const ADAPTERS = {
|
|
|
405
473
|
cpp: extractFactsCpp, 'c++': extractFactsCpp, c: extractFactsCpp, cc: extractFactsCpp,
|
|
406
474
|
php: extractFactsPhp,
|
|
407
475
|
ruby: extractFactsRuby, rb: extractFactsRuby,
|
|
476
|
+
kotlin: extractFactsKotlin, kt: extractFactsKotlin, kts: extractFactsKotlin,
|
|
477
|
+
scala: extractFactsScala, sc: extractFactsScala,
|
|
478
|
+
elixir: extractFactsElixir, ex: extractFactsElixir, exs: extractFactsElixir,
|
|
408
479
|
};
|
|
409
|
-
export const SUPPORTED_LANGUAGES = ['typescript', 'javascript', 'python', 'java', 'csharp', 'go', 'rust', 'cpp', 'php', 'ruby', 'perl'];
|
|
410
|
-
const DYNAMIC_LANGUAGES = new Set(['perl', 'javascript', 'python', 'ruby', 'php']);
|
|
480
|
+
export const SUPPORTED_LANGUAGES = ['typescript', 'javascript', 'python', 'java', 'csharp', 'go', 'rust', 'cpp', 'php', 'ruby', 'perl', 'kotlin', 'scala', 'elixir'];
|
|
481
|
+
const DYNAMIC_LANGUAGES = new Set(['perl', 'javascript', 'python', 'ruby', 'php', 'elixir']);
|
|
411
482
|
|
|
412
483
|
const LANG_DISPLAY = {
|
|
413
484
|
typescript: 'TypeScript', javascript: 'JavaScript', python: 'Python', java: 'Java',
|
|
414
485
|
csharp: 'C#', go: 'Go', rust: 'Rust', cpp: 'C++', php: 'PHP', ruby: 'Ruby', perl: 'Perl',
|
|
486
|
+
kotlin: 'Kotlin', scala: 'Scala', elixir: 'Elixir',
|
|
415
487
|
};
|
|
416
488
|
|
|
417
489
|
// Unwrap Result<T, E> / Promise<T> / T -> { output, error }
|
|
@@ -558,6 +630,9 @@ export function languageForFile(file) {
|
|
|
558
630
|
if (/\.(cpp|cc|cxx|hpp|hh|c|h)$/i.test(file)) return 'cpp';
|
|
559
631
|
if (/\.php$/i.test(file)) return 'php';
|
|
560
632
|
if (/\.rb$/i.test(file)) return 'ruby';
|
|
633
|
+
if (/\.kts?$/i.test(file)) return 'kotlin';
|
|
634
|
+
if (/\.(scala|sc)$/i.test(file)) return 'scala';
|
|
635
|
+
if (/\.exs?$/i.test(file)) return 'elixir';
|
|
561
636
|
if (/\.(mjs|cjs|jsx?)$/i.test(file)) return 'javascript';
|
|
562
637
|
return 'typescript';
|
|
563
638
|
}
|
|
@@ -577,7 +652,7 @@ function isPublicFn(fn, language) {
|
|
|
577
652
|
// Common private-helper naming conventions, across languages.
|
|
578
653
|
if (/(?:Internal|Impl|_impl|_helper|_test|Helper)$/.test(name)) return false;
|
|
579
654
|
if (language === 'go' || language === 'golang') return /^[A-Z]/.test(name) && name !== 'Test';
|
|
580
|
-
if (language === 'python' || language === 'ruby') return !name.startsWith('_') && (fn.indent == null || fn.indent <= 4);
|
|
655
|
+
if (language === 'python' || language === 'ruby' || language === 'elixir') return !name.startsWith('_') && (fn.indent == null || fn.indent <= 4);
|
|
581
656
|
return !name.startsWith('_') && name !== 'init' && name !== 'constructor';
|
|
582
657
|
}
|
|
583
658
|
|
|
@@ -643,6 +718,7 @@ export function liftRepo(files, { language } = {}) {
|
|
|
643
718
|
const LANG_EXT = {
|
|
644
719
|
typescript: 'ts', javascript: 'js', python: 'py', java: 'java', csharp: 'cs',
|
|
645
720
|
go: 'go', rust: 'rs', cpp: 'cpp', php: 'php', ruby: 'rb', perl: 'pl',
|
|
721
|
+
kotlin: 'kt', scala: 'scala', elixir: 'ex',
|
|
646
722
|
};
|
|
647
723
|
|
|
648
724
|
/**
|