@isl-lang/repl 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +915 -493
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +882 -465
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -25
- package/dist/index.d.ts +78 -25
- package/dist/index.js +882 -466
- package/dist/index.js.map +1 -1
- package/package.json +17 -15
package/dist/index.cjs
CHANGED
|
@@ -47,6 +47,12 @@ var Session = class {
|
|
|
47
47
|
loadedFiles = /* @__PURE__ */ new Set();
|
|
48
48
|
/** Session configuration */
|
|
49
49
|
config;
|
|
50
|
+
/** Evaluation context (set by .context command) */
|
|
51
|
+
evalContext = {};
|
|
52
|
+
/** Pre-state context for old() expressions */
|
|
53
|
+
preContext = null;
|
|
54
|
+
/** Loaded domain AST (from real parser) */
|
|
55
|
+
domainAST = null;
|
|
50
56
|
constructor(config = {}) {
|
|
51
57
|
this.config = {
|
|
52
58
|
colors: true,
|
|
@@ -324,6 +330,97 @@ var Session = class {
|
|
|
324
330
|
}
|
|
325
331
|
}
|
|
326
332
|
// ─────────────────────────────────────────────────────────────────────────
|
|
333
|
+
// Evaluation Context Management
|
|
334
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
335
|
+
/**
|
|
336
|
+
* Set evaluation context from JSON string
|
|
337
|
+
*/
|
|
338
|
+
setEvalContext(json) {
|
|
339
|
+
try {
|
|
340
|
+
const parsed = JSON.parse(json);
|
|
341
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
342
|
+
return { success: false, count: 0, error: "Context must be a JSON object" };
|
|
343
|
+
}
|
|
344
|
+
this.evalContext = parsed;
|
|
345
|
+
for (const [key, value] of Object.entries(this.evalContext)) {
|
|
346
|
+
this.variables.set(key, value);
|
|
347
|
+
}
|
|
348
|
+
return { success: true, count: Object.keys(parsed).length };
|
|
349
|
+
} catch (e) {
|
|
350
|
+
return { success: false, count: 0, error: e instanceof Error ? e.message : String(e) };
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Set pre-state context for old() expressions
|
|
355
|
+
*/
|
|
356
|
+
setPreContext(json) {
|
|
357
|
+
try {
|
|
358
|
+
const parsed = JSON.parse(json);
|
|
359
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
360
|
+
return { success: false, error: "Pre-context must be a JSON object" };
|
|
361
|
+
}
|
|
362
|
+
this.preContext = parsed;
|
|
363
|
+
return { success: true };
|
|
364
|
+
} catch (e) {
|
|
365
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Get evaluation context
|
|
370
|
+
*/
|
|
371
|
+
getEvalContext() {
|
|
372
|
+
return { ...this.evalContext };
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get pre-state context
|
|
376
|
+
*/
|
|
377
|
+
getPreContext() {
|
|
378
|
+
return this.preContext ? { ...this.preContext } : null;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Resolve a dot-path against the evaluation context
|
|
382
|
+
*/
|
|
383
|
+
resolveValue(dotPath) {
|
|
384
|
+
const parts = dotPath.split(".");
|
|
385
|
+
let current = this.evalContext;
|
|
386
|
+
for (const part of parts) {
|
|
387
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
388
|
+
return { found: false, value: void 0 };
|
|
389
|
+
}
|
|
390
|
+
current = current[part];
|
|
391
|
+
}
|
|
392
|
+
return { found: current !== void 0, value: current };
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Resolve a dot-path against the pre-state context
|
|
396
|
+
*/
|
|
397
|
+
resolvePreValue(dotPath) {
|
|
398
|
+
if (!this.preContext) {
|
|
399
|
+
return { found: false, value: void 0 };
|
|
400
|
+
}
|
|
401
|
+
const parts = dotPath.split(".");
|
|
402
|
+
let current = this.preContext;
|
|
403
|
+
for (const part of parts) {
|
|
404
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
405
|
+
return { found: false, value: void 0 };
|
|
406
|
+
}
|
|
407
|
+
current = current[part];
|
|
408
|
+
}
|
|
409
|
+
return { found: current !== void 0, value: current };
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Set domain AST (from real parser)
|
|
413
|
+
*/
|
|
414
|
+
setDomainAST(ast) {
|
|
415
|
+
this.domainAST = ast;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get domain AST
|
|
419
|
+
*/
|
|
420
|
+
getDomainAST() {
|
|
421
|
+
return this.domainAST;
|
|
422
|
+
}
|
|
423
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
327
424
|
// State Management
|
|
328
425
|
// ─────────────────────────────────────────────────────────────────────────
|
|
329
426
|
/**
|
|
@@ -334,6 +431,9 @@ var Session = class {
|
|
|
334
431
|
this.variables.clear();
|
|
335
432
|
this.lastResult = void 0;
|
|
336
433
|
this.loadedFiles.clear();
|
|
434
|
+
this.evalContext = {};
|
|
435
|
+
this.preContext = null;
|
|
436
|
+
this.domainAST = null;
|
|
337
437
|
}
|
|
338
438
|
/**
|
|
339
439
|
* Get session summary
|
|
@@ -747,32 +847,158 @@ function drawBox(lines, title) {
|
|
|
747
847
|
}
|
|
748
848
|
|
|
749
849
|
// src/commands.ts
|
|
850
|
+
function evaluateExpression(expr, session) {
|
|
851
|
+
const trimmed = expr.trim();
|
|
852
|
+
const oldMatch = trimmed.match(/^old\((.+)\)$/);
|
|
853
|
+
if (oldMatch) {
|
|
854
|
+
const innerPath = oldMatch[1].trim();
|
|
855
|
+
if (!session.getPreContext()) {
|
|
856
|
+
return {
|
|
857
|
+
value: void 0,
|
|
858
|
+
error: "old() requires pre-state. Set with .context --pre <json>"
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
const { found, value } = session.resolvePreValue(innerPath);
|
|
862
|
+
if (!found) {
|
|
863
|
+
return { value: void 0, error: `Cannot resolve '${innerPath}' in pre-state context` };
|
|
864
|
+
}
|
|
865
|
+
return { value };
|
|
866
|
+
}
|
|
867
|
+
if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
|
|
868
|
+
return evaluateExpression(trimmed.slice(1, -1), session);
|
|
869
|
+
}
|
|
870
|
+
if (trimmed.startsWith("!") || trimmed.startsWith("not ")) {
|
|
871
|
+
const inner = trimmed.startsWith("!") ? trimmed.slice(1) : trimmed.slice(4);
|
|
872
|
+
const result = evaluateExpression(inner.trim(), session);
|
|
873
|
+
if (result.error) return result;
|
|
874
|
+
return { value: !result.value };
|
|
875
|
+
}
|
|
876
|
+
for (const [opStr, opFn] of BINARY_OPS) {
|
|
877
|
+
const idx = findOperator(trimmed, opStr);
|
|
878
|
+
if (idx !== -1) {
|
|
879
|
+
const left = trimmed.slice(0, idx).trim();
|
|
880
|
+
const right = trimmed.slice(idx + opStr.length).trim();
|
|
881
|
+
const lResult = evaluateExpression(left, session);
|
|
882
|
+
if (lResult.error) return lResult;
|
|
883
|
+
const rResult = evaluateExpression(right, session);
|
|
884
|
+
if (rResult.error) return rResult;
|
|
885
|
+
return { value: opFn(lResult.value, rResult.value) };
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (trimmed === "true") return { value: true };
|
|
889
|
+
if (trimmed === "false") return { value: false };
|
|
890
|
+
if (trimmed === "null") return { value: null };
|
|
891
|
+
if (/^-?\d+$/.test(trimmed)) return { value: parseInt(trimmed, 10) };
|
|
892
|
+
if (/^-?\d+\.\d+$/.test(trimmed)) return { value: parseFloat(trimmed) };
|
|
893
|
+
if (/^"([^"]*)"$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
|
|
894
|
+
if (/^'([^']*)'$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
|
|
895
|
+
if (/^[\w.]+$/.test(trimmed)) {
|
|
896
|
+
const { found, value } = session.resolveValue(trimmed);
|
|
897
|
+
if (found) return { value };
|
|
898
|
+
if (session.hasVariable(trimmed)) {
|
|
899
|
+
return { value: session.getVariable(trimmed) };
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return { value: void 0, error: `Cannot evaluate: ${trimmed}` };
|
|
903
|
+
}
|
|
904
|
+
var BINARY_OPS = [
|
|
905
|
+
// Logical (lowest precedence — scanned first so they split outermost)
|
|
906
|
+
[" || ", (a, b) => Boolean(a) || Boolean(b)],
|
|
907
|
+
[" or ", (a, b) => Boolean(a) || Boolean(b)],
|
|
908
|
+
[" && ", (a, b) => Boolean(a) && Boolean(b)],
|
|
909
|
+
[" and ", (a, b) => Boolean(a) && Boolean(b)],
|
|
910
|
+
// Equality
|
|
911
|
+
[" == ", (a, b) => a === b || String(a) === String(b)],
|
|
912
|
+
[" != ", (a, b) => a !== b && String(a) !== String(b)],
|
|
913
|
+
// Comparison
|
|
914
|
+
[" >= ", (a, b) => Number(a) >= Number(b)],
|
|
915
|
+
[" <= ", (a, b) => Number(a) <= Number(b)],
|
|
916
|
+
[" > ", (a, b) => Number(a) > Number(b)],
|
|
917
|
+
[" < ", (a, b) => Number(a) < Number(b)],
|
|
918
|
+
// Arithmetic
|
|
919
|
+
[" + ", (a, b) => {
|
|
920
|
+
if (typeof a === "string" || typeof b === "string") return String(a) + String(b);
|
|
921
|
+
return Number(a) + Number(b);
|
|
922
|
+
}],
|
|
923
|
+
[" - ", (a, b) => Number(a) - Number(b)],
|
|
924
|
+
[" * ", (a, b) => Number(a) * Number(b)],
|
|
925
|
+
[" / ", (a, b) => {
|
|
926
|
+
const d = Number(b);
|
|
927
|
+
if (d === 0) return Infinity;
|
|
928
|
+
return Number(a) / d;
|
|
929
|
+
}]
|
|
930
|
+
];
|
|
931
|
+
function findOperator(expr, op) {
|
|
932
|
+
let depth = 0;
|
|
933
|
+
let inString = null;
|
|
934
|
+
for (let i = expr.length - 1; i >= 0; i--) {
|
|
935
|
+
const ch = expr[i];
|
|
936
|
+
if (inString) {
|
|
937
|
+
if (ch === inString && (i === 0 || expr[i - 1] !== "\\")) inString = null;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (ch === '"' || ch === "'") {
|
|
941
|
+
inString = ch;
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (ch === "(") depth--;
|
|
945
|
+
if (ch === ")") depth++;
|
|
946
|
+
if (depth === 0 && i + op.length <= expr.length && expr.slice(i, i + op.length) === op) {
|
|
947
|
+
return i;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return -1;
|
|
951
|
+
}
|
|
952
|
+
function prettyPrintAST(node, indent = 0) {
|
|
953
|
+
const pad = " ".repeat(indent);
|
|
954
|
+
if (node === null || node === void 0) return `${pad}${colors.gray}null${colors.reset}`;
|
|
955
|
+
if (typeof node === "string") return `${pad}${colors.green}"${node}"${colors.reset}`;
|
|
956
|
+
if (typeof node === "number") return `${pad}${colors.cyan}${node}${colors.reset}`;
|
|
957
|
+
if (typeof node === "boolean") return `${pad}${colors.magenta}${node}${colors.reset}`;
|
|
958
|
+
if (Array.isArray(node)) {
|
|
959
|
+
if (node.length === 0) return `${pad}[]`;
|
|
960
|
+
const items = node.map((item) => prettyPrintAST(item, indent + 1));
|
|
961
|
+
return `${pad}[
|
|
962
|
+
${items.join(",\n")}
|
|
963
|
+
${pad}]`;
|
|
964
|
+
}
|
|
965
|
+
if (typeof node === "object") {
|
|
966
|
+
const obj = node;
|
|
967
|
+
const kind = obj["kind"];
|
|
968
|
+
const entries = Object.entries(obj).filter(
|
|
969
|
+
([k, v]) => k !== "location" && v !== void 0 && !(Array.isArray(v) && v.length === 0)
|
|
970
|
+
);
|
|
971
|
+
if (entries.length === 0) return `${pad}{}`;
|
|
972
|
+
const header = kind ? `${pad}${colors.yellow}${kind}${colors.reset} {` : `${pad}{`;
|
|
973
|
+
const body = entries.filter(([k]) => k !== "kind").map(([k, v]) => {
|
|
974
|
+
const valStr = typeof v === "object" && v !== null ? "\n" + prettyPrintAST(v, indent + 2) : " " + prettyPrintAST(v, 0).trim();
|
|
975
|
+
return `${pad} ${colors.blue}${k}${colors.reset}:${valStr}`;
|
|
976
|
+
});
|
|
977
|
+
return `${header}
|
|
978
|
+
${body.join("\n")}
|
|
979
|
+
${pad}}`;
|
|
980
|
+
}
|
|
981
|
+
return `${pad}${String(node)}`;
|
|
982
|
+
}
|
|
750
983
|
var metaCommands = [
|
|
984
|
+
// ─── .help ──────────────────────────────────────────────────────────────
|
|
751
985
|
{
|
|
752
986
|
name: "help",
|
|
753
987
|
aliases: ["h", "?"],
|
|
754
|
-
description: "Show
|
|
988
|
+
description: "Show commands",
|
|
755
989
|
usage: ".help [command]",
|
|
756
|
-
handler: (args
|
|
990
|
+
handler: (args) => {
|
|
757
991
|
if (args.length > 0) {
|
|
758
|
-
const cmdName = args[0].toLowerCase();
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
`${colors.cyan}.${metaCmd.name}${colors.reset} - ${metaCmd.description}`,
|
|
764
|
-
`Usage: ${metaCmd.usage}`,
|
|
765
|
-
metaCmd.aliases.length > 0 ? `Aliases: ${metaCmd.aliases.map((a) => "." + a).join(", ")}` : ""
|
|
766
|
-
].filter(Boolean).join("\n")
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
const islCmd = islCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
|
|
770
|
-
if (islCmd) {
|
|
992
|
+
const cmdName = args[0].toLowerCase().replace(/^\./, "");
|
|
993
|
+
const cmd = metaCommands.find(
|
|
994
|
+
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
995
|
+
);
|
|
996
|
+
if (cmd) {
|
|
771
997
|
return {
|
|
772
998
|
output: [
|
|
773
|
-
`${colors.cyan}
|
|
774
|
-
`Usage: ${
|
|
775
|
-
|
|
999
|
+
`${colors.cyan}.${cmd.name}${colors.reset} \u2014 ${cmd.description}`,
|
|
1000
|
+
`Usage: ${cmd.usage}`,
|
|
1001
|
+
cmd.aliases.length > 0 ? `Aliases: ${cmd.aliases.map((a) => "." + a).join(", ")}` : ""
|
|
776
1002
|
].filter(Boolean).join("\n")
|
|
777
1003
|
};
|
|
778
1004
|
}
|
|
@@ -780,194 +1006,232 @@ var metaCommands = [
|
|
|
780
1006
|
}
|
|
781
1007
|
const lines = [
|
|
782
1008
|
"",
|
|
783
|
-
`${colors.bold}
|
|
1009
|
+
`${colors.bold}REPL Commands${colors.reset}`,
|
|
784
1010
|
"",
|
|
785
|
-
...metaCommands.map(
|
|
1011
|
+
...metaCommands.map(
|
|
1012
|
+
(c) => ` ${colors.cyan}.${c.name.padEnd(12)}${colors.reset} ${c.description}`
|
|
1013
|
+
),
|
|
786
1014
|
"",
|
|
787
|
-
`${colors.bold}ISL
|
|
1015
|
+
`${colors.bold}ISL Input${colors.reset}`,
|
|
788
1016
|
"",
|
|
789
|
-
|
|
1017
|
+
` Type ISL directly \u2014 multi-line supported (braces auto-detect):`,
|
|
790
1018
|
"",
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
`
|
|
794
|
-
`
|
|
795
|
-
`
|
|
1019
|
+
` ${colors.yellow}domain${colors.reset} Example {`,
|
|
1020
|
+
` ${colors.yellow}entity${colors.reset} User {`,
|
|
1021
|
+
` id: ${colors.green}UUID${colors.reset}`,
|
|
1022
|
+
` name: ${colors.green}String${colors.reset}`,
|
|
1023
|
+
` }`,
|
|
796
1024
|
` }`,
|
|
797
1025
|
"",
|
|
798
|
-
`Type ${colors.cyan}.help <command>${colors.reset} for
|
|
1026
|
+
`Type ${colors.cyan}.help <command>${colors.reset} for details.`,
|
|
799
1027
|
""
|
|
800
1028
|
];
|
|
801
1029
|
return { output: lines.join("\n") };
|
|
802
1030
|
}
|
|
803
1031
|
},
|
|
1032
|
+
// ─── .parse ─────────────────────────────────────────────────────────────
|
|
804
1033
|
{
|
|
805
|
-
name: "
|
|
806
|
-
aliases: ["
|
|
807
|
-
description: "
|
|
808
|
-
usage: ".
|
|
809
|
-
handler: () => {
|
|
810
|
-
return { exit: true };
|
|
811
|
-
}
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
name: "clear",
|
|
815
|
-
aliases: ["cls"],
|
|
816
|
-
description: "Clear session state (intents, variables)",
|
|
817
|
-
usage: ".clear",
|
|
1034
|
+
name: "parse",
|
|
1035
|
+
aliases: ["p", "ast"],
|
|
1036
|
+
description: "Parse ISL and show AST",
|
|
1037
|
+
usage: ".parse <isl>",
|
|
818
1038
|
handler: (args, session) => {
|
|
819
|
-
|
|
820
|
-
|
|
1039
|
+
const input = args.join(" ").trim();
|
|
1040
|
+
if (!input) {
|
|
1041
|
+
return { output: 'Usage: .parse <isl code>\nExample: .parse domain Foo { version: "1.0" }' };
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
const { parse } = __require("@isl-lang/parser");
|
|
1045
|
+
const result = parse(input, "<repl>");
|
|
1046
|
+
if (!result.success || result.errors.length > 0) {
|
|
1047
|
+
const errLines = result.errors.map((e) => {
|
|
1048
|
+
const loc = e.location;
|
|
1049
|
+
if (loc) {
|
|
1050
|
+
return formatParseError(input, e.message, loc.line, loc.column);
|
|
1051
|
+
}
|
|
1052
|
+
return formatError(e.message);
|
|
1053
|
+
});
|
|
1054
|
+
return { output: errLines.join("\n") };
|
|
1055
|
+
}
|
|
1056
|
+
if (result.domain) {
|
|
1057
|
+
session.setDomainAST(result.domain);
|
|
1058
|
+
return {
|
|
1059
|
+
output: formatSuccess("Parsed successfully") + "\n" + prettyPrintAST(result.domain)
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
return { output: formatWarning("Parse returned no AST") };
|
|
1063
|
+
} catch {
|
|
1064
|
+
return {
|
|
1065
|
+
output: formatWarning(
|
|
1066
|
+
"Real parser not available \u2014 install @isl-lang/parser.\nFalling back to simple parse."
|
|
1067
|
+
)
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
821
1070
|
}
|
|
822
1071
|
},
|
|
1072
|
+
// ─── .eval ──────────────────────────────────────────────────────────────
|
|
823
1073
|
{
|
|
824
|
-
name: "
|
|
825
|
-
aliases: ["
|
|
826
|
-
description: "
|
|
827
|
-
usage: ".
|
|
1074
|
+
name: "eval",
|
|
1075
|
+
aliases: ["e"],
|
|
1076
|
+
description: "Evaluate expression against context",
|
|
1077
|
+
usage: ".eval <expression>",
|
|
828
1078
|
handler: (args, session) => {
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1079
|
+
const expr = args.join(" ").trim();
|
|
1080
|
+
if (!expr) {
|
|
1081
|
+
return {
|
|
1082
|
+
output: [
|
|
1083
|
+
"Usage: .eval <expression>",
|
|
1084
|
+
"",
|
|
1085
|
+
"Examples:",
|
|
1086
|
+
' .eval user.email == "test@x.com"',
|
|
1087
|
+
" .eval user.age > 30",
|
|
1088
|
+
" .eval old(user.age)",
|
|
1089
|
+
"",
|
|
1090
|
+
'Set context first: .context { "user": { "email": "test@x.com" } }'
|
|
1091
|
+
].join("\n")
|
|
1092
|
+
};
|
|
833
1093
|
}
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
})
|
|
843
|
-
];
|
|
844
|
-
return { output: lines.join("\n") };
|
|
1094
|
+
const result = evaluateExpression(expr, session);
|
|
1095
|
+
if (result.error) {
|
|
1096
|
+
return { output: formatError(result.error) };
|
|
1097
|
+
}
|
|
1098
|
+
session.setLastResult(result.value);
|
|
1099
|
+
return {
|
|
1100
|
+
output: `${colors.cyan}\u2192${colors.reset} ${formatValue(result.value)}`
|
|
1101
|
+
};
|
|
845
1102
|
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
var islCommands = [
|
|
1103
|
+
},
|
|
1104
|
+
// ─── .check ─────────────────────────────────────────────────────────────
|
|
849
1105
|
{
|
|
850
1106
|
name: "check",
|
|
851
1107
|
aliases: ["c"],
|
|
852
|
-
description: "Type check
|
|
853
|
-
usage: "
|
|
1108
|
+
description: "Type check the current session",
|
|
1109
|
+
usage: ".check [intent]",
|
|
854
1110
|
handler: (args, session) => {
|
|
855
|
-
if (args.length
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
1111
|
+
if (args.length > 0) {
|
|
1112
|
+
const intentName = args[0];
|
|
1113
|
+
const intent = session.getIntent(intentName);
|
|
1114
|
+
if (!intent) {
|
|
1115
|
+
const available = session.getIntentNames().join(", ") || "(none)";
|
|
1116
|
+
return {
|
|
1117
|
+
output: formatError(`Unknown intent: ${intentName}
|
|
1118
|
+
Available: ${available}`)
|
|
1119
|
+
};
|
|
859
1120
|
}
|
|
860
|
-
const lines2 = [
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
for (const
|
|
865
|
-
lines2.push(
|
|
866
|
-
for (const pre of intent2.preconditions) {
|
|
867
|
-
lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightCondition(pre.expression)}`);
|
|
868
|
-
}
|
|
869
|
-
for (const post of intent2.postconditions) {
|
|
870
|
-
lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightCondition(post.expression)}`);
|
|
871
|
-
}
|
|
872
|
-
lines2.push("");
|
|
1121
|
+
const lines2 = [formatSuccess("Type check passed"), ""];
|
|
1122
|
+
for (const pre of intent.preconditions) {
|
|
1123
|
+
lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
|
|
1124
|
+
}
|
|
1125
|
+
for (const post of intent.postconditions) {
|
|
1126
|
+
lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
|
|
873
1127
|
}
|
|
874
1128
|
return { output: lines2.join("\n") };
|
|
875
1129
|
}
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
if (!intent) {
|
|
879
|
-
const available = session.getIntentNames().join(", ") || "(none)";
|
|
1130
|
+
const intents = session.getAllIntents();
|
|
1131
|
+
if (intents.length === 0) {
|
|
880
1132
|
return {
|
|
881
|
-
output:
|
|
882
|
-
Available: ${available}`)
|
|
1133
|
+
output: formatWarning("No intents defined. Write ISL or use .load <file>")
|
|
883
1134
|
};
|
|
884
1135
|
}
|
|
885
|
-
const lines = [
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1136
|
+
const lines = [formatSuccess(`Type check passed \u2014 ${intents.length} intent(s)`), ""];
|
|
1137
|
+
for (const intent of intents) {
|
|
1138
|
+
lines.push(`${colors.bold}${intent.name}${colors.reset}`);
|
|
1139
|
+
for (const pre of intent.preconditions) {
|
|
1140
|
+
lines.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
|
|
1141
|
+
}
|
|
1142
|
+
for (const post of intent.postconditions) {
|
|
1143
|
+
lines.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
|
|
1144
|
+
}
|
|
1145
|
+
lines.push("");
|
|
894
1146
|
}
|
|
895
1147
|
return { output: lines.join("\n") };
|
|
896
1148
|
}
|
|
897
1149
|
},
|
|
1150
|
+
// ─── .gen ───────────────────────────────────────────────────────────────
|
|
898
1151
|
{
|
|
899
1152
|
name: "gen",
|
|
900
1153
|
aliases: ["generate", "g"],
|
|
901
|
-
description: "Generate
|
|
902
|
-
usage: "
|
|
1154
|
+
description: "Generate TypeScript from intent",
|
|
1155
|
+
usage: ".gen [intent]",
|
|
903
1156
|
handler: (args, session) => {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
output: [
|
|
907
|
-
"Usage: :gen <target> <intent>",
|
|
908
|
-
"",
|
|
909
|
-
"Targets:",
|
|
910
|
-
" typescript Generate TypeScript contract",
|
|
911
|
-
" rust Generate Rust contract",
|
|
912
|
-
" go Generate Go contract",
|
|
913
|
-
" openapi Generate OpenAPI schema"
|
|
914
|
-
].join("\n")
|
|
915
|
-
};
|
|
916
|
-
}
|
|
917
|
-
const target = args[0].toLowerCase();
|
|
918
|
-
const intentName = args[1];
|
|
919
|
-
const intent = session.getIntent(intentName);
|
|
920
|
-
if (!intent) {
|
|
921
|
-
const available = session.getIntentNames().join(", ") || "(none)";
|
|
1157
|
+
const intents = args.length > 0 ? [session.getIntent(args[0])].filter(Boolean) : session.getAllIntents();
|
|
1158
|
+
if (intents.length === 0) {
|
|
922
1159
|
return {
|
|
923
|
-
output: formatError(`Unknown intent: ${
|
|
924
|
-
Available: ${
|
|
1160
|
+
output: args.length > 0 ? formatError(`Unknown intent: ${args[0]}
|
|
1161
|
+
Available: ${session.getIntentNames().join(", ") || "(none)"}`) : formatWarning("No intents defined. Write ISL or use .load <file>")
|
|
925
1162
|
};
|
|
926
1163
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1164
|
+
const lines = [`${colors.gray}// Generated TypeScript${colors.reset}`, ""];
|
|
1165
|
+
for (const intent of intents) {
|
|
1166
|
+
lines.push(`interface ${intent.name}Contract {`);
|
|
1167
|
+
if (intent.preconditions.length > 0) {
|
|
1168
|
+
lines.push(" /** Preconditions */");
|
|
1169
|
+
for (const pre of intent.preconditions) {
|
|
1170
|
+
lines.push(` checkPre(): boolean; // ${pre.expression}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
if (intent.postconditions.length > 0) {
|
|
1174
|
+
lines.push(" /** Postconditions */");
|
|
1175
|
+
for (const post of intent.postconditions) {
|
|
1176
|
+
lines.push(` checkPost(): boolean; // ${post.expression}`);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (intent.invariants.length > 0) {
|
|
1180
|
+
lines.push(" /** Invariants */");
|
|
1181
|
+
for (const inv of intent.invariants) {
|
|
1182
|
+
lines.push(` checkInvariant(): boolean; // ${inv.expression}`);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
lines.push("}");
|
|
1186
|
+
lines.push("");
|
|
944
1187
|
}
|
|
1188
|
+
return { output: lines.join("\n") };
|
|
945
1189
|
}
|
|
946
1190
|
},
|
|
1191
|
+
// ─── .load ──────────────────────────────────────────────────────────────
|
|
947
1192
|
{
|
|
948
1193
|
name: "load",
|
|
949
1194
|
aliases: ["l"],
|
|
950
|
-
description: "Load
|
|
951
|
-
usage: "
|
|
1195
|
+
description: "Load an .isl file",
|
|
1196
|
+
usage: ".load <file.isl>",
|
|
952
1197
|
handler: (args, session) => {
|
|
953
1198
|
if (args.length === 0) {
|
|
954
|
-
return { output: "Usage:
|
|
1199
|
+
return { output: "Usage: .load <file.isl>" };
|
|
955
1200
|
}
|
|
956
1201
|
const filePath = args[0];
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}
|
|
961
|
-
result.errors.push(String(e));
|
|
962
|
-
});
|
|
963
|
-
const fs4 = __require("fs");
|
|
964
|
-
const path4 = __require("path");
|
|
1202
|
+
const resolvedPath = path__namespace.isAbsolute(filePath) ? filePath : path__namespace.resolve(process.cwd(), filePath);
|
|
1203
|
+
if (!fs2__namespace.existsSync(resolvedPath)) {
|
|
1204
|
+
return { output: formatError(`File not found: ${resolvedPath}`) };
|
|
1205
|
+
}
|
|
965
1206
|
try {
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
|
|
1207
|
+
const content = fs2__namespace.readFileSync(resolvedPath, "utf-8");
|
|
1208
|
+
try {
|
|
1209
|
+
const { parse } = __require("@isl-lang/parser");
|
|
1210
|
+
const result = parse(content, resolvedPath);
|
|
1211
|
+
if (!result.success || result.errors.length > 0) {
|
|
1212
|
+
const errLines = result.errors.map((e) => {
|
|
1213
|
+
const loc = e.location;
|
|
1214
|
+
if (loc) {
|
|
1215
|
+
return formatParseError(content, e.message, loc.line, loc.column);
|
|
1216
|
+
}
|
|
1217
|
+
return formatError(e.message);
|
|
1218
|
+
});
|
|
1219
|
+
return { output: errLines.join("\n") };
|
|
1220
|
+
}
|
|
1221
|
+
if (result.domain) {
|
|
1222
|
+
session.setDomainAST(result.domain);
|
|
1223
|
+
const domain = result.domain;
|
|
1224
|
+
const name = domain.name?.name ?? "Unknown";
|
|
1225
|
+
const entityCount = domain.entities?.length ?? 0;
|
|
1226
|
+
const behaviorCount = domain.behaviors?.length ?? 0;
|
|
1227
|
+
return {
|
|
1228
|
+
output: formatSuccess(
|
|
1229
|
+
`Loaded: ${name} (${entityCount} entities, ${behaviorCount} behaviors) from ${path__namespace.basename(filePath)}`
|
|
1230
|
+
)
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
969
1234
|
}
|
|
970
|
-
const content = fs4.readFileSync(resolvedPath, "utf-8");
|
|
971
1235
|
const intentRegex = /(?:intent|behavior)\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
|
|
972
1236
|
let match;
|
|
973
1237
|
let count = 0;
|
|
@@ -979,37 +1243,125 @@ Available: typescript, rust, go, openapi`)
|
|
|
979
1243
|
}
|
|
980
1244
|
}
|
|
981
1245
|
if (count === 0) {
|
|
982
|
-
return { output: formatWarning("No intents found in file") };
|
|
1246
|
+
return { output: formatWarning("No intents/behaviors found in file") };
|
|
983
1247
|
}
|
|
984
1248
|
return {
|
|
985
|
-
output: formatSuccess(`Loaded ${count} intent(s) from ${filePath}`)
|
|
1249
|
+
output: formatSuccess(`Loaded ${count} intent(s) from ${path__namespace.basename(filePath)}`)
|
|
986
1250
|
};
|
|
987
1251
|
} catch (error) {
|
|
988
1252
|
return {
|
|
989
|
-
output: formatError(
|
|
1253
|
+
output: formatError(
|
|
1254
|
+
`Failed to load: ${error instanceof Error ? error.message : String(error)}`
|
|
1255
|
+
)
|
|
990
1256
|
};
|
|
991
1257
|
}
|
|
992
1258
|
}
|
|
993
1259
|
},
|
|
1260
|
+
// ─── .context ───────────────────────────────────────────────────────────
|
|
1261
|
+
{
|
|
1262
|
+
name: "context",
|
|
1263
|
+
aliases: ["ctx"],
|
|
1264
|
+
description: "Set evaluation context (JSON)",
|
|
1265
|
+
usage: ".context <json> | .context --pre <json>",
|
|
1266
|
+
handler: (args, session) => {
|
|
1267
|
+
const input = args.join(" ").trim();
|
|
1268
|
+
if (!input) {
|
|
1269
|
+
const ctx = session.getEvalContext();
|
|
1270
|
+
const pre = session.getPreContext();
|
|
1271
|
+
if (Object.keys(ctx).length === 0 && !pre) {
|
|
1272
|
+
return {
|
|
1273
|
+
output: [
|
|
1274
|
+
"No context set.",
|
|
1275
|
+
"",
|
|
1276
|
+
"Usage:",
|
|
1277
|
+
' .context { "user": { "email": "test@x.com", "age": 25 } }',
|
|
1278
|
+
' .context --pre { "user": { "age": 20 } }'
|
|
1279
|
+
].join("\n")
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
const lines = [];
|
|
1283
|
+
if (Object.keys(ctx).length > 0) {
|
|
1284
|
+
lines.push(`${colors.bold}Context:${colors.reset}`);
|
|
1285
|
+
lines.push(formatValue(ctx));
|
|
1286
|
+
}
|
|
1287
|
+
if (pre) {
|
|
1288
|
+
lines.push(`${colors.bold}Pre-state:${colors.reset}`);
|
|
1289
|
+
lines.push(formatValue(pre));
|
|
1290
|
+
}
|
|
1291
|
+
return { output: lines.join("\n") };
|
|
1292
|
+
}
|
|
1293
|
+
if (input.startsWith("--pre ")) {
|
|
1294
|
+
const json = input.slice(6).trim();
|
|
1295
|
+
const result2 = session.setPreContext(json);
|
|
1296
|
+
if (!result2.success) {
|
|
1297
|
+
return { output: formatError(`Invalid JSON: ${result2.error}`) };
|
|
1298
|
+
}
|
|
1299
|
+
return { output: formatSuccess("Pre-state context set") };
|
|
1300
|
+
}
|
|
1301
|
+
const result = session.setEvalContext(input);
|
|
1302
|
+
if (!result.success) {
|
|
1303
|
+
return { output: formatError(`Invalid JSON: ${result.error}`) };
|
|
1304
|
+
}
|
|
1305
|
+
return {
|
|
1306
|
+
output: formatSuccess(
|
|
1307
|
+
`Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`
|
|
1308
|
+
)
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1312
|
+
// ─── .clear ─────────────────────────────────────────────────────────────
|
|
1313
|
+
{
|
|
1314
|
+
name: "clear",
|
|
1315
|
+
aliases: ["cls", "reset"],
|
|
1316
|
+
description: "Reset session state",
|
|
1317
|
+
usage: ".clear",
|
|
1318
|
+
handler: (_args, session) => {
|
|
1319
|
+
session.clear();
|
|
1320
|
+
return { output: formatSuccess("Session cleared") };
|
|
1321
|
+
}
|
|
1322
|
+
},
|
|
1323
|
+
// ─── .history ───────────────────────────────────────────────────────────
|
|
1324
|
+
{
|
|
1325
|
+
name: "history",
|
|
1326
|
+
aliases: ["hist"],
|
|
1327
|
+
description: "Show command history",
|
|
1328
|
+
usage: ".history [n]",
|
|
1329
|
+
handler: (args, session) => {
|
|
1330
|
+
const count = args.length > 0 ? parseInt(args[0], 10) : 10;
|
|
1331
|
+
const history = session.getHistory(count);
|
|
1332
|
+
if (history.length === 0) {
|
|
1333
|
+
return { output: "No history." };
|
|
1334
|
+
}
|
|
1335
|
+
const lines = [
|
|
1336
|
+
`${colors.bold}History${colors.reset} (last ${history.length} entries)`,
|
|
1337
|
+
"",
|
|
1338
|
+
...history.map((entry, i) => {
|
|
1339
|
+
const num = String(i + 1).padStart(3);
|
|
1340
|
+
const preview = entry.split("\n")[0];
|
|
1341
|
+
const more = entry.includes("\n") ? ` ${colors.gray}...${colors.reset}` : "";
|
|
1342
|
+
return ` ${colors.gray}${num}${colors.reset} ${preview}${more}`;
|
|
1343
|
+
})
|
|
1344
|
+
];
|
|
1345
|
+
return { output: lines.join("\n") };
|
|
1346
|
+
}
|
|
1347
|
+
},
|
|
1348
|
+
// ─── .list ──────────────────────────────────────────────────────────────
|
|
994
1349
|
{
|
|
995
1350
|
name: "list",
|
|
996
1351
|
aliases: ["ls"],
|
|
997
|
-
description: "List
|
|
998
|
-
usage: "
|
|
999
|
-
handler: (
|
|
1352
|
+
description: "List defined intents",
|
|
1353
|
+
usage: ".list",
|
|
1354
|
+
handler: (_args, session) => {
|
|
1000
1355
|
const intents = session.getAllIntents();
|
|
1001
1356
|
if (intents.length === 0) {
|
|
1002
1357
|
return { output: "No intents defined." };
|
|
1003
1358
|
}
|
|
1004
1359
|
const lines = [""];
|
|
1005
1360
|
for (const intent of intents) {
|
|
1006
|
-
const preCount = intent.preconditions.length;
|
|
1007
|
-
const postCount = intent.postconditions.length;
|
|
1008
|
-
const invCount = intent.invariants.length;
|
|
1009
1361
|
const parts = [];
|
|
1010
|
-
if (
|
|
1011
|
-
if (
|
|
1012
|
-
if (
|
|
1362
|
+
if (intent.preconditions.length > 0) parts.push(`${intent.preconditions.length} pre`);
|
|
1363
|
+
if (intent.postconditions.length > 0) parts.push(`${intent.postconditions.length} post`);
|
|
1364
|
+
if (intent.invariants.length > 0) parts.push(`${intent.invariants.length} invariant`);
|
|
1013
1365
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
1014
1366
|
lines.push(` ${colors.cyan}${intent.name}${colors.reset}${summary}`);
|
|
1015
1367
|
}
|
|
@@ -1017,31 +1369,26 @@ Available: typescript, rust, go, openapi`)
|
|
|
1017
1369
|
return { output: lines.join("\n") };
|
|
1018
1370
|
}
|
|
1019
1371
|
},
|
|
1372
|
+
// ─── .inspect ───────────────────────────────────────────────────────────
|
|
1020
1373
|
{
|
|
1021
1374
|
name: "inspect",
|
|
1022
1375
|
aliases: ["i", "show"],
|
|
1023
1376
|
description: "Show full details of an intent",
|
|
1024
|
-
usage: "
|
|
1377
|
+
usage: ".inspect [intent]",
|
|
1025
1378
|
handler: (args, session) => {
|
|
1026
1379
|
if (args.length === 0) {
|
|
1027
1380
|
const summary = session.getSummary();
|
|
1028
|
-
const
|
|
1381
|
+
const ctx = session.getEvalContext();
|
|
1029
1382
|
const lines = [
|
|
1030
1383
|
"",
|
|
1031
1384
|
`${colors.bold}Session Summary${colors.reset}`,
|
|
1032
1385
|
"",
|
|
1033
1386
|
` Intents: ${summary.intentCount}`,
|
|
1034
1387
|
` Variables: ${summary.variableCount}`,
|
|
1035
|
-
`
|
|
1388
|
+
` Context: ${Object.keys(ctx).length} keys`,
|
|
1389
|
+
` History: ${summary.historyCount} entries`,
|
|
1390
|
+
""
|
|
1036
1391
|
];
|
|
1037
|
-
if (files.length > 0) {
|
|
1038
|
-
lines.push("");
|
|
1039
|
-
lines.push(`${colors.bold}Loaded Files${colors.reset}`);
|
|
1040
|
-
for (const file of files) {
|
|
1041
|
-
lines.push(` ${file}`);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
lines.push("");
|
|
1045
1392
|
return { output: lines.join("\n") };
|
|
1046
1393
|
}
|
|
1047
1394
|
const intentName = args[0];
|
|
@@ -1056,178 +1403,18 @@ Available: ${available}`)
|
|
|
1056
1403
|
return { output: formatIntent(intent) };
|
|
1057
1404
|
}
|
|
1058
1405
|
},
|
|
1406
|
+
// ─── .exit ──────────────────────────────────────────────────────────────
|
|
1059
1407
|
{
|
|
1060
|
-
name: "
|
|
1061
|
-
aliases: ["
|
|
1062
|
-
description: "
|
|
1063
|
-
usage: "
|
|
1064
|
-
handler: (
|
|
1065
|
-
|
|
1066
|
-
return { output: "Usage: :export <file.isl>" };
|
|
1067
|
-
}
|
|
1068
|
-
const filePath = args[0];
|
|
1069
|
-
const fs4 = __require("fs");
|
|
1070
|
-
const path4 = __require("path");
|
|
1071
|
-
try {
|
|
1072
|
-
const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
|
|
1073
|
-
const intents = session.getAllIntents();
|
|
1074
|
-
if (intents.length === 0) {
|
|
1075
|
-
return { output: formatWarning("No intents to export") };
|
|
1076
|
-
}
|
|
1077
|
-
const lines = [];
|
|
1078
|
-
lines.push("// Exported ISL intents");
|
|
1079
|
-
lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
1080
|
-
lines.push("");
|
|
1081
|
-
for (const intent of intents) {
|
|
1082
|
-
lines.push(`intent ${intent.name} {`);
|
|
1083
|
-
for (const pre of intent.preconditions) {
|
|
1084
|
-
lines.push(` pre: ${pre.expression}`);
|
|
1085
|
-
}
|
|
1086
|
-
for (const post of intent.postconditions) {
|
|
1087
|
-
lines.push(` post: ${post.expression}`);
|
|
1088
|
-
}
|
|
1089
|
-
for (const inv of intent.invariants) {
|
|
1090
|
-
lines.push(` invariant: ${inv.expression}`);
|
|
1091
|
-
}
|
|
1092
|
-
lines.push("}");
|
|
1093
|
-
lines.push("");
|
|
1094
|
-
}
|
|
1095
|
-
fs4.writeFileSync(resolvedPath, lines.join("\n"));
|
|
1096
|
-
return { output: formatSuccess(`Exported ${intents.length} intent(s) to ${filePath}`) };
|
|
1097
|
-
} catch (error) {
|
|
1098
|
-
return {
|
|
1099
|
-
output: formatError(`Failed to export: ${error instanceof Error ? error.message : String(error)}`)
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1408
|
+
name: "exit",
|
|
1409
|
+
aliases: ["quit", "q"],
|
|
1410
|
+
description: "Exit the REPL",
|
|
1411
|
+
usage: ".exit",
|
|
1412
|
+
handler: () => {
|
|
1413
|
+
return { exit: true };
|
|
1102
1414
|
}
|
|
1103
1415
|
}
|
|
1104
1416
|
];
|
|
1105
|
-
|
|
1106
|
-
const lines = [
|
|
1107
|
-
`${colors.gray}// Generated TypeScript${colors.reset}`,
|
|
1108
|
-
`interface ${intent.name}Contract {`
|
|
1109
|
-
];
|
|
1110
|
-
if (intent.preconditions.length > 0) {
|
|
1111
|
-
for (const pre of intent.preconditions) {
|
|
1112
|
-
const varName = extractVariableName(pre.expression);
|
|
1113
|
-
const type = inferType(pre.expression);
|
|
1114
|
-
lines.push(` pre: (${varName}: ${type}) => boolean;`);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
if (intent.postconditions.length > 0) {
|
|
1118
|
-
for (const post of intent.postconditions) {
|
|
1119
|
-
const varName = extractVariableName(post.expression);
|
|
1120
|
-
const type = inferType(post.expression);
|
|
1121
|
-
lines.push(` post: (${varName}: ${type}) => boolean;`);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
lines.push("}");
|
|
1125
|
-
return lines.join("\n");
|
|
1126
|
-
}
|
|
1127
|
-
function generateRust(intent) {
|
|
1128
|
-
const lines = [
|
|
1129
|
-
`${colors.gray}// Generated Rust${colors.reset}`,
|
|
1130
|
-
`pub trait ${intent.name}Contract {`
|
|
1131
|
-
];
|
|
1132
|
-
if (intent.preconditions.length > 0) {
|
|
1133
|
-
for (const pre of intent.preconditions) {
|
|
1134
|
-
const varName = extractVariableName(pre.expression);
|
|
1135
|
-
const type = inferRustType(pre.expression);
|
|
1136
|
-
lines.push(` fn check_pre(&self, ${varName}: ${type}) -> bool;`);
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
if (intent.postconditions.length > 0) {
|
|
1140
|
-
for (const post of intent.postconditions) {
|
|
1141
|
-
const varName = extractVariableName(post.expression);
|
|
1142
|
-
const type = inferRustType(post.expression);
|
|
1143
|
-
lines.push(` fn check_post(&self, ${varName}: ${type}) -> bool;`);
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
lines.push("}");
|
|
1147
|
-
return lines.join("\n");
|
|
1148
|
-
}
|
|
1149
|
-
function generateGo(intent) {
|
|
1150
|
-
const lines = [
|
|
1151
|
-
`${colors.gray}// Generated Go${colors.reset}`,
|
|
1152
|
-
`type ${intent.name}Contract interface {`
|
|
1153
|
-
];
|
|
1154
|
-
if (intent.preconditions.length > 0) {
|
|
1155
|
-
for (const pre of intent.preconditions) {
|
|
1156
|
-
const varName = extractVariableName(pre.expression);
|
|
1157
|
-
const type = inferGoType(pre.expression);
|
|
1158
|
-
lines.push(` CheckPre(${varName} ${type}) bool`);
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
if (intent.postconditions.length > 0) {
|
|
1162
|
-
for (const post of intent.postconditions) {
|
|
1163
|
-
const varName = extractVariableName(post.expression);
|
|
1164
|
-
const type = inferGoType(post.expression);
|
|
1165
|
-
lines.push(` CheckPost(${varName} ${type}) bool`);
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
lines.push("}");
|
|
1169
|
-
return lines.join("\n");
|
|
1170
|
-
}
|
|
1171
|
-
function generateOpenAPI(intent) {
|
|
1172
|
-
const lines = [
|
|
1173
|
-
`${colors.gray}# Generated OpenAPI${colors.reset}`,
|
|
1174
|
-
`openapi: 3.0.0`,
|
|
1175
|
-
`paths:`,
|
|
1176
|
-
` /${intent.name.toLowerCase()}:`,
|
|
1177
|
-
` post:`,
|
|
1178
|
-
` summary: ${intent.name}`,
|
|
1179
|
-
` requestBody:`,
|
|
1180
|
-
` content:`,
|
|
1181
|
-
` application/json:`,
|
|
1182
|
-
` schema:`,
|
|
1183
|
-
` type: object`
|
|
1184
|
-
];
|
|
1185
|
-
if (intent.preconditions.length > 0) {
|
|
1186
|
-
lines.push(` # Preconditions:`);
|
|
1187
|
-
for (const pre of intent.preconditions) {
|
|
1188
|
-
lines.push(` # - ${pre.expression}`);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
lines.push(` responses:`);
|
|
1192
|
-
lines.push(` '200':`);
|
|
1193
|
-
lines.push(` description: Success`);
|
|
1194
|
-
if (intent.postconditions.length > 0) {
|
|
1195
|
-
lines.push(` # Postconditions:`);
|
|
1196
|
-
for (const post of intent.postconditions) {
|
|
1197
|
-
lines.push(` # - ${post.expression}`);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
return lines.join("\n");
|
|
1201
|
-
}
|
|
1202
|
-
function extractVariableName(expression) {
|
|
1203
|
-
const match = expression.match(/^(\w+)/);
|
|
1204
|
-
return match ? match[1] : "input";
|
|
1205
|
-
}
|
|
1206
|
-
function inferType(expression) {
|
|
1207
|
-
if (expression.includes(".length")) return "string";
|
|
1208
|
-
if (expression.includes(".startsWith")) return "string";
|
|
1209
|
-
if (expression.includes(".endsWith")) return "string";
|
|
1210
|
-
if (expression.includes(".includes")) return "string";
|
|
1211
|
-
if (expression.includes(" > ") || expression.includes(" < ")) return "number";
|
|
1212
|
-
return "unknown";
|
|
1213
|
-
}
|
|
1214
|
-
function inferRustType(expression) {
|
|
1215
|
-
if (expression.includes(".length") || expression.includes(".len()")) return "&str";
|
|
1216
|
-
if (expression.includes(".starts_with")) return "&str";
|
|
1217
|
-
if (expression.includes(" > ") || expression.includes(" < ")) return "i32";
|
|
1218
|
-
return "&str";
|
|
1219
|
-
}
|
|
1220
|
-
function inferGoType(expression) {
|
|
1221
|
-
if (expression.includes(".length") || expression.includes("len(")) return "string";
|
|
1222
|
-
if (expression.includes(" > ") || expression.includes(" < ")) return "int";
|
|
1223
|
-
return "string";
|
|
1224
|
-
}
|
|
1225
|
-
function highlightCondition(expression) {
|
|
1226
|
-
return expression.replace(
|
|
1227
|
-
/(\w+)\s*(>|<|>=|<=|==|!=)\s*(\d+|"[^"]*")/g,
|
|
1228
|
-
`${colors.blue}$1${colors.reset} ${colors.yellow}$2${colors.reset} ${colors.green}$3${colors.reset}`
|
|
1229
|
-
).replace(/\b(true|false)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\.(length|startsWith|endsWith|includes)/g, `.${colors.cyan}$1${colors.reset}`);
|
|
1230
|
-
}
|
|
1417
|
+
var islCommands = [];
|
|
1231
1418
|
function levenshteinDistance(a, b) {
|
|
1232
1419
|
const matrix = [];
|
|
1233
1420
|
for (let i = 0; i <= b.length; i++) {
|
|
@@ -1248,9 +1435,8 @@ function levenshteinDistance(a, b) {
|
|
|
1248
1435
|
}
|
|
1249
1436
|
return matrix[b.length][a.length];
|
|
1250
1437
|
}
|
|
1251
|
-
function findSimilarCommand(input,
|
|
1252
|
-
const
|
|
1253
|
-
const names = commands.flatMap((c) => [c.name, ...c.aliases]);
|
|
1438
|
+
function findSimilarCommand(input, _type) {
|
|
1439
|
+
const names = metaCommands.flatMap((c) => [c.name, ...c.aliases]);
|
|
1254
1440
|
let bestMatch = null;
|
|
1255
1441
|
let bestDistance = Infinity;
|
|
1256
1442
|
for (const name of names) {
|
|
@@ -1265,11 +1451,31 @@ function findSimilarCommand(input, type) {
|
|
|
1265
1451
|
|
|
1266
1452
|
// src/completions.ts
|
|
1267
1453
|
var KEYWORDS = [
|
|
1268
|
-
|
|
1454
|
+
// Structure keywords
|
|
1455
|
+
{ text: "domain", type: "keyword", description: "Define a domain" },
|
|
1456
|
+
{ text: "entity", type: "keyword", description: "Define an entity" },
|
|
1269
1457
|
{ text: "behavior", type: "keyword", description: "Define a behavior" },
|
|
1458
|
+
{ text: "intent", type: "keyword", description: "Define an intent" },
|
|
1459
|
+
{ text: "input", type: "keyword", description: "Input block" },
|
|
1460
|
+
{ text: "output", type: "keyword", description: "Output block" },
|
|
1270
1461
|
{ text: "pre", type: "keyword", description: "Precondition" },
|
|
1271
1462
|
{ text: "post", type: "keyword", description: "Postcondition" },
|
|
1272
1463
|
{ text: "invariant", type: "keyword", description: "Invariant" },
|
|
1464
|
+
{ text: "scenario", type: "keyword", description: "Scenario block" },
|
|
1465
|
+
{ text: "version", type: "keyword", description: "Version declaration" },
|
|
1466
|
+
// Types
|
|
1467
|
+
{ text: "String", type: "keyword", description: "String type" },
|
|
1468
|
+
{ text: "Number", type: "keyword", description: "Number type" },
|
|
1469
|
+
{ text: "Int", type: "keyword", description: "Integer type" },
|
|
1470
|
+
{ text: "Decimal", type: "keyword", description: "Decimal type" },
|
|
1471
|
+
{ text: "Boolean", type: "keyword", description: "Boolean type" },
|
|
1472
|
+
{ text: "UUID", type: "keyword", description: "UUID type" },
|
|
1473
|
+
{ text: "Timestamp", type: "keyword", description: "Timestamp type" },
|
|
1474
|
+
{ text: "Duration", type: "keyword", description: "Duration type" },
|
|
1475
|
+
{ text: "List", type: "keyword", description: "List<T> type" },
|
|
1476
|
+
{ text: "Map", type: "keyword", description: "Map<K,V> type" },
|
|
1477
|
+
{ text: "Optional", type: "keyword", description: "Optional<T> type" },
|
|
1478
|
+
// Literals and operators
|
|
1273
1479
|
{ text: "true", type: "keyword", description: "Boolean true" },
|
|
1274
1480
|
{ text: "false", type: "keyword", description: "Boolean false" },
|
|
1275
1481
|
{ text: "null", type: "keyword", description: "Null value" },
|
|
@@ -1279,19 +1485,16 @@ var KEYWORDS = [
|
|
|
1279
1485
|
{ text: "implies", type: "keyword", description: "Logical implication" },
|
|
1280
1486
|
{ text: "forall", type: "keyword", description: "Universal quantifier" },
|
|
1281
1487
|
{ text: "exists", type: "keyword", description: "Existential quantifier" },
|
|
1282
|
-
{ text: "in", type: "keyword", description: "Membership test" }
|
|
1488
|
+
{ text: "in", type: "keyword", description: "Membership test" },
|
|
1489
|
+
{ text: "old", type: "keyword", description: "Pre-state value (old(x))" }
|
|
1283
1490
|
];
|
|
1284
1491
|
var META_COMMANDS = metaCommands.map((cmd) => ({
|
|
1285
1492
|
text: `.${cmd.name}`,
|
|
1286
1493
|
type: "command",
|
|
1287
1494
|
description: cmd.description
|
|
1288
1495
|
}));
|
|
1289
|
-
var ISL_COMMANDS =
|
|
1290
|
-
|
|
1291
|
-
type: "command",
|
|
1292
|
-
description: cmd.description
|
|
1293
|
-
}));
|
|
1294
|
-
var COMMANDS = [...META_COMMANDS, ...ISL_COMMANDS];
|
|
1496
|
+
var ISL_COMMANDS = [];
|
|
1497
|
+
var COMMANDS = [...META_COMMANDS];
|
|
1295
1498
|
var GEN_TARGETS = [
|
|
1296
1499
|
{ text: "typescript", type: "keyword", description: "Generate TypeScript contract" },
|
|
1297
1500
|
{ text: "rust", type: "keyword", description: "Generate Rust contract" },
|
|
@@ -1317,7 +1520,7 @@ var CompletionProvider = class {
|
|
|
1317
1520
|
return this.completeMetaCommand(trimmed);
|
|
1318
1521
|
}
|
|
1319
1522
|
if (trimmed.startsWith(":")) {
|
|
1320
|
-
return this.
|
|
1523
|
+
return this.completeMetaCommand("." + trimmed.slice(1));
|
|
1321
1524
|
}
|
|
1322
1525
|
return this.completeExpression(trimmed);
|
|
1323
1526
|
}
|
|
@@ -1494,7 +1697,10 @@ var ISLREPL = class {
|
|
|
1494
1697
|
this.options = {
|
|
1495
1698
|
colors: options.colors !== false,
|
|
1496
1699
|
verbose: options.verbose ?? false,
|
|
1497
|
-
historyFile: options.historyFile
|
|
1700
|
+
historyFile: options.historyFile,
|
|
1701
|
+
load: options.load,
|
|
1702
|
+
context: options.context,
|
|
1703
|
+
parseOnly: options.parseOnly ?? false
|
|
1498
1704
|
};
|
|
1499
1705
|
this.session = new Session({ colors: this.options.colors });
|
|
1500
1706
|
this.history = new History({
|
|
@@ -1508,6 +1714,11 @@ var ISLREPL = class {
|
|
|
1508
1714
|
start() {
|
|
1509
1715
|
if (this.running) return;
|
|
1510
1716
|
this.running = true;
|
|
1717
|
+
this.applyStartupOptions();
|
|
1718
|
+
if (this.options.parseOnly || !process.stdin.isTTY) {
|
|
1719
|
+
this.runPipeMode();
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1511
1722
|
this.history.load();
|
|
1512
1723
|
this.rl = readline__namespace.createInterface({
|
|
1513
1724
|
input: process.stdin,
|
|
@@ -1532,15 +1743,71 @@ var ISLREPL = class {
|
|
|
1532
1743
|
if (this.buffer.length > 0) {
|
|
1533
1744
|
this.buffer = [];
|
|
1534
1745
|
this.braceCount = 0;
|
|
1535
|
-
|
|
1746
|
+
process.stdout.write("\n" + formatWarning("Input cancelled") + "\n");
|
|
1536
1747
|
this.rl.setPrompt(PROMPT);
|
|
1537
1748
|
this.rl.prompt();
|
|
1538
1749
|
} else {
|
|
1539
|
-
|
|
1750
|
+
process.stdout.write("\n" + formatWarning("Use .exit to quit") + "\n");
|
|
1540
1751
|
this.rl.prompt();
|
|
1541
1752
|
}
|
|
1542
1753
|
});
|
|
1543
1754
|
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Apply startup options (--load, --context)
|
|
1757
|
+
*/
|
|
1758
|
+
applyStartupOptions() {
|
|
1759
|
+
if (this.options.context) {
|
|
1760
|
+
const result = this.session.setEvalContext(this.options.context);
|
|
1761
|
+
if (result.success) {
|
|
1762
|
+
process.stdout.write(
|
|
1763
|
+
formatSuccess(`Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`) + "\n"
|
|
1764
|
+
);
|
|
1765
|
+
} else {
|
|
1766
|
+
process.stdout.write(formatError(`Invalid context JSON: ${result.error}`) + "\n");
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
if (this.options.load) {
|
|
1770
|
+
const loadCmd = metaCommands.find((c) => c.name === "load");
|
|
1771
|
+
if (loadCmd) {
|
|
1772
|
+
const result = loadCmd.handler([this.options.load], this.session, this);
|
|
1773
|
+
if (result.output) {
|
|
1774
|
+
process.stdout.write(result.output + "\n");
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Run in pipe mode (read all stdin, parse, and output)
|
|
1781
|
+
*/
|
|
1782
|
+
runPipeMode() {
|
|
1783
|
+
let input = "";
|
|
1784
|
+
process.stdin.setEncoding("utf-8");
|
|
1785
|
+
process.stdin.on("data", (chunk) => {
|
|
1786
|
+
input += chunk;
|
|
1787
|
+
});
|
|
1788
|
+
process.stdin.on("end", () => {
|
|
1789
|
+
const trimmed = input.trim();
|
|
1790
|
+
if (!trimmed) {
|
|
1791
|
+
process.exit(0);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
if (this.options.parseOnly) {
|
|
1795
|
+
const parseCmd = metaCommands.find((c) => c.name === "parse");
|
|
1796
|
+
if (parseCmd) {
|
|
1797
|
+
const result = parseCmd.handler(trimmed.split(" "), this.session, this);
|
|
1798
|
+
if (result.output) {
|
|
1799
|
+
process.stdout.write(result.output + "\n");
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
} else {
|
|
1803
|
+
const lines = trimmed.split("\n");
|
|
1804
|
+
for (const line of lines) {
|
|
1805
|
+
this.handleLine(line);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
process.exit(0);
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1544
1811
|
/**
|
|
1545
1812
|
* Print the welcome banner
|
|
1546
1813
|
*/
|
|
@@ -1560,7 +1827,7 @@ ${colors.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
|
1560
1827
|
${colors.bold}ISL v${VERSION}${colors.reset} \u2014 Intent Specification Language
|
|
1561
1828
|
Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${colors.reset} to quit
|
|
1562
1829
|
`;
|
|
1563
|
-
|
|
1830
|
+
process.stdout.write(banner);
|
|
1564
1831
|
}
|
|
1565
1832
|
/**
|
|
1566
1833
|
* Handle a line of input
|
|
@@ -1571,11 +1838,11 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1571
1838
|
return;
|
|
1572
1839
|
}
|
|
1573
1840
|
if (trimmed.startsWith(".") && this.buffer.length === 0) {
|
|
1574
|
-
this.
|
|
1841
|
+
this.handleDotCommand(trimmed);
|
|
1575
1842
|
return;
|
|
1576
1843
|
}
|
|
1577
1844
|
if (trimmed.startsWith(":") && this.buffer.length === 0) {
|
|
1578
|
-
this.
|
|
1845
|
+
this.handleDotCommand("." + trimmed.slice(1));
|
|
1579
1846
|
return;
|
|
1580
1847
|
}
|
|
1581
1848
|
this.braceCount += (line.match(/\{/g) || []).length;
|
|
@@ -1591,78 +1858,134 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1591
1858
|
}
|
|
1592
1859
|
}
|
|
1593
1860
|
/**
|
|
1594
|
-
* Handle a
|
|
1861
|
+
* Handle a dot command (. prefix)
|
|
1595
1862
|
*/
|
|
1596
|
-
|
|
1863
|
+
handleDotCommand(input) {
|
|
1597
1864
|
const parts = input.slice(1).split(/\s+/);
|
|
1598
1865
|
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1599
1866
|
const args = parts.slice(1);
|
|
1867
|
+
const rawArgs = input.slice(1 + (cmdName.length || 0)).trim();
|
|
1600
1868
|
const command = metaCommands.find(
|
|
1601
1869
|
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1602
1870
|
);
|
|
1603
1871
|
if (command) {
|
|
1604
1872
|
this.history.add(input);
|
|
1605
|
-
const
|
|
1873
|
+
const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
|
|
1874
|
+
const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : args;
|
|
1875
|
+
const result = command.handler(effectiveArgs, this.session, this);
|
|
1606
1876
|
if (result.output) {
|
|
1607
|
-
|
|
1877
|
+
process.stdout.write(result.output + "\n");
|
|
1608
1878
|
}
|
|
1609
1879
|
if (result.exit) {
|
|
1610
1880
|
this.exit();
|
|
1611
1881
|
}
|
|
1612
1882
|
} else {
|
|
1613
|
-
const suggestion = findSimilarCommand(cmdName
|
|
1883
|
+
const suggestion = findSimilarCommand(cmdName);
|
|
1614
1884
|
if (suggestion) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1885
|
+
process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
|
|
1886
|
+
process.stdout.write(formatWarning(`Did you mean: .${suggestion}?`) + "\n");
|
|
1617
1887
|
} else {
|
|
1618
|
-
|
|
1619
|
-
|
|
1888
|
+
process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
|
|
1889
|
+
process.stdout.write(`Type ${colors.cyan}.help${colors.reset} for available commands
|
|
1890
|
+
`);
|
|
1620
1891
|
}
|
|
1621
1892
|
}
|
|
1622
1893
|
}
|
|
1623
1894
|
/**
|
|
1624
|
-
*
|
|
1625
|
-
*/
|
|
1626
|
-
handleISLCommand(input) {
|
|
1627
|
-
const parts = input.slice(1).split(/\s+/);
|
|
1628
|
-
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1629
|
-
const args = parts.slice(1);
|
|
1630
|
-
const command = islCommands.find(
|
|
1631
|
-
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1632
|
-
);
|
|
1633
|
-
if (command) {
|
|
1634
|
-
this.history.add(input);
|
|
1635
|
-
const result = command.handler(args, this.session, this);
|
|
1636
|
-
if (result.output) {
|
|
1637
|
-
console.log(result.output);
|
|
1638
|
-
}
|
|
1639
|
-
} else {
|
|
1640
|
-
const suggestion = findSimilarCommand(cmdName, "isl");
|
|
1641
|
-
if (suggestion) {
|
|
1642
|
-
console.log(formatError(`Unknown command: :${cmdName}`));
|
|
1643
|
-
console.log(formatWarning(`Did you mean: :${suggestion}?`));
|
|
1644
|
-
} else {
|
|
1645
|
-
console.log(formatError(`Unknown command: :${cmdName}`));
|
|
1646
|
-
console.log(`Type ${colors.cyan}.help${colors.reset} for available commands`);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
/**
|
|
1651
|
-
* Evaluate ISL code
|
|
1895
|
+
* Evaluate ISL code (multi-line input or bare expressions)
|
|
1652
1896
|
*/
|
|
1653
1897
|
evaluate(code) {
|
|
1654
1898
|
try {
|
|
1655
1899
|
const trimmed = code.trim();
|
|
1656
|
-
|
|
1900
|
+
try {
|
|
1901
|
+
const { parse } = __require("@isl-lang/parser");
|
|
1902
|
+
let parseInput = trimmed;
|
|
1903
|
+
const needsWrapper = !trimmed.startsWith("domain ");
|
|
1904
|
+
if (needsWrapper) {
|
|
1905
|
+
parseInput = `domain _REPL { version: "0.0.1"
|
|
1906
|
+
${trimmed}
|
|
1907
|
+
}`;
|
|
1908
|
+
}
|
|
1909
|
+
const result = parse(parseInput, "<repl>");
|
|
1910
|
+
if (result.errors.length > 0) {
|
|
1911
|
+
for (const err of result.errors) {
|
|
1912
|
+
const loc = err.location;
|
|
1913
|
+
if (loc) {
|
|
1914
|
+
const adjustedLine = needsWrapper ? Math.max(1, loc.line - 1) : loc.line;
|
|
1915
|
+
const lines = trimmed.split("\n");
|
|
1916
|
+
const errorLine = lines[adjustedLine - 1] || "";
|
|
1917
|
+
process.stdout.write(
|
|
1918
|
+
`${colors.red}\u2717 Error at line ${adjustedLine}, col ${loc.column}:${colors.reset}
|
|
1919
|
+
`
|
|
1920
|
+
);
|
|
1921
|
+
process.stdout.write(` ${errorLine}
|
|
1922
|
+
`);
|
|
1923
|
+
process.stdout.write(` ${" ".repeat(Math.max(0, loc.column - 1))}${colors.red}^^^^^${colors.reset}
|
|
1924
|
+
`);
|
|
1925
|
+
const typeMatch = err.message.match(/Unknown type '(\w+)'/i) || err.message.match(/unexpected.*'(\w+)'/i);
|
|
1926
|
+
if (typeMatch) {
|
|
1927
|
+
const suggestion = suggestCorrection(typeMatch[1]);
|
|
1928
|
+
if (suggestion) {
|
|
1929
|
+
process.stdout.write(
|
|
1930
|
+
` ${colors.yellow}Did you mean '${suggestion}'?${colors.reset}
|
|
1931
|
+
`
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
} else {
|
|
1935
|
+
process.stdout.write(` ${err.message}
|
|
1936
|
+
`);
|
|
1937
|
+
}
|
|
1938
|
+
} else {
|
|
1939
|
+
process.stdout.write(formatError(err.message) + "\n");
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1944
|
+
if (result.domain) {
|
|
1945
|
+
this.session.setDomainAST(result.domain);
|
|
1946
|
+
const domain = result.domain;
|
|
1947
|
+
if (needsWrapper) {
|
|
1948
|
+
const entityCount = domain.entities?.length ?? 0;
|
|
1949
|
+
const behaviorCount = domain.behaviors?.length ?? 0;
|
|
1950
|
+
const parts = [];
|
|
1951
|
+
if (entityCount > 0) parts.push(`${entityCount} entit${entityCount === 1 ? "y" : "ies"}`);
|
|
1952
|
+
if (behaviorCount > 0) parts.push(`${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"}`);
|
|
1953
|
+
if (parts.length > 0) {
|
|
1954
|
+
process.stdout.write(
|
|
1955
|
+
formatSuccess(`Parsed: ${parts.join(", ")}`) + "\n"
|
|
1956
|
+
);
|
|
1957
|
+
} else {
|
|
1958
|
+
process.stdout.write(formatSuccess("Parsed successfully") + "\n");
|
|
1959
|
+
}
|
|
1960
|
+
} else {
|
|
1961
|
+
const name = domain.name?.name ?? "Unknown";
|
|
1962
|
+
const entityCount = domain.entities?.length ?? 0;
|
|
1963
|
+
const behaviorCount = domain.behaviors?.length ?? 0;
|
|
1964
|
+
process.stdout.write(
|
|
1965
|
+
formatSuccess(
|
|
1966
|
+
`Parsed: domain ${name} (${entityCount} entit${entityCount === 1 ? "y" : "ies"}, ${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"})`
|
|
1967
|
+
) + "\n"
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
} catch {
|
|
1973
|
+
}
|
|
1974
|
+
if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
|
|
1657
1975
|
this.evaluateIntent(trimmed);
|
|
1658
1976
|
return;
|
|
1659
1977
|
}
|
|
1660
|
-
if (trimmed.startsWith("
|
|
1661
|
-
|
|
1978
|
+
if (trimmed.startsWith("domain ")) {
|
|
1979
|
+
process.stdout.write(formatSuccess("Parsed domain block") + "\n");
|
|
1662
1980
|
return;
|
|
1663
1981
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1982
|
+
process.stdout.write(
|
|
1983
|
+
formatWarning(`Cannot evaluate: ${trimmed.split("\n")[0]}...`) + "\n"
|
|
1984
|
+
);
|
|
1985
|
+
process.stdout.write(
|
|
1986
|
+
`Use ${colors.cyan}.help${colors.reset} for available commands
|
|
1987
|
+
`
|
|
1988
|
+
);
|
|
1666
1989
|
} catch (error) {
|
|
1667
1990
|
this.printError(error);
|
|
1668
1991
|
}
|
|
@@ -1682,13 +2005,15 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1682
2005
|
if (postCount > 0) parts.push(`${postCount} post`);
|
|
1683
2006
|
if (invCount > 0) parts.push(`${invCount} invariant`);
|
|
1684
2007
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
1685
|
-
|
|
2008
|
+
process.stdout.write(
|
|
2009
|
+
formatSuccess(`Intent '${intent.name}' defined${summary}`) + "\n"
|
|
2010
|
+
);
|
|
1686
2011
|
} else {
|
|
1687
2012
|
const behaviorMatch = code.match(/^behavior\s+(\w+)\s*\{([\s\S]*)\}$/);
|
|
1688
2013
|
if (behaviorMatch) {
|
|
1689
2014
|
const name = behaviorMatch[1];
|
|
1690
2015
|
const body = behaviorMatch[2];
|
|
1691
|
-
const
|
|
2016
|
+
const newIntent = {
|
|
1692
2017
|
name,
|
|
1693
2018
|
preconditions: [],
|
|
1694
2019
|
postconditions: [],
|
|
@@ -1698,51 +2023,28 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1698
2023
|
};
|
|
1699
2024
|
const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
|
|
1700
2025
|
if (preSection) {
|
|
1701
|
-
const
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
if (expr) {
|
|
1705
|
-
intent2.preconditions.push({ expression: expr });
|
|
1706
|
-
}
|
|
2026
|
+
for (const line of preSection[1].trim().split("\n")) {
|
|
2027
|
+
const expr = line.trim().replace(/^-\s*/, "").trim();
|
|
2028
|
+
if (expr) newIntent.preconditions.push({ expression: expr });
|
|
1707
2029
|
}
|
|
1708
2030
|
}
|
|
1709
2031
|
const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
|
|
1710
2032
|
if (postSection) {
|
|
1711
|
-
const
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
if (expr) {
|
|
1715
|
-
intent2.postconditions.push({ expression: expr });
|
|
1716
|
-
}
|
|
2033
|
+
for (const line of postSection[1].trim().split("\n")) {
|
|
2034
|
+
const expr = line.trim().replace(/^-\s*/, "").trim();
|
|
2035
|
+
if (expr) newIntent.postconditions.push({ expression: expr });
|
|
1717
2036
|
}
|
|
1718
2037
|
}
|
|
1719
|
-
this.session.defineIntent(
|
|
1720
|
-
const preCount = intent2.preconditions.length;
|
|
1721
|
-
const postCount = intent2.postconditions.length;
|
|
2038
|
+
this.session.defineIntent(newIntent);
|
|
1722
2039
|
const parts = [];
|
|
1723
|
-
if (
|
|
1724
|
-
if (
|
|
2040
|
+
if (newIntent.preconditions.length > 0) parts.push(`${newIntent.preconditions.length} pre`);
|
|
2041
|
+
if (newIntent.postconditions.length > 0) parts.push(`${newIntent.postconditions.length} post`);
|
|
1725
2042
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
1726
|
-
|
|
2043
|
+
process.stdout.write(
|
|
2044
|
+
formatSuccess(`Intent '${name}' defined${summary}`) + "\n"
|
|
2045
|
+
);
|
|
1727
2046
|
} else {
|
|
1728
|
-
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
}
|
|
1732
|
-
/**
|
|
1733
|
-
* Print a parse error with location info
|
|
1734
|
-
*/
|
|
1735
|
-
printParseError(code, message, line, column) {
|
|
1736
|
-
console.log(formatError(message));
|
|
1737
|
-
if (line !== void 0 && column !== void 0) {
|
|
1738
|
-
const lines = code.split("\n");
|
|
1739
|
-
const errorLine = lines[line - 1] || "";
|
|
1740
|
-
console.log(` ${colors.gray}${line} |${colors.reset} ${errorLine}`);
|
|
1741
|
-
console.log(` ${colors.gray}${" ".repeat(String(line).length)} |${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`);
|
|
1742
|
-
} else {
|
|
1743
|
-
const firstLine = code.split("\n")[0];
|
|
1744
|
-
if (firstLine) {
|
|
1745
|
-
console.log(` ${colors.gray}>${colors.reset} ${firstLine}`);
|
|
2047
|
+
process.stdout.write(formatError("Failed to parse intent definition") + "\n");
|
|
1746
2048
|
}
|
|
1747
2049
|
}
|
|
1748
2050
|
}
|
|
@@ -1751,12 +2053,12 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1751
2053
|
*/
|
|
1752
2054
|
printError(error) {
|
|
1753
2055
|
if (error instanceof Error) {
|
|
1754
|
-
|
|
2056
|
+
process.stdout.write(formatError(error.message) + "\n");
|
|
1755
2057
|
if (this.options.verbose && error.stack) {
|
|
1756
|
-
|
|
2058
|
+
process.stdout.write(colors.gray + error.stack + colors.reset + "\n");
|
|
1757
2059
|
}
|
|
1758
2060
|
} else {
|
|
1759
|
-
|
|
2061
|
+
process.stdout.write(formatError(String(error)) + "\n");
|
|
1760
2062
|
}
|
|
1761
2063
|
}
|
|
1762
2064
|
/**
|
|
@@ -1765,8 +2067,9 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1765
2067
|
exit() {
|
|
1766
2068
|
this.running = false;
|
|
1767
2069
|
this.history.save();
|
|
1768
|
-
|
|
1769
|
-
${colors.yellow}Goodbye!${colors.reset}
|
|
2070
|
+
process.stdout.write(`
|
|
2071
|
+
${colors.yellow}Goodbye!${colors.reset}
|
|
2072
|
+
`);
|
|
1770
2073
|
if (this.rl) {
|
|
1771
2074
|
this.rl.close();
|
|
1772
2075
|
}
|
|
@@ -1789,31 +2092,21 @@ ${colors.yellow}Goodbye!${colors.reset}`);
|
|
|
1789
2092
|
*/
|
|
1790
2093
|
async executeOnce(input) {
|
|
1791
2094
|
const trimmed = input.trim();
|
|
1792
|
-
if (trimmed.startsWith(".")) {
|
|
1793
|
-
const
|
|
2095
|
+
if (trimmed.startsWith(".") || trimmed.startsWith(":")) {
|
|
2096
|
+
const normalized = trimmed.startsWith(":") ? "." + trimmed.slice(1) : trimmed;
|
|
2097
|
+
const parts = normalized.slice(1).split(/\s+/);
|
|
1794
2098
|
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1795
|
-
const
|
|
2099
|
+
const rawArgs = normalized.slice(1 + (cmdName.length || 0)).trim();
|
|
1796
2100
|
const command = metaCommands.find(
|
|
1797
2101
|
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1798
2102
|
);
|
|
1799
2103
|
if (command) {
|
|
1800
|
-
const
|
|
2104
|
+
const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
|
|
2105
|
+
const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : parts.slice(1);
|
|
2106
|
+
const result = command.handler(effectiveArgs, this.session, this);
|
|
1801
2107
|
return { success: true, output: result.output };
|
|
1802
2108
|
}
|
|
1803
|
-
return { success: false, error: `Unknown command:
|
|
1804
|
-
}
|
|
1805
|
-
if (trimmed.startsWith(":")) {
|
|
1806
|
-
const parts = trimmed.slice(1).split(/\s+/);
|
|
1807
|
-
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1808
|
-
const args = parts.slice(1);
|
|
1809
|
-
const command = islCommands.find(
|
|
1810
|
-
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1811
|
-
);
|
|
1812
|
-
if (command) {
|
|
1813
|
-
const result = command.handler(args, this.session, this);
|
|
1814
|
-
return { success: true, output: result.output };
|
|
1815
|
-
}
|
|
1816
|
-
return { success: false, error: `Unknown command: :${cmdName}` };
|
|
2109
|
+
return { success: false, error: `Unknown command: ${cmdName}` };
|
|
1817
2110
|
}
|
|
1818
2111
|
if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
|
|
1819
2112
|
const intent = this.session.parseIntent(trimmed);
|
|
@@ -1826,12 +2119,135 @@ ${colors.yellow}Goodbye!${colors.reset}`);
|
|
|
1826
2119
|
return { success: false, error: "Unknown input" };
|
|
1827
2120
|
}
|
|
1828
2121
|
};
|
|
2122
|
+
var KNOWN_TYPES = [
|
|
2123
|
+
"String",
|
|
2124
|
+
"Int",
|
|
2125
|
+
"Decimal",
|
|
2126
|
+
"Boolean",
|
|
2127
|
+
"UUID",
|
|
2128
|
+
"Timestamp",
|
|
2129
|
+
"Duration",
|
|
2130
|
+
"List",
|
|
2131
|
+
"Map",
|
|
2132
|
+
"Optional",
|
|
2133
|
+
"Number"
|
|
2134
|
+
];
|
|
2135
|
+
function suggestCorrection(typo) {
|
|
2136
|
+
const lower = typo.toLowerCase();
|
|
2137
|
+
for (const t of KNOWN_TYPES) {
|
|
2138
|
+
if (t.toLowerCase() === lower) return t;
|
|
2139
|
+
if (t.toLowerCase().startsWith(lower.slice(0, 3)) && Math.abs(t.length - typo.length) <= 2) {
|
|
2140
|
+
return t;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
return null;
|
|
2144
|
+
}
|
|
1829
2145
|
function startREPL(options) {
|
|
1830
2146
|
const repl = new ISLREPL(options);
|
|
1831
2147
|
repl.start();
|
|
1832
2148
|
return repl;
|
|
1833
2149
|
}
|
|
1834
2150
|
|
|
2151
|
+
// src/cli.ts
|
|
2152
|
+
function parseArgs(argv) {
|
|
2153
|
+
const options = {
|
|
2154
|
+
help: false,
|
|
2155
|
+
colors: true,
|
|
2156
|
+
verbose: false,
|
|
2157
|
+
parseOnly: false
|
|
2158
|
+
};
|
|
2159
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2160
|
+
const arg = argv[i];
|
|
2161
|
+
switch (arg) {
|
|
2162
|
+
case "--help":
|
|
2163
|
+
case "-h":
|
|
2164
|
+
options.help = true;
|
|
2165
|
+
break;
|
|
2166
|
+
case "--no-color":
|
|
2167
|
+
options.colors = false;
|
|
2168
|
+
break;
|
|
2169
|
+
case "--verbose":
|
|
2170
|
+
case "-v":
|
|
2171
|
+
options.verbose = true;
|
|
2172
|
+
break;
|
|
2173
|
+
case "--load":
|
|
2174
|
+
options.load = argv[++i];
|
|
2175
|
+
break;
|
|
2176
|
+
case "--context":
|
|
2177
|
+
options.context = argv[++i];
|
|
2178
|
+
break;
|
|
2179
|
+
case "--parse":
|
|
2180
|
+
options.parseOnly = true;
|
|
2181
|
+
break;
|
|
2182
|
+
default:
|
|
2183
|
+
if (arg.startsWith("--load=")) {
|
|
2184
|
+
options.load = arg.slice(7);
|
|
2185
|
+
} else if (arg.startsWith("--context=")) {
|
|
2186
|
+
options.context = arg.slice(10);
|
|
2187
|
+
}
|
|
2188
|
+
break;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
return options;
|
|
2192
|
+
}
|
|
2193
|
+
function printHelp() {
|
|
2194
|
+
process.stdout.write(`
|
|
2195
|
+
ISL REPL - Intent Specification Language Interactive Shell
|
|
2196
|
+
|
|
2197
|
+
Usage: isl-repl [options]
|
|
2198
|
+
|
|
2199
|
+
Options:
|
|
2200
|
+
--load <file> Load an ISL file on start
|
|
2201
|
+
--context <json> Set initial evaluation context
|
|
2202
|
+
--parse Parse mode (non-interactive, for piped input)
|
|
2203
|
+
--no-color Disable colored output
|
|
2204
|
+
-v, --verbose Enable verbose output
|
|
2205
|
+
-h, --help Show this help message
|
|
2206
|
+
|
|
2207
|
+
Inside the REPL:
|
|
2208
|
+
.help Show all commands
|
|
2209
|
+
.parse <isl> Parse ISL and show AST
|
|
2210
|
+
.eval <expr> Evaluate expression against context
|
|
2211
|
+
.check [intent] Type check intents
|
|
2212
|
+
.gen [intent] Generate TypeScript from intent
|
|
2213
|
+
.load <file> Load an .isl file
|
|
2214
|
+
.context <json> Set evaluation context (mock data)
|
|
2215
|
+
.clear Reset session state
|
|
2216
|
+
.list List defined intents
|
|
2217
|
+
.inspect [intent] Show full details of an intent
|
|
2218
|
+
.history Show command history
|
|
2219
|
+
.exit Exit the REPL
|
|
2220
|
+
|
|
2221
|
+
Multi-line Input:
|
|
2222
|
+
Type ISL with braces \u2014 the REPL auto-detects multi-line:
|
|
2223
|
+
isl> domain Example {
|
|
2224
|
+
...> entity User {
|
|
2225
|
+
...> id: UUID
|
|
2226
|
+
...> name: String
|
|
2227
|
+
...> }
|
|
2228
|
+
...> }
|
|
2229
|
+
|
|
2230
|
+
Examples:
|
|
2231
|
+
$ isl-repl
|
|
2232
|
+
$ isl-repl --load auth.isl
|
|
2233
|
+
$ isl-repl --context '{"user": {"id": 1}}'
|
|
2234
|
+
$ echo 'domain X { version: "1.0" }' | isl-repl --parse
|
|
2235
|
+
`);
|
|
2236
|
+
}
|
|
2237
|
+
function main() {
|
|
2238
|
+
const args = process.argv.slice(2);
|
|
2239
|
+
const options = parseArgs(args);
|
|
2240
|
+
if (options.help) {
|
|
2241
|
+
printHelp();
|
|
2242
|
+
process.exit(0);
|
|
2243
|
+
}
|
|
2244
|
+
startREPL(options);
|
|
2245
|
+
}
|
|
2246
|
+
var isMainModule = typeof __require !== "undefined" ? __require.main === module : process.argv[1]?.includes("cli");
|
|
2247
|
+
if (isMainModule) {
|
|
2248
|
+
main();
|
|
2249
|
+
}
|
|
2250
|
+
|
|
1835
2251
|
exports.COMMANDS = COMMANDS;
|
|
1836
2252
|
exports.CompletionProvider = CompletionProvider;
|
|
1837
2253
|
exports.History = History;
|
|
@@ -1859,6 +2275,7 @@ exports.formatWarning = formatWarning;
|
|
|
1859
2275
|
exports.highlightExpression = highlightExpression;
|
|
1860
2276
|
exports.highlightISL = highlightISL;
|
|
1861
2277
|
exports.islCommands = islCommands;
|
|
2278
|
+
exports.main = main;
|
|
1862
2279
|
exports.metaCommands = metaCommands;
|
|
1863
2280
|
exports.startREPL = startREPL;
|
|
1864
2281
|
exports.stripColors = stripColors;
|