@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/cli.js
CHANGED
|
@@ -26,6 +26,12 @@ var Session = class {
|
|
|
26
26
|
loadedFiles = /* @__PURE__ */ new Set();
|
|
27
27
|
/** Session configuration */
|
|
28
28
|
config;
|
|
29
|
+
/** Evaluation context (set by .context command) */
|
|
30
|
+
evalContext = {};
|
|
31
|
+
/** Pre-state context for old() expressions */
|
|
32
|
+
preContext = null;
|
|
33
|
+
/** Loaded domain AST (from real parser) */
|
|
34
|
+
domainAST = null;
|
|
29
35
|
constructor(config = {}) {
|
|
30
36
|
this.config = {
|
|
31
37
|
colors: true,
|
|
@@ -303,6 +309,97 @@ var Session = class {
|
|
|
303
309
|
}
|
|
304
310
|
}
|
|
305
311
|
// ─────────────────────────────────────────────────────────────────────────
|
|
312
|
+
// Evaluation Context Management
|
|
313
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
314
|
+
/**
|
|
315
|
+
* Set evaluation context from JSON string
|
|
316
|
+
*/
|
|
317
|
+
setEvalContext(json) {
|
|
318
|
+
try {
|
|
319
|
+
const parsed = JSON.parse(json);
|
|
320
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
321
|
+
return { success: false, count: 0, error: "Context must be a JSON object" };
|
|
322
|
+
}
|
|
323
|
+
this.evalContext = parsed;
|
|
324
|
+
for (const [key, value] of Object.entries(this.evalContext)) {
|
|
325
|
+
this.variables.set(key, value);
|
|
326
|
+
}
|
|
327
|
+
return { success: true, count: Object.keys(parsed).length };
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return { success: false, count: 0, error: e instanceof Error ? e.message : String(e) };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Set pre-state context for old() expressions
|
|
334
|
+
*/
|
|
335
|
+
setPreContext(json) {
|
|
336
|
+
try {
|
|
337
|
+
const parsed = JSON.parse(json);
|
|
338
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
339
|
+
return { success: false, error: "Pre-context must be a JSON object" };
|
|
340
|
+
}
|
|
341
|
+
this.preContext = parsed;
|
|
342
|
+
return { success: true };
|
|
343
|
+
} catch (e) {
|
|
344
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Get evaluation context
|
|
349
|
+
*/
|
|
350
|
+
getEvalContext() {
|
|
351
|
+
return { ...this.evalContext };
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get pre-state context
|
|
355
|
+
*/
|
|
356
|
+
getPreContext() {
|
|
357
|
+
return this.preContext ? { ...this.preContext } : null;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Resolve a dot-path against the evaluation context
|
|
361
|
+
*/
|
|
362
|
+
resolveValue(dotPath) {
|
|
363
|
+
const parts = dotPath.split(".");
|
|
364
|
+
let current = this.evalContext;
|
|
365
|
+
for (const part of parts) {
|
|
366
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
367
|
+
return { found: false, value: void 0 };
|
|
368
|
+
}
|
|
369
|
+
current = current[part];
|
|
370
|
+
}
|
|
371
|
+
return { found: current !== void 0, value: current };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Resolve a dot-path against the pre-state context
|
|
375
|
+
*/
|
|
376
|
+
resolvePreValue(dotPath) {
|
|
377
|
+
if (!this.preContext) {
|
|
378
|
+
return { found: false, value: void 0 };
|
|
379
|
+
}
|
|
380
|
+
const parts = dotPath.split(".");
|
|
381
|
+
let current = this.preContext;
|
|
382
|
+
for (const part of parts) {
|
|
383
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
384
|
+
return { found: false, value: void 0 };
|
|
385
|
+
}
|
|
386
|
+
current = current[part];
|
|
387
|
+
}
|
|
388
|
+
return { found: current !== void 0, value: current };
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Set domain AST (from real parser)
|
|
392
|
+
*/
|
|
393
|
+
setDomainAST(ast) {
|
|
394
|
+
this.domainAST = ast;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get domain AST
|
|
398
|
+
*/
|
|
399
|
+
getDomainAST() {
|
|
400
|
+
return this.domainAST;
|
|
401
|
+
}
|
|
402
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
306
403
|
// State Management
|
|
307
404
|
// ─────────────────────────────────────────────────────────────────────────
|
|
308
405
|
/**
|
|
@@ -313,6 +410,9 @@ var Session = class {
|
|
|
313
410
|
this.variables.clear();
|
|
314
411
|
this.lastResult = void 0;
|
|
315
412
|
this.loadedFiles.clear();
|
|
413
|
+
this.evalContext = {};
|
|
414
|
+
this.preContext = null;
|
|
415
|
+
this.domainAST = null;
|
|
316
416
|
}
|
|
317
417
|
/**
|
|
318
418
|
* Get session summary
|
|
@@ -513,6 +613,10 @@ var History = class {
|
|
|
513
613
|
};
|
|
514
614
|
|
|
515
615
|
// src/completions.ts
|
|
616
|
+
import * as fs4 from "fs";
|
|
617
|
+
import * as path4 from "path";
|
|
618
|
+
|
|
619
|
+
// src/commands.ts
|
|
516
620
|
import * as fs3 from "fs";
|
|
517
621
|
import * as path3 from "path";
|
|
518
622
|
|
|
@@ -600,34 +704,205 @@ function formatIntent(intent) {
|
|
|
600
704
|
function highlightExpression(expr) {
|
|
601
705
|
return expr.replace(/\b(and|or|not|implies)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/(>=|<=|==|!=|>|<)/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(true|false|null)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\b(forall|exists|in)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(\d+(?:\.\d+)?)\b/g, `${colors.cyan}$1${colors.reset}`).replace(/"([^"]*)"/g, `${colors.green}"$1"${colors.reset}`).replace(/\.(\w+)\(/g, `.${colors.blue}$1${colors.reset}(`).replace(/\.(\w+)(?!\()/g, `.${colors.cyan}$1${colors.reset}`);
|
|
602
706
|
}
|
|
707
|
+
function formatParseError(source, message, line, column) {
|
|
708
|
+
const lines = source.split("\n");
|
|
709
|
+
const errorLine = lines[line - 1] || "";
|
|
710
|
+
const output = [
|
|
711
|
+
formatError(message),
|
|
712
|
+
"",
|
|
713
|
+
`${colors.gray}${String(line).padStart(4)} \u2502${colors.reset} ${errorLine}`,
|
|
714
|
+
`${colors.gray} \u2502${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`
|
|
715
|
+
];
|
|
716
|
+
return output.join("\n");
|
|
717
|
+
}
|
|
718
|
+
function formatValue(value, indent = 0) {
|
|
719
|
+
const pad = " ".repeat(indent);
|
|
720
|
+
if (value === null) return `${colors.gray}null${colors.reset}`;
|
|
721
|
+
if (value === void 0) return `${colors.gray}undefined${colors.reset}`;
|
|
722
|
+
if (typeof value === "string") {
|
|
723
|
+
return `${colors.green}"${value}"${colors.reset}`;
|
|
724
|
+
}
|
|
725
|
+
if (typeof value === "number") {
|
|
726
|
+
return `${colors.cyan}${value}${colors.reset}`;
|
|
727
|
+
}
|
|
728
|
+
if (typeof value === "boolean") {
|
|
729
|
+
return `${colors.magenta}${value}${colors.reset}`;
|
|
730
|
+
}
|
|
731
|
+
if (Array.isArray(value)) {
|
|
732
|
+
if (value.length === 0) return "[]";
|
|
733
|
+
const items = value.map((v) => formatValue(v, indent + 2));
|
|
734
|
+
return `[
|
|
735
|
+
${pad} ${items.join(`,
|
|
736
|
+
${pad} `)}
|
|
737
|
+
${pad}]`;
|
|
738
|
+
}
|
|
739
|
+
if (typeof value === "object") {
|
|
740
|
+
const entries = Object.entries(value);
|
|
741
|
+
if (entries.length === 0) return "{}";
|
|
742
|
+
const items = entries.map(
|
|
743
|
+
([k, v]) => `${colors.blue}${k}${colors.reset}: ${formatValue(v, indent + 2)}`
|
|
744
|
+
);
|
|
745
|
+
return `{
|
|
746
|
+
${pad} ${items.join(`,
|
|
747
|
+
${pad} `)}
|
|
748
|
+
${pad}}`;
|
|
749
|
+
}
|
|
750
|
+
return String(value);
|
|
751
|
+
}
|
|
603
752
|
|
|
604
753
|
// src/commands.ts
|
|
754
|
+
function evaluateExpression(expr, session) {
|
|
755
|
+
const trimmed = expr.trim();
|
|
756
|
+
const oldMatch = trimmed.match(/^old\((.+)\)$/);
|
|
757
|
+
if (oldMatch) {
|
|
758
|
+
const innerPath = oldMatch[1].trim();
|
|
759
|
+
if (!session.getPreContext()) {
|
|
760
|
+
return {
|
|
761
|
+
value: void 0,
|
|
762
|
+
error: "old() requires pre-state. Set with .context --pre <json>"
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
const { found, value } = session.resolvePreValue(innerPath);
|
|
766
|
+
if (!found) {
|
|
767
|
+
return { value: void 0, error: `Cannot resolve '${innerPath}' in pre-state context` };
|
|
768
|
+
}
|
|
769
|
+
return { value };
|
|
770
|
+
}
|
|
771
|
+
if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
|
|
772
|
+
return evaluateExpression(trimmed.slice(1, -1), session);
|
|
773
|
+
}
|
|
774
|
+
if (trimmed.startsWith("!") || trimmed.startsWith("not ")) {
|
|
775
|
+
const inner = trimmed.startsWith("!") ? trimmed.slice(1) : trimmed.slice(4);
|
|
776
|
+
const result = evaluateExpression(inner.trim(), session);
|
|
777
|
+
if (result.error) return result;
|
|
778
|
+
return { value: !result.value };
|
|
779
|
+
}
|
|
780
|
+
for (const [opStr, opFn] of BINARY_OPS) {
|
|
781
|
+
const idx = findOperator(trimmed, opStr);
|
|
782
|
+
if (idx !== -1) {
|
|
783
|
+
const left = trimmed.slice(0, idx).trim();
|
|
784
|
+
const right = trimmed.slice(idx + opStr.length).trim();
|
|
785
|
+
const lResult = evaluateExpression(left, session);
|
|
786
|
+
if (lResult.error) return lResult;
|
|
787
|
+
const rResult = evaluateExpression(right, session);
|
|
788
|
+
if (rResult.error) return rResult;
|
|
789
|
+
return { value: opFn(lResult.value, rResult.value) };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (trimmed === "true") return { value: true };
|
|
793
|
+
if (trimmed === "false") return { value: false };
|
|
794
|
+
if (trimmed === "null") return { value: null };
|
|
795
|
+
if (/^-?\d+$/.test(trimmed)) return { value: parseInt(trimmed, 10) };
|
|
796
|
+
if (/^-?\d+\.\d+$/.test(trimmed)) return { value: parseFloat(trimmed) };
|
|
797
|
+
if (/^"([^"]*)"$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
|
|
798
|
+
if (/^'([^']*)'$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
|
|
799
|
+
if (/^[\w.]+$/.test(trimmed)) {
|
|
800
|
+
const { found, value } = session.resolveValue(trimmed);
|
|
801
|
+
if (found) return { value };
|
|
802
|
+
if (session.hasVariable(trimmed)) {
|
|
803
|
+
return { value: session.getVariable(trimmed) };
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return { value: void 0, error: `Cannot evaluate: ${trimmed}` };
|
|
807
|
+
}
|
|
808
|
+
var BINARY_OPS = [
|
|
809
|
+
// Logical (lowest precedence — scanned first so they split outermost)
|
|
810
|
+
[" || ", (a, b) => Boolean(a) || Boolean(b)],
|
|
811
|
+
[" or ", (a, b) => Boolean(a) || Boolean(b)],
|
|
812
|
+
[" && ", (a, b) => Boolean(a) && Boolean(b)],
|
|
813
|
+
[" and ", (a, b) => Boolean(a) && Boolean(b)],
|
|
814
|
+
// Equality
|
|
815
|
+
[" == ", (a, b) => a === b || String(a) === String(b)],
|
|
816
|
+
[" != ", (a, b) => a !== b && String(a) !== String(b)],
|
|
817
|
+
// Comparison
|
|
818
|
+
[" >= ", (a, b) => Number(a) >= Number(b)],
|
|
819
|
+
[" <= ", (a, b) => Number(a) <= Number(b)],
|
|
820
|
+
[" > ", (a, b) => Number(a) > Number(b)],
|
|
821
|
+
[" < ", (a, b) => Number(a) < Number(b)],
|
|
822
|
+
// Arithmetic
|
|
823
|
+
[" + ", (a, b) => {
|
|
824
|
+
if (typeof a === "string" || typeof b === "string") return String(a) + String(b);
|
|
825
|
+
return Number(a) + Number(b);
|
|
826
|
+
}],
|
|
827
|
+
[" - ", (a, b) => Number(a) - Number(b)],
|
|
828
|
+
[" * ", (a, b) => Number(a) * Number(b)],
|
|
829
|
+
[" / ", (a, b) => {
|
|
830
|
+
const d = Number(b);
|
|
831
|
+
if (d === 0) return Infinity;
|
|
832
|
+
return Number(a) / d;
|
|
833
|
+
}]
|
|
834
|
+
];
|
|
835
|
+
function findOperator(expr, op) {
|
|
836
|
+
let depth = 0;
|
|
837
|
+
let inString = null;
|
|
838
|
+
for (let i = expr.length - 1; i >= 0; i--) {
|
|
839
|
+
const ch = expr[i];
|
|
840
|
+
if (inString) {
|
|
841
|
+
if (ch === inString && (i === 0 || expr[i - 1] !== "\\")) inString = null;
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (ch === '"' || ch === "'") {
|
|
845
|
+
inString = ch;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (ch === "(") depth--;
|
|
849
|
+
if (ch === ")") depth++;
|
|
850
|
+
if (depth === 0 && i + op.length <= expr.length && expr.slice(i, i + op.length) === op) {
|
|
851
|
+
return i;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return -1;
|
|
855
|
+
}
|
|
856
|
+
function prettyPrintAST(node, indent = 0) {
|
|
857
|
+
const pad = " ".repeat(indent);
|
|
858
|
+
if (node === null || node === void 0) return `${pad}${colors.gray}null${colors.reset}`;
|
|
859
|
+
if (typeof node === "string") return `${pad}${colors.green}"${node}"${colors.reset}`;
|
|
860
|
+
if (typeof node === "number") return `${pad}${colors.cyan}${node}${colors.reset}`;
|
|
861
|
+
if (typeof node === "boolean") return `${pad}${colors.magenta}${node}${colors.reset}`;
|
|
862
|
+
if (Array.isArray(node)) {
|
|
863
|
+
if (node.length === 0) return `${pad}[]`;
|
|
864
|
+
const items = node.map((item) => prettyPrintAST(item, indent + 1));
|
|
865
|
+
return `${pad}[
|
|
866
|
+
${items.join(",\n")}
|
|
867
|
+
${pad}]`;
|
|
868
|
+
}
|
|
869
|
+
if (typeof node === "object") {
|
|
870
|
+
const obj = node;
|
|
871
|
+
const kind = obj["kind"];
|
|
872
|
+
const entries = Object.entries(obj).filter(
|
|
873
|
+
([k, v]) => k !== "location" && v !== void 0 && !(Array.isArray(v) && v.length === 0)
|
|
874
|
+
);
|
|
875
|
+
if (entries.length === 0) return `${pad}{}`;
|
|
876
|
+
const header = kind ? `${pad}${colors.yellow}${kind}${colors.reset} {` : `${pad}{`;
|
|
877
|
+
const body = entries.filter(([k]) => k !== "kind").map(([k, v]) => {
|
|
878
|
+
const valStr = typeof v === "object" && v !== null ? "\n" + prettyPrintAST(v, indent + 2) : " " + prettyPrintAST(v, 0).trim();
|
|
879
|
+
return `${pad} ${colors.blue}${k}${colors.reset}:${valStr}`;
|
|
880
|
+
});
|
|
881
|
+
return `${header}
|
|
882
|
+
${body.join("\n")}
|
|
883
|
+
${pad}}`;
|
|
884
|
+
}
|
|
885
|
+
return `${pad}${String(node)}`;
|
|
886
|
+
}
|
|
605
887
|
var metaCommands = [
|
|
888
|
+
// ─── .help ──────────────────────────────────────────────────────────────
|
|
606
889
|
{
|
|
607
890
|
name: "help",
|
|
608
891
|
aliases: ["h", "?"],
|
|
609
|
-
description: "Show
|
|
892
|
+
description: "Show commands",
|
|
610
893
|
usage: ".help [command]",
|
|
611
|
-
handler: (args
|
|
894
|
+
handler: (args) => {
|
|
612
895
|
if (args.length > 0) {
|
|
613
|
-
const cmdName = args[0].toLowerCase();
|
|
614
|
-
const
|
|
615
|
-
|
|
896
|
+
const cmdName = args[0].toLowerCase().replace(/^\./, "");
|
|
897
|
+
const cmd = metaCommands.find(
|
|
898
|
+
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
899
|
+
);
|
|
900
|
+
if (cmd) {
|
|
616
901
|
return {
|
|
617
902
|
output: [
|
|
618
|
-
`${colors.cyan}.${
|
|
619
|
-
`Usage: ${
|
|
620
|
-
|
|
621
|
-
].filter(Boolean).join("\n")
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
const islCmd = islCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
|
|
625
|
-
if (islCmd) {
|
|
626
|
-
return {
|
|
627
|
-
output: [
|
|
628
|
-
`${colors.cyan}:${islCmd.name}${colors.reset} - ${islCmd.description}`,
|
|
629
|
-
`Usage: ${islCmd.usage}`,
|
|
630
|
-
islCmd.aliases.length > 0 ? `Aliases: ${islCmd.aliases.map((a) => ":" + a).join(", ")}` : ""
|
|
903
|
+
`${colors.cyan}.${cmd.name}${colors.reset} \u2014 ${cmd.description}`,
|
|
904
|
+
`Usage: ${cmd.usage}`,
|
|
905
|
+
cmd.aliases.length > 0 ? `Aliases: ${cmd.aliases.map((a) => "." + a).join(", ")}` : ""
|
|
631
906
|
].filter(Boolean).join("\n")
|
|
632
907
|
};
|
|
633
908
|
}
|
|
@@ -635,194 +910,232 @@ var metaCommands = [
|
|
|
635
910
|
}
|
|
636
911
|
const lines = [
|
|
637
912
|
"",
|
|
638
|
-
`${colors.bold}
|
|
639
|
-
"",
|
|
640
|
-
...metaCommands.map((c) => ` ${colors.cyan}.${c.name.padEnd(10)}${colors.reset} ${c.description}`),
|
|
913
|
+
`${colors.bold}REPL Commands${colors.reset}`,
|
|
641
914
|
"",
|
|
642
|
-
|
|
915
|
+
...metaCommands.map(
|
|
916
|
+
(c) => ` ${colors.cyan}.${c.name.padEnd(12)}${colors.reset} ${c.description}`
|
|
917
|
+
),
|
|
643
918
|
"",
|
|
644
|
-
|
|
919
|
+
`${colors.bold}ISL Input${colors.reset}`,
|
|
645
920
|
"",
|
|
646
|
-
|
|
921
|
+
` Type ISL directly \u2014 multi-line supported (braces auto-detect):`,
|
|
647
922
|
"",
|
|
648
|
-
` ${colors.yellow}
|
|
649
|
-
` ${colors.
|
|
650
|
-
`
|
|
923
|
+
` ${colors.yellow}domain${colors.reset} Example {`,
|
|
924
|
+
` ${colors.yellow}entity${colors.reset} User {`,
|
|
925
|
+
` id: ${colors.green}UUID${colors.reset}`,
|
|
926
|
+
` name: ${colors.green}String${colors.reset}`,
|
|
927
|
+
` }`,
|
|
651
928
|
` }`,
|
|
652
929
|
"",
|
|
653
|
-
`Type ${colors.cyan}.help <command>${colors.reset} for
|
|
930
|
+
`Type ${colors.cyan}.help <command>${colors.reset} for details.`,
|
|
654
931
|
""
|
|
655
932
|
];
|
|
656
933
|
return { output: lines.join("\n") };
|
|
657
934
|
}
|
|
658
935
|
},
|
|
936
|
+
// ─── .parse ─────────────────────────────────────────────────────────────
|
|
659
937
|
{
|
|
660
|
-
name: "
|
|
661
|
-
aliases: ["
|
|
662
|
-
description: "
|
|
663
|
-
usage: ".
|
|
664
|
-
handler: () => {
|
|
665
|
-
return { exit: true };
|
|
666
|
-
}
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
name: "clear",
|
|
670
|
-
aliases: ["cls"],
|
|
671
|
-
description: "Clear session state (intents, variables)",
|
|
672
|
-
usage: ".clear",
|
|
938
|
+
name: "parse",
|
|
939
|
+
aliases: ["p", "ast"],
|
|
940
|
+
description: "Parse ISL and show AST",
|
|
941
|
+
usage: ".parse <isl>",
|
|
673
942
|
handler: (args, session) => {
|
|
674
|
-
|
|
675
|
-
|
|
943
|
+
const input = args.join(" ").trim();
|
|
944
|
+
if (!input) {
|
|
945
|
+
return { output: 'Usage: .parse <isl code>\nExample: .parse domain Foo { version: "1.0" }' };
|
|
946
|
+
}
|
|
947
|
+
try {
|
|
948
|
+
const { parse } = __require("@isl-lang/parser");
|
|
949
|
+
const result = parse(input, "<repl>");
|
|
950
|
+
if (!result.success || result.errors.length > 0) {
|
|
951
|
+
const errLines = result.errors.map((e) => {
|
|
952
|
+
const loc = e.location;
|
|
953
|
+
if (loc) {
|
|
954
|
+
return formatParseError(input, e.message, loc.line, loc.column);
|
|
955
|
+
}
|
|
956
|
+
return formatError(e.message);
|
|
957
|
+
});
|
|
958
|
+
return { output: errLines.join("\n") };
|
|
959
|
+
}
|
|
960
|
+
if (result.domain) {
|
|
961
|
+
session.setDomainAST(result.domain);
|
|
962
|
+
return {
|
|
963
|
+
output: formatSuccess("Parsed successfully") + "\n" + prettyPrintAST(result.domain)
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
return { output: formatWarning("Parse returned no AST") };
|
|
967
|
+
} catch {
|
|
968
|
+
return {
|
|
969
|
+
output: formatWarning(
|
|
970
|
+
"Real parser not available \u2014 install @isl-lang/parser.\nFalling back to simple parse."
|
|
971
|
+
)
|
|
972
|
+
};
|
|
973
|
+
}
|
|
676
974
|
}
|
|
677
975
|
},
|
|
976
|
+
// ─── .eval ──────────────────────────────────────────────────────────────
|
|
678
977
|
{
|
|
679
|
-
name: "
|
|
680
|
-
aliases: ["
|
|
681
|
-
description: "
|
|
682
|
-
usage: ".
|
|
978
|
+
name: "eval",
|
|
979
|
+
aliases: ["e"],
|
|
980
|
+
description: "Evaluate expression against context",
|
|
981
|
+
usage: ".eval <expression>",
|
|
683
982
|
handler: (args, session) => {
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
983
|
+
const expr = args.join(" ").trim();
|
|
984
|
+
if (!expr) {
|
|
985
|
+
return {
|
|
986
|
+
output: [
|
|
987
|
+
"Usage: .eval <expression>",
|
|
988
|
+
"",
|
|
989
|
+
"Examples:",
|
|
990
|
+
' .eval user.email == "test@x.com"',
|
|
991
|
+
" .eval user.age > 30",
|
|
992
|
+
" .eval old(user.age)",
|
|
993
|
+
"",
|
|
994
|
+
'Set context first: .context { "user": { "email": "test@x.com" } }'
|
|
995
|
+
].join("\n")
|
|
996
|
+
};
|
|
688
997
|
}
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
})
|
|
698
|
-
];
|
|
699
|
-
return { output: lines.join("\n") };
|
|
998
|
+
const result = evaluateExpression(expr, session);
|
|
999
|
+
if (result.error) {
|
|
1000
|
+
return { output: formatError(result.error) };
|
|
1001
|
+
}
|
|
1002
|
+
session.setLastResult(result.value);
|
|
1003
|
+
return {
|
|
1004
|
+
output: `${colors.cyan}\u2192${colors.reset} ${formatValue(result.value)}`
|
|
1005
|
+
};
|
|
700
1006
|
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
var islCommands = [
|
|
1007
|
+
},
|
|
1008
|
+
// ─── .check ─────────────────────────────────────────────────────────────
|
|
704
1009
|
{
|
|
705
1010
|
name: "check",
|
|
706
1011
|
aliases: ["c"],
|
|
707
|
-
description: "Type check
|
|
708
|
-
usage: "
|
|
1012
|
+
description: "Type check the current session",
|
|
1013
|
+
usage: ".check [intent]",
|
|
709
1014
|
handler: (args, session) => {
|
|
710
|
-
if (args.length
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
1015
|
+
if (args.length > 0) {
|
|
1016
|
+
const intentName = args[0];
|
|
1017
|
+
const intent = session.getIntent(intentName);
|
|
1018
|
+
if (!intent) {
|
|
1019
|
+
const available = session.getIntentNames().join(", ") || "(none)";
|
|
1020
|
+
return {
|
|
1021
|
+
output: formatError(`Unknown intent: ${intentName}
|
|
1022
|
+
Available: ${available}`)
|
|
1023
|
+
};
|
|
714
1024
|
}
|
|
715
|
-
const lines2 = [
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
for (const
|
|
720
|
-
lines2.push(
|
|
721
|
-
for (const pre of intent2.preconditions) {
|
|
722
|
-
lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightCondition(pre.expression)}`);
|
|
723
|
-
}
|
|
724
|
-
for (const post of intent2.postconditions) {
|
|
725
|
-
lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightCondition(post.expression)}`);
|
|
726
|
-
}
|
|
727
|
-
lines2.push("");
|
|
1025
|
+
const lines2 = [formatSuccess("Type check passed"), ""];
|
|
1026
|
+
for (const pre of intent.preconditions) {
|
|
1027
|
+
lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
|
|
1028
|
+
}
|
|
1029
|
+
for (const post of intent.postconditions) {
|
|
1030
|
+
lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
|
|
728
1031
|
}
|
|
729
1032
|
return { output: lines2.join("\n") };
|
|
730
1033
|
}
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
if (!intent) {
|
|
734
|
-
const available = session.getIntentNames().join(", ") || "(none)";
|
|
1034
|
+
const intents = session.getAllIntents();
|
|
1035
|
+
if (intents.length === 0) {
|
|
735
1036
|
return {
|
|
736
|
-
output:
|
|
737
|
-
Available: ${available}`)
|
|
1037
|
+
output: formatWarning("No intents defined. Write ISL or use .load <file>")
|
|
738
1038
|
};
|
|
739
1039
|
}
|
|
740
|
-
const lines = [
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1040
|
+
const lines = [formatSuccess(`Type check passed \u2014 ${intents.length} intent(s)`), ""];
|
|
1041
|
+
for (const intent of intents) {
|
|
1042
|
+
lines.push(`${colors.bold}${intent.name}${colors.reset}`);
|
|
1043
|
+
for (const pre of intent.preconditions) {
|
|
1044
|
+
lines.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
|
|
1045
|
+
}
|
|
1046
|
+
for (const post of intent.postconditions) {
|
|
1047
|
+
lines.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
|
|
1048
|
+
}
|
|
1049
|
+
lines.push("");
|
|
749
1050
|
}
|
|
750
1051
|
return { output: lines.join("\n") };
|
|
751
1052
|
}
|
|
752
1053
|
},
|
|
1054
|
+
// ─── .gen ───────────────────────────────────────────────────────────────
|
|
753
1055
|
{
|
|
754
1056
|
name: "gen",
|
|
755
1057
|
aliases: ["generate", "g"],
|
|
756
|
-
description: "Generate
|
|
757
|
-
usage: "
|
|
1058
|
+
description: "Generate TypeScript from intent",
|
|
1059
|
+
usage: ".gen [intent]",
|
|
758
1060
|
handler: (args, session) => {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
output: [
|
|
762
|
-
"Usage: :gen <target> <intent>",
|
|
763
|
-
"",
|
|
764
|
-
"Targets:",
|
|
765
|
-
" typescript Generate TypeScript contract",
|
|
766
|
-
" rust Generate Rust contract",
|
|
767
|
-
" go Generate Go contract",
|
|
768
|
-
" openapi Generate OpenAPI schema"
|
|
769
|
-
].join("\n")
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
const target = args[0].toLowerCase();
|
|
773
|
-
const intentName = args[1];
|
|
774
|
-
const intent = session.getIntent(intentName);
|
|
775
|
-
if (!intent) {
|
|
776
|
-
const available = session.getIntentNames().join(", ") || "(none)";
|
|
1061
|
+
const intents = args.length > 0 ? [session.getIntent(args[0])].filter(Boolean) : session.getAllIntents();
|
|
1062
|
+
if (intents.length === 0) {
|
|
777
1063
|
return {
|
|
778
|
-
output: formatError(`Unknown intent: ${
|
|
779
|
-
Available: ${
|
|
1064
|
+
output: args.length > 0 ? formatError(`Unknown intent: ${args[0]}
|
|
1065
|
+
Available: ${session.getIntentNames().join(", ") || "(none)"}`) : formatWarning("No intents defined. Write ISL or use .load <file>")
|
|
780
1066
|
};
|
|
781
1067
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1068
|
+
const lines = [`${colors.gray}// Generated TypeScript${colors.reset}`, ""];
|
|
1069
|
+
for (const intent of intents) {
|
|
1070
|
+
lines.push(`interface ${intent.name}Contract {`);
|
|
1071
|
+
if (intent.preconditions.length > 0) {
|
|
1072
|
+
lines.push(" /** Preconditions */");
|
|
1073
|
+
for (const pre of intent.preconditions) {
|
|
1074
|
+
lines.push(` checkPre(): boolean; // ${pre.expression}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (intent.postconditions.length > 0) {
|
|
1078
|
+
lines.push(" /** Postconditions */");
|
|
1079
|
+
for (const post of intent.postconditions) {
|
|
1080
|
+
lines.push(` checkPost(): boolean; // ${post.expression}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (intent.invariants.length > 0) {
|
|
1084
|
+
lines.push(" /** Invariants */");
|
|
1085
|
+
for (const inv of intent.invariants) {
|
|
1086
|
+
lines.push(` checkInvariant(): boolean; // ${inv.expression}`);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
lines.push("}");
|
|
1090
|
+
lines.push("");
|
|
799
1091
|
}
|
|
1092
|
+
return { output: lines.join("\n") };
|
|
800
1093
|
}
|
|
801
1094
|
},
|
|
1095
|
+
// ─── .load ──────────────────────────────────────────────────────────────
|
|
802
1096
|
{
|
|
803
1097
|
name: "load",
|
|
804
1098
|
aliases: ["l"],
|
|
805
|
-
description: "Load
|
|
806
|
-
usage: "
|
|
1099
|
+
description: "Load an .isl file",
|
|
1100
|
+
usage: ".load <file.isl>",
|
|
807
1101
|
handler: (args, session) => {
|
|
808
1102
|
if (args.length === 0) {
|
|
809
|
-
return { output: "Usage:
|
|
1103
|
+
return { output: "Usage: .load <file.isl>" };
|
|
810
1104
|
}
|
|
811
1105
|
const filePath = args[0];
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
}
|
|
816
|
-
result.errors.push(String(e));
|
|
817
|
-
});
|
|
818
|
-
const fs4 = __require("fs");
|
|
819
|
-
const path4 = __require("path");
|
|
1106
|
+
const resolvedPath = path3.isAbsolute(filePath) ? filePath : path3.resolve(process.cwd(), filePath);
|
|
1107
|
+
if (!fs3.existsSync(resolvedPath)) {
|
|
1108
|
+
return { output: formatError(`File not found: ${resolvedPath}`) };
|
|
1109
|
+
}
|
|
820
1110
|
try {
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
1111
|
+
const content = fs3.readFileSync(resolvedPath, "utf-8");
|
|
1112
|
+
try {
|
|
1113
|
+
const { parse } = __require("@isl-lang/parser");
|
|
1114
|
+
const result = parse(content, resolvedPath);
|
|
1115
|
+
if (!result.success || result.errors.length > 0) {
|
|
1116
|
+
const errLines = result.errors.map((e) => {
|
|
1117
|
+
const loc = e.location;
|
|
1118
|
+
if (loc) {
|
|
1119
|
+
return formatParseError(content, e.message, loc.line, loc.column);
|
|
1120
|
+
}
|
|
1121
|
+
return formatError(e.message);
|
|
1122
|
+
});
|
|
1123
|
+
return { output: errLines.join("\n") };
|
|
1124
|
+
}
|
|
1125
|
+
if (result.domain) {
|
|
1126
|
+
session.setDomainAST(result.domain);
|
|
1127
|
+
const domain = result.domain;
|
|
1128
|
+
const name = domain.name?.name ?? "Unknown";
|
|
1129
|
+
const entityCount = domain.entities?.length ?? 0;
|
|
1130
|
+
const behaviorCount = domain.behaviors?.length ?? 0;
|
|
1131
|
+
return {
|
|
1132
|
+
output: formatSuccess(
|
|
1133
|
+
`Loaded: ${name} (${entityCount} entities, ${behaviorCount} behaviors) from ${path3.basename(filePath)}`
|
|
1134
|
+
)
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
} catch {
|
|
824
1138
|
}
|
|
825
|
-
const content = fs4.readFileSync(resolvedPath, "utf-8");
|
|
826
1139
|
const intentRegex = /(?:intent|behavior)\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
|
|
827
1140
|
let match;
|
|
828
1141
|
let count = 0;
|
|
@@ -834,37 +1147,125 @@ Available: typescript, rust, go, openapi`)
|
|
|
834
1147
|
}
|
|
835
1148
|
}
|
|
836
1149
|
if (count === 0) {
|
|
837
|
-
return { output: formatWarning("No intents found in file") };
|
|
1150
|
+
return { output: formatWarning("No intents/behaviors found in file") };
|
|
838
1151
|
}
|
|
839
1152
|
return {
|
|
840
|
-
output: formatSuccess(`Loaded ${count} intent(s) from ${filePath}`)
|
|
1153
|
+
output: formatSuccess(`Loaded ${count} intent(s) from ${path3.basename(filePath)}`)
|
|
841
1154
|
};
|
|
842
1155
|
} catch (error) {
|
|
843
1156
|
return {
|
|
844
|
-
output: formatError(
|
|
1157
|
+
output: formatError(
|
|
1158
|
+
`Failed to load: ${error instanceof Error ? error.message : String(error)}`
|
|
1159
|
+
)
|
|
845
1160
|
};
|
|
846
1161
|
}
|
|
847
1162
|
}
|
|
848
1163
|
},
|
|
1164
|
+
// ─── .context ───────────────────────────────────────────────────────────
|
|
1165
|
+
{
|
|
1166
|
+
name: "context",
|
|
1167
|
+
aliases: ["ctx"],
|
|
1168
|
+
description: "Set evaluation context (JSON)",
|
|
1169
|
+
usage: ".context <json> | .context --pre <json>",
|
|
1170
|
+
handler: (args, session) => {
|
|
1171
|
+
const input = args.join(" ").trim();
|
|
1172
|
+
if (!input) {
|
|
1173
|
+
const ctx = session.getEvalContext();
|
|
1174
|
+
const pre = session.getPreContext();
|
|
1175
|
+
if (Object.keys(ctx).length === 0 && !pre) {
|
|
1176
|
+
return {
|
|
1177
|
+
output: [
|
|
1178
|
+
"No context set.",
|
|
1179
|
+
"",
|
|
1180
|
+
"Usage:",
|
|
1181
|
+
' .context { "user": { "email": "test@x.com", "age": 25 } }',
|
|
1182
|
+
' .context --pre { "user": { "age": 20 } }'
|
|
1183
|
+
].join("\n")
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
const lines = [];
|
|
1187
|
+
if (Object.keys(ctx).length > 0) {
|
|
1188
|
+
lines.push(`${colors.bold}Context:${colors.reset}`);
|
|
1189
|
+
lines.push(formatValue(ctx));
|
|
1190
|
+
}
|
|
1191
|
+
if (pre) {
|
|
1192
|
+
lines.push(`${colors.bold}Pre-state:${colors.reset}`);
|
|
1193
|
+
lines.push(formatValue(pre));
|
|
1194
|
+
}
|
|
1195
|
+
return { output: lines.join("\n") };
|
|
1196
|
+
}
|
|
1197
|
+
if (input.startsWith("--pre ")) {
|
|
1198
|
+
const json = input.slice(6).trim();
|
|
1199
|
+
const result2 = session.setPreContext(json);
|
|
1200
|
+
if (!result2.success) {
|
|
1201
|
+
return { output: formatError(`Invalid JSON: ${result2.error}`) };
|
|
1202
|
+
}
|
|
1203
|
+
return { output: formatSuccess("Pre-state context set") };
|
|
1204
|
+
}
|
|
1205
|
+
const result = session.setEvalContext(input);
|
|
1206
|
+
if (!result.success) {
|
|
1207
|
+
return { output: formatError(`Invalid JSON: ${result.error}`) };
|
|
1208
|
+
}
|
|
1209
|
+
return {
|
|
1210
|
+
output: formatSuccess(
|
|
1211
|
+
`Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`
|
|
1212
|
+
)
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
},
|
|
1216
|
+
// ─── .clear ─────────────────────────────────────────────────────────────
|
|
1217
|
+
{
|
|
1218
|
+
name: "clear",
|
|
1219
|
+
aliases: ["cls", "reset"],
|
|
1220
|
+
description: "Reset session state",
|
|
1221
|
+
usage: ".clear",
|
|
1222
|
+
handler: (_args, session) => {
|
|
1223
|
+
session.clear();
|
|
1224
|
+
return { output: formatSuccess("Session cleared") };
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
// ─── .history ───────────────────────────────────────────────────────────
|
|
1228
|
+
{
|
|
1229
|
+
name: "history",
|
|
1230
|
+
aliases: ["hist"],
|
|
1231
|
+
description: "Show command history",
|
|
1232
|
+
usage: ".history [n]",
|
|
1233
|
+
handler: (args, session) => {
|
|
1234
|
+
const count = args.length > 0 ? parseInt(args[0], 10) : 10;
|
|
1235
|
+
const history = session.getHistory(count);
|
|
1236
|
+
if (history.length === 0) {
|
|
1237
|
+
return { output: "No history." };
|
|
1238
|
+
}
|
|
1239
|
+
const lines = [
|
|
1240
|
+
`${colors.bold}History${colors.reset} (last ${history.length} entries)`,
|
|
1241
|
+
"",
|
|
1242
|
+
...history.map((entry, i) => {
|
|
1243
|
+
const num = String(i + 1).padStart(3);
|
|
1244
|
+
const preview = entry.split("\n")[0];
|
|
1245
|
+
const more = entry.includes("\n") ? ` ${colors.gray}...${colors.reset}` : "";
|
|
1246
|
+
return ` ${colors.gray}${num}${colors.reset} ${preview}${more}`;
|
|
1247
|
+
})
|
|
1248
|
+
];
|
|
1249
|
+
return { output: lines.join("\n") };
|
|
1250
|
+
}
|
|
1251
|
+
},
|
|
1252
|
+
// ─── .list ──────────────────────────────────────────────────────────────
|
|
849
1253
|
{
|
|
850
1254
|
name: "list",
|
|
851
1255
|
aliases: ["ls"],
|
|
852
|
-
description: "List
|
|
853
|
-
usage: "
|
|
854
|
-
handler: (
|
|
1256
|
+
description: "List defined intents",
|
|
1257
|
+
usage: ".list",
|
|
1258
|
+
handler: (_args, session) => {
|
|
855
1259
|
const intents = session.getAllIntents();
|
|
856
1260
|
if (intents.length === 0) {
|
|
857
1261
|
return { output: "No intents defined." };
|
|
858
1262
|
}
|
|
859
1263
|
const lines = [""];
|
|
860
1264
|
for (const intent of intents) {
|
|
861
|
-
const preCount = intent.preconditions.length;
|
|
862
|
-
const postCount = intent.postconditions.length;
|
|
863
|
-
const invCount = intent.invariants.length;
|
|
864
1265
|
const parts = [];
|
|
865
|
-
if (
|
|
866
|
-
if (
|
|
867
|
-
if (
|
|
1266
|
+
if (intent.preconditions.length > 0) parts.push(`${intent.preconditions.length} pre`);
|
|
1267
|
+
if (intent.postconditions.length > 0) parts.push(`${intent.postconditions.length} post`);
|
|
1268
|
+
if (intent.invariants.length > 0) parts.push(`${intent.invariants.length} invariant`);
|
|
868
1269
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
869
1270
|
lines.push(` ${colors.cyan}${intent.name}${colors.reset}${summary}`);
|
|
870
1271
|
}
|
|
@@ -872,31 +1273,26 @@ Available: typescript, rust, go, openapi`)
|
|
|
872
1273
|
return { output: lines.join("\n") };
|
|
873
1274
|
}
|
|
874
1275
|
},
|
|
1276
|
+
// ─── .inspect ───────────────────────────────────────────────────────────
|
|
875
1277
|
{
|
|
876
1278
|
name: "inspect",
|
|
877
1279
|
aliases: ["i", "show"],
|
|
878
1280
|
description: "Show full details of an intent",
|
|
879
|
-
usage: "
|
|
1281
|
+
usage: ".inspect [intent]",
|
|
880
1282
|
handler: (args, session) => {
|
|
881
1283
|
if (args.length === 0) {
|
|
882
1284
|
const summary = session.getSummary();
|
|
883
|
-
const
|
|
1285
|
+
const ctx = session.getEvalContext();
|
|
884
1286
|
const lines = [
|
|
885
1287
|
"",
|
|
886
1288
|
`${colors.bold}Session Summary${colors.reset}`,
|
|
887
1289
|
"",
|
|
888
1290
|
` Intents: ${summary.intentCount}`,
|
|
889
1291
|
` Variables: ${summary.variableCount}`,
|
|
890
|
-
`
|
|
1292
|
+
` Context: ${Object.keys(ctx).length} keys`,
|
|
1293
|
+
` History: ${summary.historyCount} entries`,
|
|
1294
|
+
""
|
|
891
1295
|
];
|
|
892
|
-
if (files.length > 0) {
|
|
893
|
-
lines.push("");
|
|
894
|
-
lines.push(`${colors.bold}Loaded Files${colors.reset}`);
|
|
895
|
-
for (const file of files) {
|
|
896
|
-
lines.push(` ${file}`);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
lines.push("");
|
|
900
1296
|
return { output: lines.join("\n") };
|
|
901
1297
|
}
|
|
902
1298
|
const intentName = args[0];
|
|
@@ -911,178 +1307,17 @@ Available: ${available}`)
|
|
|
911
1307
|
return { output: formatIntent(intent) };
|
|
912
1308
|
}
|
|
913
1309
|
},
|
|
1310
|
+
// ─── .exit ──────────────────────────────────────────────────────────────
|
|
914
1311
|
{
|
|
915
|
-
name: "
|
|
916
|
-
aliases: ["
|
|
917
|
-
description: "
|
|
918
|
-
usage: "
|
|
919
|
-
handler: (
|
|
920
|
-
|
|
921
|
-
return { output: "Usage: :export <file.isl>" };
|
|
922
|
-
}
|
|
923
|
-
const filePath = args[0];
|
|
924
|
-
const fs4 = __require("fs");
|
|
925
|
-
const path4 = __require("path");
|
|
926
|
-
try {
|
|
927
|
-
const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
|
|
928
|
-
const intents = session.getAllIntents();
|
|
929
|
-
if (intents.length === 0) {
|
|
930
|
-
return { output: formatWarning("No intents to export") };
|
|
931
|
-
}
|
|
932
|
-
const lines = [];
|
|
933
|
-
lines.push("// Exported ISL intents");
|
|
934
|
-
lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
935
|
-
lines.push("");
|
|
936
|
-
for (const intent of intents) {
|
|
937
|
-
lines.push(`intent ${intent.name} {`);
|
|
938
|
-
for (const pre of intent.preconditions) {
|
|
939
|
-
lines.push(` pre: ${pre.expression}`);
|
|
940
|
-
}
|
|
941
|
-
for (const post of intent.postconditions) {
|
|
942
|
-
lines.push(` post: ${post.expression}`);
|
|
943
|
-
}
|
|
944
|
-
for (const inv of intent.invariants) {
|
|
945
|
-
lines.push(` invariant: ${inv.expression}`);
|
|
946
|
-
}
|
|
947
|
-
lines.push("}");
|
|
948
|
-
lines.push("");
|
|
949
|
-
}
|
|
950
|
-
fs4.writeFileSync(resolvedPath, lines.join("\n"));
|
|
951
|
-
return { output: formatSuccess(`Exported ${intents.length} intent(s) to ${filePath}`) };
|
|
952
|
-
} catch (error) {
|
|
953
|
-
return {
|
|
954
|
-
output: formatError(`Failed to export: ${error instanceof Error ? error.message : String(error)}`)
|
|
955
|
-
};
|
|
956
|
-
}
|
|
1312
|
+
name: "exit",
|
|
1313
|
+
aliases: ["quit", "q"],
|
|
1314
|
+
description: "Exit the REPL",
|
|
1315
|
+
usage: ".exit",
|
|
1316
|
+
handler: () => {
|
|
1317
|
+
return { exit: true };
|
|
957
1318
|
}
|
|
958
1319
|
}
|
|
959
1320
|
];
|
|
960
|
-
function generateTypeScript(intent) {
|
|
961
|
-
const lines = [
|
|
962
|
-
`${colors.gray}// Generated TypeScript${colors.reset}`,
|
|
963
|
-
`interface ${intent.name}Contract {`
|
|
964
|
-
];
|
|
965
|
-
if (intent.preconditions.length > 0) {
|
|
966
|
-
for (const pre of intent.preconditions) {
|
|
967
|
-
const varName = extractVariableName(pre.expression);
|
|
968
|
-
const type = inferType(pre.expression);
|
|
969
|
-
lines.push(` pre: (${varName}: ${type}) => boolean;`);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
if (intent.postconditions.length > 0) {
|
|
973
|
-
for (const post of intent.postconditions) {
|
|
974
|
-
const varName = extractVariableName(post.expression);
|
|
975
|
-
const type = inferType(post.expression);
|
|
976
|
-
lines.push(` post: (${varName}: ${type}) => boolean;`);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
lines.push("}");
|
|
980
|
-
return lines.join("\n");
|
|
981
|
-
}
|
|
982
|
-
function generateRust(intent) {
|
|
983
|
-
const lines = [
|
|
984
|
-
`${colors.gray}// Generated Rust${colors.reset}`,
|
|
985
|
-
`pub trait ${intent.name}Contract {`
|
|
986
|
-
];
|
|
987
|
-
if (intent.preconditions.length > 0) {
|
|
988
|
-
for (const pre of intent.preconditions) {
|
|
989
|
-
const varName = extractVariableName(pre.expression);
|
|
990
|
-
const type = inferRustType(pre.expression);
|
|
991
|
-
lines.push(` fn check_pre(&self, ${varName}: ${type}) -> bool;`);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
if (intent.postconditions.length > 0) {
|
|
995
|
-
for (const post of intent.postconditions) {
|
|
996
|
-
const varName = extractVariableName(post.expression);
|
|
997
|
-
const type = inferRustType(post.expression);
|
|
998
|
-
lines.push(` fn check_post(&self, ${varName}: ${type}) -> bool;`);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
lines.push("}");
|
|
1002
|
-
return lines.join("\n");
|
|
1003
|
-
}
|
|
1004
|
-
function generateGo(intent) {
|
|
1005
|
-
const lines = [
|
|
1006
|
-
`${colors.gray}// Generated Go${colors.reset}`,
|
|
1007
|
-
`type ${intent.name}Contract interface {`
|
|
1008
|
-
];
|
|
1009
|
-
if (intent.preconditions.length > 0) {
|
|
1010
|
-
for (const pre of intent.preconditions) {
|
|
1011
|
-
const varName = extractVariableName(pre.expression);
|
|
1012
|
-
const type = inferGoType(pre.expression);
|
|
1013
|
-
lines.push(` CheckPre(${varName} ${type}) bool`);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
if (intent.postconditions.length > 0) {
|
|
1017
|
-
for (const post of intent.postconditions) {
|
|
1018
|
-
const varName = extractVariableName(post.expression);
|
|
1019
|
-
const type = inferGoType(post.expression);
|
|
1020
|
-
lines.push(` CheckPost(${varName} ${type}) bool`);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
lines.push("}");
|
|
1024
|
-
return lines.join("\n");
|
|
1025
|
-
}
|
|
1026
|
-
function generateOpenAPI(intent) {
|
|
1027
|
-
const lines = [
|
|
1028
|
-
`${colors.gray}# Generated OpenAPI${colors.reset}`,
|
|
1029
|
-
`openapi: 3.0.0`,
|
|
1030
|
-
`paths:`,
|
|
1031
|
-
` /${intent.name.toLowerCase()}:`,
|
|
1032
|
-
` post:`,
|
|
1033
|
-
` summary: ${intent.name}`,
|
|
1034
|
-
` requestBody:`,
|
|
1035
|
-
` content:`,
|
|
1036
|
-
` application/json:`,
|
|
1037
|
-
` schema:`,
|
|
1038
|
-
` type: object`
|
|
1039
|
-
];
|
|
1040
|
-
if (intent.preconditions.length > 0) {
|
|
1041
|
-
lines.push(` # Preconditions:`);
|
|
1042
|
-
for (const pre of intent.preconditions) {
|
|
1043
|
-
lines.push(` # - ${pre.expression}`);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
lines.push(` responses:`);
|
|
1047
|
-
lines.push(` '200':`);
|
|
1048
|
-
lines.push(` description: Success`);
|
|
1049
|
-
if (intent.postconditions.length > 0) {
|
|
1050
|
-
lines.push(` # Postconditions:`);
|
|
1051
|
-
for (const post of intent.postconditions) {
|
|
1052
|
-
lines.push(` # - ${post.expression}`);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
return lines.join("\n");
|
|
1056
|
-
}
|
|
1057
|
-
function extractVariableName(expression) {
|
|
1058
|
-
const match = expression.match(/^(\w+)/);
|
|
1059
|
-
return match ? match[1] : "input";
|
|
1060
|
-
}
|
|
1061
|
-
function inferType(expression) {
|
|
1062
|
-
if (expression.includes(".length")) return "string";
|
|
1063
|
-
if (expression.includes(".startsWith")) return "string";
|
|
1064
|
-
if (expression.includes(".endsWith")) return "string";
|
|
1065
|
-
if (expression.includes(".includes")) return "string";
|
|
1066
|
-
if (expression.includes(" > ") || expression.includes(" < ")) return "number";
|
|
1067
|
-
return "unknown";
|
|
1068
|
-
}
|
|
1069
|
-
function inferRustType(expression) {
|
|
1070
|
-
if (expression.includes(".length") || expression.includes(".len()")) return "&str";
|
|
1071
|
-
if (expression.includes(".starts_with")) return "&str";
|
|
1072
|
-
if (expression.includes(" > ") || expression.includes(" < ")) return "i32";
|
|
1073
|
-
return "&str";
|
|
1074
|
-
}
|
|
1075
|
-
function inferGoType(expression) {
|
|
1076
|
-
if (expression.includes(".length") || expression.includes("len(")) return "string";
|
|
1077
|
-
if (expression.includes(" > ") || expression.includes(" < ")) return "int";
|
|
1078
|
-
return "string";
|
|
1079
|
-
}
|
|
1080
|
-
function highlightCondition(expression) {
|
|
1081
|
-
return expression.replace(
|
|
1082
|
-
/(\w+)\s*(>|<|>=|<=|==|!=)\s*(\d+|"[^"]*")/g,
|
|
1083
|
-
`${colors.blue}$1${colors.reset} ${colors.yellow}$2${colors.reset} ${colors.green}$3${colors.reset}`
|
|
1084
|
-
).replace(/\b(true|false)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\.(length|startsWith|endsWith|includes)/g, `.${colors.cyan}$1${colors.reset}`);
|
|
1085
|
-
}
|
|
1086
1321
|
function levenshteinDistance(a, b) {
|
|
1087
1322
|
const matrix = [];
|
|
1088
1323
|
for (let i = 0; i <= b.length; i++) {
|
|
@@ -1103,9 +1338,8 @@ function levenshteinDistance(a, b) {
|
|
|
1103
1338
|
}
|
|
1104
1339
|
return matrix[b.length][a.length];
|
|
1105
1340
|
}
|
|
1106
|
-
function findSimilarCommand(input,
|
|
1107
|
-
const
|
|
1108
|
-
const names = commands.flatMap((c) => [c.name, ...c.aliases]);
|
|
1341
|
+
function findSimilarCommand(input, _type) {
|
|
1342
|
+
const names = metaCommands.flatMap((c) => [c.name, ...c.aliases]);
|
|
1109
1343
|
let bestMatch = null;
|
|
1110
1344
|
let bestDistance = Infinity;
|
|
1111
1345
|
for (const name of names) {
|
|
@@ -1120,11 +1354,31 @@ function findSimilarCommand(input, type) {
|
|
|
1120
1354
|
|
|
1121
1355
|
// src/completions.ts
|
|
1122
1356
|
var KEYWORDS = [
|
|
1123
|
-
|
|
1357
|
+
// Structure keywords
|
|
1358
|
+
{ text: "domain", type: "keyword", description: "Define a domain" },
|
|
1359
|
+
{ text: "entity", type: "keyword", description: "Define an entity" },
|
|
1124
1360
|
{ text: "behavior", type: "keyword", description: "Define a behavior" },
|
|
1361
|
+
{ text: "intent", type: "keyword", description: "Define an intent" },
|
|
1362
|
+
{ text: "input", type: "keyword", description: "Input block" },
|
|
1363
|
+
{ text: "output", type: "keyword", description: "Output block" },
|
|
1125
1364
|
{ text: "pre", type: "keyword", description: "Precondition" },
|
|
1126
1365
|
{ text: "post", type: "keyword", description: "Postcondition" },
|
|
1127
1366
|
{ text: "invariant", type: "keyword", description: "Invariant" },
|
|
1367
|
+
{ text: "scenario", type: "keyword", description: "Scenario block" },
|
|
1368
|
+
{ text: "version", type: "keyword", description: "Version declaration" },
|
|
1369
|
+
// Types
|
|
1370
|
+
{ text: "String", type: "keyword", description: "String type" },
|
|
1371
|
+
{ text: "Number", type: "keyword", description: "Number type" },
|
|
1372
|
+
{ text: "Int", type: "keyword", description: "Integer type" },
|
|
1373
|
+
{ text: "Decimal", type: "keyword", description: "Decimal type" },
|
|
1374
|
+
{ text: "Boolean", type: "keyword", description: "Boolean type" },
|
|
1375
|
+
{ text: "UUID", type: "keyword", description: "UUID type" },
|
|
1376
|
+
{ text: "Timestamp", type: "keyword", description: "Timestamp type" },
|
|
1377
|
+
{ text: "Duration", type: "keyword", description: "Duration type" },
|
|
1378
|
+
{ text: "List", type: "keyword", description: "List<T> type" },
|
|
1379
|
+
{ text: "Map", type: "keyword", description: "Map<K,V> type" },
|
|
1380
|
+
{ text: "Optional", type: "keyword", description: "Optional<T> type" },
|
|
1381
|
+
// Literals and operators
|
|
1128
1382
|
{ text: "true", type: "keyword", description: "Boolean true" },
|
|
1129
1383
|
{ text: "false", type: "keyword", description: "Boolean false" },
|
|
1130
1384
|
{ text: "null", type: "keyword", description: "Null value" },
|
|
@@ -1134,19 +1388,16 @@ var KEYWORDS = [
|
|
|
1134
1388
|
{ text: "implies", type: "keyword", description: "Logical implication" },
|
|
1135
1389
|
{ text: "forall", type: "keyword", description: "Universal quantifier" },
|
|
1136
1390
|
{ text: "exists", type: "keyword", description: "Existential quantifier" },
|
|
1137
|
-
{ text: "in", type: "keyword", description: "Membership test" }
|
|
1391
|
+
{ text: "in", type: "keyword", description: "Membership test" },
|
|
1392
|
+
{ text: "old", type: "keyword", description: "Pre-state value (old(x))" }
|
|
1138
1393
|
];
|
|
1139
1394
|
var META_COMMANDS = metaCommands.map((cmd) => ({
|
|
1140
1395
|
text: `.${cmd.name}`,
|
|
1141
1396
|
type: "command",
|
|
1142
1397
|
description: cmd.description
|
|
1143
1398
|
}));
|
|
1144
|
-
var ISL_COMMANDS =
|
|
1145
|
-
|
|
1146
|
-
type: "command",
|
|
1147
|
-
description: cmd.description
|
|
1148
|
-
}));
|
|
1149
|
-
var COMMANDS = [...META_COMMANDS, ...ISL_COMMANDS];
|
|
1399
|
+
var ISL_COMMANDS = [];
|
|
1400
|
+
var COMMANDS = [...META_COMMANDS];
|
|
1150
1401
|
var GEN_TARGETS = [
|
|
1151
1402
|
{ text: "typescript", type: "keyword", description: "Generate TypeScript contract" },
|
|
1152
1403
|
{ text: "rust", type: "keyword", description: "Generate Rust contract" },
|
|
@@ -1172,7 +1423,7 @@ var CompletionProvider = class {
|
|
|
1172
1423
|
return this.completeMetaCommand(trimmed);
|
|
1173
1424
|
}
|
|
1174
1425
|
if (trimmed.startsWith(":")) {
|
|
1175
|
-
return this.
|
|
1426
|
+
return this.completeMetaCommand("." + trimmed.slice(1));
|
|
1176
1427
|
}
|
|
1177
1428
|
return this.completeExpression(trimmed);
|
|
1178
1429
|
}
|
|
@@ -1257,18 +1508,18 @@ var CompletionProvider = class {
|
|
|
1257
1508
|
*/
|
|
1258
1509
|
completeFilePath(partial) {
|
|
1259
1510
|
try {
|
|
1260
|
-
const dir =
|
|
1261
|
-
const base =
|
|
1262
|
-
const resolvedDir =
|
|
1263
|
-
if (!
|
|
1511
|
+
const dir = path4.dirname(partial) || ".";
|
|
1512
|
+
const base = path4.basename(partial);
|
|
1513
|
+
const resolvedDir = path4.resolve(this.session.getConfig().cwd || process.cwd(), dir);
|
|
1514
|
+
if (!fs4.existsSync(resolvedDir)) {
|
|
1264
1515
|
return [[], partial];
|
|
1265
1516
|
}
|
|
1266
|
-
const entries =
|
|
1517
|
+
const entries = fs4.readdirSync(resolvedDir, { withFileTypes: true });
|
|
1267
1518
|
const items = entries.filter((e) => {
|
|
1268
1519
|
const name = e.name.toLowerCase();
|
|
1269
1520
|
return name.startsWith(base.toLowerCase()) && (e.isDirectory() || name.endsWith(".isl"));
|
|
1270
1521
|
}).map((e) => ({
|
|
1271
|
-
text:
|
|
1522
|
+
text: path4.join(dir, e.name + (e.isDirectory() ? "/" : "")),
|
|
1272
1523
|
type: "file",
|
|
1273
1524
|
description: e.isDirectory() ? "Directory" : "ISL file"
|
|
1274
1525
|
}));
|
|
@@ -1349,7 +1600,10 @@ var ISLREPL = class {
|
|
|
1349
1600
|
this.options = {
|
|
1350
1601
|
colors: options.colors !== false,
|
|
1351
1602
|
verbose: options.verbose ?? false,
|
|
1352
|
-
historyFile: options.historyFile
|
|
1603
|
+
historyFile: options.historyFile,
|
|
1604
|
+
load: options.load,
|
|
1605
|
+
context: options.context,
|
|
1606
|
+
parseOnly: options.parseOnly ?? false
|
|
1353
1607
|
};
|
|
1354
1608
|
this.session = new Session({ colors: this.options.colors });
|
|
1355
1609
|
this.history = new History({
|
|
@@ -1363,6 +1617,11 @@ var ISLREPL = class {
|
|
|
1363
1617
|
start() {
|
|
1364
1618
|
if (this.running) return;
|
|
1365
1619
|
this.running = true;
|
|
1620
|
+
this.applyStartupOptions();
|
|
1621
|
+
if (this.options.parseOnly || !process.stdin.isTTY) {
|
|
1622
|
+
this.runPipeMode();
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1366
1625
|
this.history.load();
|
|
1367
1626
|
this.rl = readline.createInterface({
|
|
1368
1627
|
input: process.stdin,
|
|
@@ -1387,15 +1646,71 @@ var ISLREPL = class {
|
|
|
1387
1646
|
if (this.buffer.length > 0) {
|
|
1388
1647
|
this.buffer = [];
|
|
1389
1648
|
this.braceCount = 0;
|
|
1390
|
-
|
|
1649
|
+
process.stdout.write("\n" + formatWarning("Input cancelled") + "\n");
|
|
1391
1650
|
this.rl.setPrompt(PROMPT);
|
|
1392
1651
|
this.rl.prompt();
|
|
1393
1652
|
} else {
|
|
1394
|
-
|
|
1653
|
+
process.stdout.write("\n" + formatWarning("Use .exit to quit") + "\n");
|
|
1395
1654
|
this.rl.prompt();
|
|
1396
1655
|
}
|
|
1397
1656
|
});
|
|
1398
1657
|
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Apply startup options (--load, --context)
|
|
1660
|
+
*/
|
|
1661
|
+
applyStartupOptions() {
|
|
1662
|
+
if (this.options.context) {
|
|
1663
|
+
const result = this.session.setEvalContext(this.options.context);
|
|
1664
|
+
if (result.success) {
|
|
1665
|
+
process.stdout.write(
|
|
1666
|
+
formatSuccess(`Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`) + "\n"
|
|
1667
|
+
);
|
|
1668
|
+
} else {
|
|
1669
|
+
process.stdout.write(formatError(`Invalid context JSON: ${result.error}`) + "\n");
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
if (this.options.load) {
|
|
1673
|
+
const loadCmd = metaCommands.find((c) => c.name === "load");
|
|
1674
|
+
if (loadCmd) {
|
|
1675
|
+
const result = loadCmd.handler([this.options.load], this.session, this);
|
|
1676
|
+
if (result.output) {
|
|
1677
|
+
process.stdout.write(result.output + "\n");
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Run in pipe mode (read all stdin, parse, and output)
|
|
1684
|
+
*/
|
|
1685
|
+
runPipeMode() {
|
|
1686
|
+
let input = "";
|
|
1687
|
+
process.stdin.setEncoding("utf-8");
|
|
1688
|
+
process.stdin.on("data", (chunk) => {
|
|
1689
|
+
input += chunk;
|
|
1690
|
+
});
|
|
1691
|
+
process.stdin.on("end", () => {
|
|
1692
|
+
const trimmed = input.trim();
|
|
1693
|
+
if (!trimmed) {
|
|
1694
|
+
process.exit(0);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
if (this.options.parseOnly) {
|
|
1698
|
+
const parseCmd = metaCommands.find((c) => c.name === "parse");
|
|
1699
|
+
if (parseCmd) {
|
|
1700
|
+
const result = parseCmd.handler(trimmed.split(" "), this.session, this);
|
|
1701
|
+
if (result.output) {
|
|
1702
|
+
process.stdout.write(result.output + "\n");
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
} else {
|
|
1706
|
+
const lines = trimmed.split("\n");
|
|
1707
|
+
for (const line of lines) {
|
|
1708
|
+
this.handleLine(line);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
process.exit(0);
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1399
1714
|
/**
|
|
1400
1715
|
* Print the welcome banner
|
|
1401
1716
|
*/
|
|
@@ -1415,7 +1730,7 @@ ${colors.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
|
1415
1730
|
${colors.bold}ISL v${VERSION}${colors.reset} \u2014 Intent Specification Language
|
|
1416
1731
|
Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${colors.reset} to quit
|
|
1417
1732
|
`;
|
|
1418
|
-
|
|
1733
|
+
process.stdout.write(banner);
|
|
1419
1734
|
}
|
|
1420
1735
|
/**
|
|
1421
1736
|
* Handle a line of input
|
|
@@ -1426,11 +1741,11 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1426
1741
|
return;
|
|
1427
1742
|
}
|
|
1428
1743
|
if (trimmed.startsWith(".") && this.buffer.length === 0) {
|
|
1429
|
-
this.
|
|
1744
|
+
this.handleDotCommand(trimmed);
|
|
1430
1745
|
return;
|
|
1431
1746
|
}
|
|
1432
1747
|
if (trimmed.startsWith(":") && this.buffer.length === 0) {
|
|
1433
|
-
this.
|
|
1748
|
+
this.handleDotCommand("." + trimmed.slice(1));
|
|
1434
1749
|
return;
|
|
1435
1750
|
}
|
|
1436
1751
|
this.braceCount += (line.match(/\{/g) || []).length;
|
|
@@ -1446,78 +1761,134 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1446
1761
|
}
|
|
1447
1762
|
}
|
|
1448
1763
|
/**
|
|
1449
|
-
* Handle a
|
|
1764
|
+
* Handle a dot command (. prefix)
|
|
1450
1765
|
*/
|
|
1451
|
-
|
|
1766
|
+
handleDotCommand(input) {
|
|
1452
1767
|
const parts = input.slice(1).split(/\s+/);
|
|
1453
1768
|
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1454
1769
|
const args = parts.slice(1);
|
|
1770
|
+
const rawArgs = input.slice(1 + (cmdName.length || 0)).trim();
|
|
1455
1771
|
const command = metaCommands.find(
|
|
1456
1772
|
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1457
1773
|
);
|
|
1458
1774
|
if (command) {
|
|
1459
1775
|
this.history.add(input);
|
|
1460
|
-
const
|
|
1776
|
+
const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
|
|
1777
|
+
const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : args;
|
|
1778
|
+
const result = command.handler(effectiveArgs, this.session, this);
|
|
1461
1779
|
if (result.output) {
|
|
1462
|
-
|
|
1780
|
+
process.stdout.write(result.output + "\n");
|
|
1463
1781
|
}
|
|
1464
1782
|
if (result.exit) {
|
|
1465
1783
|
this.exit();
|
|
1466
1784
|
}
|
|
1467
1785
|
} else {
|
|
1468
|
-
const suggestion = findSimilarCommand(cmdName
|
|
1786
|
+
const suggestion = findSimilarCommand(cmdName);
|
|
1469
1787
|
if (suggestion) {
|
|
1470
|
-
|
|
1471
|
-
|
|
1788
|
+
process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
|
|
1789
|
+
process.stdout.write(formatWarning(`Did you mean: .${suggestion}?`) + "\n");
|
|
1472
1790
|
} else {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
/**
|
|
1479
|
-
* Handle an ISL command (: prefix)
|
|
1480
|
-
*/
|
|
1481
|
-
handleISLCommand(input) {
|
|
1482
|
-
const parts = input.slice(1).split(/\s+/);
|
|
1483
|
-
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1484
|
-
const args = parts.slice(1);
|
|
1485
|
-
const command = islCommands.find(
|
|
1486
|
-
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1487
|
-
);
|
|
1488
|
-
if (command) {
|
|
1489
|
-
this.history.add(input);
|
|
1490
|
-
const result = command.handler(args, this.session, this);
|
|
1491
|
-
if (result.output) {
|
|
1492
|
-
console.log(result.output);
|
|
1493
|
-
}
|
|
1494
|
-
} else {
|
|
1495
|
-
const suggestion = findSimilarCommand(cmdName, "isl");
|
|
1496
|
-
if (suggestion) {
|
|
1497
|
-
console.log(formatError(`Unknown command: :${cmdName}`));
|
|
1498
|
-
console.log(formatWarning(`Did you mean: :${suggestion}?`));
|
|
1499
|
-
} else {
|
|
1500
|
-
console.log(formatError(`Unknown command: :${cmdName}`));
|
|
1501
|
-
console.log(`Type ${colors.cyan}.help${colors.reset} for available commands`);
|
|
1791
|
+
process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
|
|
1792
|
+
process.stdout.write(`Type ${colors.cyan}.help${colors.reset} for available commands
|
|
1793
|
+
`);
|
|
1502
1794
|
}
|
|
1503
1795
|
}
|
|
1504
1796
|
}
|
|
1505
1797
|
/**
|
|
1506
|
-
* Evaluate ISL code
|
|
1798
|
+
* Evaluate ISL code (multi-line input or bare expressions)
|
|
1507
1799
|
*/
|
|
1508
1800
|
evaluate(code) {
|
|
1509
1801
|
try {
|
|
1510
1802
|
const trimmed = code.trim();
|
|
1511
|
-
|
|
1803
|
+
try {
|
|
1804
|
+
const { parse } = __require("@isl-lang/parser");
|
|
1805
|
+
let parseInput = trimmed;
|
|
1806
|
+
const needsWrapper = !trimmed.startsWith("domain ");
|
|
1807
|
+
if (needsWrapper) {
|
|
1808
|
+
parseInput = `domain _REPL { version: "0.0.1"
|
|
1809
|
+
${trimmed}
|
|
1810
|
+
}`;
|
|
1811
|
+
}
|
|
1812
|
+
const result = parse(parseInput, "<repl>");
|
|
1813
|
+
if (result.errors.length > 0) {
|
|
1814
|
+
for (const err of result.errors) {
|
|
1815
|
+
const loc = err.location;
|
|
1816
|
+
if (loc) {
|
|
1817
|
+
const adjustedLine = needsWrapper ? Math.max(1, loc.line - 1) : loc.line;
|
|
1818
|
+
const lines = trimmed.split("\n");
|
|
1819
|
+
const errorLine = lines[adjustedLine - 1] || "";
|
|
1820
|
+
process.stdout.write(
|
|
1821
|
+
`${colors.red}\u2717 Error at line ${adjustedLine}, col ${loc.column}:${colors.reset}
|
|
1822
|
+
`
|
|
1823
|
+
);
|
|
1824
|
+
process.stdout.write(` ${errorLine}
|
|
1825
|
+
`);
|
|
1826
|
+
process.stdout.write(` ${" ".repeat(Math.max(0, loc.column - 1))}${colors.red}^^^^^${colors.reset}
|
|
1827
|
+
`);
|
|
1828
|
+
const typeMatch = err.message.match(/Unknown type '(\w+)'/i) || err.message.match(/unexpected.*'(\w+)'/i);
|
|
1829
|
+
if (typeMatch) {
|
|
1830
|
+
const suggestion = suggestCorrection(typeMatch[1]);
|
|
1831
|
+
if (suggestion) {
|
|
1832
|
+
process.stdout.write(
|
|
1833
|
+
` ${colors.yellow}Did you mean '${suggestion}'?${colors.reset}
|
|
1834
|
+
`
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
} else {
|
|
1838
|
+
process.stdout.write(` ${err.message}
|
|
1839
|
+
`);
|
|
1840
|
+
}
|
|
1841
|
+
} else {
|
|
1842
|
+
process.stdout.write(formatError(err.message) + "\n");
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
if (result.domain) {
|
|
1848
|
+
this.session.setDomainAST(result.domain);
|
|
1849
|
+
const domain = result.domain;
|
|
1850
|
+
if (needsWrapper) {
|
|
1851
|
+
const entityCount = domain.entities?.length ?? 0;
|
|
1852
|
+
const behaviorCount = domain.behaviors?.length ?? 0;
|
|
1853
|
+
const parts = [];
|
|
1854
|
+
if (entityCount > 0) parts.push(`${entityCount} entit${entityCount === 1 ? "y" : "ies"}`);
|
|
1855
|
+
if (behaviorCount > 0) parts.push(`${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"}`);
|
|
1856
|
+
if (parts.length > 0) {
|
|
1857
|
+
process.stdout.write(
|
|
1858
|
+
formatSuccess(`Parsed: ${parts.join(", ")}`) + "\n"
|
|
1859
|
+
);
|
|
1860
|
+
} else {
|
|
1861
|
+
process.stdout.write(formatSuccess("Parsed successfully") + "\n");
|
|
1862
|
+
}
|
|
1863
|
+
} else {
|
|
1864
|
+
const name = domain.name?.name ?? "Unknown";
|
|
1865
|
+
const entityCount = domain.entities?.length ?? 0;
|
|
1866
|
+
const behaviorCount = domain.behaviors?.length ?? 0;
|
|
1867
|
+
process.stdout.write(
|
|
1868
|
+
formatSuccess(
|
|
1869
|
+
`Parsed: domain ${name} (${entityCount} entit${entityCount === 1 ? "y" : "ies"}, ${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"})`
|
|
1870
|
+
) + "\n"
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
} catch {
|
|
1876
|
+
}
|
|
1877
|
+
if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
|
|
1512
1878
|
this.evaluateIntent(trimmed);
|
|
1513
1879
|
return;
|
|
1514
1880
|
}
|
|
1515
|
-
if (trimmed.startsWith("
|
|
1516
|
-
|
|
1881
|
+
if (trimmed.startsWith("domain ")) {
|
|
1882
|
+
process.stdout.write(formatSuccess("Parsed domain block") + "\n");
|
|
1517
1883
|
return;
|
|
1518
1884
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1885
|
+
process.stdout.write(
|
|
1886
|
+
formatWarning(`Cannot evaluate: ${trimmed.split("\n")[0]}...`) + "\n"
|
|
1887
|
+
);
|
|
1888
|
+
process.stdout.write(
|
|
1889
|
+
`Use ${colors.cyan}.help${colors.reset} for available commands
|
|
1890
|
+
`
|
|
1891
|
+
);
|
|
1521
1892
|
} catch (error) {
|
|
1522
1893
|
this.printError(error);
|
|
1523
1894
|
}
|
|
@@ -1537,13 +1908,15 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1537
1908
|
if (postCount > 0) parts.push(`${postCount} post`);
|
|
1538
1909
|
if (invCount > 0) parts.push(`${invCount} invariant`);
|
|
1539
1910
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
1540
|
-
|
|
1911
|
+
process.stdout.write(
|
|
1912
|
+
formatSuccess(`Intent '${intent.name}' defined${summary}`) + "\n"
|
|
1913
|
+
);
|
|
1541
1914
|
} else {
|
|
1542
1915
|
const behaviorMatch = code.match(/^behavior\s+(\w+)\s*\{([\s\S]*)\}$/);
|
|
1543
1916
|
if (behaviorMatch) {
|
|
1544
1917
|
const name = behaviorMatch[1];
|
|
1545
1918
|
const body = behaviorMatch[2];
|
|
1546
|
-
const
|
|
1919
|
+
const newIntent = {
|
|
1547
1920
|
name,
|
|
1548
1921
|
preconditions: [],
|
|
1549
1922
|
postconditions: [],
|
|
@@ -1553,51 +1926,28 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1553
1926
|
};
|
|
1554
1927
|
const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
|
|
1555
1928
|
if (preSection) {
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
if (expr) {
|
|
1560
|
-
intent2.preconditions.push({ expression: expr });
|
|
1561
|
-
}
|
|
1929
|
+
for (const line of preSection[1].trim().split("\n")) {
|
|
1930
|
+
const expr = line.trim().replace(/^-\s*/, "").trim();
|
|
1931
|
+
if (expr) newIntent.preconditions.push({ expression: expr });
|
|
1562
1932
|
}
|
|
1563
1933
|
}
|
|
1564
1934
|
const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
|
|
1565
1935
|
if (postSection) {
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
if (expr) {
|
|
1570
|
-
intent2.postconditions.push({ expression: expr });
|
|
1571
|
-
}
|
|
1936
|
+
for (const line of postSection[1].trim().split("\n")) {
|
|
1937
|
+
const expr = line.trim().replace(/^-\s*/, "").trim();
|
|
1938
|
+
if (expr) newIntent.postconditions.push({ expression: expr });
|
|
1572
1939
|
}
|
|
1573
1940
|
}
|
|
1574
|
-
this.session.defineIntent(
|
|
1575
|
-
const preCount = intent2.preconditions.length;
|
|
1576
|
-
const postCount = intent2.postconditions.length;
|
|
1941
|
+
this.session.defineIntent(newIntent);
|
|
1577
1942
|
const parts = [];
|
|
1578
|
-
if (
|
|
1579
|
-
if (
|
|
1943
|
+
if (newIntent.preconditions.length > 0) parts.push(`${newIntent.preconditions.length} pre`);
|
|
1944
|
+
if (newIntent.postconditions.length > 0) parts.push(`${newIntent.postconditions.length} post`);
|
|
1580
1945
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
1581
|
-
|
|
1946
|
+
process.stdout.write(
|
|
1947
|
+
formatSuccess(`Intent '${name}' defined${summary}`) + "\n"
|
|
1948
|
+
);
|
|
1582
1949
|
} else {
|
|
1583
|
-
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* Print a parse error with location info
|
|
1589
|
-
*/
|
|
1590
|
-
printParseError(code, message, line, column) {
|
|
1591
|
-
console.log(formatError(message));
|
|
1592
|
-
if (line !== void 0 && column !== void 0) {
|
|
1593
|
-
const lines = code.split("\n");
|
|
1594
|
-
const errorLine = lines[line - 1] || "";
|
|
1595
|
-
console.log(` ${colors.gray}${line} |${colors.reset} ${errorLine}`);
|
|
1596
|
-
console.log(` ${colors.gray}${" ".repeat(String(line).length)} |${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`);
|
|
1597
|
-
} else {
|
|
1598
|
-
const firstLine = code.split("\n")[0];
|
|
1599
|
-
if (firstLine) {
|
|
1600
|
-
console.log(` ${colors.gray}>${colors.reset} ${firstLine}`);
|
|
1950
|
+
process.stdout.write(formatError("Failed to parse intent definition") + "\n");
|
|
1601
1951
|
}
|
|
1602
1952
|
}
|
|
1603
1953
|
}
|
|
@@ -1606,12 +1956,12 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1606
1956
|
*/
|
|
1607
1957
|
printError(error) {
|
|
1608
1958
|
if (error instanceof Error) {
|
|
1609
|
-
|
|
1959
|
+
process.stdout.write(formatError(error.message) + "\n");
|
|
1610
1960
|
if (this.options.verbose && error.stack) {
|
|
1611
|
-
|
|
1961
|
+
process.stdout.write(colors.gray + error.stack + colors.reset + "\n");
|
|
1612
1962
|
}
|
|
1613
1963
|
} else {
|
|
1614
|
-
|
|
1964
|
+
process.stdout.write(formatError(String(error)) + "\n");
|
|
1615
1965
|
}
|
|
1616
1966
|
}
|
|
1617
1967
|
/**
|
|
@@ -1620,8 +1970,9 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
|
|
|
1620
1970
|
exit() {
|
|
1621
1971
|
this.running = false;
|
|
1622
1972
|
this.history.save();
|
|
1623
|
-
|
|
1624
|
-
${colors.yellow}Goodbye!${colors.reset}
|
|
1973
|
+
process.stdout.write(`
|
|
1974
|
+
${colors.yellow}Goodbye!${colors.reset}
|
|
1975
|
+
`);
|
|
1625
1976
|
if (this.rl) {
|
|
1626
1977
|
this.rl.close();
|
|
1627
1978
|
}
|
|
@@ -1644,31 +1995,21 @@ ${colors.yellow}Goodbye!${colors.reset}`);
|
|
|
1644
1995
|
*/
|
|
1645
1996
|
async executeOnce(input) {
|
|
1646
1997
|
const trimmed = input.trim();
|
|
1647
|
-
if (trimmed.startsWith(".")) {
|
|
1648
|
-
const
|
|
1998
|
+
if (trimmed.startsWith(".") || trimmed.startsWith(":")) {
|
|
1999
|
+
const normalized = trimmed.startsWith(":") ? "." + trimmed.slice(1) : trimmed;
|
|
2000
|
+
const parts = normalized.slice(1).split(/\s+/);
|
|
1649
2001
|
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1650
|
-
const
|
|
2002
|
+
const rawArgs = normalized.slice(1 + (cmdName.length || 0)).trim();
|
|
1651
2003
|
const command = metaCommands.find(
|
|
1652
2004
|
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1653
2005
|
);
|
|
1654
2006
|
if (command) {
|
|
1655
|
-
const
|
|
2007
|
+
const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
|
|
2008
|
+
const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : parts.slice(1);
|
|
2009
|
+
const result = command.handler(effectiveArgs, this.session, this);
|
|
1656
2010
|
return { success: true, output: result.output };
|
|
1657
2011
|
}
|
|
1658
|
-
return { success: false, error: `Unknown command:
|
|
1659
|
-
}
|
|
1660
|
-
if (trimmed.startsWith(":")) {
|
|
1661
|
-
const parts = trimmed.slice(1).split(/\s+/);
|
|
1662
|
-
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1663
|
-
const args = parts.slice(1);
|
|
1664
|
-
const command = islCommands.find(
|
|
1665
|
-
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1666
|
-
);
|
|
1667
|
-
if (command) {
|
|
1668
|
-
const result = command.handler(args, this.session, this);
|
|
1669
|
-
return { success: true, output: result.output };
|
|
1670
|
-
}
|
|
1671
|
-
return { success: false, error: `Unknown command: :${cmdName}` };
|
|
2012
|
+
return { success: false, error: `Unknown command: ${cmdName}` };
|
|
1672
2013
|
}
|
|
1673
2014
|
if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
|
|
1674
2015
|
const intent = this.session.parseIntent(trimmed);
|
|
@@ -1681,6 +2022,29 @@ ${colors.yellow}Goodbye!${colors.reset}`);
|
|
|
1681
2022
|
return { success: false, error: "Unknown input" };
|
|
1682
2023
|
}
|
|
1683
2024
|
};
|
|
2025
|
+
var KNOWN_TYPES = [
|
|
2026
|
+
"String",
|
|
2027
|
+
"Int",
|
|
2028
|
+
"Decimal",
|
|
2029
|
+
"Boolean",
|
|
2030
|
+
"UUID",
|
|
2031
|
+
"Timestamp",
|
|
2032
|
+
"Duration",
|
|
2033
|
+
"List",
|
|
2034
|
+
"Map",
|
|
2035
|
+
"Optional",
|
|
2036
|
+
"Number"
|
|
2037
|
+
];
|
|
2038
|
+
function suggestCorrection(typo) {
|
|
2039
|
+
const lower = typo.toLowerCase();
|
|
2040
|
+
for (const t of KNOWN_TYPES) {
|
|
2041
|
+
if (t.toLowerCase() === lower) return t;
|
|
2042
|
+
if (t.toLowerCase().startsWith(lower.slice(0, 3)) && Math.abs(t.length - typo.length) <= 2) {
|
|
2043
|
+
return t;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
1684
2048
|
function startREPL(options) {
|
|
1685
2049
|
const repl = new ISLREPL(options);
|
|
1686
2050
|
repl.start();
|
|
@@ -1688,44 +2052,102 @@ function startREPL(options) {
|
|
|
1688
2052
|
}
|
|
1689
2053
|
|
|
1690
2054
|
// src/cli.ts
|
|
1691
|
-
function
|
|
1692
|
-
const args = process.argv.slice(2);
|
|
2055
|
+
function parseArgs(argv) {
|
|
1693
2056
|
const options = {
|
|
1694
|
-
|
|
1695
|
-
|
|
2057
|
+
help: false,
|
|
2058
|
+
colors: true,
|
|
2059
|
+
verbose: false,
|
|
2060
|
+
parseOnly: false
|
|
1696
2061
|
};
|
|
1697
|
-
|
|
1698
|
-
|
|
2062
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2063
|
+
const arg = argv[i];
|
|
2064
|
+
switch (arg) {
|
|
2065
|
+
case "--help":
|
|
2066
|
+
case "-h":
|
|
2067
|
+
options.help = true;
|
|
2068
|
+
break;
|
|
2069
|
+
case "--no-color":
|
|
2070
|
+
options.colors = false;
|
|
2071
|
+
break;
|
|
2072
|
+
case "--verbose":
|
|
2073
|
+
case "-v":
|
|
2074
|
+
options.verbose = true;
|
|
2075
|
+
break;
|
|
2076
|
+
case "--load":
|
|
2077
|
+
options.load = argv[++i];
|
|
2078
|
+
break;
|
|
2079
|
+
case "--context":
|
|
2080
|
+
options.context = argv[++i];
|
|
2081
|
+
break;
|
|
2082
|
+
case "--parse":
|
|
2083
|
+
options.parseOnly = true;
|
|
2084
|
+
break;
|
|
2085
|
+
default:
|
|
2086
|
+
if (arg.startsWith("--load=")) {
|
|
2087
|
+
options.load = arg.slice(7);
|
|
2088
|
+
} else if (arg.startsWith("--context=")) {
|
|
2089
|
+
options.context = arg.slice(10);
|
|
2090
|
+
}
|
|
2091
|
+
break;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return options;
|
|
2095
|
+
}
|
|
2096
|
+
function printHelp() {
|
|
2097
|
+
process.stdout.write(`
|
|
1699
2098
|
ISL REPL - Intent Specification Language Interactive Shell
|
|
1700
2099
|
|
|
1701
2100
|
Usage: isl-repl [options]
|
|
1702
2101
|
|
|
1703
2102
|
Options:
|
|
1704
|
-
--
|
|
1705
|
-
|
|
1706
|
-
-
|
|
2103
|
+
--load <file> Load an ISL file on start
|
|
2104
|
+
--context <json> Set initial evaluation context
|
|
2105
|
+
--parse Parse mode (non-interactive, for piped input)
|
|
2106
|
+
--no-color Disable colored output
|
|
2107
|
+
-v, --verbose Enable verbose output
|
|
2108
|
+
-h, --help Show this help message
|
|
1707
2109
|
|
|
1708
2110
|
Inside the REPL:
|
|
1709
|
-
.help
|
|
1710
|
-
.
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
2111
|
+
.help Show all commands
|
|
2112
|
+
.parse <isl> Parse ISL and show AST
|
|
2113
|
+
.eval <expr> Evaluate expression against context
|
|
2114
|
+
.check [intent] Type check intents
|
|
2115
|
+
.gen [intent] Generate TypeScript from intent
|
|
2116
|
+
.load <file> Load an .isl file
|
|
2117
|
+
.context <json> Set evaluation context (mock data)
|
|
2118
|
+
.clear Reset session state
|
|
2119
|
+
.list List defined intents
|
|
2120
|
+
.inspect [intent] Show full details of an intent
|
|
2121
|
+
.history Show command history
|
|
2122
|
+
.exit Exit the REPL
|
|
1715
2123
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
isl>
|
|
1719
|
-
...>
|
|
1720
|
-
...>
|
|
2124
|
+
Multi-line Input:
|
|
2125
|
+
Type ISL with braces \u2014 the REPL auto-detects multi-line:
|
|
2126
|
+
isl> domain Example {
|
|
2127
|
+
...> entity User {
|
|
2128
|
+
...> id: UUID
|
|
2129
|
+
...> name: String
|
|
2130
|
+
...> }
|
|
1721
2131
|
...> }
|
|
1722
|
-
|
|
2132
|
+
|
|
2133
|
+
Examples:
|
|
2134
|
+
$ isl-repl
|
|
2135
|
+
$ isl-repl --load auth.isl
|
|
2136
|
+
$ isl-repl --context '{"user": {"id": 1}}'
|
|
2137
|
+
$ echo 'domain X { version: "1.0" }' | isl-repl --parse
|
|
1723
2138
|
`);
|
|
2139
|
+
}
|
|
2140
|
+
function main() {
|
|
2141
|
+
const args = process.argv.slice(2);
|
|
2142
|
+
const options = parseArgs(args);
|
|
2143
|
+
if (options.help) {
|
|
2144
|
+
printHelp();
|
|
1724
2145
|
process.exit(0);
|
|
1725
2146
|
}
|
|
1726
2147
|
startREPL(options);
|
|
1727
2148
|
}
|
|
1728
|
-
|
|
2149
|
+
var isMainModule = typeof __require !== "undefined" ? __require.main === module : process.argv[1]?.includes("cli");
|
|
2150
|
+
if (isMainModule) {
|
|
1729
2151
|
main();
|
|
1730
2152
|
}
|
|
1731
2153
|
export {
|