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