@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.cjs ADDED
@@ -0,0 +1,2284 @@
1
+ 'use strict';
2
+
3
+ var readline = require('readline');
4
+ var fs2 = require('fs');
5
+ var path = require('path');
6
+ var os = require('os');
7
+
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
27
+ var fs2__namespace = /*#__PURE__*/_interopNamespace(fs2);
28
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
29
+ var os__namespace = /*#__PURE__*/_interopNamespace(os);
30
+
31
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
32
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
33
+ }) : x)(function(x) {
34
+ if (typeof require !== "undefined") return require.apply(this, arguments);
35
+ throw Error('Dynamic require of "' + x + '" is not supported');
36
+ });
37
+ var Session = class {
38
+ /** Defined intents in this session */
39
+ intents = /* @__PURE__ */ new Map();
40
+ /** Variables set during the session */
41
+ variables = /* @__PURE__ */ new Map();
42
+ /** Command history */
43
+ history = [];
44
+ /** Last evaluation result */
45
+ lastResult = void 0;
46
+ /** Loaded files */
47
+ loadedFiles = /* @__PURE__ */ new Set();
48
+ /** Session configuration */
49
+ config;
50
+ /** Evaluation context (set by .context command) */
51
+ evalContext = {};
52
+ /** Pre-state context for old() expressions */
53
+ preContext = null;
54
+ /** Loaded domain AST (from real parser) */
55
+ domainAST = null;
56
+ constructor(config = {}) {
57
+ this.config = {
58
+ colors: true,
59
+ verbose: false,
60
+ cwd: process.cwd(),
61
+ ...config
62
+ };
63
+ }
64
+ // ─────────────────────────────────────────────────────────────────────────
65
+ // Intent Management
66
+ // ─────────────────────────────────────────────────────────────────────────
67
+ /**
68
+ * Define a new intent
69
+ */
70
+ defineIntent(intent) {
71
+ this.intents.set(intent.name, intent);
72
+ }
73
+ /**
74
+ * Get an intent by name
75
+ */
76
+ getIntent(name) {
77
+ return this.intents.get(name);
78
+ }
79
+ /**
80
+ * Get all defined intents
81
+ */
82
+ getAllIntents() {
83
+ return Array.from(this.intents.values());
84
+ }
85
+ /**
86
+ * Check if an intent exists
87
+ */
88
+ hasIntent(name) {
89
+ return this.intents.has(name);
90
+ }
91
+ /**
92
+ * Remove an intent
93
+ */
94
+ removeIntent(name) {
95
+ return this.intents.delete(name);
96
+ }
97
+ /**
98
+ * Get intent names for completion
99
+ */
100
+ getIntentNames() {
101
+ return Array.from(this.intents.keys());
102
+ }
103
+ /**
104
+ * Parse an intent definition from source code
105
+ */
106
+ parseIntent(source) {
107
+ const trimmed = source.trim();
108
+ const match = trimmed.match(/^intent\s+(\w+)\s*\{([\s\S]*)\}$/);
109
+ if (!match) {
110
+ return null;
111
+ }
112
+ const name = match[1];
113
+ const body = match[2];
114
+ const intent = {
115
+ name,
116
+ preconditions: [],
117
+ postconditions: [],
118
+ invariants: [],
119
+ scenarios: [],
120
+ rawSource: source
121
+ };
122
+ const preMatch = body.match(/pre(?:conditions?)?\s*:\s*([^\n]+)/g);
123
+ if (preMatch) {
124
+ for (const pre of preMatch) {
125
+ const expr = pre.replace(/pre(?:conditions?)?\s*:\s*/, "").trim();
126
+ if (expr) {
127
+ intent.preconditions.push({ expression: expr });
128
+ }
129
+ }
130
+ }
131
+ const postMatch = body.match(/post(?:conditions?)?\s*:\s*([^\n]+)/g);
132
+ if (postMatch) {
133
+ for (const post of postMatch) {
134
+ const expr = post.replace(/post(?:conditions?)?\s*:\s*/, "").trim();
135
+ if (expr) {
136
+ intent.postconditions.push({ expression: expr });
137
+ }
138
+ }
139
+ }
140
+ const invMatch = body.match(/invariants?\s*:\s*([^\n]+)/g);
141
+ if (invMatch) {
142
+ for (const inv of invMatch) {
143
+ const expr = inv.replace(/invariants?\s*:\s*/, "").trim();
144
+ if (expr) {
145
+ intent.invariants.push({ expression: expr });
146
+ }
147
+ }
148
+ }
149
+ return intent;
150
+ }
151
+ // ─────────────────────────────────────────────────────────────────────────
152
+ // Variable Management
153
+ // ─────────────────────────────────────────────────────────────────────────
154
+ /**
155
+ * Set a variable
156
+ */
157
+ setVariable(name, value) {
158
+ this.variables.set(name, value);
159
+ }
160
+ /**
161
+ * Get a variable
162
+ */
163
+ getVariable(name) {
164
+ return this.variables.get(name);
165
+ }
166
+ /**
167
+ * Get all variables
168
+ */
169
+ getAllVariables() {
170
+ return new Map(this.variables);
171
+ }
172
+ /**
173
+ * Check if a variable exists
174
+ */
175
+ hasVariable(name) {
176
+ return this.variables.has(name);
177
+ }
178
+ /**
179
+ * Set the last result (accessible as _)
180
+ */
181
+ setLastResult(value) {
182
+ this.lastResult = value;
183
+ this.variables.set("_", value);
184
+ }
185
+ /**
186
+ * Get the last result
187
+ */
188
+ getLastResult() {
189
+ return this.lastResult;
190
+ }
191
+ // ─────────────────────────────────────────────────────────────────────────
192
+ // History Management
193
+ // ─────────────────────────────────────────────────────────────────────────
194
+ /**
195
+ * Add to history
196
+ */
197
+ addToHistory(entry) {
198
+ const trimmed = entry.trim();
199
+ if (trimmed && (this.history.length === 0 || this.history[this.history.length - 1] !== trimmed)) {
200
+ this.history.push(trimmed);
201
+ }
202
+ }
203
+ /**
204
+ * Get history
205
+ */
206
+ getHistory(count) {
207
+ if (count) {
208
+ return this.history.slice(-count);
209
+ }
210
+ return [...this.history];
211
+ }
212
+ // ─────────────────────────────────────────────────────────────────────────
213
+ // File Loading
214
+ // ─────────────────────────────────────────────────────────────────────────
215
+ /**
216
+ * Load intents from an ISL file
217
+ */
218
+ async loadFile(filePath) {
219
+ const resolvedPath = path__namespace.isAbsolute(filePath) ? filePath : path__namespace.resolve(this.config.cwd, filePath);
220
+ if (!fs2__namespace.existsSync(resolvedPath)) {
221
+ return { intents: [], errors: [`File not found: ${resolvedPath}`] };
222
+ }
223
+ try {
224
+ const content = fs2__namespace.readFileSync(resolvedPath, "utf-8");
225
+ const loadedIntents = [];
226
+ const errors = [];
227
+ const intentRegex = /intent\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
228
+ let match;
229
+ while ((match = intentRegex.exec(content)) !== null) {
230
+ const intent = this.parseIntent(match[0]);
231
+ if (intent) {
232
+ this.defineIntent(intent);
233
+ loadedIntents.push(intent);
234
+ } else {
235
+ errors.push(`Failed to parse intent starting at position ${match.index}`);
236
+ }
237
+ }
238
+ const behaviorRegex = /behavior\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
239
+ while ((match = behaviorRegex.exec(content)) !== null) {
240
+ const intent = this.parseBehaviorAsIntent(match[1], match[2]);
241
+ if (intent) {
242
+ this.defineIntent(intent);
243
+ loadedIntents.push(intent);
244
+ }
245
+ }
246
+ this.loadedFiles.add(resolvedPath);
247
+ return { intents: loadedIntents, errors };
248
+ } catch (error) {
249
+ return {
250
+ intents: [],
251
+ errors: [`Failed to load file: ${error instanceof Error ? error.message : String(error)}`]
252
+ };
253
+ }
254
+ }
255
+ /**
256
+ * Parse a behavior block as an intent
257
+ */
258
+ parseBehaviorAsIntent(name, body) {
259
+ const intent = {
260
+ name,
261
+ preconditions: [],
262
+ postconditions: [],
263
+ invariants: [],
264
+ scenarios: [],
265
+ rawSource: `behavior ${name} {${body}}`
266
+ };
267
+ const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
268
+ if (preSection) {
269
+ const conditions = preSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
270
+ for (const cond of conditions) {
271
+ const expr = cond.replace(/^-\s*/, "").trim();
272
+ if (expr) {
273
+ intent.preconditions.push({ expression: expr });
274
+ }
275
+ }
276
+ }
277
+ const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
278
+ if (postSection) {
279
+ const conditions = postSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
280
+ for (const cond of conditions) {
281
+ const expr = cond.replace(/^-\s*/, "").trim();
282
+ if (expr) {
283
+ intent.postconditions.push({ expression: expr });
284
+ }
285
+ }
286
+ }
287
+ const invSection = body.match(/invariants?\s*\{([^}]*)\}/s);
288
+ if (invSection) {
289
+ const conditions = invSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
290
+ for (const cond of conditions) {
291
+ const expr = cond.replace(/^-\s*/, "").trim();
292
+ if (expr) {
293
+ intent.invariants.push({ expression: expr });
294
+ }
295
+ }
296
+ }
297
+ return intent;
298
+ }
299
+ /**
300
+ * Export session intents to a file
301
+ */
302
+ async exportToFile(filePath) {
303
+ const resolvedPath = path__namespace.isAbsolute(filePath) ? filePath : path__namespace.resolve(this.config.cwd, filePath);
304
+ try {
305
+ const lines = [];
306
+ lines.push("// Exported ISL intents");
307
+ lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
308
+ lines.push("");
309
+ for (const intent of this.intents.values()) {
310
+ lines.push(`intent ${intent.name} {`);
311
+ for (const pre of intent.preconditions) {
312
+ lines.push(` pre: ${pre.expression}`);
313
+ }
314
+ for (const post of intent.postconditions) {
315
+ lines.push(` post: ${post.expression}`);
316
+ }
317
+ for (const inv of intent.invariants) {
318
+ lines.push(` invariant: ${inv.expression}`);
319
+ }
320
+ lines.push("}");
321
+ lines.push("");
322
+ }
323
+ fs2__namespace.writeFileSync(resolvedPath, lines.join("\n"));
324
+ return { success: true };
325
+ } catch (error) {
326
+ return {
327
+ success: false,
328
+ error: `Failed to export: ${error instanceof Error ? error.message : String(error)}`
329
+ };
330
+ }
331
+ }
332
+ // ─────────────────────────────────────────────────────────────────────────
333
+ // Evaluation Context Management
334
+ // ─────────────────────────────────────────────────────────────────────────
335
+ /**
336
+ * Set evaluation context from JSON string
337
+ */
338
+ setEvalContext(json) {
339
+ try {
340
+ const parsed = JSON.parse(json);
341
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
342
+ return { success: false, count: 0, error: "Context must be a JSON object" };
343
+ }
344
+ this.evalContext = parsed;
345
+ for (const [key, value] of Object.entries(this.evalContext)) {
346
+ this.variables.set(key, value);
347
+ }
348
+ return { success: true, count: Object.keys(parsed).length };
349
+ } catch (e) {
350
+ return { success: false, count: 0, error: e instanceof Error ? e.message : String(e) };
351
+ }
352
+ }
353
+ /**
354
+ * Set pre-state context for old() expressions
355
+ */
356
+ setPreContext(json) {
357
+ try {
358
+ const parsed = JSON.parse(json);
359
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
360
+ return { success: false, error: "Pre-context must be a JSON object" };
361
+ }
362
+ this.preContext = parsed;
363
+ return { success: true };
364
+ } catch (e) {
365
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
366
+ }
367
+ }
368
+ /**
369
+ * Get evaluation context
370
+ */
371
+ getEvalContext() {
372
+ return { ...this.evalContext };
373
+ }
374
+ /**
375
+ * Get pre-state context
376
+ */
377
+ getPreContext() {
378
+ return this.preContext ? { ...this.preContext } : null;
379
+ }
380
+ /**
381
+ * Resolve a dot-path against the evaluation context
382
+ */
383
+ resolveValue(dotPath) {
384
+ const parts = dotPath.split(".");
385
+ let current = this.evalContext;
386
+ for (const part of parts) {
387
+ if (current === null || current === void 0 || typeof current !== "object") {
388
+ return { found: false, value: void 0 };
389
+ }
390
+ current = current[part];
391
+ }
392
+ return { found: current !== void 0, value: current };
393
+ }
394
+ /**
395
+ * Resolve a dot-path against the pre-state context
396
+ */
397
+ resolvePreValue(dotPath) {
398
+ if (!this.preContext) {
399
+ return { found: false, value: void 0 };
400
+ }
401
+ const parts = dotPath.split(".");
402
+ let current = this.preContext;
403
+ for (const part of parts) {
404
+ if (current === null || current === void 0 || typeof current !== "object") {
405
+ return { found: false, value: void 0 };
406
+ }
407
+ current = current[part];
408
+ }
409
+ return { found: current !== void 0, value: current };
410
+ }
411
+ /**
412
+ * Set domain AST (from real parser)
413
+ */
414
+ setDomainAST(ast) {
415
+ this.domainAST = ast;
416
+ }
417
+ /**
418
+ * Get domain AST
419
+ */
420
+ getDomainAST() {
421
+ return this.domainAST;
422
+ }
423
+ // ─────────────────────────────────────────────────────────────────────────
424
+ // State Management
425
+ // ─────────────────────────────────────────────────────────────────────────
426
+ /**
427
+ * Clear all session state
428
+ */
429
+ clear() {
430
+ this.intents.clear();
431
+ this.variables.clear();
432
+ this.lastResult = void 0;
433
+ this.loadedFiles.clear();
434
+ this.evalContext = {};
435
+ this.preContext = null;
436
+ this.domainAST = null;
437
+ }
438
+ /**
439
+ * Get session summary
440
+ */
441
+ getSummary() {
442
+ return {
443
+ intentCount: this.intents.size,
444
+ variableCount: this.variables.size,
445
+ loadedFileCount: this.loadedFiles.size,
446
+ historyCount: this.history.length
447
+ };
448
+ }
449
+ /**
450
+ * Get loaded files
451
+ */
452
+ getLoadedFiles() {
453
+ return Array.from(this.loadedFiles);
454
+ }
455
+ /**
456
+ * Get config
457
+ */
458
+ getConfig() {
459
+ return { ...this.config };
460
+ }
461
+ /**
462
+ * Update config
463
+ */
464
+ setConfig(config) {
465
+ this.config = { ...this.config, ...config };
466
+ }
467
+ };
468
+ function createSession(config) {
469
+ return new Session(config);
470
+ }
471
+ var History = class {
472
+ entries = [];
473
+ position = -1;
474
+ maxSize;
475
+ historyFile;
476
+ unsavedCount = 0;
477
+ autoSaveThreshold = 10;
478
+ constructor(options = {}) {
479
+ this.maxSize = options.maxSize ?? 1e3;
480
+ this.historyFile = options.historyFile ?? this.getDefaultHistoryFile();
481
+ this.autoSaveThreshold = options.autoSaveThreshold ?? 10;
482
+ }
483
+ /**
484
+ * Get default history file path
485
+ */
486
+ getDefaultHistoryFile() {
487
+ const homeDir = os__namespace.homedir();
488
+ return path__namespace.join(homeDir, ".isl_repl_history");
489
+ }
490
+ /**
491
+ * Load history from file
492
+ */
493
+ load() {
494
+ try {
495
+ if (fs2__namespace.existsSync(this.historyFile)) {
496
+ const content = fs2__namespace.readFileSync(this.historyFile, "utf-8");
497
+ this.entries = content.split("\n").filter((line) => line.trim() !== "").slice(-this.maxSize);
498
+ this.position = this.entries.length;
499
+ }
500
+ } catch {
501
+ }
502
+ }
503
+ /**
504
+ * Save history to file
505
+ */
506
+ save() {
507
+ try {
508
+ const dir = path__namespace.dirname(this.historyFile);
509
+ if (!fs2__namespace.existsSync(dir)) {
510
+ fs2__namespace.mkdirSync(dir, { recursive: true });
511
+ }
512
+ fs2__namespace.writeFileSync(this.historyFile, this.entries.join("\n") + "\n");
513
+ this.unsavedCount = 0;
514
+ } catch {
515
+ }
516
+ }
517
+ /**
518
+ * Add entry to history
519
+ */
520
+ add(entry) {
521
+ const trimmed = entry.trim();
522
+ if (trimmed === "") return;
523
+ if (this.entries.length > 0 && this.entries[this.entries.length - 1] === trimmed) {
524
+ this.position = this.entries.length;
525
+ return;
526
+ }
527
+ this.entries.push(trimmed);
528
+ if (this.entries.length > this.maxSize) {
529
+ this.entries = this.entries.slice(-this.maxSize);
530
+ }
531
+ this.position = this.entries.length;
532
+ this.unsavedCount++;
533
+ if (this.unsavedCount >= this.autoSaveThreshold) {
534
+ this.save();
535
+ }
536
+ }
537
+ /**
538
+ * Get previous entry (for up arrow)
539
+ */
540
+ previous() {
541
+ if (this.entries.length === 0) return null;
542
+ if (this.position > 0) {
543
+ this.position--;
544
+ }
545
+ return this.entries[this.position] ?? null;
546
+ }
547
+ /**
548
+ * Get next entry (for down arrow)
549
+ */
550
+ next() {
551
+ if (this.entries.length === 0) return null;
552
+ if (this.position < this.entries.length - 1) {
553
+ this.position++;
554
+ return this.entries[this.position] ?? null;
555
+ }
556
+ this.position = this.entries.length;
557
+ return "";
558
+ }
559
+ /**
560
+ * Reset position to end
561
+ */
562
+ resetPosition() {
563
+ this.position = this.entries.length;
564
+ }
565
+ /**
566
+ * Search history for entries containing text
567
+ */
568
+ search(text) {
569
+ const lower = text.toLowerCase();
570
+ return this.entries.filter(
571
+ (entry) => entry.toLowerCase().includes(lower)
572
+ );
573
+ }
574
+ /**
575
+ * Search backwards from current position
576
+ */
577
+ searchBackward(text) {
578
+ const lower = text.toLowerCase();
579
+ for (let i = this.position - 1; i >= 0; i--) {
580
+ if (this.entries[i].toLowerCase().includes(lower)) {
581
+ this.position = i;
582
+ return this.entries[i];
583
+ }
584
+ }
585
+ return null;
586
+ }
587
+ /**
588
+ * Search forward from current position
589
+ */
590
+ searchForward(text) {
591
+ const lower = text.toLowerCase();
592
+ for (let i = this.position + 1; i < this.entries.length; i++) {
593
+ if (this.entries[i].toLowerCase().includes(lower)) {
594
+ this.position = i;
595
+ return this.entries[i];
596
+ }
597
+ }
598
+ return null;
599
+ }
600
+ /**
601
+ * Get all entries
602
+ */
603
+ getAll() {
604
+ return [...this.entries];
605
+ }
606
+ /**
607
+ * Get recent entries
608
+ */
609
+ getRecent(count) {
610
+ return this.entries.slice(-count);
611
+ }
612
+ /**
613
+ * Clear history
614
+ */
615
+ clear() {
616
+ this.entries = [];
617
+ this.position = 0;
618
+ this.save();
619
+ }
620
+ /**
621
+ * Get history size
622
+ */
623
+ get size() {
624
+ return this.entries.length;
625
+ }
626
+ /**
627
+ * Get current position
628
+ */
629
+ get currentPosition() {
630
+ return this.position;
631
+ }
632
+ };
633
+ var MemoryHistory = class extends History {
634
+ constructor(maxSize = 1e3) {
635
+ super({ maxSize, historyFile: "" });
636
+ }
637
+ load() {
638
+ }
639
+ save() {
640
+ }
641
+ };
642
+
643
+ // src/formatter.ts
644
+ var colors = {
645
+ reset: "\x1B[0m",
646
+ bold: "\x1B[1m",
647
+ dim: "\x1B[2m",
648
+ italic: "\x1B[3m",
649
+ underline: "\x1B[4m",
650
+ // Foreground colors
651
+ black: "\x1B[30m",
652
+ red: "\x1B[31m",
653
+ green: "\x1B[32m",
654
+ yellow: "\x1B[33m",
655
+ blue: "\x1B[34m",
656
+ magenta: "\x1B[35m",
657
+ cyan: "\x1B[36m",
658
+ white: "\x1B[37m",
659
+ gray: "\x1B[90m",
660
+ // Bright foreground
661
+ brightRed: "\x1B[91m",
662
+ brightGreen: "\x1B[92m",
663
+ brightYellow: "\x1B[93m",
664
+ brightBlue: "\x1B[94m",
665
+ brightMagenta: "\x1B[95m",
666
+ brightCyan: "\x1B[96m",
667
+ brightWhite: "\x1B[97m",
668
+ // Background colors
669
+ bgBlack: "\x1B[40m",
670
+ bgRed: "\x1B[41m",
671
+ bgGreen: "\x1B[42m",
672
+ bgYellow: "\x1B[43m",
673
+ bgBlue: "\x1B[44m",
674
+ bgMagenta: "\x1B[45m",
675
+ bgCyan: "\x1B[46m",
676
+ bgWhite: "\x1B[47m"
677
+ };
678
+ function formatSuccess(message) {
679
+ return `${colors.green}\u2713${colors.reset} ${message}`;
680
+ }
681
+ function formatError(message) {
682
+ return `${colors.red}\u2717 Error:${colors.reset} ${message}`;
683
+ }
684
+ function formatWarning(message) {
685
+ return `${colors.yellow}\u26A0${colors.reset} ${message}`;
686
+ }
687
+ function formatInfo(message) {
688
+ return `${colors.cyan}\u2139${colors.reset} ${message}`;
689
+ }
690
+ function formatIntent(intent) {
691
+ const lines = [
692
+ "",
693
+ `${colors.bold}Intent: ${colors.cyan}${intent.name}${colors.reset}`,
694
+ colors.gray + "\u2500".repeat(40) + colors.reset
695
+ ];
696
+ if (intent.preconditions.length > 0) {
697
+ lines.push("");
698
+ lines.push(`${colors.bold}Preconditions:${colors.reset}`);
699
+ for (const pre of intent.preconditions) {
700
+ lines.push(` ${colors.magenta}pre:${colors.reset} ${highlightExpression(pre.expression)}`);
701
+ }
702
+ }
703
+ if (intent.postconditions.length > 0) {
704
+ lines.push("");
705
+ lines.push(`${colors.bold}Postconditions:${colors.reset}`);
706
+ for (const post of intent.postconditions) {
707
+ lines.push(` ${colors.magenta}post:${colors.reset} ${highlightExpression(post.expression)}`);
708
+ }
709
+ }
710
+ if (intent.invariants.length > 0) {
711
+ lines.push("");
712
+ lines.push(`${colors.bold}Invariants:${colors.reset}`);
713
+ for (const inv of intent.invariants) {
714
+ lines.push(` ${colors.magenta}invariant:${colors.reset} ${highlightExpression(inv.expression)}`);
715
+ }
716
+ }
717
+ if (intent.scenarios.length > 0) {
718
+ lines.push("");
719
+ lines.push(`${colors.bold}Scenarios:${colors.reset}`);
720
+ for (const scenario of intent.scenarios) {
721
+ lines.push(` ${colors.yellow}${scenario.name}${colors.reset}`);
722
+ }
723
+ }
724
+ lines.push("");
725
+ return lines.join("\n");
726
+ }
727
+ function formatCondition(condition, type) {
728
+ const prefix = {
729
+ pre: colors.magenta + "pre" + colors.reset,
730
+ post: colors.magenta + "post" + colors.reset,
731
+ invariant: colors.magenta + "invariant" + colors.reset
732
+ }[type];
733
+ return `${prefix}: ${highlightExpression(condition.expression)}`;
734
+ }
735
+ function highlightExpression(expr) {
736
+ 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}`);
737
+ }
738
+ function highlightISL(source) {
739
+ 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(
740
+ /\b(String|Int|Boolean|UUID|Timestamp|Decimal|Duration|List|Map|Optional)\b/g,
741
+ `${colors.green}$1${colors.reset}`
742
+ ).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}`);
743
+ }
744
+ function formatTable(headers, rows, options = {}) {
745
+ const useColors = options.colors !== false;
746
+ const widths = headers.map((h, i) => {
747
+ const cellWidths = [h.length, ...rows.map((r) => (r[i] ?? "").length)];
748
+ return Math.max(...cellWidths);
749
+ });
750
+ const headerRow = headers.map((h, i) => h.padEnd(widths[i])).join(" \u2502 ");
751
+ const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u253C\u2500");
752
+ const dataRows = rows.map(
753
+ (row) => row.map((cell, i) => (cell ?? "").padEnd(widths[i])).join(" \u2502 ")
754
+ );
755
+ const headerFormatted = useColors ? `${colors.bold}${headerRow}${colors.reset}` : headerRow;
756
+ const sepFormatted = useColors ? `${colors.gray}${separator}${colors.reset}` : separator;
757
+ return [headerFormatted, sepFormatted, ...dataRows].join("\n");
758
+ }
759
+ function formatParseError(source, message, line, column) {
760
+ const lines = source.split("\n");
761
+ const errorLine = lines[line - 1] || "";
762
+ const output = [
763
+ formatError(message),
764
+ "",
765
+ `${colors.gray}${String(line).padStart(4)} \u2502${colors.reset} ${errorLine}`,
766
+ `${colors.gray} \u2502${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`
767
+ ];
768
+ return output.join("\n");
769
+ }
770
+ function formatTypeError(message, expected, actual, context) {
771
+ const output = [
772
+ formatError(message),
773
+ "",
774
+ ` Expected: ${colors.green}${expected}${colors.reset}`,
775
+ ` Actual: ${colors.red}${actual}${colors.reset}`
776
+ ];
777
+ if (context) {
778
+ output.push("");
779
+ output.push(` Context: ${colors.gray}${context}${colors.reset}`);
780
+ }
781
+ return output.join("\n");
782
+ }
783
+ function formatValue(value, indent = 0) {
784
+ const pad = " ".repeat(indent);
785
+ if (value === null) return `${colors.gray}null${colors.reset}`;
786
+ if (value === void 0) return `${colors.gray}undefined${colors.reset}`;
787
+ if (typeof value === "string") {
788
+ return `${colors.green}"${value}"${colors.reset}`;
789
+ }
790
+ if (typeof value === "number") {
791
+ return `${colors.cyan}${value}${colors.reset}`;
792
+ }
793
+ if (typeof value === "boolean") {
794
+ return `${colors.magenta}${value}${colors.reset}`;
795
+ }
796
+ if (Array.isArray(value)) {
797
+ if (value.length === 0) return "[]";
798
+ const items = value.map((v) => formatValue(v, indent + 2));
799
+ return `[
800
+ ${pad} ${items.join(`,
801
+ ${pad} `)}
802
+ ${pad}]`;
803
+ }
804
+ if (typeof value === "object") {
805
+ const entries = Object.entries(value);
806
+ if (entries.length === 0) return "{}";
807
+ const items = entries.map(
808
+ ([k, v]) => `${colors.blue}${k}${colors.reset}: ${formatValue(v, indent + 2)}`
809
+ );
810
+ return `{
811
+ ${pad} ${items.join(`,
812
+ ${pad} `)}
813
+ ${pad}}`;
814
+ }
815
+ return String(value);
816
+ }
817
+ function stripColors(str) {
818
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
819
+ }
820
+ function wrapText(text, maxWidth) {
821
+ const words = text.split(" ");
822
+ const lines = [];
823
+ let currentLine = "";
824
+ for (const word of words) {
825
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
826
+ if (stripColors(testLine).length > maxWidth && currentLine) {
827
+ lines.push(currentLine);
828
+ currentLine = word;
829
+ } else {
830
+ currentLine = testLine;
831
+ }
832
+ }
833
+ if (currentLine) {
834
+ lines.push(currentLine);
835
+ }
836
+ return lines.join("\n");
837
+ }
838
+ function drawBox(lines, title) {
839
+ const maxLen = Math.max(...lines.map((l) => stripColors(l).length), title ? title.length + 4 : 0);
840
+ const top = title ? `\u2554\u2550 ${title} ${"\u2550".repeat(maxLen - title.length - 3)}\u2557` : `\u2554${"\u2550".repeat(maxLen + 2)}\u2557`;
841
+ const bottom = `\u255A${"\u2550".repeat(maxLen + 2)}\u255D`;
842
+ const paddedLines = lines.map((l) => {
843
+ const padding = maxLen - stripColors(l).length;
844
+ return `\u2551 ${l}${" ".repeat(padding)} \u2551`;
845
+ });
846
+ return [top, ...paddedLines, bottom].join("\n");
847
+ }
848
+
849
+ // src/commands.ts
850
+ function evaluateExpression(expr, session) {
851
+ const trimmed = expr.trim();
852
+ const oldMatch = trimmed.match(/^old\((.+)\)$/);
853
+ if (oldMatch) {
854
+ const innerPath = oldMatch[1].trim();
855
+ if (!session.getPreContext()) {
856
+ return {
857
+ value: void 0,
858
+ error: "old() requires pre-state. Set with .context --pre <json>"
859
+ };
860
+ }
861
+ const { found, value } = session.resolvePreValue(innerPath);
862
+ if (!found) {
863
+ return { value: void 0, error: `Cannot resolve '${innerPath}' in pre-state context` };
864
+ }
865
+ return { value };
866
+ }
867
+ if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
868
+ return evaluateExpression(trimmed.slice(1, -1), session);
869
+ }
870
+ if (trimmed.startsWith("!") || trimmed.startsWith("not ")) {
871
+ const inner = trimmed.startsWith("!") ? trimmed.slice(1) : trimmed.slice(4);
872
+ const result = evaluateExpression(inner.trim(), session);
873
+ if (result.error) return result;
874
+ return { value: !result.value };
875
+ }
876
+ for (const [opStr, opFn] of BINARY_OPS) {
877
+ const idx = findOperator(trimmed, opStr);
878
+ if (idx !== -1) {
879
+ const left = trimmed.slice(0, idx).trim();
880
+ const right = trimmed.slice(idx + opStr.length).trim();
881
+ const lResult = evaluateExpression(left, session);
882
+ if (lResult.error) return lResult;
883
+ const rResult = evaluateExpression(right, session);
884
+ if (rResult.error) return rResult;
885
+ return { value: opFn(lResult.value, rResult.value) };
886
+ }
887
+ }
888
+ if (trimmed === "true") return { value: true };
889
+ if (trimmed === "false") return { value: false };
890
+ if (trimmed === "null") return { value: null };
891
+ if (/^-?\d+$/.test(trimmed)) return { value: parseInt(trimmed, 10) };
892
+ if (/^-?\d+\.\d+$/.test(trimmed)) return { value: parseFloat(trimmed) };
893
+ if (/^"([^"]*)"$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
894
+ if (/^'([^']*)'$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
895
+ if (/^[\w.]+$/.test(trimmed)) {
896
+ const { found, value } = session.resolveValue(trimmed);
897
+ if (found) return { value };
898
+ if (session.hasVariable(trimmed)) {
899
+ return { value: session.getVariable(trimmed) };
900
+ }
901
+ }
902
+ return { value: void 0, error: `Cannot evaluate: ${trimmed}` };
903
+ }
904
+ var BINARY_OPS = [
905
+ // Logical (lowest precedence — scanned first so they split outermost)
906
+ [" || ", (a, b) => Boolean(a) || Boolean(b)],
907
+ [" or ", (a, b) => Boolean(a) || Boolean(b)],
908
+ [" && ", (a, b) => Boolean(a) && Boolean(b)],
909
+ [" and ", (a, b) => Boolean(a) && Boolean(b)],
910
+ // Equality
911
+ [" == ", (a, b) => a === b || String(a) === String(b)],
912
+ [" != ", (a, b) => a !== b && String(a) !== String(b)],
913
+ // Comparison
914
+ [" >= ", (a, b) => Number(a) >= Number(b)],
915
+ [" <= ", (a, b) => Number(a) <= Number(b)],
916
+ [" > ", (a, b) => Number(a) > Number(b)],
917
+ [" < ", (a, b) => Number(a) < Number(b)],
918
+ // Arithmetic
919
+ [" + ", (a, b) => {
920
+ if (typeof a === "string" || typeof b === "string") return String(a) + String(b);
921
+ return Number(a) + Number(b);
922
+ }],
923
+ [" - ", (a, b) => Number(a) - Number(b)],
924
+ [" * ", (a, b) => Number(a) * Number(b)],
925
+ [" / ", (a, b) => {
926
+ const d = Number(b);
927
+ if (d === 0) return Infinity;
928
+ return Number(a) / d;
929
+ }]
930
+ ];
931
+ function findOperator(expr, op) {
932
+ let depth = 0;
933
+ let inString = null;
934
+ for (let i = expr.length - 1; i >= 0; i--) {
935
+ const ch = expr[i];
936
+ if (inString) {
937
+ if (ch === inString && (i === 0 || expr[i - 1] !== "\\")) inString = null;
938
+ continue;
939
+ }
940
+ if (ch === '"' || ch === "'") {
941
+ inString = ch;
942
+ continue;
943
+ }
944
+ if (ch === "(") depth--;
945
+ if (ch === ")") depth++;
946
+ if (depth === 0 && i + op.length <= expr.length && expr.slice(i, i + op.length) === op) {
947
+ return i;
948
+ }
949
+ }
950
+ return -1;
951
+ }
952
+ function prettyPrintAST(node, indent = 0) {
953
+ const pad = " ".repeat(indent);
954
+ if (node === null || node === void 0) return `${pad}${colors.gray}null${colors.reset}`;
955
+ if (typeof node === "string") return `${pad}${colors.green}"${node}"${colors.reset}`;
956
+ if (typeof node === "number") return `${pad}${colors.cyan}${node}${colors.reset}`;
957
+ if (typeof node === "boolean") return `${pad}${colors.magenta}${node}${colors.reset}`;
958
+ if (Array.isArray(node)) {
959
+ if (node.length === 0) return `${pad}[]`;
960
+ const items = node.map((item) => prettyPrintAST(item, indent + 1));
961
+ return `${pad}[
962
+ ${items.join(",\n")}
963
+ ${pad}]`;
964
+ }
965
+ if (typeof node === "object") {
966
+ const obj = node;
967
+ const kind = obj["kind"];
968
+ const entries = Object.entries(obj).filter(
969
+ ([k, v]) => k !== "location" && v !== void 0 && !(Array.isArray(v) && v.length === 0)
970
+ );
971
+ if (entries.length === 0) return `${pad}{}`;
972
+ const header = kind ? `${pad}${colors.yellow}${kind}${colors.reset} {` : `${pad}{`;
973
+ const body = entries.filter(([k]) => k !== "kind").map(([k, v]) => {
974
+ const valStr = typeof v === "object" && v !== null ? "\n" + prettyPrintAST(v, indent + 2) : " " + prettyPrintAST(v, 0).trim();
975
+ return `${pad} ${colors.blue}${k}${colors.reset}:${valStr}`;
976
+ });
977
+ return `${header}
978
+ ${body.join("\n")}
979
+ ${pad}}`;
980
+ }
981
+ return `${pad}${String(node)}`;
982
+ }
983
+ var metaCommands = [
984
+ // ─── .help ──────────────────────────────────────────────────────────────
985
+ {
986
+ name: "help",
987
+ aliases: ["h", "?"],
988
+ description: "Show commands",
989
+ usage: ".help [command]",
990
+ handler: (args) => {
991
+ if (args.length > 0) {
992
+ const cmdName = args[0].toLowerCase().replace(/^\./, "");
993
+ const cmd = metaCommands.find(
994
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
995
+ );
996
+ if (cmd) {
997
+ return {
998
+ output: [
999
+ `${colors.cyan}.${cmd.name}${colors.reset} \u2014 ${cmd.description}`,
1000
+ `Usage: ${cmd.usage}`,
1001
+ cmd.aliases.length > 0 ? `Aliases: ${cmd.aliases.map((a) => "." + a).join(", ")}` : ""
1002
+ ].filter(Boolean).join("\n")
1003
+ };
1004
+ }
1005
+ return { output: formatError(`Unknown command: ${cmdName}`) };
1006
+ }
1007
+ const lines = [
1008
+ "",
1009
+ `${colors.bold}REPL Commands${colors.reset}`,
1010
+ "",
1011
+ ...metaCommands.map(
1012
+ (c) => ` ${colors.cyan}.${c.name.padEnd(12)}${colors.reset} ${c.description}`
1013
+ ),
1014
+ "",
1015
+ `${colors.bold}ISL Input${colors.reset}`,
1016
+ "",
1017
+ ` Type ISL directly \u2014 multi-line supported (braces auto-detect):`,
1018
+ "",
1019
+ ` ${colors.yellow}domain${colors.reset} Example {`,
1020
+ ` ${colors.yellow}entity${colors.reset} User {`,
1021
+ ` id: ${colors.green}UUID${colors.reset}`,
1022
+ ` name: ${colors.green}String${colors.reset}`,
1023
+ ` }`,
1024
+ ` }`,
1025
+ "",
1026
+ `Type ${colors.cyan}.help <command>${colors.reset} for details.`,
1027
+ ""
1028
+ ];
1029
+ return { output: lines.join("\n") };
1030
+ }
1031
+ },
1032
+ // ─── .parse ─────────────────────────────────────────────────────────────
1033
+ {
1034
+ name: "parse",
1035
+ aliases: ["p", "ast"],
1036
+ description: "Parse ISL and show AST",
1037
+ usage: ".parse <isl>",
1038
+ handler: (args, session) => {
1039
+ const input = args.join(" ").trim();
1040
+ if (!input) {
1041
+ return { output: 'Usage: .parse <isl code>\nExample: .parse domain Foo { version: "1.0" }' };
1042
+ }
1043
+ try {
1044
+ const { parse } = __require("@isl-lang/parser");
1045
+ const result = parse(input, "<repl>");
1046
+ if (!result.success || result.errors.length > 0) {
1047
+ const errLines = result.errors.map((e) => {
1048
+ const loc = e.location;
1049
+ if (loc) {
1050
+ return formatParseError(input, e.message, loc.line, loc.column);
1051
+ }
1052
+ return formatError(e.message);
1053
+ });
1054
+ return { output: errLines.join("\n") };
1055
+ }
1056
+ if (result.domain) {
1057
+ session.setDomainAST(result.domain);
1058
+ return {
1059
+ output: formatSuccess("Parsed successfully") + "\n" + prettyPrintAST(result.domain)
1060
+ };
1061
+ }
1062
+ return { output: formatWarning("Parse returned no AST") };
1063
+ } catch {
1064
+ return {
1065
+ output: formatWarning(
1066
+ "Real parser not available \u2014 install @isl-lang/parser.\nFalling back to simple parse."
1067
+ )
1068
+ };
1069
+ }
1070
+ }
1071
+ },
1072
+ // ─── .eval ──────────────────────────────────────────────────────────────
1073
+ {
1074
+ name: "eval",
1075
+ aliases: ["e"],
1076
+ description: "Evaluate expression against context",
1077
+ usage: ".eval <expression>",
1078
+ handler: (args, session) => {
1079
+ const expr = args.join(" ").trim();
1080
+ if (!expr) {
1081
+ return {
1082
+ output: [
1083
+ "Usage: .eval <expression>",
1084
+ "",
1085
+ "Examples:",
1086
+ ' .eval user.email == "test@x.com"',
1087
+ " .eval user.age > 30",
1088
+ " .eval old(user.age)",
1089
+ "",
1090
+ 'Set context first: .context { "user": { "email": "test@x.com" } }'
1091
+ ].join("\n")
1092
+ };
1093
+ }
1094
+ const result = evaluateExpression(expr, session);
1095
+ if (result.error) {
1096
+ return { output: formatError(result.error) };
1097
+ }
1098
+ session.setLastResult(result.value);
1099
+ return {
1100
+ output: `${colors.cyan}\u2192${colors.reset} ${formatValue(result.value)}`
1101
+ };
1102
+ }
1103
+ },
1104
+ // ─── .check ─────────────────────────────────────────────────────────────
1105
+ {
1106
+ name: "check",
1107
+ aliases: ["c"],
1108
+ description: "Type check the current session",
1109
+ usage: ".check [intent]",
1110
+ handler: (args, session) => {
1111
+ if (args.length > 0) {
1112
+ const intentName = args[0];
1113
+ const intent = session.getIntent(intentName);
1114
+ if (!intent) {
1115
+ const available = session.getIntentNames().join(", ") || "(none)";
1116
+ return {
1117
+ output: formatError(`Unknown intent: ${intentName}
1118
+ Available: ${available}`)
1119
+ };
1120
+ }
1121
+ const lines2 = [formatSuccess("Type check passed"), ""];
1122
+ for (const pre of intent.preconditions) {
1123
+ lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
1124
+ }
1125
+ for (const post of intent.postconditions) {
1126
+ lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
1127
+ }
1128
+ return { output: lines2.join("\n") };
1129
+ }
1130
+ const intents = session.getAllIntents();
1131
+ if (intents.length === 0) {
1132
+ return {
1133
+ output: formatWarning("No intents defined. Write ISL or use .load <file>")
1134
+ };
1135
+ }
1136
+ const lines = [formatSuccess(`Type check passed \u2014 ${intents.length} intent(s)`), ""];
1137
+ for (const intent of intents) {
1138
+ lines.push(`${colors.bold}${intent.name}${colors.reset}`);
1139
+ for (const pre of intent.preconditions) {
1140
+ lines.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
1141
+ }
1142
+ for (const post of intent.postconditions) {
1143
+ lines.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
1144
+ }
1145
+ lines.push("");
1146
+ }
1147
+ return { output: lines.join("\n") };
1148
+ }
1149
+ },
1150
+ // ─── .gen ───────────────────────────────────────────────────────────────
1151
+ {
1152
+ name: "gen",
1153
+ aliases: ["generate", "g"],
1154
+ description: "Generate TypeScript from intent",
1155
+ usage: ".gen [intent]",
1156
+ handler: (args, session) => {
1157
+ const intents = args.length > 0 ? [session.getIntent(args[0])].filter(Boolean) : session.getAllIntents();
1158
+ if (intents.length === 0) {
1159
+ return {
1160
+ output: args.length > 0 ? formatError(`Unknown intent: ${args[0]}
1161
+ Available: ${session.getIntentNames().join(", ") || "(none)"}`) : formatWarning("No intents defined. Write ISL or use .load <file>")
1162
+ };
1163
+ }
1164
+ const lines = [`${colors.gray}// Generated TypeScript${colors.reset}`, ""];
1165
+ for (const intent of intents) {
1166
+ lines.push(`interface ${intent.name}Contract {`);
1167
+ if (intent.preconditions.length > 0) {
1168
+ lines.push(" /** Preconditions */");
1169
+ for (const pre of intent.preconditions) {
1170
+ lines.push(` checkPre(): boolean; // ${pre.expression}`);
1171
+ }
1172
+ }
1173
+ if (intent.postconditions.length > 0) {
1174
+ lines.push(" /** Postconditions */");
1175
+ for (const post of intent.postconditions) {
1176
+ lines.push(` checkPost(): boolean; // ${post.expression}`);
1177
+ }
1178
+ }
1179
+ if (intent.invariants.length > 0) {
1180
+ lines.push(" /** Invariants */");
1181
+ for (const inv of intent.invariants) {
1182
+ lines.push(` checkInvariant(): boolean; // ${inv.expression}`);
1183
+ }
1184
+ }
1185
+ lines.push("}");
1186
+ lines.push("");
1187
+ }
1188
+ return { output: lines.join("\n") };
1189
+ }
1190
+ },
1191
+ // ─── .load ──────────────────────────────────────────────────────────────
1192
+ {
1193
+ name: "load",
1194
+ aliases: ["l"],
1195
+ description: "Load an .isl file",
1196
+ usage: ".load <file.isl>",
1197
+ handler: (args, session) => {
1198
+ if (args.length === 0) {
1199
+ return { output: "Usage: .load <file.isl>" };
1200
+ }
1201
+ const filePath = args[0];
1202
+ const resolvedPath = path__namespace.isAbsolute(filePath) ? filePath : path__namespace.resolve(process.cwd(), filePath);
1203
+ if (!fs2__namespace.existsSync(resolvedPath)) {
1204
+ return { output: formatError(`File not found: ${resolvedPath}`) };
1205
+ }
1206
+ try {
1207
+ const content = fs2__namespace.readFileSync(resolvedPath, "utf-8");
1208
+ try {
1209
+ const { parse } = __require("@isl-lang/parser");
1210
+ const result = parse(content, resolvedPath);
1211
+ if (!result.success || result.errors.length > 0) {
1212
+ const errLines = result.errors.map((e) => {
1213
+ const loc = e.location;
1214
+ if (loc) {
1215
+ return formatParseError(content, e.message, loc.line, loc.column);
1216
+ }
1217
+ return formatError(e.message);
1218
+ });
1219
+ return { output: errLines.join("\n") };
1220
+ }
1221
+ if (result.domain) {
1222
+ session.setDomainAST(result.domain);
1223
+ const domain = result.domain;
1224
+ const name = domain.name?.name ?? "Unknown";
1225
+ const entityCount = domain.entities?.length ?? 0;
1226
+ const behaviorCount = domain.behaviors?.length ?? 0;
1227
+ return {
1228
+ output: formatSuccess(
1229
+ `Loaded: ${name} (${entityCount} entities, ${behaviorCount} behaviors) from ${path__namespace.basename(filePath)}`
1230
+ )
1231
+ };
1232
+ }
1233
+ } catch {
1234
+ }
1235
+ const intentRegex = /(?:intent|behavior)\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
1236
+ let match;
1237
+ let count = 0;
1238
+ while ((match = intentRegex.exec(content)) !== null) {
1239
+ const intent = session.parseIntent(match[0]);
1240
+ if (intent) {
1241
+ session.defineIntent(intent);
1242
+ count++;
1243
+ }
1244
+ }
1245
+ if (count === 0) {
1246
+ return { output: formatWarning("No intents/behaviors found in file") };
1247
+ }
1248
+ return {
1249
+ output: formatSuccess(`Loaded ${count} intent(s) from ${path__namespace.basename(filePath)}`)
1250
+ };
1251
+ } catch (error) {
1252
+ return {
1253
+ output: formatError(
1254
+ `Failed to load: ${error instanceof Error ? error.message : String(error)}`
1255
+ )
1256
+ };
1257
+ }
1258
+ }
1259
+ },
1260
+ // ─── .context ───────────────────────────────────────────────────────────
1261
+ {
1262
+ name: "context",
1263
+ aliases: ["ctx"],
1264
+ description: "Set evaluation context (JSON)",
1265
+ usage: ".context <json> | .context --pre <json>",
1266
+ handler: (args, session) => {
1267
+ const input = args.join(" ").trim();
1268
+ if (!input) {
1269
+ const ctx = session.getEvalContext();
1270
+ const pre = session.getPreContext();
1271
+ if (Object.keys(ctx).length === 0 && !pre) {
1272
+ return {
1273
+ output: [
1274
+ "No context set.",
1275
+ "",
1276
+ "Usage:",
1277
+ ' .context { "user": { "email": "test@x.com", "age": 25 } }',
1278
+ ' .context --pre { "user": { "age": 20 } }'
1279
+ ].join("\n")
1280
+ };
1281
+ }
1282
+ const lines = [];
1283
+ if (Object.keys(ctx).length > 0) {
1284
+ lines.push(`${colors.bold}Context:${colors.reset}`);
1285
+ lines.push(formatValue(ctx));
1286
+ }
1287
+ if (pre) {
1288
+ lines.push(`${colors.bold}Pre-state:${colors.reset}`);
1289
+ lines.push(formatValue(pre));
1290
+ }
1291
+ return { output: lines.join("\n") };
1292
+ }
1293
+ if (input.startsWith("--pre ")) {
1294
+ const json = input.slice(6).trim();
1295
+ const result2 = session.setPreContext(json);
1296
+ if (!result2.success) {
1297
+ return { output: formatError(`Invalid JSON: ${result2.error}`) };
1298
+ }
1299
+ return { output: formatSuccess("Pre-state context set") };
1300
+ }
1301
+ const result = session.setEvalContext(input);
1302
+ if (!result.success) {
1303
+ return { output: formatError(`Invalid JSON: ${result.error}`) };
1304
+ }
1305
+ return {
1306
+ output: formatSuccess(
1307
+ `Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`
1308
+ )
1309
+ };
1310
+ }
1311
+ },
1312
+ // ─── .clear ─────────────────────────────────────────────────────────────
1313
+ {
1314
+ name: "clear",
1315
+ aliases: ["cls", "reset"],
1316
+ description: "Reset session state",
1317
+ usage: ".clear",
1318
+ handler: (_args, session) => {
1319
+ session.clear();
1320
+ return { output: formatSuccess("Session cleared") };
1321
+ }
1322
+ },
1323
+ // ─── .history ───────────────────────────────────────────────────────────
1324
+ {
1325
+ name: "history",
1326
+ aliases: ["hist"],
1327
+ description: "Show command history",
1328
+ usage: ".history [n]",
1329
+ handler: (args, session) => {
1330
+ const count = args.length > 0 ? parseInt(args[0], 10) : 10;
1331
+ const history = session.getHistory(count);
1332
+ if (history.length === 0) {
1333
+ return { output: "No history." };
1334
+ }
1335
+ const lines = [
1336
+ `${colors.bold}History${colors.reset} (last ${history.length} entries)`,
1337
+ "",
1338
+ ...history.map((entry, i) => {
1339
+ const num = String(i + 1).padStart(3);
1340
+ const preview = entry.split("\n")[0];
1341
+ const more = entry.includes("\n") ? ` ${colors.gray}...${colors.reset}` : "";
1342
+ return ` ${colors.gray}${num}${colors.reset} ${preview}${more}`;
1343
+ })
1344
+ ];
1345
+ return { output: lines.join("\n") };
1346
+ }
1347
+ },
1348
+ // ─── .list ──────────────────────────────────────────────────────────────
1349
+ {
1350
+ name: "list",
1351
+ aliases: ["ls"],
1352
+ description: "List defined intents",
1353
+ usage: ".list",
1354
+ handler: (_args, session) => {
1355
+ const intents = session.getAllIntents();
1356
+ if (intents.length === 0) {
1357
+ return { output: "No intents defined." };
1358
+ }
1359
+ const lines = [""];
1360
+ for (const intent of intents) {
1361
+ const parts = [];
1362
+ if (intent.preconditions.length > 0) parts.push(`${intent.preconditions.length} pre`);
1363
+ if (intent.postconditions.length > 0) parts.push(`${intent.postconditions.length} post`);
1364
+ if (intent.invariants.length > 0) parts.push(`${intent.invariants.length} invariant`);
1365
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1366
+ lines.push(` ${colors.cyan}${intent.name}${colors.reset}${summary}`);
1367
+ }
1368
+ lines.push("");
1369
+ return { output: lines.join("\n") };
1370
+ }
1371
+ },
1372
+ // ─── .inspect ───────────────────────────────────────────────────────────
1373
+ {
1374
+ name: "inspect",
1375
+ aliases: ["i", "show"],
1376
+ description: "Show full details of an intent",
1377
+ usage: ".inspect [intent]",
1378
+ handler: (args, session) => {
1379
+ if (args.length === 0) {
1380
+ const summary = session.getSummary();
1381
+ const ctx = session.getEvalContext();
1382
+ const lines = [
1383
+ "",
1384
+ `${colors.bold}Session Summary${colors.reset}`,
1385
+ "",
1386
+ ` Intents: ${summary.intentCount}`,
1387
+ ` Variables: ${summary.variableCount}`,
1388
+ ` Context: ${Object.keys(ctx).length} keys`,
1389
+ ` History: ${summary.historyCount} entries`,
1390
+ ""
1391
+ ];
1392
+ return { output: lines.join("\n") };
1393
+ }
1394
+ const intentName = args[0];
1395
+ const intent = session.getIntent(intentName);
1396
+ if (!intent) {
1397
+ const available = session.getIntentNames().join(", ") || "(none)";
1398
+ return {
1399
+ output: formatError(`Unknown intent: ${intentName}
1400
+ Available: ${available}`)
1401
+ };
1402
+ }
1403
+ return { output: formatIntent(intent) };
1404
+ }
1405
+ },
1406
+ // ─── .exit ──────────────────────────────────────────────────────────────
1407
+ {
1408
+ name: "exit",
1409
+ aliases: ["quit", "q"],
1410
+ description: "Exit the REPL",
1411
+ usage: ".exit",
1412
+ handler: () => {
1413
+ return { exit: true };
1414
+ }
1415
+ }
1416
+ ];
1417
+ var islCommands = [];
1418
+ function levenshteinDistance(a, b) {
1419
+ const matrix = [];
1420
+ for (let i = 0; i <= b.length; i++) {
1421
+ matrix[i] = [i];
1422
+ }
1423
+ for (let j = 0; j <= a.length; j++) {
1424
+ matrix[0][j] = j;
1425
+ }
1426
+ for (let i = 1; i <= b.length; i++) {
1427
+ for (let j = 1; j <= a.length; j++) {
1428
+ const cost = a[j - 1] === b[i - 1] ? 0 : 1;
1429
+ matrix[i][j] = Math.min(
1430
+ matrix[i - 1][j] + 1,
1431
+ matrix[i][j - 1] + 1,
1432
+ matrix[i - 1][j - 1] + cost
1433
+ );
1434
+ }
1435
+ }
1436
+ return matrix[b.length][a.length];
1437
+ }
1438
+ function findSimilarCommand(input, _type) {
1439
+ const names = metaCommands.flatMap((c) => [c.name, ...c.aliases]);
1440
+ let bestMatch = null;
1441
+ let bestDistance = Infinity;
1442
+ for (const name of names) {
1443
+ const distance = levenshteinDistance(input.toLowerCase(), name.toLowerCase());
1444
+ if (distance < bestDistance && distance <= 2) {
1445
+ bestDistance = distance;
1446
+ bestMatch = name;
1447
+ }
1448
+ }
1449
+ return bestMatch;
1450
+ }
1451
+
1452
+ // src/completions.ts
1453
+ var KEYWORDS = [
1454
+ // Structure keywords
1455
+ { text: "domain", type: "keyword", description: "Define a domain" },
1456
+ { text: "entity", type: "keyword", description: "Define an entity" },
1457
+ { text: "behavior", type: "keyword", description: "Define a behavior" },
1458
+ { text: "intent", type: "keyword", description: "Define an intent" },
1459
+ { text: "input", type: "keyword", description: "Input block" },
1460
+ { text: "output", type: "keyword", description: "Output block" },
1461
+ { text: "pre", type: "keyword", description: "Precondition" },
1462
+ { text: "post", type: "keyword", description: "Postcondition" },
1463
+ { text: "invariant", type: "keyword", description: "Invariant" },
1464
+ { text: "scenario", type: "keyword", description: "Scenario block" },
1465
+ { text: "version", type: "keyword", description: "Version declaration" },
1466
+ // Types
1467
+ { text: "String", type: "keyword", description: "String type" },
1468
+ { text: "Number", type: "keyword", description: "Number type" },
1469
+ { text: "Int", type: "keyword", description: "Integer type" },
1470
+ { text: "Decimal", type: "keyword", description: "Decimal type" },
1471
+ { text: "Boolean", type: "keyword", description: "Boolean type" },
1472
+ { text: "UUID", type: "keyword", description: "UUID type" },
1473
+ { text: "Timestamp", type: "keyword", description: "Timestamp type" },
1474
+ { text: "Duration", type: "keyword", description: "Duration type" },
1475
+ { text: "List", type: "keyword", description: "List<T> type" },
1476
+ { text: "Map", type: "keyword", description: "Map<K,V> type" },
1477
+ { text: "Optional", type: "keyword", description: "Optional<T> type" },
1478
+ // Literals and operators
1479
+ { text: "true", type: "keyword", description: "Boolean true" },
1480
+ { text: "false", type: "keyword", description: "Boolean false" },
1481
+ { text: "null", type: "keyword", description: "Null value" },
1482
+ { text: "and", type: "keyword", description: "Logical AND" },
1483
+ { text: "or", type: "keyword", description: "Logical OR" },
1484
+ { text: "not", type: "keyword", description: "Logical NOT" },
1485
+ { text: "implies", type: "keyword", description: "Logical implication" },
1486
+ { text: "forall", type: "keyword", description: "Universal quantifier" },
1487
+ { text: "exists", type: "keyword", description: "Existential quantifier" },
1488
+ { text: "in", type: "keyword", description: "Membership test" },
1489
+ { text: "old", type: "keyword", description: "Pre-state value (old(x))" }
1490
+ ];
1491
+ var META_COMMANDS = metaCommands.map((cmd) => ({
1492
+ text: `.${cmd.name}`,
1493
+ type: "command",
1494
+ description: cmd.description
1495
+ }));
1496
+ var ISL_COMMANDS = [];
1497
+ var COMMANDS = [...META_COMMANDS];
1498
+ var GEN_TARGETS = [
1499
+ { text: "typescript", type: "keyword", description: "Generate TypeScript contract" },
1500
+ { text: "rust", type: "keyword", description: "Generate Rust contract" },
1501
+ { text: "go", type: "keyword", description: "Generate Go contract" },
1502
+ { text: "openapi", type: "keyword", description: "Generate OpenAPI schema" }
1503
+ ];
1504
+ var CompletionProvider = class {
1505
+ constructor(session) {
1506
+ this.session = session;
1507
+ }
1508
+ /**
1509
+ * Update the session reference
1510
+ */
1511
+ setSession(session) {
1512
+ this.session = session;
1513
+ }
1514
+ /**
1515
+ * Get completions for a line
1516
+ */
1517
+ complete(line) {
1518
+ const trimmed = line.trimStart();
1519
+ if (trimmed.startsWith(".")) {
1520
+ return this.completeMetaCommand(trimmed);
1521
+ }
1522
+ if (trimmed.startsWith(":")) {
1523
+ return this.completeMetaCommand("." + trimmed.slice(1));
1524
+ }
1525
+ return this.completeExpression(trimmed);
1526
+ }
1527
+ /**
1528
+ * Complete meta commands
1529
+ */
1530
+ completeMetaCommand(line) {
1531
+ const parts = line.slice(1).split(/\s+/);
1532
+ const cmdPart = parts[0] || "";
1533
+ if (parts.length === 1) {
1534
+ const matches = META_COMMANDS.filter(
1535
+ (c) => c.text.toLowerCase().startsWith(`.${cmdPart.toLowerCase()}`)
1536
+ );
1537
+ return [matches.length > 0 ? matches : META_COMMANDS, "." + cmdPart];
1538
+ }
1539
+ return [[], line];
1540
+ }
1541
+ /**
1542
+ * Complete ISL commands
1543
+ */
1544
+ completeISLCommand(line) {
1545
+ const parts = line.slice(1).split(/\s+/);
1546
+ const cmdPart = parts[0] || "";
1547
+ const args = parts.slice(1);
1548
+ if (parts.length === 1) {
1549
+ const matches = ISL_COMMANDS.filter(
1550
+ (c) => c.text.toLowerCase().startsWith(`:${cmdPart.toLowerCase()}`)
1551
+ );
1552
+ return [matches.length > 0 ? matches : ISL_COMMANDS, ":" + cmdPart];
1553
+ }
1554
+ const cmd = cmdPart.toLowerCase();
1555
+ switch (cmd) {
1556
+ case "gen":
1557
+ case "generate":
1558
+ case "g":
1559
+ return this.completeGenCommand(args);
1560
+ case "check":
1561
+ case "c":
1562
+ case "inspect":
1563
+ case "i":
1564
+ case "show":
1565
+ return this.completeIntentName(args[0] || "");
1566
+ case "load":
1567
+ case "l":
1568
+ case "export":
1569
+ case "save":
1570
+ return this.completeFilePath(args[0] || "");
1571
+ default:
1572
+ return [[], line];
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Complete :gen command arguments
1577
+ */
1578
+ completeGenCommand(args) {
1579
+ if (args.length <= 1) {
1580
+ const partial = args[0] || "";
1581
+ const matches = GEN_TARGETS.filter(
1582
+ (t) => t.text.toLowerCase().startsWith(partial.toLowerCase())
1583
+ );
1584
+ return [matches.length > 0 ? matches : GEN_TARGETS, partial];
1585
+ }
1586
+ return this.completeIntentName(args[1] || "");
1587
+ }
1588
+ /**
1589
+ * Complete intent names
1590
+ */
1591
+ completeIntentName(partial) {
1592
+ const intents = this.session.getAllIntents();
1593
+ const items = intents.map((intent) => ({
1594
+ text: intent.name,
1595
+ type: "intent",
1596
+ description: `${intent.preconditions.length} pre, ${intent.postconditions.length} post`
1597
+ }));
1598
+ const matches = items.filter(
1599
+ (i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
1600
+ );
1601
+ return [matches.length > 0 ? matches : items, partial];
1602
+ }
1603
+ /**
1604
+ * Complete file paths
1605
+ */
1606
+ completeFilePath(partial) {
1607
+ try {
1608
+ const dir = path__namespace.dirname(partial) || ".";
1609
+ const base = path__namespace.basename(partial);
1610
+ const resolvedDir = path__namespace.resolve(this.session.getConfig().cwd || process.cwd(), dir);
1611
+ if (!fs2__namespace.existsSync(resolvedDir)) {
1612
+ return [[], partial];
1613
+ }
1614
+ const entries = fs2__namespace.readdirSync(resolvedDir, { withFileTypes: true });
1615
+ const items = entries.filter((e) => {
1616
+ const name = e.name.toLowerCase();
1617
+ return name.startsWith(base.toLowerCase()) && (e.isDirectory() || name.endsWith(".isl"));
1618
+ }).map((e) => ({
1619
+ text: path__namespace.join(dir, e.name + (e.isDirectory() ? "/" : "")),
1620
+ type: "file",
1621
+ description: e.isDirectory() ? "Directory" : "ISL file"
1622
+ }));
1623
+ return [items, partial];
1624
+ } catch {
1625
+ return [[], partial];
1626
+ }
1627
+ }
1628
+ /**
1629
+ * Complete expressions
1630
+ */
1631
+ completeExpression(line) {
1632
+ const items = [...KEYWORDS];
1633
+ for (const intent of this.session.getAllIntents()) {
1634
+ items.push({
1635
+ text: intent.name,
1636
+ type: "intent",
1637
+ description: "Defined intent"
1638
+ });
1639
+ }
1640
+ for (const [name] of this.session.getAllVariables()) {
1641
+ items.push({
1642
+ text: name,
1643
+ type: "variable",
1644
+ description: "Variable"
1645
+ });
1646
+ }
1647
+ items.push({
1648
+ text: "_",
1649
+ type: "variable",
1650
+ description: "Last result"
1651
+ });
1652
+ const match = line.match(/[\w.]+$/);
1653
+ const partial = match ? match[0] : "";
1654
+ const matches = items.filter(
1655
+ (i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
1656
+ );
1657
+ return [matches.length > 0 ? matches : items, partial];
1658
+ }
1659
+ /**
1660
+ * Get all available completions (for help)
1661
+ */
1662
+ getAllCompletions() {
1663
+ return {
1664
+ metaCommands: META_COMMANDS,
1665
+ islCommands: ISL_COMMANDS,
1666
+ keywords: KEYWORDS,
1667
+ intents: this.session.getAllIntents().map((i) => ({
1668
+ text: i.name,
1669
+ type: "intent",
1670
+ description: `${i.preconditions.length} pre, ${i.postconditions.length} post`
1671
+ }))
1672
+ };
1673
+ }
1674
+ };
1675
+ function createCompleter(provider) {
1676
+ return (line) => {
1677
+ const [items, partial] = provider.complete(line);
1678
+ const completions = items.map((i) => i.text);
1679
+ return [completions, partial];
1680
+ };
1681
+ }
1682
+
1683
+ // src/repl.ts
1684
+ var VERSION = "0.1.0";
1685
+ var PROMPT = `${colors.cyan}isl>${colors.reset} `;
1686
+ var CONTINUATION_PROMPT = `${colors.cyan}...>${colors.reset} `;
1687
+ var ISLREPL = class {
1688
+ session;
1689
+ history;
1690
+ completionProvider;
1691
+ rl = null;
1692
+ buffer = [];
1693
+ braceCount = 0;
1694
+ options;
1695
+ running = false;
1696
+ constructor(options = {}) {
1697
+ this.options = {
1698
+ colors: options.colors !== false,
1699
+ verbose: options.verbose ?? false,
1700
+ historyFile: options.historyFile,
1701
+ load: options.load,
1702
+ context: options.context,
1703
+ parseOnly: options.parseOnly ?? false
1704
+ };
1705
+ this.session = new Session({ colors: this.options.colors });
1706
+ this.history = new History({
1707
+ historyFile: this.options.historyFile
1708
+ });
1709
+ this.completionProvider = new CompletionProvider(this.session);
1710
+ }
1711
+ /**
1712
+ * Start the REPL
1713
+ */
1714
+ start() {
1715
+ if (this.running) return;
1716
+ this.running = true;
1717
+ this.applyStartupOptions();
1718
+ if (this.options.parseOnly || !process.stdin.isTTY) {
1719
+ this.runPipeMode();
1720
+ return;
1721
+ }
1722
+ this.history.load();
1723
+ this.rl = readline__namespace.createInterface({
1724
+ input: process.stdin,
1725
+ output: process.stdout,
1726
+ prompt: PROMPT,
1727
+ completer: createCompleter(this.completionProvider),
1728
+ terminal: true
1729
+ });
1730
+ this.printBanner();
1731
+ this.rl.prompt();
1732
+ this.rl.on("line", (line) => {
1733
+ this.handleLine(line);
1734
+ if (this.rl && this.running) {
1735
+ this.rl.setPrompt(this.braceCount > 0 ? CONTINUATION_PROMPT : PROMPT);
1736
+ this.rl.prompt();
1737
+ }
1738
+ });
1739
+ this.rl.on("close", () => {
1740
+ this.exit();
1741
+ });
1742
+ this.rl.on("SIGINT", () => {
1743
+ if (this.buffer.length > 0) {
1744
+ this.buffer = [];
1745
+ this.braceCount = 0;
1746
+ process.stdout.write("\n" + formatWarning("Input cancelled") + "\n");
1747
+ this.rl.setPrompt(PROMPT);
1748
+ this.rl.prompt();
1749
+ } else {
1750
+ process.stdout.write("\n" + formatWarning("Use .exit to quit") + "\n");
1751
+ this.rl.prompt();
1752
+ }
1753
+ });
1754
+ }
1755
+ /**
1756
+ * Apply startup options (--load, --context)
1757
+ */
1758
+ applyStartupOptions() {
1759
+ if (this.options.context) {
1760
+ const result = this.session.setEvalContext(this.options.context);
1761
+ if (result.success) {
1762
+ process.stdout.write(
1763
+ formatSuccess(`Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`) + "\n"
1764
+ );
1765
+ } else {
1766
+ process.stdout.write(formatError(`Invalid context JSON: ${result.error}`) + "\n");
1767
+ }
1768
+ }
1769
+ if (this.options.load) {
1770
+ const loadCmd = metaCommands.find((c) => c.name === "load");
1771
+ if (loadCmd) {
1772
+ const result = loadCmd.handler([this.options.load], this.session, this);
1773
+ if (result.output) {
1774
+ process.stdout.write(result.output + "\n");
1775
+ }
1776
+ }
1777
+ }
1778
+ }
1779
+ /**
1780
+ * Run in pipe mode (read all stdin, parse, and output)
1781
+ */
1782
+ runPipeMode() {
1783
+ let input = "";
1784
+ process.stdin.setEncoding("utf-8");
1785
+ process.stdin.on("data", (chunk) => {
1786
+ input += chunk;
1787
+ });
1788
+ process.stdin.on("end", () => {
1789
+ const trimmed = input.trim();
1790
+ if (!trimmed) {
1791
+ process.exit(0);
1792
+ return;
1793
+ }
1794
+ if (this.options.parseOnly) {
1795
+ const parseCmd = metaCommands.find((c) => c.name === "parse");
1796
+ if (parseCmd) {
1797
+ const result = parseCmd.handler(trimmed.split(" "), this.session, this);
1798
+ if (result.output) {
1799
+ process.stdout.write(result.output + "\n");
1800
+ }
1801
+ }
1802
+ } else {
1803
+ const lines = trimmed.split("\n");
1804
+ for (const line of lines) {
1805
+ this.handleLine(line);
1806
+ }
1807
+ }
1808
+ process.exit(0);
1809
+ });
1810
+ }
1811
+ /**
1812
+ * Print the welcome banner
1813
+ */
1814
+ printBanner() {
1815
+ const banner = `
1816
+ ${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
1817
+ \u2551 \u2551
1818
+ \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
1819
+ \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
1820
+ \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
1821
+ \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
1822
+ \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
1823
+ \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
1824
+ \u2551 \u2551
1825
+ \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}
1826
+
1827
+ ${colors.bold}ISL v${VERSION}${colors.reset} \u2014 Intent Specification Language
1828
+ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${colors.reset} to quit
1829
+ `;
1830
+ process.stdout.write(banner);
1831
+ }
1832
+ /**
1833
+ * Handle a line of input
1834
+ */
1835
+ handleLine(line) {
1836
+ const trimmed = line.trim();
1837
+ if (!trimmed && this.buffer.length === 0) {
1838
+ return;
1839
+ }
1840
+ if (trimmed.startsWith(".") && this.buffer.length === 0) {
1841
+ this.handleDotCommand(trimmed);
1842
+ return;
1843
+ }
1844
+ if (trimmed.startsWith(":") && this.buffer.length === 0) {
1845
+ this.handleDotCommand("." + trimmed.slice(1));
1846
+ return;
1847
+ }
1848
+ this.braceCount += (line.match(/\{/g) || []).length;
1849
+ this.braceCount -= (line.match(/\}/g) || []).length;
1850
+ this.buffer.push(line);
1851
+ if (this.braceCount <= 0) {
1852
+ const code = this.buffer.join("\n");
1853
+ this.buffer = [];
1854
+ this.braceCount = 0;
1855
+ this.history.add(code);
1856
+ this.session.addToHistory(code);
1857
+ this.evaluate(code);
1858
+ }
1859
+ }
1860
+ /**
1861
+ * Handle a dot command (. prefix)
1862
+ */
1863
+ handleDotCommand(input) {
1864
+ const parts = input.slice(1).split(/\s+/);
1865
+ const cmdName = parts[0]?.toLowerCase() || "";
1866
+ const args = parts.slice(1);
1867
+ const rawArgs = input.slice(1 + (cmdName.length || 0)).trim();
1868
+ const command = metaCommands.find(
1869
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
1870
+ );
1871
+ if (command) {
1872
+ this.history.add(input);
1873
+ const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
1874
+ const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : args;
1875
+ const result = command.handler(effectiveArgs, this.session, this);
1876
+ if (result.output) {
1877
+ process.stdout.write(result.output + "\n");
1878
+ }
1879
+ if (result.exit) {
1880
+ this.exit();
1881
+ }
1882
+ } else {
1883
+ const suggestion = findSimilarCommand(cmdName);
1884
+ if (suggestion) {
1885
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1886
+ process.stdout.write(formatWarning(`Did you mean: .${suggestion}?`) + "\n");
1887
+ } else {
1888
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1889
+ process.stdout.write(`Type ${colors.cyan}.help${colors.reset} for available commands
1890
+ `);
1891
+ }
1892
+ }
1893
+ }
1894
+ /**
1895
+ * Evaluate ISL code (multi-line input or bare expressions)
1896
+ */
1897
+ evaluate(code) {
1898
+ try {
1899
+ const trimmed = code.trim();
1900
+ try {
1901
+ const { parse } = __require("@isl-lang/parser");
1902
+ let parseInput = trimmed;
1903
+ const needsWrapper = !trimmed.startsWith("domain ");
1904
+ if (needsWrapper) {
1905
+ parseInput = `domain _REPL { version: "0.0.1"
1906
+ ${trimmed}
1907
+ }`;
1908
+ }
1909
+ const result = parse(parseInput, "<repl>");
1910
+ if (result.errors.length > 0) {
1911
+ for (const err of result.errors) {
1912
+ const loc = err.location;
1913
+ if (loc) {
1914
+ const adjustedLine = needsWrapper ? Math.max(1, loc.line - 1) : loc.line;
1915
+ const lines = trimmed.split("\n");
1916
+ const errorLine = lines[adjustedLine - 1] || "";
1917
+ process.stdout.write(
1918
+ `${colors.red}\u2717 Error at line ${adjustedLine}, col ${loc.column}:${colors.reset}
1919
+ `
1920
+ );
1921
+ process.stdout.write(` ${errorLine}
1922
+ `);
1923
+ process.stdout.write(` ${" ".repeat(Math.max(0, loc.column - 1))}${colors.red}^^^^^${colors.reset}
1924
+ `);
1925
+ const typeMatch = err.message.match(/Unknown type '(\w+)'/i) || err.message.match(/unexpected.*'(\w+)'/i);
1926
+ if (typeMatch) {
1927
+ const suggestion = suggestCorrection(typeMatch[1]);
1928
+ if (suggestion) {
1929
+ process.stdout.write(
1930
+ ` ${colors.yellow}Did you mean '${suggestion}'?${colors.reset}
1931
+ `
1932
+ );
1933
+ }
1934
+ } else {
1935
+ process.stdout.write(` ${err.message}
1936
+ `);
1937
+ }
1938
+ } else {
1939
+ process.stdout.write(formatError(err.message) + "\n");
1940
+ }
1941
+ }
1942
+ return;
1943
+ }
1944
+ if (result.domain) {
1945
+ this.session.setDomainAST(result.domain);
1946
+ const domain = result.domain;
1947
+ if (needsWrapper) {
1948
+ const entityCount = domain.entities?.length ?? 0;
1949
+ const behaviorCount = domain.behaviors?.length ?? 0;
1950
+ const parts = [];
1951
+ if (entityCount > 0) parts.push(`${entityCount} entit${entityCount === 1 ? "y" : "ies"}`);
1952
+ if (behaviorCount > 0) parts.push(`${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"}`);
1953
+ if (parts.length > 0) {
1954
+ process.stdout.write(
1955
+ formatSuccess(`Parsed: ${parts.join(", ")}`) + "\n"
1956
+ );
1957
+ } else {
1958
+ process.stdout.write(formatSuccess("Parsed successfully") + "\n");
1959
+ }
1960
+ } else {
1961
+ const name = domain.name?.name ?? "Unknown";
1962
+ const entityCount = domain.entities?.length ?? 0;
1963
+ const behaviorCount = domain.behaviors?.length ?? 0;
1964
+ process.stdout.write(
1965
+ formatSuccess(
1966
+ `Parsed: domain ${name} (${entityCount} entit${entityCount === 1 ? "y" : "ies"}, ${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"})`
1967
+ ) + "\n"
1968
+ );
1969
+ }
1970
+ return;
1971
+ }
1972
+ } catch {
1973
+ }
1974
+ if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
1975
+ this.evaluateIntent(trimmed);
1976
+ return;
1977
+ }
1978
+ if (trimmed.startsWith("domain ")) {
1979
+ process.stdout.write(formatSuccess("Parsed domain block") + "\n");
1980
+ return;
1981
+ }
1982
+ process.stdout.write(
1983
+ formatWarning(`Cannot evaluate: ${trimmed.split("\n")[0]}...`) + "\n"
1984
+ );
1985
+ process.stdout.write(
1986
+ `Use ${colors.cyan}.help${colors.reset} for available commands
1987
+ `
1988
+ );
1989
+ } catch (error) {
1990
+ this.printError(error);
1991
+ }
1992
+ }
1993
+ /**
1994
+ * Evaluate an intent definition
1995
+ */
1996
+ evaluateIntent(code) {
1997
+ const intent = this.session.parseIntent(code);
1998
+ if (intent) {
1999
+ this.session.defineIntent(intent);
2000
+ const preCount = intent.preconditions.length;
2001
+ const postCount = intent.postconditions.length;
2002
+ const invCount = intent.invariants.length;
2003
+ const parts = [];
2004
+ if (preCount > 0) parts.push(`${preCount} pre`);
2005
+ if (postCount > 0) parts.push(`${postCount} post`);
2006
+ if (invCount > 0) parts.push(`${invCount} invariant`);
2007
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
2008
+ process.stdout.write(
2009
+ formatSuccess(`Intent '${intent.name}' defined${summary}`) + "\n"
2010
+ );
2011
+ } else {
2012
+ const behaviorMatch = code.match(/^behavior\s+(\w+)\s*\{([\s\S]*)\}$/);
2013
+ if (behaviorMatch) {
2014
+ const name = behaviorMatch[1];
2015
+ const body = behaviorMatch[2];
2016
+ const newIntent = {
2017
+ name,
2018
+ preconditions: [],
2019
+ postconditions: [],
2020
+ invariants: [],
2021
+ scenarios: [],
2022
+ rawSource: code
2023
+ };
2024
+ const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
2025
+ if (preSection) {
2026
+ for (const line of preSection[1].trim().split("\n")) {
2027
+ const expr = line.trim().replace(/^-\s*/, "").trim();
2028
+ if (expr) newIntent.preconditions.push({ expression: expr });
2029
+ }
2030
+ }
2031
+ const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
2032
+ if (postSection) {
2033
+ for (const line of postSection[1].trim().split("\n")) {
2034
+ const expr = line.trim().replace(/^-\s*/, "").trim();
2035
+ if (expr) newIntent.postconditions.push({ expression: expr });
2036
+ }
2037
+ }
2038
+ this.session.defineIntent(newIntent);
2039
+ const parts = [];
2040
+ if (newIntent.preconditions.length > 0) parts.push(`${newIntent.preconditions.length} pre`);
2041
+ if (newIntent.postconditions.length > 0) parts.push(`${newIntent.postconditions.length} post`);
2042
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
2043
+ process.stdout.write(
2044
+ formatSuccess(`Intent '${name}' defined${summary}`) + "\n"
2045
+ );
2046
+ } else {
2047
+ process.stdout.write(formatError("Failed to parse intent definition") + "\n");
2048
+ }
2049
+ }
2050
+ }
2051
+ /**
2052
+ * Print an error
2053
+ */
2054
+ printError(error) {
2055
+ if (error instanceof Error) {
2056
+ process.stdout.write(formatError(error.message) + "\n");
2057
+ if (this.options.verbose && error.stack) {
2058
+ process.stdout.write(colors.gray + error.stack + colors.reset + "\n");
2059
+ }
2060
+ } else {
2061
+ process.stdout.write(formatError(String(error)) + "\n");
2062
+ }
2063
+ }
2064
+ /**
2065
+ * Exit the REPL
2066
+ */
2067
+ exit() {
2068
+ this.running = false;
2069
+ this.history.save();
2070
+ process.stdout.write(`
2071
+ ${colors.yellow}Goodbye!${colors.reset}
2072
+ `);
2073
+ if (this.rl) {
2074
+ this.rl.close();
2075
+ }
2076
+ process.exit(0);
2077
+ }
2078
+ /**
2079
+ * Get the session
2080
+ */
2081
+ getSession() {
2082
+ return this.session;
2083
+ }
2084
+ /**
2085
+ * Get history
2086
+ */
2087
+ getHistory() {
2088
+ return this.history;
2089
+ }
2090
+ /**
2091
+ * Execute a single command and return result (for testing)
2092
+ */
2093
+ async executeOnce(input) {
2094
+ const trimmed = input.trim();
2095
+ if (trimmed.startsWith(".") || trimmed.startsWith(":")) {
2096
+ const normalized = trimmed.startsWith(":") ? "." + trimmed.slice(1) : trimmed;
2097
+ const parts = normalized.slice(1).split(/\s+/);
2098
+ const cmdName = parts[0]?.toLowerCase() || "";
2099
+ const rawArgs = normalized.slice(1 + (cmdName.length || 0)).trim();
2100
+ const command = metaCommands.find(
2101
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
2102
+ );
2103
+ if (command) {
2104
+ const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
2105
+ const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : parts.slice(1);
2106
+ const result = command.handler(effectiveArgs, this.session, this);
2107
+ return { success: true, output: result.output };
2108
+ }
2109
+ return { success: false, error: `Unknown command: ${cmdName}` };
2110
+ }
2111
+ if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
2112
+ const intent = this.session.parseIntent(trimmed);
2113
+ if (intent) {
2114
+ this.session.defineIntent(intent);
2115
+ return { success: true, output: `Intent '${intent.name}' defined` };
2116
+ }
2117
+ return { success: false, error: "Failed to parse intent" };
2118
+ }
2119
+ return { success: false, error: "Unknown input" };
2120
+ }
2121
+ };
2122
+ var KNOWN_TYPES = [
2123
+ "String",
2124
+ "Int",
2125
+ "Decimal",
2126
+ "Boolean",
2127
+ "UUID",
2128
+ "Timestamp",
2129
+ "Duration",
2130
+ "List",
2131
+ "Map",
2132
+ "Optional",
2133
+ "Number"
2134
+ ];
2135
+ function suggestCorrection(typo) {
2136
+ const lower = typo.toLowerCase();
2137
+ for (const t of KNOWN_TYPES) {
2138
+ if (t.toLowerCase() === lower) return t;
2139
+ if (t.toLowerCase().startsWith(lower.slice(0, 3)) && Math.abs(t.length - typo.length) <= 2) {
2140
+ return t;
2141
+ }
2142
+ }
2143
+ return null;
2144
+ }
2145
+ function startREPL(options) {
2146
+ const repl = new ISLREPL(options);
2147
+ repl.start();
2148
+ return repl;
2149
+ }
2150
+
2151
+ // src/cli.ts
2152
+ function parseArgs(argv) {
2153
+ const options = {
2154
+ help: false,
2155
+ colors: true,
2156
+ verbose: false,
2157
+ parseOnly: false
2158
+ };
2159
+ for (let i = 0; i < argv.length; i++) {
2160
+ const arg = argv[i];
2161
+ switch (arg) {
2162
+ case "--help":
2163
+ case "-h":
2164
+ options.help = true;
2165
+ break;
2166
+ case "--no-color":
2167
+ options.colors = false;
2168
+ break;
2169
+ case "--verbose":
2170
+ case "-v":
2171
+ options.verbose = true;
2172
+ break;
2173
+ case "--load":
2174
+ options.load = argv[++i];
2175
+ break;
2176
+ case "--context":
2177
+ options.context = argv[++i];
2178
+ break;
2179
+ case "--parse":
2180
+ options.parseOnly = true;
2181
+ break;
2182
+ default:
2183
+ if (arg.startsWith("--load=")) {
2184
+ options.load = arg.slice(7);
2185
+ } else if (arg.startsWith("--context=")) {
2186
+ options.context = arg.slice(10);
2187
+ }
2188
+ break;
2189
+ }
2190
+ }
2191
+ return options;
2192
+ }
2193
+ function printHelp() {
2194
+ process.stdout.write(`
2195
+ ISL REPL - Intent Specification Language Interactive Shell
2196
+
2197
+ Usage: isl-repl [options]
2198
+
2199
+ Options:
2200
+ --load <file> Load an ISL file on start
2201
+ --context <json> Set initial evaluation context
2202
+ --parse Parse mode (non-interactive, for piped input)
2203
+ --no-color Disable colored output
2204
+ -v, --verbose Enable verbose output
2205
+ -h, --help Show this help message
2206
+
2207
+ Inside the REPL:
2208
+ .help Show all commands
2209
+ .parse <isl> Parse ISL and show AST
2210
+ .eval <expr> Evaluate expression against context
2211
+ .check [intent] Type check intents
2212
+ .gen [intent] Generate TypeScript from intent
2213
+ .load <file> Load an .isl file
2214
+ .context <json> Set evaluation context (mock data)
2215
+ .clear Reset session state
2216
+ .list List defined intents
2217
+ .inspect [intent] Show full details of an intent
2218
+ .history Show command history
2219
+ .exit Exit the REPL
2220
+
2221
+ Multi-line Input:
2222
+ Type ISL with braces \u2014 the REPL auto-detects multi-line:
2223
+ isl> domain Example {
2224
+ ...> entity User {
2225
+ ...> id: UUID
2226
+ ...> name: String
2227
+ ...> }
2228
+ ...> }
2229
+
2230
+ Examples:
2231
+ $ isl-repl
2232
+ $ isl-repl --load auth.isl
2233
+ $ isl-repl --context '{"user": {"id": 1}}'
2234
+ $ echo 'domain X { version: "1.0" }' | isl-repl --parse
2235
+ `);
2236
+ }
2237
+ function main() {
2238
+ const args = process.argv.slice(2);
2239
+ const options = parseArgs(args);
2240
+ if (options.help) {
2241
+ printHelp();
2242
+ process.exit(0);
2243
+ }
2244
+ startREPL(options);
2245
+ }
2246
+ var isMainModule = typeof __require !== "undefined" ? __require.main === module : process.argv[1]?.includes("cli");
2247
+ if (isMainModule) {
2248
+ main();
2249
+ }
2250
+
2251
+ exports.COMMANDS = COMMANDS;
2252
+ exports.CompletionProvider = CompletionProvider;
2253
+ exports.History = History;
2254
+ exports.ISLREPL = ISLREPL;
2255
+ exports.ISL_COMMANDS = ISL_COMMANDS;
2256
+ exports.KEYWORDS = KEYWORDS;
2257
+ exports.META_COMMANDS = META_COMMANDS;
2258
+ exports.MemoryHistory = MemoryHistory;
2259
+ exports.Session = Session;
2260
+ exports.colors = colors;
2261
+ exports.createCompleter = createCompleter;
2262
+ exports.createSession = createSession;
2263
+ exports.drawBox = drawBox;
2264
+ exports.findSimilarCommand = findSimilarCommand;
2265
+ exports.formatCondition = formatCondition;
2266
+ exports.formatError = formatError;
2267
+ exports.formatInfo = formatInfo;
2268
+ exports.formatIntent = formatIntent;
2269
+ exports.formatParseError = formatParseError;
2270
+ exports.formatSuccess = formatSuccess;
2271
+ exports.formatTable = formatTable;
2272
+ exports.formatTypeError = formatTypeError;
2273
+ exports.formatValue = formatValue;
2274
+ exports.formatWarning = formatWarning;
2275
+ exports.highlightExpression = highlightExpression;
2276
+ exports.highlightISL = highlightISL;
2277
+ exports.islCommands = islCommands;
2278
+ exports.main = main;
2279
+ exports.metaCommands = metaCommands;
2280
+ exports.startREPL = startREPL;
2281
+ exports.stripColors = stripColors;
2282
+ exports.wrapText = wrapText;
2283
+ //# sourceMappingURL=index.cjs.map
2284
+ //# sourceMappingURL=index.cjs.map