@isl-lang/repl 0.0.1 → 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/index.js ADDED
@@ -0,0 +1,2228 @@
1
+ import * as readline from 'readline';
2
+ import * as fs2 from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+
6
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
+ }) : x)(function(x) {
9
+ if (typeof require !== "undefined") return require.apply(this, arguments);
10
+ throw Error('Dynamic require of "' + x + '" is not supported');
11
+ });
12
+ var Session = class {
13
+ /** Defined intents in this session */
14
+ intents = /* @__PURE__ */ new Map();
15
+ /** Variables set during the session */
16
+ variables = /* @__PURE__ */ new Map();
17
+ /** Command history */
18
+ history = [];
19
+ /** Last evaluation result */
20
+ lastResult = void 0;
21
+ /** Loaded files */
22
+ loadedFiles = /* @__PURE__ */ new Set();
23
+ /** Session configuration */
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;
31
+ constructor(config = {}) {
32
+ this.config = {
33
+ colors: true,
34
+ verbose: false,
35
+ cwd: process.cwd(),
36
+ ...config
37
+ };
38
+ }
39
+ // ─────────────────────────────────────────────────────────────────────────
40
+ // Intent Management
41
+ // ─────────────────────────────────────────────────────────────────────────
42
+ /**
43
+ * Define a new intent
44
+ */
45
+ defineIntent(intent) {
46
+ this.intents.set(intent.name, intent);
47
+ }
48
+ /**
49
+ * Get an intent by name
50
+ */
51
+ getIntent(name) {
52
+ return this.intents.get(name);
53
+ }
54
+ /**
55
+ * Get all defined intents
56
+ */
57
+ getAllIntents() {
58
+ return Array.from(this.intents.values());
59
+ }
60
+ /**
61
+ * Check if an intent exists
62
+ */
63
+ hasIntent(name) {
64
+ return this.intents.has(name);
65
+ }
66
+ /**
67
+ * Remove an intent
68
+ */
69
+ removeIntent(name) {
70
+ return this.intents.delete(name);
71
+ }
72
+ /**
73
+ * Get intent names for completion
74
+ */
75
+ getIntentNames() {
76
+ return Array.from(this.intents.keys());
77
+ }
78
+ /**
79
+ * Parse an intent definition from source code
80
+ */
81
+ parseIntent(source) {
82
+ const trimmed = source.trim();
83
+ const match = trimmed.match(/^intent\s+(\w+)\s*\{([\s\S]*)\}$/);
84
+ if (!match) {
85
+ return null;
86
+ }
87
+ const name = match[1];
88
+ const body = match[2];
89
+ const intent = {
90
+ name,
91
+ preconditions: [],
92
+ postconditions: [],
93
+ invariants: [],
94
+ scenarios: [],
95
+ rawSource: source
96
+ };
97
+ const preMatch = body.match(/pre(?:conditions?)?\s*:\s*([^\n]+)/g);
98
+ if (preMatch) {
99
+ for (const pre of preMatch) {
100
+ const expr = pre.replace(/pre(?:conditions?)?\s*:\s*/, "").trim();
101
+ if (expr) {
102
+ intent.preconditions.push({ expression: expr });
103
+ }
104
+ }
105
+ }
106
+ const postMatch = body.match(/post(?:conditions?)?\s*:\s*([^\n]+)/g);
107
+ if (postMatch) {
108
+ for (const post of postMatch) {
109
+ const expr = post.replace(/post(?:conditions?)?\s*:\s*/, "").trim();
110
+ if (expr) {
111
+ intent.postconditions.push({ expression: expr });
112
+ }
113
+ }
114
+ }
115
+ const invMatch = body.match(/invariants?\s*:\s*([^\n]+)/g);
116
+ if (invMatch) {
117
+ for (const inv of invMatch) {
118
+ const expr = inv.replace(/invariants?\s*:\s*/, "").trim();
119
+ if (expr) {
120
+ intent.invariants.push({ expression: expr });
121
+ }
122
+ }
123
+ }
124
+ return intent;
125
+ }
126
+ // ─────────────────────────────────────────────────────────────────────────
127
+ // Variable Management
128
+ // ─────────────────────────────────────────────────────────────────────────
129
+ /**
130
+ * Set a variable
131
+ */
132
+ setVariable(name, value) {
133
+ this.variables.set(name, value);
134
+ }
135
+ /**
136
+ * Get a variable
137
+ */
138
+ getVariable(name) {
139
+ return this.variables.get(name);
140
+ }
141
+ /**
142
+ * Get all variables
143
+ */
144
+ getAllVariables() {
145
+ return new Map(this.variables);
146
+ }
147
+ /**
148
+ * Check if a variable exists
149
+ */
150
+ hasVariable(name) {
151
+ return this.variables.has(name);
152
+ }
153
+ /**
154
+ * Set the last result (accessible as _)
155
+ */
156
+ setLastResult(value) {
157
+ this.lastResult = value;
158
+ this.variables.set("_", value);
159
+ }
160
+ /**
161
+ * Get the last result
162
+ */
163
+ getLastResult() {
164
+ return this.lastResult;
165
+ }
166
+ // ─────────────────────────────────────────────────────────────────────────
167
+ // History Management
168
+ // ─────────────────────────────────────────────────────────────────────────
169
+ /**
170
+ * Add to history
171
+ */
172
+ addToHistory(entry) {
173
+ const trimmed = entry.trim();
174
+ if (trimmed && (this.history.length === 0 || this.history[this.history.length - 1] !== trimmed)) {
175
+ this.history.push(trimmed);
176
+ }
177
+ }
178
+ /**
179
+ * Get history
180
+ */
181
+ getHistory(count) {
182
+ if (count) {
183
+ return this.history.slice(-count);
184
+ }
185
+ return [...this.history];
186
+ }
187
+ // ─────────────────────────────────────────────────────────────────────────
188
+ // File Loading
189
+ // ─────────────────────────────────────────────────────────────────────────
190
+ /**
191
+ * Load intents from an ISL file
192
+ */
193
+ async loadFile(filePath) {
194
+ const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(this.config.cwd, filePath);
195
+ if (!fs2.existsSync(resolvedPath)) {
196
+ return { intents: [], errors: [`File not found: ${resolvedPath}`] };
197
+ }
198
+ try {
199
+ const content = fs2.readFileSync(resolvedPath, "utf-8");
200
+ const loadedIntents = [];
201
+ const errors = [];
202
+ const intentRegex = /intent\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
203
+ let match;
204
+ while ((match = intentRegex.exec(content)) !== null) {
205
+ const intent = this.parseIntent(match[0]);
206
+ if (intent) {
207
+ this.defineIntent(intent);
208
+ loadedIntents.push(intent);
209
+ } else {
210
+ errors.push(`Failed to parse intent starting at position ${match.index}`);
211
+ }
212
+ }
213
+ const behaviorRegex = /behavior\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
214
+ while ((match = behaviorRegex.exec(content)) !== null) {
215
+ const intent = this.parseBehaviorAsIntent(match[1], match[2]);
216
+ if (intent) {
217
+ this.defineIntent(intent);
218
+ loadedIntents.push(intent);
219
+ }
220
+ }
221
+ this.loadedFiles.add(resolvedPath);
222
+ return { intents: loadedIntents, errors };
223
+ } catch (error) {
224
+ return {
225
+ intents: [],
226
+ errors: [`Failed to load file: ${error instanceof Error ? error.message : String(error)}`]
227
+ };
228
+ }
229
+ }
230
+ /**
231
+ * Parse a behavior block as an intent
232
+ */
233
+ parseBehaviorAsIntent(name, body) {
234
+ const intent = {
235
+ name,
236
+ preconditions: [],
237
+ postconditions: [],
238
+ invariants: [],
239
+ scenarios: [],
240
+ rawSource: `behavior ${name} {${body}}`
241
+ };
242
+ const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
243
+ if (preSection) {
244
+ const conditions = preSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
245
+ for (const cond of conditions) {
246
+ const expr = cond.replace(/^-\s*/, "").trim();
247
+ if (expr) {
248
+ intent.preconditions.push({ expression: expr });
249
+ }
250
+ }
251
+ }
252
+ const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
253
+ if (postSection) {
254
+ const conditions = postSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
255
+ for (const cond of conditions) {
256
+ const expr = cond.replace(/^-\s*/, "").trim();
257
+ if (expr) {
258
+ intent.postconditions.push({ expression: expr });
259
+ }
260
+ }
261
+ }
262
+ const invSection = body.match(/invariants?\s*\{([^}]*)\}/s);
263
+ if (invSection) {
264
+ const conditions = invSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
265
+ for (const cond of conditions) {
266
+ const expr = cond.replace(/^-\s*/, "").trim();
267
+ if (expr) {
268
+ intent.invariants.push({ expression: expr });
269
+ }
270
+ }
271
+ }
272
+ return intent;
273
+ }
274
+ /**
275
+ * Export session intents to a file
276
+ */
277
+ async exportToFile(filePath) {
278
+ const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(this.config.cwd, filePath);
279
+ try {
280
+ const lines = [];
281
+ lines.push("// Exported ISL intents");
282
+ lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
283
+ lines.push("");
284
+ for (const intent of this.intents.values()) {
285
+ lines.push(`intent ${intent.name} {`);
286
+ for (const pre of intent.preconditions) {
287
+ lines.push(` pre: ${pre.expression}`);
288
+ }
289
+ for (const post of intent.postconditions) {
290
+ lines.push(` post: ${post.expression}`);
291
+ }
292
+ for (const inv of intent.invariants) {
293
+ lines.push(` invariant: ${inv.expression}`);
294
+ }
295
+ lines.push("}");
296
+ lines.push("");
297
+ }
298
+ fs2.writeFileSync(resolvedPath, lines.join("\n"));
299
+ return { success: true };
300
+ } catch (error) {
301
+ return {
302
+ success: false,
303
+ error: `Failed to export: ${error instanceof Error ? error.message : String(error)}`
304
+ };
305
+ }
306
+ }
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
+ // ─────────────────────────────────────────────────────────────────────────
399
+ // State Management
400
+ // ─────────────────────────────────────────────────────────────────────────
401
+ /**
402
+ * Clear all session state
403
+ */
404
+ clear() {
405
+ this.intents.clear();
406
+ this.variables.clear();
407
+ this.lastResult = void 0;
408
+ this.loadedFiles.clear();
409
+ this.evalContext = {};
410
+ this.preContext = null;
411
+ this.domainAST = null;
412
+ }
413
+ /**
414
+ * Get session summary
415
+ */
416
+ getSummary() {
417
+ return {
418
+ intentCount: this.intents.size,
419
+ variableCount: this.variables.size,
420
+ loadedFileCount: this.loadedFiles.size,
421
+ historyCount: this.history.length
422
+ };
423
+ }
424
+ /**
425
+ * Get loaded files
426
+ */
427
+ getLoadedFiles() {
428
+ return Array.from(this.loadedFiles);
429
+ }
430
+ /**
431
+ * Get config
432
+ */
433
+ getConfig() {
434
+ return { ...this.config };
435
+ }
436
+ /**
437
+ * Update config
438
+ */
439
+ setConfig(config) {
440
+ this.config = { ...this.config, ...config };
441
+ }
442
+ };
443
+ function createSession(config) {
444
+ return new Session(config);
445
+ }
446
+ var History = class {
447
+ entries = [];
448
+ position = -1;
449
+ maxSize;
450
+ historyFile;
451
+ unsavedCount = 0;
452
+ autoSaveThreshold = 10;
453
+ constructor(options = {}) {
454
+ this.maxSize = options.maxSize ?? 1e3;
455
+ this.historyFile = options.historyFile ?? this.getDefaultHistoryFile();
456
+ this.autoSaveThreshold = options.autoSaveThreshold ?? 10;
457
+ }
458
+ /**
459
+ * Get default history file path
460
+ */
461
+ getDefaultHistoryFile() {
462
+ const homeDir = os.homedir();
463
+ return path.join(homeDir, ".isl_repl_history");
464
+ }
465
+ /**
466
+ * Load history from file
467
+ */
468
+ load() {
469
+ try {
470
+ if (fs2.existsSync(this.historyFile)) {
471
+ const content = fs2.readFileSync(this.historyFile, "utf-8");
472
+ this.entries = content.split("\n").filter((line) => line.trim() !== "").slice(-this.maxSize);
473
+ this.position = this.entries.length;
474
+ }
475
+ } catch {
476
+ }
477
+ }
478
+ /**
479
+ * Save history to file
480
+ */
481
+ save() {
482
+ try {
483
+ const dir = path.dirname(this.historyFile);
484
+ if (!fs2.existsSync(dir)) {
485
+ fs2.mkdirSync(dir, { recursive: true });
486
+ }
487
+ fs2.writeFileSync(this.historyFile, this.entries.join("\n") + "\n");
488
+ this.unsavedCount = 0;
489
+ } catch {
490
+ }
491
+ }
492
+ /**
493
+ * Add entry to history
494
+ */
495
+ add(entry) {
496
+ const trimmed = entry.trim();
497
+ if (trimmed === "") return;
498
+ if (this.entries.length > 0 && this.entries[this.entries.length - 1] === trimmed) {
499
+ this.position = this.entries.length;
500
+ return;
501
+ }
502
+ this.entries.push(trimmed);
503
+ if (this.entries.length > this.maxSize) {
504
+ this.entries = this.entries.slice(-this.maxSize);
505
+ }
506
+ this.position = this.entries.length;
507
+ this.unsavedCount++;
508
+ if (this.unsavedCount >= this.autoSaveThreshold) {
509
+ this.save();
510
+ }
511
+ }
512
+ /**
513
+ * Get previous entry (for up arrow)
514
+ */
515
+ previous() {
516
+ if (this.entries.length === 0) return null;
517
+ if (this.position > 0) {
518
+ this.position--;
519
+ }
520
+ return this.entries[this.position] ?? null;
521
+ }
522
+ /**
523
+ * Get next entry (for down arrow)
524
+ */
525
+ next() {
526
+ if (this.entries.length === 0) return null;
527
+ if (this.position < this.entries.length - 1) {
528
+ this.position++;
529
+ return this.entries[this.position] ?? null;
530
+ }
531
+ this.position = this.entries.length;
532
+ return "";
533
+ }
534
+ /**
535
+ * Reset position to end
536
+ */
537
+ resetPosition() {
538
+ this.position = this.entries.length;
539
+ }
540
+ /**
541
+ * Search history for entries containing text
542
+ */
543
+ search(text) {
544
+ const lower = text.toLowerCase();
545
+ return this.entries.filter(
546
+ (entry) => entry.toLowerCase().includes(lower)
547
+ );
548
+ }
549
+ /**
550
+ * Search backwards from current position
551
+ */
552
+ searchBackward(text) {
553
+ const lower = text.toLowerCase();
554
+ for (let i = this.position - 1; i >= 0; i--) {
555
+ if (this.entries[i].toLowerCase().includes(lower)) {
556
+ this.position = i;
557
+ return this.entries[i];
558
+ }
559
+ }
560
+ return null;
561
+ }
562
+ /**
563
+ * Search forward from current position
564
+ */
565
+ searchForward(text) {
566
+ const lower = text.toLowerCase();
567
+ for (let i = this.position + 1; i < this.entries.length; i++) {
568
+ if (this.entries[i].toLowerCase().includes(lower)) {
569
+ this.position = i;
570
+ return this.entries[i];
571
+ }
572
+ }
573
+ return null;
574
+ }
575
+ /**
576
+ * Get all entries
577
+ */
578
+ getAll() {
579
+ return [...this.entries];
580
+ }
581
+ /**
582
+ * Get recent entries
583
+ */
584
+ getRecent(count) {
585
+ return this.entries.slice(-count);
586
+ }
587
+ /**
588
+ * Clear history
589
+ */
590
+ clear() {
591
+ this.entries = [];
592
+ this.position = 0;
593
+ this.save();
594
+ }
595
+ /**
596
+ * Get history size
597
+ */
598
+ get size() {
599
+ return this.entries.length;
600
+ }
601
+ /**
602
+ * Get current position
603
+ */
604
+ get currentPosition() {
605
+ return this.position;
606
+ }
607
+ };
608
+ var MemoryHistory = class extends History {
609
+ constructor(maxSize = 1e3) {
610
+ super({ maxSize, historyFile: "" });
611
+ }
612
+ load() {
613
+ }
614
+ save() {
615
+ }
616
+ };
617
+
618
+ // src/formatter.ts
619
+ var colors = {
620
+ reset: "\x1B[0m",
621
+ bold: "\x1B[1m",
622
+ dim: "\x1B[2m",
623
+ italic: "\x1B[3m",
624
+ underline: "\x1B[4m",
625
+ // Foreground colors
626
+ black: "\x1B[30m",
627
+ red: "\x1B[31m",
628
+ green: "\x1B[32m",
629
+ yellow: "\x1B[33m",
630
+ blue: "\x1B[34m",
631
+ magenta: "\x1B[35m",
632
+ cyan: "\x1B[36m",
633
+ white: "\x1B[37m",
634
+ gray: "\x1B[90m",
635
+ // Bright foreground
636
+ brightRed: "\x1B[91m",
637
+ brightGreen: "\x1B[92m",
638
+ brightYellow: "\x1B[93m",
639
+ brightBlue: "\x1B[94m",
640
+ brightMagenta: "\x1B[95m",
641
+ brightCyan: "\x1B[96m",
642
+ brightWhite: "\x1B[97m",
643
+ // Background colors
644
+ bgBlack: "\x1B[40m",
645
+ bgRed: "\x1B[41m",
646
+ bgGreen: "\x1B[42m",
647
+ bgYellow: "\x1B[43m",
648
+ bgBlue: "\x1B[44m",
649
+ bgMagenta: "\x1B[45m",
650
+ bgCyan: "\x1B[46m",
651
+ bgWhite: "\x1B[47m"
652
+ };
653
+ function formatSuccess(message) {
654
+ return `${colors.green}\u2713${colors.reset} ${message}`;
655
+ }
656
+ function formatError(message) {
657
+ return `${colors.red}\u2717 Error:${colors.reset} ${message}`;
658
+ }
659
+ function formatWarning(message) {
660
+ return `${colors.yellow}\u26A0${colors.reset} ${message}`;
661
+ }
662
+ function formatInfo(message) {
663
+ return `${colors.cyan}\u2139${colors.reset} ${message}`;
664
+ }
665
+ function formatIntent(intent) {
666
+ const lines = [
667
+ "",
668
+ `${colors.bold}Intent: ${colors.cyan}${intent.name}${colors.reset}`,
669
+ colors.gray + "\u2500".repeat(40) + colors.reset
670
+ ];
671
+ if (intent.preconditions.length > 0) {
672
+ lines.push("");
673
+ lines.push(`${colors.bold}Preconditions:${colors.reset}`);
674
+ for (const pre of intent.preconditions) {
675
+ lines.push(` ${colors.magenta}pre:${colors.reset} ${highlightExpression(pre.expression)}`);
676
+ }
677
+ }
678
+ if (intent.postconditions.length > 0) {
679
+ lines.push("");
680
+ lines.push(`${colors.bold}Postconditions:${colors.reset}`);
681
+ for (const post of intent.postconditions) {
682
+ lines.push(` ${colors.magenta}post:${colors.reset} ${highlightExpression(post.expression)}`);
683
+ }
684
+ }
685
+ if (intent.invariants.length > 0) {
686
+ lines.push("");
687
+ lines.push(`${colors.bold}Invariants:${colors.reset}`);
688
+ for (const inv of intent.invariants) {
689
+ lines.push(` ${colors.magenta}invariant:${colors.reset} ${highlightExpression(inv.expression)}`);
690
+ }
691
+ }
692
+ if (intent.scenarios.length > 0) {
693
+ lines.push("");
694
+ lines.push(`${colors.bold}Scenarios:${colors.reset}`);
695
+ for (const scenario of intent.scenarios) {
696
+ lines.push(` ${colors.yellow}${scenario.name}${colors.reset}`);
697
+ }
698
+ }
699
+ lines.push("");
700
+ return lines.join("\n");
701
+ }
702
+ function formatCondition(condition, type) {
703
+ const prefix = {
704
+ pre: colors.magenta + "pre" + colors.reset,
705
+ post: colors.magenta + "post" + colors.reset,
706
+ invariant: colors.magenta + "invariant" + colors.reset
707
+ }[type];
708
+ return `${prefix}: ${highlightExpression(condition.expression)}`;
709
+ }
710
+ function highlightExpression(expr) {
711
+ 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}`);
712
+ }
713
+ function highlightISL(source) {
714
+ return source.replace(/\b(intent|behavior|entity|domain|type|enum)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(pre|post|invariant|scenario)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\b(input|output|success|errors?)\b/g, `${colors.blue}$1${colors.reset}`).replace(
715
+ /\b(String|Int|Boolean|UUID|Timestamp|Decimal|Duration|List|Map|Optional)\b/g,
716
+ `${colors.green}$1${colors.reset}`
717
+ ).replace(/\b(and|or|not|implies|forall|exists|in)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(true|false|null)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\b(\d+(?:\.\d+)?)\b/g, `${colors.cyan}$1${colors.reset}`).replace(/"([^"]*)"/g, `${colors.green}"$1"${colors.reset}`).replace(/(\/\/[^\n]*)/g, `${colors.gray}$1${colors.reset}`).replace(/(#[^\n]*)/g, `${colors.gray}$1${colors.reset}`);
718
+ }
719
+ function formatTable(headers, rows, options = {}) {
720
+ const useColors = options.colors !== false;
721
+ const widths = headers.map((h, i) => {
722
+ const cellWidths = [h.length, ...rows.map((r) => (r[i] ?? "").length)];
723
+ return Math.max(...cellWidths);
724
+ });
725
+ const headerRow = headers.map((h, i) => h.padEnd(widths[i])).join(" \u2502 ");
726
+ const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u253C\u2500");
727
+ const dataRows = rows.map(
728
+ (row) => row.map((cell, i) => (cell ?? "").padEnd(widths[i])).join(" \u2502 ")
729
+ );
730
+ const headerFormatted = useColors ? `${colors.bold}${headerRow}${colors.reset}` : headerRow;
731
+ const sepFormatted = useColors ? `${colors.gray}${separator}${colors.reset}` : separator;
732
+ return [headerFormatted, sepFormatted, ...dataRows].join("\n");
733
+ }
734
+ function formatParseError(source, message, line, column) {
735
+ const lines = source.split("\n");
736
+ const errorLine = lines[line - 1] || "";
737
+ const output = [
738
+ formatError(message),
739
+ "",
740
+ `${colors.gray}${String(line).padStart(4)} \u2502${colors.reset} ${errorLine}`,
741
+ `${colors.gray} \u2502${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`
742
+ ];
743
+ return output.join("\n");
744
+ }
745
+ function formatTypeError(message, expected, actual, context) {
746
+ const output = [
747
+ formatError(message),
748
+ "",
749
+ ` Expected: ${colors.green}${expected}${colors.reset}`,
750
+ ` Actual: ${colors.red}${actual}${colors.reset}`
751
+ ];
752
+ if (context) {
753
+ output.push("");
754
+ output.push(` Context: ${colors.gray}${context}${colors.reset}`);
755
+ }
756
+ return output.join("\n");
757
+ }
758
+ function formatValue(value, indent = 0) {
759
+ const pad = " ".repeat(indent);
760
+ if (value === null) return `${colors.gray}null${colors.reset}`;
761
+ if (value === void 0) return `${colors.gray}undefined${colors.reset}`;
762
+ if (typeof value === "string") {
763
+ return `${colors.green}"${value}"${colors.reset}`;
764
+ }
765
+ if (typeof value === "number") {
766
+ return `${colors.cyan}${value}${colors.reset}`;
767
+ }
768
+ if (typeof value === "boolean") {
769
+ return `${colors.magenta}${value}${colors.reset}`;
770
+ }
771
+ if (Array.isArray(value)) {
772
+ if (value.length === 0) return "[]";
773
+ const items = value.map((v) => formatValue(v, indent + 2));
774
+ return `[
775
+ ${pad} ${items.join(`,
776
+ ${pad} `)}
777
+ ${pad}]`;
778
+ }
779
+ if (typeof value === "object") {
780
+ const entries = Object.entries(value);
781
+ if (entries.length === 0) return "{}";
782
+ const items = entries.map(
783
+ ([k, v]) => `${colors.blue}${k}${colors.reset}: ${formatValue(v, indent + 2)}`
784
+ );
785
+ return `{
786
+ ${pad} ${items.join(`,
787
+ ${pad} `)}
788
+ ${pad}}`;
789
+ }
790
+ return String(value);
791
+ }
792
+ function stripColors(str) {
793
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
794
+ }
795
+ function wrapText(text, maxWidth) {
796
+ const words = text.split(" ");
797
+ const lines = [];
798
+ let currentLine = "";
799
+ for (const word of words) {
800
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
801
+ if (stripColors(testLine).length > maxWidth && currentLine) {
802
+ lines.push(currentLine);
803
+ currentLine = word;
804
+ } else {
805
+ currentLine = testLine;
806
+ }
807
+ }
808
+ if (currentLine) {
809
+ lines.push(currentLine);
810
+ }
811
+ return lines.join("\n");
812
+ }
813
+ function drawBox(lines, title) {
814
+ const maxLen = Math.max(...lines.map((l) => stripColors(l).length), title ? title.length + 4 : 0);
815
+ const top = title ? `\u2554\u2550 ${title} ${"\u2550".repeat(maxLen - title.length - 3)}\u2557` : `\u2554${"\u2550".repeat(maxLen + 2)}\u2557`;
816
+ const bottom = `\u255A${"\u2550".repeat(maxLen + 2)}\u255D`;
817
+ const paddedLines = lines.map((l) => {
818
+ const padding = maxLen - stripColors(l).length;
819
+ return `\u2551 ${l}${" ".repeat(padding)} \u2551`;
820
+ });
821
+ return [top, ...paddedLines, bottom].join("\n");
822
+ }
823
+
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
+ }
958
+ var metaCommands = [
959
+ // ─── .help ──────────────────────────────────────────────────────────────
960
+ {
961
+ name: "help",
962
+ aliases: ["h", "?"],
963
+ description: "Show commands",
964
+ usage: ".help [command]",
965
+ handler: (args) => {
966
+ if (args.length > 0) {
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) {
972
+ return {
973
+ output: [
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(", ")}` : ""
977
+ ].filter(Boolean).join("\n")
978
+ };
979
+ }
980
+ return { output: formatError(`Unknown command: ${cmdName}`) };
981
+ }
982
+ const lines = [
983
+ "",
984
+ `${colors.bold}REPL Commands${colors.reset}`,
985
+ "",
986
+ ...metaCommands.map(
987
+ (c) => ` ${colors.cyan}.${c.name.padEnd(12)}${colors.reset} ${c.description}`
988
+ ),
989
+ "",
990
+ `${colors.bold}ISL Input${colors.reset}`,
991
+ "",
992
+ ` Type ISL directly \u2014 multi-line supported (braces auto-detect):`,
993
+ "",
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
+ ` }`,
999
+ ` }`,
1000
+ "",
1001
+ `Type ${colors.cyan}.help <command>${colors.reset} for details.`,
1002
+ ""
1003
+ ];
1004
+ return { output: lines.join("\n") };
1005
+ }
1006
+ },
1007
+ // ─── .parse ─────────────────────────────────────────────────────────────
1008
+ {
1009
+ name: "parse",
1010
+ aliases: ["p", "ast"],
1011
+ description: "Parse ISL and show AST",
1012
+ usage: ".parse <isl>",
1013
+ handler: (args, session) => {
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
+ }
1045
+ }
1046
+ },
1047
+ // ─── .eval ──────────────────────────────────────────────────────────────
1048
+ {
1049
+ name: "eval",
1050
+ aliases: ["e"],
1051
+ description: "Evaluate expression against context",
1052
+ usage: ".eval <expression>",
1053
+ handler: (args, session) => {
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
+ };
1068
+ }
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
+ };
1077
+ }
1078
+ },
1079
+ // ─── .check ─────────────────────────────────────────────────────────────
1080
+ {
1081
+ name: "check",
1082
+ aliases: ["c"],
1083
+ description: "Type check the current session",
1084
+ usage: ".check [intent]",
1085
+ handler: (args, session) => {
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
+ };
1095
+ }
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)}`);
1102
+ }
1103
+ return { output: lines2.join("\n") };
1104
+ }
1105
+ const intents = session.getAllIntents();
1106
+ if (intents.length === 0) {
1107
+ return {
1108
+ output: formatWarning("No intents defined. Write ISL or use .load <file>")
1109
+ };
1110
+ }
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("");
1121
+ }
1122
+ return { output: lines.join("\n") };
1123
+ }
1124
+ },
1125
+ // ─── .gen ───────────────────────────────────────────────────────────────
1126
+ {
1127
+ name: "gen",
1128
+ aliases: ["generate", "g"],
1129
+ description: "Generate TypeScript from intent",
1130
+ usage: ".gen [intent]",
1131
+ handler: (args, session) => {
1132
+ const intents = args.length > 0 ? [session.getIntent(args[0])].filter(Boolean) : session.getAllIntents();
1133
+ if (intents.length === 0) {
1134
+ return {
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>")
1137
+ };
1138
+ }
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("");
1162
+ }
1163
+ return { output: lines.join("\n") };
1164
+ }
1165
+ },
1166
+ // ─── .load ──────────────────────────────────────────────────────────────
1167
+ {
1168
+ name: "load",
1169
+ aliases: ["l"],
1170
+ description: "Load an .isl file",
1171
+ usage: ".load <file.isl>",
1172
+ handler: (args, session) => {
1173
+ if (args.length === 0) {
1174
+ return { output: "Usage: .load <file.isl>" };
1175
+ }
1176
+ const filePath = args[0];
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
+ }
1181
+ try {
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 {
1209
+ }
1210
+ const intentRegex = /(?:intent|behavior)\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
1211
+ let match;
1212
+ let count = 0;
1213
+ while ((match = intentRegex.exec(content)) !== null) {
1214
+ const intent = session.parseIntent(match[0]);
1215
+ if (intent) {
1216
+ session.defineIntent(intent);
1217
+ count++;
1218
+ }
1219
+ }
1220
+ if (count === 0) {
1221
+ return { output: formatWarning("No intents/behaviors found in file") };
1222
+ }
1223
+ return {
1224
+ output: formatSuccess(`Loaded ${count} intent(s) from ${path.basename(filePath)}`)
1225
+ };
1226
+ } catch (error) {
1227
+ return {
1228
+ output: formatError(
1229
+ `Failed to load: ${error instanceof Error ? error.message : String(error)}`
1230
+ )
1231
+ };
1232
+ }
1233
+ }
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 ──────────────────────────────────────────────────────────────
1324
+ {
1325
+ name: "list",
1326
+ aliases: ["ls"],
1327
+ description: "List defined intents",
1328
+ usage: ".list",
1329
+ handler: (_args, session) => {
1330
+ const intents = session.getAllIntents();
1331
+ if (intents.length === 0) {
1332
+ return { output: "No intents defined." };
1333
+ }
1334
+ const lines = [""];
1335
+ for (const intent of intents) {
1336
+ const parts = [];
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`);
1340
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1341
+ lines.push(` ${colors.cyan}${intent.name}${colors.reset}${summary}`);
1342
+ }
1343
+ lines.push("");
1344
+ return { output: lines.join("\n") };
1345
+ }
1346
+ },
1347
+ // ─── .inspect ───────────────────────────────────────────────────────────
1348
+ {
1349
+ name: "inspect",
1350
+ aliases: ["i", "show"],
1351
+ description: "Show full details of an intent",
1352
+ usage: ".inspect [intent]",
1353
+ handler: (args, session) => {
1354
+ if (args.length === 0) {
1355
+ const summary = session.getSummary();
1356
+ const ctx = session.getEvalContext();
1357
+ const lines = [
1358
+ "",
1359
+ `${colors.bold}Session Summary${colors.reset}`,
1360
+ "",
1361
+ ` Intents: ${summary.intentCount}`,
1362
+ ` Variables: ${summary.variableCount}`,
1363
+ ` Context: ${Object.keys(ctx).length} keys`,
1364
+ ` History: ${summary.historyCount} entries`,
1365
+ ""
1366
+ ];
1367
+ return { output: lines.join("\n") };
1368
+ }
1369
+ const intentName = args[0];
1370
+ const intent = session.getIntent(intentName);
1371
+ if (!intent) {
1372
+ const available = session.getIntentNames().join(", ") || "(none)";
1373
+ return {
1374
+ output: formatError(`Unknown intent: ${intentName}
1375
+ Available: ${available}`)
1376
+ };
1377
+ }
1378
+ return { output: formatIntent(intent) };
1379
+ }
1380
+ },
1381
+ // ─── .exit ──────────────────────────────────────────────────────────────
1382
+ {
1383
+ name: "exit",
1384
+ aliases: ["quit", "q"],
1385
+ description: "Exit the REPL",
1386
+ usage: ".exit",
1387
+ handler: () => {
1388
+ return { exit: true };
1389
+ }
1390
+ }
1391
+ ];
1392
+ var islCommands = [];
1393
+ function levenshteinDistance(a, b) {
1394
+ const matrix = [];
1395
+ for (let i = 0; i <= b.length; i++) {
1396
+ matrix[i] = [i];
1397
+ }
1398
+ for (let j = 0; j <= a.length; j++) {
1399
+ matrix[0][j] = j;
1400
+ }
1401
+ for (let i = 1; i <= b.length; i++) {
1402
+ for (let j = 1; j <= a.length; j++) {
1403
+ const cost = a[j - 1] === b[i - 1] ? 0 : 1;
1404
+ matrix[i][j] = Math.min(
1405
+ matrix[i - 1][j] + 1,
1406
+ matrix[i][j - 1] + 1,
1407
+ matrix[i - 1][j - 1] + cost
1408
+ );
1409
+ }
1410
+ }
1411
+ return matrix[b.length][a.length];
1412
+ }
1413
+ function findSimilarCommand(input, _type) {
1414
+ const names = metaCommands.flatMap((c) => [c.name, ...c.aliases]);
1415
+ let bestMatch = null;
1416
+ let bestDistance = Infinity;
1417
+ for (const name of names) {
1418
+ const distance = levenshteinDistance(input.toLowerCase(), name.toLowerCase());
1419
+ if (distance < bestDistance && distance <= 2) {
1420
+ bestDistance = distance;
1421
+ bestMatch = name;
1422
+ }
1423
+ }
1424
+ return bestMatch;
1425
+ }
1426
+
1427
+ // src/completions.ts
1428
+ var KEYWORDS = [
1429
+ // Structure keywords
1430
+ { text: "domain", type: "keyword", description: "Define a domain" },
1431
+ { text: "entity", type: "keyword", description: "Define an entity" },
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" },
1436
+ { text: "pre", type: "keyword", description: "Precondition" },
1437
+ { text: "post", type: "keyword", description: "Postcondition" },
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
1454
+ { text: "true", type: "keyword", description: "Boolean true" },
1455
+ { text: "false", type: "keyword", description: "Boolean false" },
1456
+ { text: "null", type: "keyword", description: "Null value" },
1457
+ { text: "and", type: "keyword", description: "Logical AND" },
1458
+ { text: "or", type: "keyword", description: "Logical OR" },
1459
+ { text: "not", type: "keyword", description: "Logical NOT" },
1460
+ { text: "implies", type: "keyword", description: "Logical implication" },
1461
+ { text: "forall", type: "keyword", description: "Universal quantifier" },
1462
+ { text: "exists", type: "keyword", description: "Existential quantifier" },
1463
+ { text: "in", type: "keyword", description: "Membership test" },
1464
+ { text: "old", type: "keyword", description: "Pre-state value (old(x))" }
1465
+ ];
1466
+ var META_COMMANDS = metaCommands.map((cmd) => ({
1467
+ text: `.${cmd.name}`,
1468
+ type: "command",
1469
+ description: cmd.description
1470
+ }));
1471
+ var ISL_COMMANDS = [];
1472
+ var COMMANDS = [...META_COMMANDS];
1473
+ var GEN_TARGETS = [
1474
+ { text: "typescript", type: "keyword", description: "Generate TypeScript contract" },
1475
+ { text: "rust", type: "keyword", description: "Generate Rust contract" },
1476
+ { text: "go", type: "keyword", description: "Generate Go contract" },
1477
+ { text: "openapi", type: "keyword", description: "Generate OpenAPI schema" }
1478
+ ];
1479
+ var CompletionProvider = class {
1480
+ constructor(session) {
1481
+ this.session = session;
1482
+ }
1483
+ /**
1484
+ * Update the session reference
1485
+ */
1486
+ setSession(session) {
1487
+ this.session = session;
1488
+ }
1489
+ /**
1490
+ * Get completions for a line
1491
+ */
1492
+ complete(line) {
1493
+ const trimmed = line.trimStart();
1494
+ if (trimmed.startsWith(".")) {
1495
+ return this.completeMetaCommand(trimmed);
1496
+ }
1497
+ if (trimmed.startsWith(":")) {
1498
+ return this.completeMetaCommand("." + trimmed.slice(1));
1499
+ }
1500
+ return this.completeExpression(trimmed);
1501
+ }
1502
+ /**
1503
+ * Complete meta commands
1504
+ */
1505
+ completeMetaCommand(line) {
1506
+ const parts = line.slice(1).split(/\s+/);
1507
+ const cmdPart = parts[0] || "";
1508
+ if (parts.length === 1) {
1509
+ const matches = META_COMMANDS.filter(
1510
+ (c) => c.text.toLowerCase().startsWith(`.${cmdPart.toLowerCase()}`)
1511
+ );
1512
+ return [matches.length > 0 ? matches : META_COMMANDS, "." + cmdPart];
1513
+ }
1514
+ return [[], line];
1515
+ }
1516
+ /**
1517
+ * Complete ISL commands
1518
+ */
1519
+ completeISLCommand(line) {
1520
+ const parts = line.slice(1).split(/\s+/);
1521
+ const cmdPart = parts[0] || "";
1522
+ const args = parts.slice(1);
1523
+ if (parts.length === 1) {
1524
+ const matches = ISL_COMMANDS.filter(
1525
+ (c) => c.text.toLowerCase().startsWith(`:${cmdPart.toLowerCase()}`)
1526
+ );
1527
+ return [matches.length > 0 ? matches : ISL_COMMANDS, ":" + cmdPart];
1528
+ }
1529
+ const cmd = cmdPart.toLowerCase();
1530
+ switch (cmd) {
1531
+ case "gen":
1532
+ case "generate":
1533
+ case "g":
1534
+ return this.completeGenCommand(args);
1535
+ case "check":
1536
+ case "c":
1537
+ case "inspect":
1538
+ case "i":
1539
+ case "show":
1540
+ return this.completeIntentName(args[0] || "");
1541
+ case "load":
1542
+ case "l":
1543
+ case "export":
1544
+ case "save":
1545
+ return this.completeFilePath(args[0] || "");
1546
+ default:
1547
+ return [[], line];
1548
+ }
1549
+ }
1550
+ /**
1551
+ * Complete :gen command arguments
1552
+ */
1553
+ completeGenCommand(args) {
1554
+ if (args.length <= 1) {
1555
+ const partial = args[0] || "";
1556
+ const matches = GEN_TARGETS.filter(
1557
+ (t) => t.text.toLowerCase().startsWith(partial.toLowerCase())
1558
+ );
1559
+ return [matches.length > 0 ? matches : GEN_TARGETS, partial];
1560
+ }
1561
+ return this.completeIntentName(args[1] || "");
1562
+ }
1563
+ /**
1564
+ * Complete intent names
1565
+ */
1566
+ completeIntentName(partial) {
1567
+ const intents = this.session.getAllIntents();
1568
+ const items = intents.map((intent) => ({
1569
+ text: intent.name,
1570
+ type: "intent",
1571
+ description: `${intent.preconditions.length} pre, ${intent.postconditions.length} post`
1572
+ }));
1573
+ const matches = items.filter(
1574
+ (i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
1575
+ );
1576
+ return [matches.length > 0 ? matches : items, partial];
1577
+ }
1578
+ /**
1579
+ * Complete file paths
1580
+ */
1581
+ completeFilePath(partial) {
1582
+ try {
1583
+ const dir = path.dirname(partial) || ".";
1584
+ const base = path.basename(partial);
1585
+ const resolvedDir = path.resolve(this.session.getConfig().cwd || process.cwd(), dir);
1586
+ if (!fs2.existsSync(resolvedDir)) {
1587
+ return [[], partial];
1588
+ }
1589
+ const entries = fs2.readdirSync(resolvedDir, { withFileTypes: true });
1590
+ const items = entries.filter((e) => {
1591
+ const name = e.name.toLowerCase();
1592
+ return name.startsWith(base.toLowerCase()) && (e.isDirectory() || name.endsWith(".isl"));
1593
+ }).map((e) => ({
1594
+ text: path.join(dir, e.name + (e.isDirectory() ? "/" : "")),
1595
+ type: "file",
1596
+ description: e.isDirectory() ? "Directory" : "ISL file"
1597
+ }));
1598
+ return [items, partial];
1599
+ } catch {
1600
+ return [[], partial];
1601
+ }
1602
+ }
1603
+ /**
1604
+ * Complete expressions
1605
+ */
1606
+ completeExpression(line) {
1607
+ const items = [...KEYWORDS];
1608
+ for (const intent of this.session.getAllIntents()) {
1609
+ items.push({
1610
+ text: intent.name,
1611
+ type: "intent",
1612
+ description: "Defined intent"
1613
+ });
1614
+ }
1615
+ for (const [name] of this.session.getAllVariables()) {
1616
+ items.push({
1617
+ text: name,
1618
+ type: "variable",
1619
+ description: "Variable"
1620
+ });
1621
+ }
1622
+ items.push({
1623
+ text: "_",
1624
+ type: "variable",
1625
+ description: "Last result"
1626
+ });
1627
+ const match = line.match(/[\w.]+$/);
1628
+ const partial = match ? match[0] : "";
1629
+ const matches = items.filter(
1630
+ (i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
1631
+ );
1632
+ return [matches.length > 0 ? matches : items, partial];
1633
+ }
1634
+ /**
1635
+ * Get all available completions (for help)
1636
+ */
1637
+ getAllCompletions() {
1638
+ return {
1639
+ metaCommands: META_COMMANDS,
1640
+ islCommands: ISL_COMMANDS,
1641
+ keywords: KEYWORDS,
1642
+ intents: this.session.getAllIntents().map((i) => ({
1643
+ text: i.name,
1644
+ type: "intent",
1645
+ description: `${i.preconditions.length} pre, ${i.postconditions.length} post`
1646
+ }))
1647
+ };
1648
+ }
1649
+ };
1650
+ function createCompleter(provider) {
1651
+ return (line) => {
1652
+ const [items, partial] = provider.complete(line);
1653
+ const completions = items.map((i) => i.text);
1654
+ return [completions, partial];
1655
+ };
1656
+ }
1657
+
1658
+ // src/repl.ts
1659
+ var VERSION = "0.1.0";
1660
+ var PROMPT = `${colors.cyan}isl>${colors.reset} `;
1661
+ var CONTINUATION_PROMPT = `${colors.cyan}...>${colors.reset} `;
1662
+ var ISLREPL = class {
1663
+ session;
1664
+ history;
1665
+ completionProvider;
1666
+ rl = null;
1667
+ buffer = [];
1668
+ braceCount = 0;
1669
+ options;
1670
+ running = false;
1671
+ constructor(options = {}) {
1672
+ this.options = {
1673
+ colors: options.colors !== false,
1674
+ verbose: options.verbose ?? false,
1675
+ historyFile: options.historyFile,
1676
+ load: options.load,
1677
+ context: options.context,
1678
+ parseOnly: options.parseOnly ?? false
1679
+ };
1680
+ this.session = new Session({ colors: this.options.colors });
1681
+ this.history = new History({
1682
+ historyFile: this.options.historyFile
1683
+ });
1684
+ this.completionProvider = new CompletionProvider(this.session);
1685
+ }
1686
+ /**
1687
+ * Start the REPL
1688
+ */
1689
+ start() {
1690
+ if (this.running) return;
1691
+ this.running = true;
1692
+ this.applyStartupOptions();
1693
+ if (this.options.parseOnly || !process.stdin.isTTY) {
1694
+ this.runPipeMode();
1695
+ return;
1696
+ }
1697
+ this.history.load();
1698
+ this.rl = readline.createInterface({
1699
+ input: process.stdin,
1700
+ output: process.stdout,
1701
+ prompt: PROMPT,
1702
+ completer: createCompleter(this.completionProvider),
1703
+ terminal: true
1704
+ });
1705
+ this.printBanner();
1706
+ this.rl.prompt();
1707
+ this.rl.on("line", (line) => {
1708
+ this.handleLine(line);
1709
+ if (this.rl && this.running) {
1710
+ this.rl.setPrompt(this.braceCount > 0 ? CONTINUATION_PROMPT : PROMPT);
1711
+ this.rl.prompt();
1712
+ }
1713
+ });
1714
+ this.rl.on("close", () => {
1715
+ this.exit();
1716
+ });
1717
+ this.rl.on("SIGINT", () => {
1718
+ if (this.buffer.length > 0) {
1719
+ this.buffer = [];
1720
+ this.braceCount = 0;
1721
+ process.stdout.write("\n" + formatWarning("Input cancelled") + "\n");
1722
+ this.rl.setPrompt(PROMPT);
1723
+ this.rl.prompt();
1724
+ } else {
1725
+ process.stdout.write("\n" + formatWarning("Use .exit to quit") + "\n");
1726
+ this.rl.prompt();
1727
+ }
1728
+ });
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
+ }
1786
+ /**
1787
+ * Print the welcome banner
1788
+ */
1789
+ printBanner() {
1790
+ const banner = `
1791
+ ${colors.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1792
+ \u2551 \u2551
1793
+ \u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2551
1794
+ \u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2551
1795
+ \u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2551
1796
+ \u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2551
1797
+ \u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2551
1798
+ \u2551 \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u2551
1799
+ \u2551 \u2551
1800
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${colors.reset}
1801
+
1802
+ ${colors.bold}ISL v${VERSION}${colors.reset} \u2014 Intent Specification Language
1803
+ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${colors.reset} to quit
1804
+ `;
1805
+ process.stdout.write(banner);
1806
+ }
1807
+ /**
1808
+ * Handle a line of input
1809
+ */
1810
+ handleLine(line) {
1811
+ const trimmed = line.trim();
1812
+ if (!trimmed && this.buffer.length === 0) {
1813
+ return;
1814
+ }
1815
+ if (trimmed.startsWith(".") && this.buffer.length === 0) {
1816
+ this.handleDotCommand(trimmed);
1817
+ return;
1818
+ }
1819
+ if (trimmed.startsWith(":") && this.buffer.length === 0) {
1820
+ this.handleDotCommand("." + trimmed.slice(1));
1821
+ return;
1822
+ }
1823
+ this.braceCount += (line.match(/\{/g) || []).length;
1824
+ this.braceCount -= (line.match(/\}/g) || []).length;
1825
+ this.buffer.push(line);
1826
+ if (this.braceCount <= 0) {
1827
+ const code = this.buffer.join("\n");
1828
+ this.buffer = [];
1829
+ this.braceCount = 0;
1830
+ this.history.add(code);
1831
+ this.session.addToHistory(code);
1832
+ this.evaluate(code);
1833
+ }
1834
+ }
1835
+ /**
1836
+ * Handle a dot command (. prefix)
1837
+ */
1838
+ handleDotCommand(input) {
1839
+ const parts = input.slice(1).split(/\s+/);
1840
+ const cmdName = parts[0]?.toLowerCase() || "";
1841
+ const args = parts.slice(1);
1842
+ const rawArgs = input.slice(1 + (cmdName.length || 0)).trim();
1843
+ const command = metaCommands.find(
1844
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
1845
+ );
1846
+ if (command) {
1847
+ this.history.add(input);
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);
1851
+ if (result.output) {
1852
+ process.stdout.write(result.output + "\n");
1853
+ }
1854
+ if (result.exit) {
1855
+ this.exit();
1856
+ }
1857
+ } else {
1858
+ const suggestion = findSimilarCommand(cmdName);
1859
+ if (suggestion) {
1860
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1861
+ process.stdout.write(formatWarning(`Did you mean: .${suggestion}?`) + "\n");
1862
+ } else {
1863
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1864
+ process.stdout.write(`Type ${colors.cyan}.help${colors.reset} for available commands
1865
+ `);
1866
+ }
1867
+ }
1868
+ }
1869
+ /**
1870
+ * Evaluate ISL code (multi-line input or bare expressions)
1871
+ */
1872
+ evaluate(code) {
1873
+ try {
1874
+ const trimmed = code.trim();
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 ")) {
1950
+ this.evaluateIntent(trimmed);
1951
+ return;
1952
+ }
1953
+ if (trimmed.startsWith("domain ")) {
1954
+ process.stdout.write(formatSuccess("Parsed domain block") + "\n");
1955
+ return;
1956
+ }
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
+ );
1964
+ } catch (error) {
1965
+ this.printError(error);
1966
+ }
1967
+ }
1968
+ /**
1969
+ * Evaluate an intent definition
1970
+ */
1971
+ evaluateIntent(code) {
1972
+ const intent = this.session.parseIntent(code);
1973
+ if (intent) {
1974
+ this.session.defineIntent(intent);
1975
+ const preCount = intent.preconditions.length;
1976
+ const postCount = intent.postconditions.length;
1977
+ const invCount = intent.invariants.length;
1978
+ const parts = [];
1979
+ if (preCount > 0) parts.push(`${preCount} pre`);
1980
+ if (postCount > 0) parts.push(`${postCount} post`);
1981
+ if (invCount > 0) parts.push(`${invCount} invariant`);
1982
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1983
+ process.stdout.write(
1984
+ formatSuccess(`Intent '${intent.name}' defined${summary}`) + "\n"
1985
+ );
1986
+ } else {
1987
+ const behaviorMatch = code.match(/^behavior\s+(\w+)\s*\{([\s\S]*)\}$/);
1988
+ if (behaviorMatch) {
1989
+ const name = behaviorMatch[1];
1990
+ const body = behaviorMatch[2];
1991
+ const newIntent = {
1992
+ name,
1993
+ preconditions: [],
1994
+ postconditions: [],
1995
+ invariants: [],
1996
+ scenarios: [],
1997
+ rawSource: code
1998
+ };
1999
+ const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
2000
+ if (preSection) {
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 });
2004
+ }
2005
+ }
2006
+ const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
2007
+ if (postSection) {
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 });
2011
+ }
2012
+ }
2013
+ this.session.defineIntent(newIntent);
2014
+ const parts = [];
2015
+ if (newIntent.preconditions.length > 0) parts.push(`${newIntent.preconditions.length} pre`);
2016
+ if (newIntent.postconditions.length > 0) parts.push(`${newIntent.postconditions.length} post`);
2017
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
2018
+ process.stdout.write(
2019
+ formatSuccess(`Intent '${name}' defined${summary}`) + "\n"
2020
+ );
2021
+ } else {
2022
+ process.stdout.write(formatError("Failed to parse intent definition") + "\n");
2023
+ }
2024
+ }
2025
+ }
2026
+ /**
2027
+ * Print an error
2028
+ */
2029
+ printError(error) {
2030
+ if (error instanceof Error) {
2031
+ process.stdout.write(formatError(error.message) + "\n");
2032
+ if (this.options.verbose && error.stack) {
2033
+ process.stdout.write(colors.gray + error.stack + colors.reset + "\n");
2034
+ }
2035
+ } else {
2036
+ process.stdout.write(formatError(String(error)) + "\n");
2037
+ }
2038
+ }
2039
+ /**
2040
+ * Exit the REPL
2041
+ */
2042
+ exit() {
2043
+ this.running = false;
2044
+ this.history.save();
2045
+ process.stdout.write(`
2046
+ ${colors.yellow}Goodbye!${colors.reset}
2047
+ `);
2048
+ if (this.rl) {
2049
+ this.rl.close();
2050
+ }
2051
+ process.exit(0);
2052
+ }
2053
+ /**
2054
+ * Get the session
2055
+ */
2056
+ getSession() {
2057
+ return this.session;
2058
+ }
2059
+ /**
2060
+ * Get history
2061
+ */
2062
+ getHistory() {
2063
+ return this.history;
2064
+ }
2065
+ /**
2066
+ * Execute a single command and return result (for testing)
2067
+ */
2068
+ async executeOnce(input) {
2069
+ const trimmed = input.trim();
2070
+ if (trimmed.startsWith(".") || trimmed.startsWith(":")) {
2071
+ const normalized = trimmed.startsWith(":") ? "." + trimmed.slice(1) : trimmed;
2072
+ const parts = normalized.slice(1).split(/\s+/);
2073
+ const cmdName = parts[0]?.toLowerCase() || "";
2074
+ const rawArgs = normalized.slice(1 + (cmdName.length || 0)).trim();
2075
+ const command = metaCommands.find(
2076
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
2077
+ );
2078
+ if (command) {
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);
2082
+ return { success: true, output: result.output };
2083
+ }
2084
+ return { success: false, error: `Unknown command: ${cmdName}` };
2085
+ }
2086
+ if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
2087
+ const intent = this.session.parseIntent(trimmed);
2088
+ if (intent) {
2089
+ this.session.defineIntent(intent);
2090
+ return { success: true, output: `Intent '${intent.name}' defined` };
2091
+ }
2092
+ return { success: false, error: "Failed to parse intent" };
2093
+ }
2094
+ return { success: false, error: "Unknown input" };
2095
+ }
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
+ }
2120
+ function startREPL(options) {
2121
+ const repl = new ISLREPL(options);
2122
+ repl.start();
2123
+ return repl;
2124
+ }
2125
+
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 };
2227
+ //# sourceMappingURL=index.js.map
2228
+ //# sourceMappingURL=index.js.map