@isl-lang/repl 0.0.1 → 0.1.0
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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/cli.js +1734 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1867 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +536 -0
- package/dist/index.d.ts +536 -0
- package/dist/index.js +1812 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -14
- package/index.js +0 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1734 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/repl.ts
|
|
11
|
+
import * as readline from "readline";
|
|
12
|
+
|
|
13
|
+
// src/session.ts
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
var Session = class {
|
|
17
|
+
/** Defined intents in this session */
|
|
18
|
+
intents = /* @__PURE__ */ new Map();
|
|
19
|
+
/** Variables set during the session */
|
|
20
|
+
variables = /* @__PURE__ */ new Map();
|
|
21
|
+
/** Command history */
|
|
22
|
+
history = [];
|
|
23
|
+
/** Last evaluation result */
|
|
24
|
+
lastResult = void 0;
|
|
25
|
+
/** Loaded files */
|
|
26
|
+
loadedFiles = /* @__PURE__ */ new Set();
|
|
27
|
+
/** Session configuration */
|
|
28
|
+
config;
|
|
29
|
+
constructor(config = {}) {
|
|
30
|
+
this.config = {
|
|
31
|
+
colors: true,
|
|
32
|
+
verbose: false,
|
|
33
|
+
cwd: process.cwd(),
|
|
34
|
+
...config
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// Intent Management
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* Define a new intent
|
|
42
|
+
*/
|
|
43
|
+
defineIntent(intent) {
|
|
44
|
+
this.intents.set(intent.name, intent);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get an intent by name
|
|
48
|
+
*/
|
|
49
|
+
getIntent(name) {
|
|
50
|
+
return this.intents.get(name);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get all defined intents
|
|
54
|
+
*/
|
|
55
|
+
getAllIntents() {
|
|
56
|
+
return Array.from(this.intents.values());
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if an intent exists
|
|
60
|
+
*/
|
|
61
|
+
hasIntent(name) {
|
|
62
|
+
return this.intents.has(name);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Remove an intent
|
|
66
|
+
*/
|
|
67
|
+
removeIntent(name) {
|
|
68
|
+
return this.intents.delete(name);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get intent names for completion
|
|
72
|
+
*/
|
|
73
|
+
getIntentNames() {
|
|
74
|
+
return Array.from(this.intents.keys());
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse an intent definition from source code
|
|
78
|
+
*/
|
|
79
|
+
parseIntent(source) {
|
|
80
|
+
const trimmed = source.trim();
|
|
81
|
+
const match = trimmed.match(/^intent\s+(\w+)\s*\{([\s\S]*)\}$/);
|
|
82
|
+
if (!match) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const name = match[1];
|
|
86
|
+
const body = match[2];
|
|
87
|
+
const intent = {
|
|
88
|
+
name,
|
|
89
|
+
preconditions: [],
|
|
90
|
+
postconditions: [],
|
|
91
|
+
invariants: [],
|
|
92
|
+
scenarios: [],
|
|
93
|
+
rawSource: source
|
|
94
|
+
};
|
|
95
|
+
const preMatch = body.match(/pre(?:conditions?)?\s*:\s*([^\n]+)/g);
|
|
96
|
+
if (preMatch) {
|
|
97
|
+
for (const pre of preMatch) {
|
|
98
|
+
const expr = pre.replace(/pre(?:conditions?)?\s*:\s*/, "").trim();
|
|
99
|
+
if (expr) {
|
|
100
|
+
intent.preconditions.push({ expression: expr });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const postMatch = body.match(/post(?:conditions?)?\s*:\s*([^\n]+)/g);
|
|
105
|
+
if (postMatch) {
|
|
106
|
+
for (const post of postMatch) {
|
|
107
|
+
const expr = post.replace(/post(?:conditions?)?\s*:\s*/, "").trim();
|
|
108
|
+
if (expr) {
|
|
109
|
+
intent.postconditions.push({ expression: expr });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const invMatch = body.match(/invariants?\s*:\s*([^\n]+)/g);
|
|
114
|
+
if (invMatch) {
|
|
115
|
+
for (const inv of invMatch) {
|
|
116
|
+
const expr = inv.replace(/invariants?\s*:\s*/, "").trim();
|
|
117
|
+
if (expr) {
|
|
118
|
+
intent.invariants.push({ expression: expr });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return intent;
|
|
123
|
+
}
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
125
|
+
// Variable Management
|
|
126
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Set a variable
|
|
129
|
+
*/
|
|
130
|
+
setVariable(name, value) {
|
|
131
|
+
this.variables.set(name, value);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get a variable
|
|
135
|
+
*/
|
|
136
|
+
getVariable(name) {
|
|
137
|
+
return this.variables.get(name);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get all variables
|
|
141
|
+
*/
|
|
142
|
+
getAllVariables() {
|
|
143
|
+
return new Map(this.variables);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if a variable exists
|
|
147
|
+
*/
|
|
148
|
+
hasVariable(name) {
|
|
149
|
+
return this.variables.has(name);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Set the last result (accessible as _)
|
|
153
|
+
*/
|
|
154
|
+
setLastResult(value) {
|
|
155
|
+
this.lastResult = value;
|
|
156
|
+
this.variables.set("_", value);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the last result
|
|
160
|
+
*/
|
|
161
|
+
getLastResult() {
|
|
162
|
+
return this.lastResult;
|
|
163
|
+
}
|
|
164
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
165
|
+
// History Management
|
|
166
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
167
|
+
/**
|
|
168
|
+
* Add to history
|
|
169
|
+
*/
|
|
170
|
+
addToHistory(entry) {
|
|
171
|
+
const trimmed = entry.trim();
|
|
172
|
+
if (trimmed && (this.history.length === 0 || this.history[this.history.length - 1] !== trimmed)) {
|
|
173
|
+
this.history.push(trimmed);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get history
|
|
178
|
+
*/
|
|
179
|
+
getHistory(count) {
|
|
180
|
+
if (count) {
|
|
181
|
+
return this.history.slice(-count);
|
|
182
|
+
}
|
|
183
|
+
return [...this.history];
|
|
184
|
+
}
|
|
185
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
186
|
+
// File Loading
|
|
187
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
188
|
+
/**
|
|
189
|
+
* Load intents from an ISL file
|
|
190
|
+
*/
|
|
191
|
+
async loadFile(filePath) {
|
|
192
|
+
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(this.config.cwd, filePath);
|
|
193
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
194
|
+
return { intents: [], errors: [`File not found: ${resolvedPath}`] };
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
198
|
+
const loadedIntents = [];
|
|
199
|
+
const errors = [];
|
|
200
|
+
const intentRegex = /intent\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
|
|
201
|
+
let match;
|
|
202
|
+
while ((match = intentRegex.exec(content)) !== null) {
|
|
203
|
+
const intent = this.parseIntent(match[0]);
|
|
204
|
+
if (intent) {
|
|
205
|
+
this.defineIntent(intent);
|
|
206
|
+
loadedIntents.push(intent);
|
|
207
|
+
} else {
|
|
208
|
+
errors.push(`Failed to parse intent starting at position ${match.index}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const behaviorRegex = /behavior\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
|
|
212
|
+
while ((match = behaviorRegex.exec(content)) !== null) {
|
|
213
|
+
const intent = this.parseBehaviorAsIntent(match[1], match[2]);
|
|
214
|
+
if (intent) {
|
|
215
|
+
this.defineIntent(intent);
|
|
216
|
+
loadedIntents.push(intent);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this.loadedFiles.add(resolvedPath);
|
|
220
|
+
return { intents: loadedIntents, errors };
|
|
221
|
+
} catch (error) {
|
|
222
|
+
return {
|
|
223
|
+
intents: [],
|
|
224
|
+
errors: [`Failed to load file: ${error instanceof Error ? error.message : String(error)}`]
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Parse a behavior block as an intent
|
|
230
|
+
*/
|
|
231
|
+
parseBehaviorAsIntent(name, body) {
|
|
232
|
+
const intent = {
|
|
233
|
+
name,
|
|
234
|
+
preconditions: [],
|
|
235
|
+
postconditions: [],
|
|
236
|
+
invariants: [],
|
|
237
|
+
scenarios: [],
|
|
238
|
+
rawSource: `behavior ${name} {${body}}`
|
|
239
|
+
};
|
|
240
|
+
const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
|
|
241
|
+
if (preSection) {
|
|
242
|
+
const conditions = preSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
243
|
+
for (const cond of conditions) {
|
|
244
|
+
const expr = cond.replace(/^-\s*/, "").trim();
|
|
245
|
+
if (expr) {
|
|
246
|
+
intent.preconditions.push({ expression: expr });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
|
|
251
|
+
if (postSection) {
|
|
252
|
+
const conditions = postSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
253
|
+
for (const cond of conditions) {
|
|
254
|
+
const expr = cond.replace(/^-\s*/, "").trim();
|
|
255
|
+
if (expr) {
|
|
256
|
+
intent.postconditions.push({ expression: expr });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const invSection = body.match(/invariants?\s*\{([^}]*)\}/s);
|
|
261
|
+
if (invSection) {
|
|
262
|
+
const conditions = invSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
263
|
+
for (const cond of conditions) {
|
|
264
|
+
const expr = cond.replace(/^-\s*/, "").trim();
|
|
265
|
+
if (expr) {
|
|
266
|
+
intent.invariants.push({ expression: expr });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return intent;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Export session intents to a file
|
|
274
|
+
*/
|
|
275
|
+
async exportToFile(filePath) {
|
|
276
|
+
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(this.config.cwd, filePath);
|
|
277
|
+
try {
|
|
278
|
+
const lines = [];
|
|
279
|
+
lines.push("// Exported ISL intents");
|
|
280
|
+
lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
281
|
+
lines.push("");
|
|
282
|
+
for (const intent of this.intents.values()) {
|
|
283
|
+
lines.push(`intent ${intent.name} {`);
|
|
284
|
+
for (const pre of intent.preconditions) {
|
|
285
|
+
lines.push(` pre: ${pre.expression}`);
|
|
286
|
+
}
|
|
287
|
+
for (const post of intent.postconditions) {
|
|
288
|
+
lines.push(` post: ${post.expression}`);
|
|
289
|
+
}
|
|
290
|
+
for (const inv of intent.invariants) {
|
|
291
|
+
lines.push(` invariant: ${inv.expression}`);
|
|
292
|
+
}
|
|
293
|
+
lines.push("}");
|
|
294
|
+
lines.push("");
|
|
295
|
+
}
|
|
296
|
+
fs.writeFileSync(resolvedPath, lines.join("\n"));
|
|
297
|
+
return { success: true };
|
|
298
|
+
} catch (error) {
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
error: `Failed to export: ${error instanceof Error ? error.message : String(error)}`
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
306
|
+
// State Management
|
|
307
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
308
|
+
/**
|
|
309
|
+
* Clear all session state
|
|
310
|
+
*/
|
|
311
|
+
clear() {
|
|
312
|
+
this.intents.clear();
|
|
313
|
+
this.variables.clear();
|
|
314
|
+
this.lastResult = void 0;
|
|
315
|
+
this.loadedFiles.clear();
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Get session summary
|
|
319
|
+
*/
|
|
320
|
+
getSummary() {
|
|
321
|
+
return {
|
|
322
|
+
intentCount: this.intents.size,
|
|
323
|
+
variableCount: this.variables.size,
|
|
324
|
+
loadedFileCount: this.loadedFiles.size,
|
|
325
|
+
historyCount: this.history.length
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get loaded files
|
|
330
|
+
*/
|
|
331
|
+
getLoadedFiles() {
|
|
332
|
+
return Array.from(this.loadedFiles);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get config
|
|
336
|
+
*/
|
|
337
|
+
getConfig() {
|
|
338
|
+
return { ...this.config };
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Update config
|
|
342
|
+
*/
|
|
343
|
+
setConfig(config) {
|
|
344
|
+
this.config = { ...this.config, ...config };
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/history.ts
|
|
349
|
+
import * as fs2 from "fs";
|
|
350
|
+
import * as path2 from "path";
|
|
351
|
+
import * as os from "os";
|
|
352
|
+
var History = class {
|
|
353
|
+
entries = [];
|
|
354
|
+
position = -1;
|
|
355
|
+
maxSize;
|
|
356
|
+
historyFile;
|
|
357
|
+
unsavedCount = 0;
|
|
358
|
+
autoSaveThreshold = 10;
|
|
359
|
+
constructor(options = {}) {
|
|
360
|
+
this.maxSize = options.maxSize ?? 1e3;
|
|
361
|
+
this.historyFile = options.historyFile ?? this.getDefaultHistoryFile();
|
|
362
|
+
this.autoSaveThreshold = options.autoSaveThreshold ?? 10;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Get default history file path
|
|
366
|
+
*/
|
|
367
|
+
getDefaultHistoryFile() {
|
|
368
|
+
const homeDir = os.homedir();
|
|
369
|
+
return path2.join(homeDir, ".isl_repl_history");
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Load history from file
|
|
373
|
+
*/
|
|
374
|
+
load() {
|
|
375
|
+
try {
|
|
376
|
+
if (fs2.existsSync(this.historyFile)) {
|
|
377
|
+
const content = fs2.readFileSync(this.historyFile, "utf-8");
|
|
378
|
+
this.entries = content.split("\n").filter((line) => line.trim() !== "").slice(-this.maxSize);
|
|
379
|
+
this.position = this.entries.length;
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Save history to file
|
|
386
|
+
*/
|
|
387
|
+
save() {
|
|
388
|
+
try {
|
|
389
|
+
const dir = path2.dirname(this.historyFile);
|
|
390
|
+
if (!fs2.existsSync(dir)) {
|
|
391
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
392
|
+
}
|
|
393
|
+
fs2.writeFileSync(this.historyFile, this.entries.join("\n") + "\n");
|
|
394
|
+
this.unsavedCount = 0;
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Add entry to history
|
|
400
|
+
*/
|
|
401
|
+
add(entry) {
|
|
402
|
+
const trimmed = entry.trim();
|
|
403
|
+
if (trimmed === "") return;
|
|
404
|
+
if (this.entries.length > 0 && this.entries[this.entries.length - 1] === trimmed) {
|
|
405
|
+
this.position = this.entries.length;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
this.entries.push(trimmed);
|
|
409
|
+
if (this.entries.length > this.maxSize) {
|
|
410
|
+
this.entries = this.entries.slice(-this.maxSize);
|
|
411
|
+
}
|
|
412
|
+
this.position = this.entries.length;
|
|
413
|
+
this.unsavedCount++;
|
|
414
|
+
if (this.unsavedCount >= this.autoSaveThreshold) {
|
|
415
|
+
this.save();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Get previous entry (for up arrow)
|
|
420
|
+
*/
|
|
421
|
+
previous() {
|
|
422
|
+
if (this.entries.length === 0) return null;
|
|
423
|
+
if (this.position > 0) {
|
|
424
|
+
this.position--;
|
|
425
|
+
}
|
|
426
|
+
return this.entries[this.position] ?? null;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get next entry (for down arrow)
|
|
430
|
+
*/
|
|
431
|
+
next() {
|
|
432
|
+
if (this.entries.length === 0) return null;
|
|
433
|
+
if (this.position < this.entries.length - 1) {
|
|
434
|
+
this.position++;
|
|
435
|
+
return this.entries[this.position] ?? null;
|
|
436
|
+
}
|
|
437
|
+
this.position = this.entries.length;
|
|
438
|
+
return "";
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Reset position to end
|
|
442
|
+
*/
|
|
443
|
+
resetPosition() {
|
|
444
|
+
this.position = this.entries.length;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Search history for entries containing text
|
|
448
|
+
*/
|
|
449
|
+
search(text) {
|
|
450
|
+
const lower = text.toLowerCase();
|
|
451
|
+
return this.entries.filter(
|
|
452
|
+
(entry) => entry.toLowerCase().includes(lower)
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Search backwards from current position
|
|
457
|
+
*/
|
|
458
|
+
searchBackward(text) {
|
|
459
|
+
const lower = text.toLowerCase();
|
|
460
|
+
for (let i = this.position - 1; i >= 0; i--) {
|
|
461
|
+
if (this.entries[i].toLowerCase().includes(lower)) {
|
|
462
|
+
this.position = i;
|
|
463
|
+
return this.entries[i];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Search forward from current position
|
|
470
|
+
*/
|
|
471
|
+
searchForward(text) {
|
|
472
|
+
const lower = text.toLowerCase();
|
|
473
|
+
for (let i = this.position + 1; i < this.entries.length; i++) {
|
|
474
|
+
if (this.entries[i].toLowerCase().includes(lower)) {
|
|
475
|
+
this.position = i;
|
|
476
|
+
return this.entries[i];
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Get all entries
|
|
483
|
+
*/
|
|
484
|
+
getAll() {
|
|
485
|
+
return [...this.entries];
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Get recent entries
|
|
489
|
+
*/
|
|
490
|
+
getRecent(count) {
|
|
491
|
+
return this.entries.slice(-count);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Clear history
|
|
495
|
+
*/
|
|
496
|
+
clear() {
|
|
497
|
+
this.entries = [];
|
|
498
|
+
this.position = 0;
|
|
499
|
+
this.save();
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Get history size
|
|
503
|
+
*/
|
|
504
|
+
get size() {
|
|
505
|
+
return this.entries.length;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Get current position
|
|
509
|
+
*/
|
|
510
|
+
get currentPosition() {
|
|
511
|
+
return this.position;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// src/completions.ts
|
|
516
|
+
import * as fs3 from "fs";
|
|
517
|
+
import * as path3 from "path";
|
|
518
|
+
|
|
519
|
+
// src/formatter.ts
|
|
520
|
+
var colors = {
|
|
521
|
+
reset: "\x1B[0m",
|
|
522
|
+
bold: "\x1B[1m",
|
|
523
|
+
dim: "\x1B[2m",
|
|
524
|
+
italic: "\x1B[3m",
|
|
525
|
+
underline: "\x1B[4m",
|
|
526
|
+
// Foreground colors
|
|
527
|
+
black: "\x1B[30m",
|
|
528
|
+
red: "\x1B[31m",
|
|
529
|
+
green: "\x1B[32m",
|
|
530
|
+
yellow: "\x1B[33m",
|
|
531
|
+
blue: "\x1B[34m",
|
|
532
|
+
magenta: "\x1B[35m",
|
|
533
|
+
cyan: "\x1B[36m",
|
|
534
|
+
white: "\x1B[37m",
|
|
535
|
+
gray: "\x1B[90m",
|
|
536
|
+
// Bright foreground
|
|
537
|
+
brightRed: "\x1B[91m",
|
|
538
|
+
brightGreen: "\x1B[92m",
|
|
539
|
+
brightYellow: "\x1B[93m",
|
|
540
|
+
brightBlue: "\x1B[94m",
|
|
541
|
+
brightMagenta: "\x1B[95m",
|
|
542
|
+
brightCyan: "\x1B[96m",
|
|
543
|
+
brightWhite: "\x1B[97m",
|
|
544
|
+
// Background colors
|
|
545
|
+
bgBlack: "\x1B[40m",
|
|
546
|
+
bgRed: "\x1B[41m",
|
|
547
|
+
bgGreen: "\x1B[42m",
|
|
548
|
+
bgYellow: "\x1B[43m",
|
|
549
|
+
bgBlue: "\x1B[44m",
|
|
550
|
+
bgMagenta: "\x1B[45m",
|
|
551
|
+
bgCyan: "\x1B[46m",
|
|
552
|
+
bgWhite: "\x1B[47m"
|
|
553
|
+
};
|
|
554
|
+
function formatSuccess(message) {
|
|
555
|
+
return `${colors.green}\u2713${colors.reset} ${message}`;
|
|
556
|
+
}
|
|
557
|
+
function formatError(message) {
|
|
558
|
+
return `${colors.red}\u2717 Error:${colors.reset} ${message}`;
|
|
559
|
+
}
|
|
560
|
+
function formatWarning(message) {
|
|
561
|
+
return `${colors.yellow}\u26A0${colors.reset} ${message}`;
|
|
562
|
+
}
|
|
563
|
+
function formatIntent(intent) {
|
|
564
|
+
const lines = [
|
|
565
|
+
"",
|
|
566
|
+
`${colors.bold}Intent: ${colors.cyan}${intent.name}${colors.reset}`,
|
|
567
|
+
colors.gray + "\u2500".repeat(40) + colors.reset
|
|
568
|
+
];
|
|
569
|
+
if (intent.preconditions.length > 0) {
|
|
570
|
+
lines.push("");
|
|
571
|
+
lines.push(`${colors.bold}Preconditions:${colors.reset}`);
|
|
572
|
+
for (const pre of intent.preconditions) {
|
|
573
|
+
lines.push(` ${colors.magenta}pre:${colors.reset} ${highlightExpression(pre.expression)}`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (intent.postconditions.length > 0) {
|
|
577
|
+
lines.push("");
|
|
578
|
+
lines.push(`${colors.bold}Postconditions:${colors.reset}`);
|
|
579
|
+
for (const post of intent.postconditions) {
|
|
580
|
+
lines.push(` ${colors.magenta}post:${colors.reset} ${highlightExpression(post.expression)}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (intent.invariants.length > 0) {
|
|
584
|
+
lines.push("");
|
|
585
|
+
lines.push(`${colors.bold}Invariants:${colors.reset}`);
|
|
586
|
+
for (const inv of intent.invariants) {
|
|
587
|
+
lines.push(` ${colors.magenta}invariant:${colors.reset} ${highlightExpression(inv.expression)}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (intent.scenarios.length > 0) {
|
|
591
|
+
lines.push("");
|
|
592
|
+
lines.push(`${colors.bold}Scenarios:${colors.reset}`);
|
|
593
|
+
for (const scenario of intent.scenarios) {
|
|
594
|
+
lines.push(` ${colors.yellow}${scenario.name}${colors.reset}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
lines.push("");
|
|
598
|
+
return lines.join("\n");
|
|
599
|
+
}
|
|
600
|
+
function highlightExpression(expr) {
|
|
601
|
+
return expr.replace(/\b(and|or|not|implies)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/(>=|<=|==|!=|>|<)/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(true|false|null)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\b(forall|exists|in)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(\d+(?:\.\d+)?)\b/g, `${colors.cyan}$1${colors.reset}`).replace(/"([^"]*)"/g, `${colors.green}"$1"${colors.reset}`).replace(/\.(\w+)\(/g, `.${colors.blue}$1${colors.reset}(`).replace(/\.(\w+)(?!\()/g, `.${colors.cyan}$1${colors.reset}`);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/commands.ts
|
|
605
|
+
var metaCommands = [
|
|
606
|
+
{
|
|
607
|
+
name: "help",
|
|
608
|
+
aliases: ["h", "?"],
|
|
609
|
+
description: "Show help for commands",
|
|
610
|
+
usage: ".help [command]",
|
|
611
|
+
handler: (args, session) => {
|
|
612
|
+
if (args.length > 0) {
|
|
613
|
+
const cmdName = args[0].toLowerCase();
|
|
614
|
+
const metaCmd = metaCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
|
|
615
|
+
if (metaCmd) {
|
|
616
|
+
return {
|
|
617
|
+
output: [
|
|
618
|
+
`${colors.cyan}.${metaCmd.name}${colors.reset} - ${metaCmd.description}`,
|
|
619
|
+
`Usage: ${metaCmd.usage}`,
|
|
620
|
+
metaCmd.aliases.length > 0 ? `Aliases: ${metaCmd.aliases.map((a) => "." + a).join(", ")}` : ""
|
|
621
|
+
].filter(Boolean).join("\n")
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
const islCmd = islCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
|
|
625
|
+
if (islCmd) {
|
|
626
|
+
return {
|
|
627
|
+
output: [
|
|
628
|
+
`${colors.cyan}:${islCmd.name}${colors.reset} - ${islCmd.description}`,
|
|
629
|
+
`Usage: ${islCmd.usage}`,
|
|
630
|
+
islCmd.aliases.length > 0 ? `Aliases: ${islCmd.aliases.map((a) => ":" + a).join(", ")}` : ""
|
|
631
|
+
].filter(Boolean).join("\n")
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return { output: formatError(`Unknown command: ${cmdName}`) };
|
|
635
|
+
}
|
|
636
|
+
const lines = [
|
|
637
|
+
"",
|
|
638
|
+
`${colors.bold}Meta Commands${colors.reset} ${colors.gray}(REPL control)${colors.reset}`,
|
|
639
|
+
"",
|
|
640
|
+
...metaCommands.map((c) => ` ${colors.cyan}.${c.name.padEnd(10)}${colors.reset} ${c.description}`),
|
|
641
|
+
"",
|
|
642
|
+
`${colors.bold}ISL Commands${colors.reset} ${colors.gray}(specification operations)${colors.reset}`,
|
|
643
|
+
"",
|
|
644
|
+
...islCommands.map((c) => ` ${colors.cyan}:${c.name.padEnd(10)}${colors.reset} ${c.description}`),
|
|
645
|
+
"",
|
|
646
|
+
`${colors.bold}ISL Syntax${colors.reset}`,
|
|
647
|
+
"",
|
|
648
|
+
` ${colors.yellow}intent${colors.reset} Name {`,
|
|
649
|
+
` ${colors.magenta}pre${colors.reset}: condition`,
|
|
650
|
+
` ${colors.magenta}post${colors.reset}: condition`,
|
|
651
|
+
` }`,
|
|
652
|
+
"",
|
|
653
|
+
`Type ${colors.cyan}.help <command>${colors.reset} for detailed help.`,
|
|
654
|
+
""
|
|
655
|
+
];
|
|
656
|
+
return { output: lines.join("\n") };
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: "exit",
|
|
661
|
+
aliases: ["quit", "q"],
|
|
662
|
+
description: "Exit the REPL",
|
|
663
|
+
usage: ".exit",
|
|
664
|
+
handler: () => {
|
|
665
|
+
return { exit: true };
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
name: "clear",
|
|
670
|
+
aliases: ["cls"],
|
|
671
|
+
description: "Clear session state (intents, variables)",
|
|
672
|
+
usage: ".clear",
|
|
673
|
+
handler: (args, session) => {
|
|
674
|
+
session.clear();
|
|
675
|
+
return { output: formatSuccess("Session cleared") };
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: "history",
|
|
680
|
+
aliases: ["hist"],
|
|
681
|
+
description: "Show command history",
|
|
682
|
+
usage: ".history [n]",
|
|
683
|
+
handler: (args, session) => {
|
|
684
|
+
const count = args.length > 0 ? parseInt(args[0]) : 10;
|
|
685
|
+
const history = session.getHistory(count);
|
|
686
|
+
if (history.length === 0) {
|
|
687
|
+
return { output: "No history." };
|
|
688
|
+
}
|
|
689
|
+
const lines = [
|
|
690
|
+
`${colors.bold}History${colors.reset} (last ${history.length} entries)`,
|
|
691
|
+
"",
|
|
692
|
+
...history.map((entry, i) => {
|
|
693
|
+
const num = String(i + 1).padStart(3);
|
|
694
|
+
const preview = entry.split("\n")[0];
|
|
695
|
+
const more = entry.includes("\n") ? ` ${colors.gray}...${colors.reset}` : "";
|
|
696
|
+
return ` ${colors.gray}${num}${colors.reset} ${preview}${more}`;
|
|
697
|
+
})
|
|
698
|
+
];
|
|
699
|
+
return { output: lines.join("\n") };
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
];
|
|
703
|
+
var islCommands = [
|
|
704
|
+
{
|
|
705
|
+
name: "check",
|
|
706
|
+
aliases: ["c"],
|
|
707
|
+
description: "Type check an intent",
|
|
708
|
+
usage: ":check <intent>",
|
|
709
|
+
handler: (args, session) => {
|
|
710
|
+
if (args.length === 0) {
|
|
711
|
+
const intents = session.getAllIntents();
|
|
712
|
+
if (intents.length === 0) {
|
|
713
|
+
return { output: formatWarning("No intents defined. Define one with: intent Name { ... }") };
|
|
714
|
+
}
|
|
715
|
+
const lines2 = [
|
|
716
|
+
formatSuccess("Type check passed"),
|
|
717
|
+
""
|
|
718
|
+
];
|
|
719
|
+
for (const intent2 of intents) {
|
|
720
|
+
lines2.push(`${colors.bold}${intent2.name}${colors.reset}`);
|
|
721
|
+
for (const pre of intent2.preconditions) {
|
|
722
|
+
lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightCondition(pre.expression)}`);
|
|
723
|
+
}
|
|
724
|
+
for (const post of intent2.postconditions) {
|
|
725
|
+
lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightCondition(post.expression)}`);
|
|
726
|
+
}
|
|
727
|
+
lines2.push("");
|
|
728
|
+
}
|
|
729
|
+
return { output: lines2.join("\n") };
|
|
730
|
+
}
|
|
731
|
+
const intentName = args[0];
|
|
732
|
+
const intent = session.getIntent(intentName);
|
|
733
|
+
if (!intent) {
|
|
734
|
+
const available = session.getIntentNames().join(", ") || "(none)";
|
|
735
|
+
return {
|
|
736
|
+
output: formatError(`Unknown intent: ${intentName}
|
|
737
|
+
Available: ${available}`)
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const lines = [
|
|
741
|
+
formatSuccess("Type check passed"),
|
|
742
|
+
""
|
|
743
|
+
];
|
|
744
|
+
for (const pre of intent.preconditions) {
|
|
745
|
+
lines.push(` pre: ${highlightCondition(pre.expression)} ${colors.green}\u2713${colors.reset}`);
|
|
746
|
+
}
|
|
747
|
+
for (const post of intent.postconditions) {
|
|
748
|
+
lines.push(` post: ${highlightCondition(post.expression)} ${colors.green}\u2713${colors.reset}`);
|
|
749
|
+
}
|
|
750
|
+
return { output: lines.join("\n") };
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
name: "gen",
|
|
755
|
+
aliases: ["generate", "g"],
|
|
756
|
+
description: "Generate code from an intent",
|
|
757
|
+
usage: ":gen <target> <intent>",
|
|
758
|
+
handler: (args, session) => {
|
|
759
|
+
if (args.length < 2) {
|
|
760
|
+
return {
|
|
761
|
+
output: [
|
|
762
|
+
"Usage: :gen <target> <intent>",
|
|
763
|
+
"",
|
|
764
|
+
"Targets:",
|
|
765
|
+
" typescript Generate TypeScript contract",
|
|
766
|
+
" rust Generate Rust contract",
|
|
767
|
+
" go Generate Go contract",
|
|
768
|
+
" openapi Generate OpenAPI schema"
|
|
769
|
+
].join("\n")
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const target = args[0].toLowerCase();
|
|
773
|
+
const intentName = args[1];
|
|
774
|
+
const intent = session.getIntent(intentName);
|
|
775
|
+
if (!intent) {
|
|
776
|
+
const available = session.getIntentNames().join(", ") || "(none)";
|
|
777
|
+
return {
|
|
778
|
+
output: formatError(`Unknown intent: ${intentName}
|
|
779
|
+
Available: ${available}`)
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
switch (target) {
|
|
783
|
+
case "typescript":
|
|
784
|
+
case "ts":
|
|
785
|
+
return { output: generateTypeScript(intent) };
|
|
786
|
+
case "rust":
|
|
787
|
+
case "rs":
|
|
788
|
+
return { output: generateRust(intent) };
|
|
789
|
+
case "go":
|
|
790
|
+
return { output: generateGo(intent) };
|
|
791
|
+
case "openapi":
|
|
792
|
+
case "oas":
|
|
793
|
+
return { output: generateOpenAPI(intent) };
|
|
794
|
+
default:
|
|
795
|
+
return {
|
|
796
|
+
output: formatError(`Unknown target: ${target}
|
|
797
|
+
Available: typescript, rust, go, openapi`)
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
name: "load",
|
|
804
|
+
aliases: ["l"],
|
|
805
|
+
description: "Load intents from a file",
|
|
806
|
+
usage: ":load <file.isl>",
|
|
807
|
+
handler: (args, session) => {
|
|
808
|
+
if (args.length === 0) {
|
|
809
|
+
return { output: "Usage: :load <file.isl>" };
|
|
810
|
+
}
|
|
811
|
+
const filePath = args[0];
|
|
812
|
+
let result = { intents: [], errors: [] };
|
|
813
|
+
session.loadFile(filePath).then((r) => {
|
|
814
|
+
result = r;
|
|
815
|
+
}).catch((e) => {
|
|
816
|
+
result.errors.push(String(e));
|
|
817
|
+
});
|
|
818
|
+
const fs4 = __require("fs");
|
|
819
|
+
const path4 = __require("path");
|
|
820
|
+
try {
|
|
821
|
+
const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
|
|
822
|
+
if (!fs4.existsSync(resolvedPath)) {
|
|
823
|
+
return { output: formatError(`File not found: ${resolvedPath}`) };
|
|
824
|
+
}
|
|
825
|
+
const content = fs4.readFileSync(resolvedPath, "utf-8");
|
|
826
|
+
const intentRegex = /(?:intent|behavior)\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
|
|
827
|
+
let match;
|
|
828
|
+
let count = 0;
|
|
829
|
+
while ((match = intentRegex.exec(content)) !== null) {
|
|
830
|
+
const intent = session.parseIntent(match[0]);
|
|
831
|
+
if (intent) {
|
|
832
|
+
session.defineIntent(intent);
|
|
833
|
+
count++;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (count === 0) {
|
|
837
|
+
return { output: formatWarning("No intents found in file") };
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
output: formatSuccess(`Loaded ${count} intent(s) from ${filePath}`)
|
|
841
|
+
};
|
|
842
|
+
} catch (error) {
|
|
843
|
+
return {
|
|
844
|
+
output: formatError(`Failed to load: ${error instanceof Error ? error.message : String(error)}`)
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
name: "list",
|
|
851
|
+
aliases: ["ls"],
|
|
852
|
+
description: "List all defined intents",
|
|
853
|
+
usage: ":list",
|
|
854
|
+
handler: (args, session) => {
|
|
855
|
+
const intents = session.getAllIntents();
|
|
856
|
+
if (intents.length === 0) {
|
|
857
|
+
return { output: "No intents defined." };
|
|
858
|
+
}
|
|
859
|
+
const lines = [""];
|
|
860
|
+
for (const intent of intents) {
|
|
861
|
+
const preCount = intent.preconditions.length;
|
|
862
|
+
const postCount = intent.postconditions.length;
|
|
863
|
+
const invCount = intent.invariants.length;
|
|
864
|
+
const parts = [];
|
|
865
|
+
if (preCount > 0) parts.push(`${preCount} pre`);
|
|
866
|
+
if (postCount > 0) parts.push(`${postCount} post`);
|
|
867
|
+
if (invCount > 0) parts.push(`${invCount} invariant`);
|
|
868
|
+
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
869
|
+
lines.push(` ${colors.cyan}${intent.name}${colors.reset}${summary}`);
|
|
870
|
+
}
|
|
871
|
+
lines.push("");
|
|
872
|
+
return { output: lines.join("\n") };
|
|
873
|
+
}
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
name: "inspect",
|
|
877
|
+
aliases: ["i", "show"],
|
|
878
|
+
description: "Show full details of an intent",
|
|
879
|
+
usage: ":inspect <intent>",
|
|
880
|
+
handler: (args, session) => {
|
|
881
|
+
if (args.length === 0) {
|
|
882
|
+
const summary = session.getSummary();
|
|
883
|
+
const files = session.getLoadedFiles();
|
|
884
|
+
const lines = [
|
|
885
|
+
"",
|
|
886
|
+
`${colors.bold}Session Summary${colors.reset}`,
|
|
887
|
+
"",
|
|
888
|
+
` Intents: ${summary.intentCount}`,
|
|
889
|
+
` Variables: ${summary.variableCount}`,
|
|
890
|
+
` History: ${summary.historyCount} entries`
|
|
891
|
+
];
|
|
892
|
+
if (files.length > 0) {
|
|
893
|
+
lines.push("");
|
|
894
|
+
lines.push(`${colors.bold}Loaded Files${colors.reset}`);
|
|
895
|
+
for (const file of files) {
|
|
896
|
+
lines.push(` ${file}`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
lines.push("");
|
|
900
|
+
return { output: lines.join("\n") };
|
|
901
|
+
}
|
|
902
|
+
const intentName = args[0];
|
|
903
|
+
const intent = session.getIntent(intentName);
|
|
904
|
+
if (!intent) {
|
|
905
|
+
const available = session.getIntentNames().join(", ") || "(none)";
|
|
906
|
+
return {
|
|
907
|
+
output: formatError(`Unknown intent: ${intentName}
|
|
908
|
+
Available: ${available}`)
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
return { output: formatIntent(intent) };
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
name: "export",
|
|
916
|
+
aliases: ["save"],
|
|
917
|
+
description: "Export intents to a file",
|
|
918
|
+
usage: ":export <file.isl>",
|
|
919
|
+
handler: (args, session) => {
|
|
920
|
+
if (args.length === 0) {
|
|
921
|
+
return { output: "Usage: :export <file.isl>" };
|
|
922
|
+
}
|
|
923
|
+
const filePath = args[0];
|
|
924
|
+
const fs4 = __require("fs");
|
|
925
|
+
const path4 = __require("path");
|
|
926
|
+
try {
|
|
927
|
+
const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
|
|
928
|
+
const intents = session.getAllIntents();
|
|
929
|
+
if (intents.length === 0) {
|
|
930
|
+
return { output: formatWarning("No intents to export") };
|
|
931
|
+
}
|
|
932
|
+
const lines = [];
|
|
933
|
+
lines.push("// Exported ISL intents");
|
|
934
|
+
lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
935
|
+
lines.push("");
|
|
936
|
+
for (const intent of intents) {
|
|
937
|
+
lines.push(`intent ${intent.name} {`);
|
|
938
|
+
for (const pre of intent.preconditions) {
|
|
939
|
+
lines.push(` pre: ${pre.expression}`);
|
|
940
|
+
}
|
|
941
|
+
for (const post of intent.postconditions) {
|
|
942
|
+
lines.push(` post: ${post.expression}`);
|
|
943
|
+
}
|
|
944
|
+
for (const inv of intent.invariants) {
|
|
945
|
+
lines.push(` invariant: ${inv.expression}`);
|
|
946
|
+
}
|
|
947
|
+
lines.push("}");
|
|
948
|
+
lines.push("");
|
|
949
|
+
}
|
|
950
|
+
fs4.writeFileSync(resolvedPath, lines.join("\n"));
|
|
951
|
+
return { output: formatSuccess(`Exported ${intents.length} intent(s) to ${filePath}`) };
|
|
952
|
+
} catch (error) {
|
|
953
|
+
return {
|
|
954
|
+
output: formatError(`Failed to export: ${error instanceof Error ? error.message : String(error)}`)
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
];
|
|
960
|
+
function generateTypeScript(intent) {
|
|
961
|
+
const lines = [
|
|
962
|
+
`${colors.gray}// Generated TypeScript${colors.reset}`,
|
|
963
|
+
`interface ${intent.name}Contract {`
|
|
964
|
+
];
|
|
965
|
+
if (intent.preconditions.length > 0) {
|
|
966
|
+
for (const pre of intent.preconditions) {
|
|
967
|
+
const varName = extractVariableName(pre.expression);
|
|
968
|
+
const type = inferType(pre.expression);
|
|
969
|
+
lines.push(` pre: (${varName}: ${type}) => boolean;`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (intent.postconditions.length > 0) {
|
|
973
|
+
for (const post of intent.postconditions) {
|
|
974
|
+
const varName = extractVariableName(post.expression);
|
|
975
|
+
const type = inferType(post.expression);
|
|
976
|
+
lines.push(` post: (${varName}: ${type}) => boolean;`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
lines.push("}");
|
|
980
|
+
return lines.join("\n");
|
|
981
|
+
}
|
|
982
|
+
function generateRust(intent) {
|
|
983
|
+
const lines = [
|
|
984
|
+
`${colors.gray}// Generated Rust${colors.reset}`,
|
|
985
|
+
`pub trait ${intent.name}Contract {`
|
|
986
|
+
];
|
|
987
|
+
if (intent.preconditions.length > 0) {
|
|
988
|
+
for (const pre of intent.preconditions) {
|
|
989
|
+
const varName = extractVariableName(pre.expression);
|
|
990
|
+
const type = inferRustType(pre.expression);
|
|
991
|
+
lines.push(` fn check_pre(&self, ${varName}: ${type}) -> bool;`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (intent.postconditions.length > 0) {
|
|
995
|
+
for (const post of intent.postconditions) {
|
|
996
|
+
const varName = extractVariableName(post.expression);
|
|
997
|
+
const type = inferRustType(post.expression);
|
|
998
|
+
lines.push(` fn check_post(&self, ${varName}: ${type}) -> bool;`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
lines.push("}");
|
|
1002
|
+
return lines.join("\n");
|
|
1003
|
+
}
|
|
1004
|
+
function generateGo(intent) {
|
|
1005
|
+
const lines = [
|
|
1006
|
+
`${colors.gray}// Generated Go${colors.reset}`,
|
|
1007
|
+
`type ${intent.name}Contract interface {`
|
|
1008
|
+
];
|
|
1009
|
+
if (intent.preconditions.length > 0) {
|
|
1010
|
+
for (const pre of intent.preconditions) {
|
|
1011
|
+
const varName = extractVariableName(pre.expression);
|
|
1012
|
+
const type = inferGoType(pre.expression);
|
|
1013
|
+
lines.push(` CheckPre(${varName} ${type}) bool`);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
if (intent.postconditions.length > 0) {
|
|
1017
|
+
for (const post of intent.postconditions) {
|
|
1018
|
+
const varName = extractVariableName(post.expression);
|
|
1019
|
+
const type = inferGoType(post.expression);
|
|
1020
|
+
lines.push(` CheckPost(${varName} ${type}) bool`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
lines.push("}");
|
|
1024
|
+
return lines.join("\n");
|
|
1025
|
+
}
|
|
1026
|
+
function generateOpenAPI(intent) {
|
|
1027
|
+
const lines = [
|
|
1028
|
+
`${colors.gray}# Generated OpenAPI${colors.reset}`,
|
|
1029
|
+
`openapi: 3.0.0`,
|
|
1030
|
+
`paths:`,
|
|
1031
|
+
` /${intent.name.toLowerCase()}:`,
|
|
1032
|
+
` post:`,
|
|
1033
|
+
` summary: ${intent.name}`,
|
|
1034
|
+
` requestBody:`,
|
|
1035
|
+
` content:`,
|
|
1036
|
+
` application/json:`,
|
|
1037
|
+
` schema:`,
|
|
1038
|
+
` type: object`
|
|
1039
|
+
];
|
|
1040
|
+
if (intent.preconditions.length > 0) {
|
|
1041
|
+
lines.push(` # Preconditions:`);
|
|
1042
|
+
for (const pre of intent.preconditions) {
|
|
1043
|
+
lines.push(` # - ${pre.expression}`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
lines.push(` responses:`);
|
|
1047
|
+
lines.push(` '200':`);
|
|
1048
|
+
lines.push(` description: Success`);
|
|
1049
|
+
if (intent.postconditions.length > 0) {
|
|
1050
|
+
lines.push(` # Postconditions:`);
|
|
1051
|
+
for (const post of intent.postconditions) {
|
|
1052
|
+
lines.push(` # - ${post.expression}`);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return lines.join("\n");
|
|
1056
|
+
}
|
|
1057
|
+
function extractVariableName(expression) {
|
|
1058
|
+
const match = expression.match(/^(\w+)/);
|
|
1059
|
+
return match ? match[1] : "input";
|
|
1060
|
+
}
|
|
1061
|
+
function inferType(expression) {
|
|
1062
|
+
if (expression.includes(".length")) return "string";
|
|
1063
|
+
if (expression.includes(".startsWith")) return "string";
|
|
1064
|
+
if (expression.includes(".endsWith")) return "string";
|
|
1065
|
+
if (expression.includes(".includes")) return "string";
|
|
1066
|
+
if (expression.includes(" > ") || expression.includes(" < ")) return "number";
|
|
1067
|
+
return "unknown";
|
|
1068
|
+
}
|
|
1069
|
+
function inferRustType(expression) {
|
|
1070
|
+
if (expression.includes(".length") || expression.includes(".len()")) return "&str";
|
|
1071
|
+
if (expression.includes(".starts_with")) return "&str";
|
|
1072
|
+
if (expression.includes(" > ") || expression.includes(" < ")) return "i32";
|
|
1073
|
+
return "&str";
|
|
1074
|
+
}
|
|
1075
|
+
function inferGoType(expression) {
|
|
1076
|
+
if (expression.includes(".length") || expression.includes("len(")) return "string";
|
|
1077
|
+
if (expression.includes(" > ") || expression.includes(" < ")) return "int";
|
|
1078
|
+
return "string";
|
|
1079
|
+
}
|
|
1080
|
+
function highlightCondition(expression) {
|
|
1081
|
+
return expression.replace(
|
|
1082
|
+
/(\w+)\s*(>|<|>=|<=|==|!=)\s*(\d+|"[^"]*")/g,
|
|
1083
|
+
`${colors.blue}$1${colors.reset} ${colors.yellow}$2${colors.reset} ${colors.green}$3${colors.reset}`
|
|
1084
|
+
).replace(/\b(true|false)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\.(length|startsWith|endsWith|includes)/g, `.${colors.cyan}$1${colors.reset}`);
|
|
1085
|
+
}
|
|
1086
|
+
function levenshteinDistance(a, b) {
|
|
1087
|
+
const matrix = [];
|
|
1088
|
+
for (let i = 0; i <= b.length; i++) {
|
|
1089
|
+
matrix[i] = [i];
|
|
1090
|
+
}
|
|
1091
|
+
for (let j = 0; j <= a.length; j++) {
|
|
1092
|
+
matrix[0][j] = j;
|
|
1093
|
+
}
|
|
1094
|
+
for (let i = 1; i <= b.length; i++) {
|
|
1095
|
+
for (let j = 1; j <= a.length; j++) {
|
|
1096
|
+
const cost = a[j - 1] === b[i - 1] ? 0 : 1;
|
|
1097
|
+
matrix[i][j] = Math.min(
|
|
1098
|
+
matrix[i - 1][j] + 1,
|
|
1099
|
+
matrix[i][j - 1] + 1,
|
|
1100
|
+
matrix[i - 1][j - 1] + cost
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return matrix[b.length][a.length];
|
|
1105
|
+
}
|
|
1106
|
+
function findSimilarCommand(input, type) {
|
|
1107
|
+
const commands = type === "meta" ? metaCommands : islCommands;
|
|
1108
|
+
const names = commands.flatMap((c) => [c.name, ...c.aliases]);
|
|
1109
|
+
let bestMatch = null;
|
|
1110
|
+
let bestDistance = Infinity;
|
|
1111
|
+
for (const name of names) {
|
|
1112
|
+
const distance = levenshteinDistance(input.toLowerCase(), name.toLowerCase());
|
|
1113
|
+
if (distance < bestDistance && distance <= 2) {
|
|
1114
|
+
bestDistance = distance;
|
|
1115
|
+
bestMatch = name;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return bestMatch;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/completions.ts
|
|
1122
|
+
var KEYWORDS = [
|
|
1123
|
+
{ text: "intent", type: "keyword", description: "Define an intent" },
|
|
1124
|
+
{ text: "behavior", type: "keyword", description: "Define a behavior" },
|
|
1125
|
+
{ text: "pre", type: "keyword", description: "Precondition" },
|
|
1126
|
+
{ text: "post", type: "keyword", description: "Postcondition" },
|
|
1127
|
+
{ text: "invariant", type: "keyword", description: "Invariant" },
|
|
1128
|
+
{ text: "true", type: "keyword", description: "Boolean true" },
|
|
1129
|
+
{ text: "false", type: "keyword", description: "Boolean false" },
|
|
1130
|
+
{ text: "null", type: "keyword", description: "Null value" },
|
|
1131
|
+
{ text: "and", type: "keyword", description: "Logical AND" },
|
|
1132
|
+
{ text: "or", type: "keyword", description: "Logical OR" },
|
|
1133
|
+
{ text: "not", type: "keyword", description: "Logical NOT" },
|
|
1134
|
+
{ text: "implies", type: "keyword", description: "Logical implication" },
|
|
1135
|
+
{ text: "forall", type: "keyword", description: "Universal quantifier" },
|
|
1136
|
+
{ text: "exists", type: "keyword", description: "Existential quantifier" },
|
|
1137
|
+
{ text: "in", type: "keyword", description: "Membership test" }
|
|
1138
|
+
];
|
|
1139
|
+
var META_COMMANDS = metaCommands.map((cmd) => ({
|
|
1140
|
+
text: `.${cmd.name}`,
|
|
1141
|
+
type: "command",
|
|
1142
|
+
description: cmd.description
|
|
1143
|
+
}));
|
|
1144
|
+
var ISL_COMMANDS = islCommands.map((cmd) => ({
|
|
1145
|
+
text: `:${cmd.name}`,
|
|
1146
|
+
type: "command",
|
|
1147
|
+
description: cmd.description
|
|
1148
|
+
}));
|
|
1149
|
+
var COMMANDS = [...META_COMMANDS, ...ISL_COMMANDS];
|
|
1150
|
+
var GEN_TARGETS = [
|
|
1151
|
+
{ text: "typescript", type: "keyword", description: "Generate TypeScript contract" },
|
|
1152
|
+
{ text: "rust", type: "keyword", description: "Generate Rust contract" },
|
|
1153
|
+
{ text: "go", type: "keyword", description: "Generate Go contract" },
|
|
1154
|
+
{ text: "openapi", type: "keyword", description: "Generate OpenAPI schema" }
|
|
1155
|
+
];
|
|
1156
|
+
var CompletionProvider = class {
|
|
1157
|
+
constructor(session) {
|
|
1158
|
+
this.session = session;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Update the session reference
|
|
1162
|
+
*/
|
|
1163
|
+
setSession(session) {
|
|
1164
|
+
this.session = session;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Get completions for a line
|
|
1168
|
+
*/
|
|
1169
|
+
complete(line) {
|
|
1170
|
+
const trimmed = line.trimStart();
|
|
1171
|
+
if (trimmed.startsWith(".")) {
|
|
1172
|
+
return this.completeMetaCommand(trimmed);
|
|
1173
|
+
}
|
|
1174
|
+
if (trimmed.startsWith(":")) {
|
|
1175
|
+
return this.completeISLCommand(trimmed);
|
|
1176
|
+
}
|
|
1177
|
+
return this.completeExpression(trimmed);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Complete meta commands
|
|
1181
|
+
*/
|
|
1182
|
+
completeMetaCommand(line) {
|
|
1183
|
+
const parts = line.slice(1).split(/\s+/);
|
|
1184
|
+
const cmdPart = parts[0] || "";
|
|
1185
|
+
if (parts.length === 1) {
|
|
1186
|
+
const matches = META_COMMANDS.filter(
|
|
1187
|
+
(c) => c.text.toLowerCase().startsWith(`.${cmdPart.toLowerCase()}`)
|
|
1188
|
+
);
|
|
1189
|
+
return [matches.length > 0 ? matches : META_COMMANDS, "." + cmdPart];
|
|
1190
|
+
}
|
|
1191
|
+
return [[], line];
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Complete ISL commands
|
|
1195
|
+
*/
|
|
1196
|
+
completeISLCommand(line) {
|
|
1197
|
+
const parts = line.slice(1).split(/\s+/);
|
|
1198
|
+
const cmdPart = parts[0] || "";
|
|
1199
|
+
const args = parts.slice(1);
|
|
1200
|
+
if (parts.length === 1) {
|
|
1201
|
+
const matches = ISL_COMMANDS.filter(
|
|
1202
|
+
(c) => c.text.toLowerCase().startsWith(`:${cmdPart.toLowerCase()}`)
|
|
1203
|
+
);
|
|
1204
|
+
return [matches.length > 0 ? matches : ISL_COMMANDS, ":" + cmdPart];
|
|
1205
|
+
}
|
|
1206
|
+
const cmd = cmdPart.toLowerCase();
|
|
1207
|
+
switch (cmd) {
|
|
1208
|
+
case "gen":
|
|
1209
|
+
case "generate":
|
|
1210
|
+
case "g":
|
|
1211
|
+
return this.completeGenCommand(args);
|
|
1212
|
+
case "check":
|
|
1213
|
+
case "c":
|
|
1214
|
+
case "inspect":
|
|
1215
|
+
case "i":
|
|
1216
|
+
case "show":
|
|
1217
|
+
return this.completeIntentName(args[0] || "");
|
|
1218
|
+
case "load":
|
|
1219
|
+
case "l":
|
|
1220
|
+
case "export":
|
|
1221
|
+
case "save":
|
|
1222
|
+
return this.completeFilePath(args[0] || "");
|
|
1223
|
+
default:
|
|
1224
|
+
return [[], line];
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Complete :gen command arguments
|
|
1229
|
+
*/
|
|
1230
|
+
completeGenCommand(args) {
|
|
1231
|
+
if (args.length <= 1) {
|
|
1232
|
+
const partial = args[0] || "";
|
|
1233
|
+
const matches = GEN_TARGETS.filter(
|
|
1234
|
+
(t) => t.text.toLowerCase().startsWith(partial.toLowerCase())
|
|
1235
|
+
);
|
|
1236
|
+
return [matches.length > 0 ? matches : GEN_TARGETS, partial];
|
|
1237
|
+
}
|
|
1238
|
+
return this.completeIntentName(args[1] || "");
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Complete intent names
|
|
1242
|
+
*/
|
|
1243
|
+
completeIntentName(partial) {
|
|
1244
|
+
const intents = this.session.getAllIntents();
|
|
1245
|
+
const items = intents.map((intent) => ({
|
|
1246
|
+
text: intent.name,
|
|
1247
|
+
type: "intent",
|
|
1248
|
+
description: `${intent.preconditions.length} pre, ${intent.postconditions.length} post`
|
|
1249
|
+
}));
|
|
1250
|
+
const matches = items.filter(
|
|
1251
|
+
(i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
|
|
1252
|
+
);
|
|
1253
|
+
return [matches.length > 0 ? matches : items, partial];
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Complete file paths
|
|
1257
|
+
*/
|
|
1258
|
+
completeFilePath(partial) {
|
|
1259
|
+
try {
|
|
1260
|
+
const dir = path3.dirname(partial) || ".";
|
|
1261
|
+
const base = path3.basename(partial);
|
|
1262
|
+
const resolvedDir = path3.resolve(this.session.getConfig().cwd || process.cwd(), dir);
|
|
1263
|
+
if (!fs3.existsSync(resolvedDir)) {
|
|
1264
|
+
return [[], partial];
|
|
1265
|
+
}
|
|
1266
|
+
const entries = fs3.readdirSync(resolvedDir, { withFileTypes: true });
|
|
1267
|
+
const items = entries.filter((e) => {
|
|
1268
|
+
const name = e.name.toLowerCase();
|
|
1269
|
+
return name.startsWith(base.toLowerCase()) && (e.isDirectory() || name.endsWith(".isl"));
|
|
1270
|
+
}).map((e) => ({
|
|
1271
|
+
text: path3.join(dir, e.name + (e.isDirectory() ? "/" : "")),
|
|
1272
|
+
type: "file",
|
|
1273
|
+
description: e.isDirectory() ? "Directory" : "ISL file"
|
|
1274
|
+
}));
|
|
1275
|
+
return [items, partial];
|
|
1276
|
+
} catch {
|
|
1277
|
+
return [[], partial];
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Complete expressions
|
|
1282
|
+
*/
|
|
1283
|
+
completeExpression(line) {
|
|
1284
|
+
const items = [...KEYWORDS];
|
|
1285
|
+
for (const intent of this.session.getAllIntents()) {
|
|
1286
|
+
items.push({
|
|
1287
|
+
text: intent.name,
|
|
1288
|
+
type: "intent",
|
|
1289
|
+
description: "Defined intent"
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
for (const [name] of this.session.getAllVariables()) {
|
|
1293
|
+
items.push({
|
|
1294
|
+
text: name,
|
|
1295
|
+
type: "variable",
|
|
1296
|
+
description: "Variable"
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
items.push({
|
|
1300
|
+
text: "_",
|
|
1301
|
+
type: "variable",
|
|
1302
|
+
description: "Last result"
|
|
1303
|
+
});
|
|
1304
|
+
const match = line.match(/[\w.]+$/);
|
|
1305
|
+
const partial = match ? match[0] : "";
|
|
1306
|
+
const matches = items.filter(
|
|
1307
|
+
(i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
|
|
1308
|
+
);
|
|
1309
|
+
return [matches.length > 0 ? matches : items, partial];
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Get all available completions (for help)
|
|
1313
|
+
*/
|
|
1314
|
+
getAllCompletions() {
|
|
1315
|
+
return {
|
|
1316
|
+
metaCommands: META_COMMANDS,
|
|
1317
|
+
islCommands: ISL_COMMANDS,
|
|
1318
|
+
keywords: KEYWORDS,
|
|
1319
|
+
intents: this.session.getAllIntents().map((i) => ({
|
|
1320
|
+
text: i.name,
|
|
1321
|
+
type: "intent",
|
|
1322
|
+
description: `${i.preconditions.length} pre, ${i.postconditions.length} post`
|
|
1323
|
+
}))
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
function createCompleter(provider) {
|
|
1328
|
+
return (line) => {
|
|
1329
|
+
const [items, partial] = provider.complete(line);
|
|
1330
|
+
const completions = items.map((i) => i.text);
|
|
1331
|
+
return [completions, partial];
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/repl.ts
|
|
1336
|
+
var VERSION = "0.1.0";
|
|
1337
|
+
var PROMPT = `${colors.cyan}isl>${colors.reset} `;
|
|
1338
|
+
var CONTINUATION_PROMPT = `${colors.cyan}...>${colors.reset} `;
|
|
1339
|
+
var ISLREPL = class {
|
|
1340
|
+
session;
|
|
1341
|
+
history;
|
|
1342
|
+
completionProvider;
|
|
1343
|
+
rl = null;
|
|
1344
|
+
buffer = [];
|
|
1345
|
+
braceCount = 0;
|
|
1346
|
+
options;
|
|
1347
|
+
running = false;
|
|
1348
|
+
constructor(options = {}) {
|
|
1349
|
+
this.options = {
|
|
1350
|
+
colors: options.colors !== false,
|
|
1351
|
+
verbose: options.verbose ?? false,
|
|
1352
|
+
historyFile: options.historyFile
|
|
1353
|
+
};
|
|
1354
|
+
this.session = new Session({ colors: this.options.colors });
|
|
1355
|
+
this.history = new History({
|
|
1356
|
+
historyFile: this.options.historyFile
|
|
1357
|
+
});
|
|
1358
|
+
this.completionProvider = new CompletionProvider(this.session);
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Start the REPL
|
|
1362
|
+
*/
|
|
1363
|
+
start() {
|
|
1364
|
+
if (this.running) return;
|
|
1365
|
+
this.running = true;
|
|
1366
|
+
this.history.load();
|
|
1367
|
+
this.rl = readline.createInterface({
|
|
1368
|
+
input: process.stdin,
|
|
1369
|
+
output: process.stdout,
|
|
1370
|
+
prompt: PROMPT,
|
|
1371
|
+
completer: createCompleter(this.completionProvider),
|
|
1372
|
+
terminal: true
|
|
1373
|
+
});
|
|
1374
|
+
this.printBanner();
|
|
1375
|
+
this.rl.prompt();
|
|
1376
|
+
this.rl.on("line", (line) => {
|
|
1377
|
+
this.handleLine(line);
|
|
1378
|
+
if (this.rl && this.running) {
|
|
1379
|
+
this.rl.setPrompt(this.braceCount > 0 ? CONTINUATION_PROMPT : PROMPT);
|
|
1380
|
+
this.rl.prompt();
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
this.rl.on("close", () => {
|
|
1384
|
+
this.exit();
|
|
1385
|
+
});
|
|
1386
|
+
this.rl.on("SIGINT", () => {
|
|
1387
|
+
if (this.buffer.length > 0) {
|
|
1388
|
+
this.buffer = [];
|
|
1389
|
+
this.braceCount = 0;
|
|
1390
|
+
console.log("\n" + formatWarning("Input cancelled"));
|
|
1391
|
+
this.rl.setPrompt(PROMPT);
|
|
1392
|
+
this.rl.prompt();
|
|
1393
|
+
} else {
|
|
1394
|
+
console.log("\n" + formatWarning("Use .exit or :quit to exit"));
|
|
1395
|
+
this.rl.prompt();
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Print the welcome banner
|
|
1401
|
+
*/
|
|
1402
|
+
printBanner() {
|
|
1403
|
+
const banner = `
|
|
1404
|
+
${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
|
|
1405
|
+
\u2551 \u2551
|
|
1406
|
+
\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
|
|
1407
|
+
\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
|
|
1408
|
+
\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
|
|
1409
|
+
\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
|
|
1410
|
+
\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
|
|
1411
|
+
\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
|
|
1412
|
+
\u2551 \u2551
|
|
1413
|
+
\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}
|
|
1414
|
+
|
|
1415
|
+
${colors.bold}ISL v${VERSION}${colors.reset} \u2014 Intent Specification Language
|
|
1416
|
+
Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${colors.reset} to quit
|
|
1417
|
+
`;
|
|
1418
|
+
console.log(banner);
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Handle a line of input
|
|
1422
|
+
*/
|
|
1423
|
+
handleLine(line) {
|
|
1424
|
+
const trimmed = line.trim();
|
|
1425
|
+
if (!trimmed && this.buffer.length === 0) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
if (trimmed.startsWith(".") && this.buffer.length === 0) {
|
|
1429
|
+
this.handleMetaCommand(trimmed);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
if (trimmed.startsWith(":") && this.buffer.length === 0) {
|
|
1433
|
+
this.handleISLCommand(trimmed);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
this.braceCount += (line.match(/\{/g) || []).length;
|
|
1437
|
+
this.braceCount -= (line.match(/\}/g) || []).length;
|
|
1438
|
+
this.buffer.push(line);
|
|
1439
|
+
if (this.braceCount <= 0) {
|
|
1440
|
+
const code = this.buffer.join("\n");
|
|
1441
|
+
this.buffer = [];
|
|
1442
|
+
this.braceCount = 0;
|
|
1443
|
+
this.history.add(code);
|
|
1444
|
+
this.session.addToHistory(code);
|
|
1445
|
+
this.evaluate(code);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Handle a meta command (. prefix)
|
|
1450
|
+
*/
|
|
1451
|
+
handleMetaCommand(input) {
|
|
1452
|
+
const parts = input.slice(1).split(/\s+/);
|
|
1453
|
+
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1454
|
+
const args = parts.slice(1);
|
|
1455
|
+
const command = metaCommands.find(
|
|
1456
|
+
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1457
|
+
);
|
|
1458
|
+
if (command) {
|
|
1459
|
+
this.history.add(input);
|
|
1460
|
+
const result = command.handler(args, this.session, this);
|
|
1461
|
+
if (result.output) {
|
|
1462
|
+
console.log(result.output);
|
|
1463
|
+
}
|
|
1464
|
+
if (result.exit) {
|
|
1465
|
+
this.exit();
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
const suggestion = findSimilarCommand(cmdName, "meta");
|
|
1469
|
+
if (suggestion) {
|
|
1470
|
+
console.log(formatError(`Unknown command: .${cmdName}`));
|
|
1471
|
+
console.log(formatWarning(`Did you mean: .${suggestion}?`));
|
|
1472
|
+
} else {
|
|
1473
|
+
console.log(formatError(`Unknown command: .${cmdName}`));
|
|
1474
|
+
console.log(`Type ${colors.cyan}.help${colors.reset} for available commands`);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Handle an ISL command (: prefix)
|
|
1480
|
+
*/
|
|
1481
|
+
handleISLCommand(input) {
|
|
1482
|
+
const parts = input.slice(1).split(/\s+/);
|
|
1483
|
+
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1484
|
+
const args = parts.slice(1);
|
|
1485
|
+
const command = islCommands.find(
|
|
1486
|
+
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1487
|
+
);
|
|
1488
|
+
if (command) {
|
|
1489
|
+
this.history.add(input);
|
|
1490
|
+
const result = command.handler(args, this.session, this);
|
|
1491
|
+
if (result.output) {
|
|
1492
|
+
console.log(result.output);
|
|
1493
|
+
}
|
|
1494
|
+
} else {
|
|
1495
|
+
const suggestion = findSimilarCommand(cmdName, "isl");
|
|
1496
|
+
if (suggestion) {
|
|
1497
|
+
console.log(formatError(`Unknown command: :${cmdName}`));
|
|
1498
|
+
console.log(formatWarning(`Did you mean: :${suggestion}?`));
|
|
1499
|
+
} else {
|
|
1500
|
+
console.log(formatError(`Unknown command: :${cmdName}`));
|
|
1501
|
+
console.log(`Type ${colors.cyan}.help${colors.reset} for available commands`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Evaluate ISL code
|
|
1507
|
+
*/
|
|
1508
|
+
evaluate(code) {
|
|
1509
|
+
try {
|
|
1510
|
+
const trimmed = code.trim();
|
|
1511
|
+
if (trimmed.startsWith("intent ")) {
|
|
1512
|
+
this.evaluateIntent(trimmed);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
if (trimmed.startsWith("behavior ")) {
|
|
1516
|
+
this.evaluateIntent(trimmed);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
console.log(formatWarning(`Cannot evaluate: ${trimmed.split("\n")[0]}...`));
|
|
1520
|
+
console.log(`Use ${colors.cyan}intent Name { ... }${colors.reset} to define an intent`);
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
this.printError(error);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Evaluate an intent definition
|
|
1527
|
+
*/
|
|
1528
|
+
evaluateIntent(code) {
|
|
1529
|
+
const intent = this.session.parseIntent(code);
|
|
1530
|
+
if (intent) {
|
|
1531
|
+
this.session.defineIntent(intent);
|
|
1532
|
+
const preCount = intent.preconditions.length;
|
|
1533
|
+
const postCount = intent.postconditions.length;
|
|
1534
|
+
const invCount = intent.invariants.length;
|
|
1535
|
+
const parts = [];
|
|
1536
|
+
if (preCount > 0) parts.push(`${preCount} pre`);
|
|
1537
|
+
if (postCount > 0) parts.push(`${postCount} post`);
|
|
1538
|
+
if (invCount > 0) parts.push(`${invCount} invariant`);
|
|
1539
|
+
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
1540
|
+
console.log(formatSuccess(`Intent '${intent.name}' defined${summary}`));
|
|
1541
|
+
} else {
|
|
1542
|
+
const behaviorMatch = code.match(/^behavior\s+(\w+)\s*\{([\s\S]*)\}$/);
|
|
1543
|
+
if (behaviorMatch) {
|
|
1544
|
+
const name = behaviorMatch[1];
|
|
1545
|
+
const body = behaviorMatch[2];
|
|
1546
|
+
const intent2 = {
|
|
1547
|
+
name,
|
|
1548
|
+
preconditions: [],
|
|
1549
|
+
postconditions: [],
|
|
1550
|
+
invariants: [],
|
|
1551
|
+
scenarios: [],
|
|
1552
|
+
rawSource: code
|
|
1553
|
+
};
|
|
1554
|
+
const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
|
|
1555
|
+
if (preSection) {
|
|
1556
|
+
const conditions = preSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1557
|
+
for (const cond of conditions) {
|
|
1558
|
+
const expr = cond.replace(/^-\s*/, "").trim();
|
|
1559
|
+
if (expr) {
|
|
1560
|
+
intent2.preconditions.push({ expression: expr });
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
|
|
1565
|
+
if (postSection) {
|
|
1566
|
+
const conditions = postSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1567
|
+
for (const cond of conditions) {
|
|
1568
|
+
const expr = cond.replace(/^-\s*/, "").trim();
|
|
1569
|
+
if (expr) {
|
|
1570
|
+
intent2.postconditions.push({ expression: expr });
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
this.session.defineIntent(intent2);
|
|
1575
|
+
const preCount = intent2.preconditions.length;
|
|
1576
|
+
const postCount = intent2.postconditions.length;
|
|
1577
|
+
const parts = [];
|
|
1578
|
+
if (preCount > 0) parts.push(`${preCount} pre`);
|
|
1579
|
+
if (postCount > 0) parts.push(`${postCount} post`);
|
|
1580
|
+
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
1581
|
+
console.log(formatSuccess(`Intent '${intent2.name}' defined${summary}`));
|
|
1582
|
+
} else {
|
|
1583
|
+
this.printParseError(code, "Failed to parse intent definition");
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Print a parse error with location info
|
|
1589
|
+
*/
|
|
1590
|
+
printParseError(code, message, line, column) {
|
|
1591
|
+
console.log(formatError(message));
|
|
1592
|
+
if (line !== void 0 && column !== void 0) {
|
|
1593
|
+
const lines = code.split("\n");
|
|
1594
|
+
const errorLine = lines[line - 1] || "";
|
|
1595
|
+
console.log(` ${colors.gray}${line} |${colors.reset} ${errorLine}`);
|
|
1596
|
+
console.log(` ${colors.gray}${" ".repeat(String(line).length)} |${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`);
|
|
1597
|
+
} else {
|
|
1598
|
+
const firstLine = code.split("\n")[0];
|
|
1599
|
+
if (firstLine) {
|
|
1600
|
+
console.log(` ${colors.gray}>${colors.reset} ${firstLine}`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Print an error
|
|
1606
|
+
*/
|
|
1607
|
+
printError(error) {
|
|
1608
|
+
if (error instanceof Error) {
|
|
1609
|
+
console.log(formatError(error.message));
|
|
1610
|
+
if (this.options.verbose && error.stack) {
|
|
1611
|
+
console.log(colors.gray + error.stack + colors.reset);
|
|
1612
|
+
}
|
|
1613
|
+
} else {
|
|
1614
|
+
console.log(formatError(String(error)));
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Exit the REPL
|
|
1619
|
+
*/
|
|
1620
|
+
exit() {
|
|
1621
|
+
this.running = false;
|
|
1622
|
+
this.history.save();
|
|
1623
|
+
console.log(`
|
|
1624
|
+
${colors.yellow}Goodbye!${colors.reset}`);
|
|
1625
|
+
if (this.rl) {
|
|
1626
|
+
this.rl.close();
|
|
1627
|
+
}
|
|
1628
|
+
process.exit(0);
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Get the session
|
|
1632
|
+
*/
|
|
1633
|
+
getSession() {
|
|
1634
|
+
return this.session;
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Get history
|
|
1638
|
+
*/
|
|
1639
|
+
getHistory() {
|
|
1640
|
+
return this.history;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Execute a single command and return result (for testing)
|
|
1644
|
+
*/
|
|
1645
|
+
async executeOnce(input) {
|
|
1646
|
+
const trimmed = input.trim();
|
|
1647
|
+
if (trimmed.startsWith(".")) {
|
|
1648
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
1649
|
+
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1650
|
+
const args = parts.slice(1);
|
|
1651
|
+
const command = metaCommands.find(
|
|
1652
|
+
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1653
|
+
);
|
|
1654
|
+
if (command) {
|
|
1655
|
+
const result = command.handler(args, this.session, this);
|
|
1656
|
+
return { success: true, output: result.output };
|
|
1657
|
+
}
|
|
1658
|
+
return { success: false, error: `Unknown command: .${cmdName}` };
|
|
1659
|
+
}
|
|
1660
|
+
if (trimmed.startsWith(":")) {
|
|
1661
|
+
const parts = trimmed.slice(1).split(/\s+/);
|
|
1662
|
+
const cmdName = parts[0]?.toLowerCase() || "";
|
|
1663
|
+
const args = parts.slice(1);
|
|
1664
|
+
const command = islCommands.find(
|
|
1665
|
+
(c) => c.name === cmdName || c.aliases.includes(cmdName)
|
|
1666
|
+
);
|
|
1667
|
+
if (command) {
|
|
1668
|
+
const result = command.handler(args, this.session, this);
|
|
1669
|
+
return { success: true, output: result.output };
|
|
1670
|
+
}
|
|
1671
|
+
return { success: false, error: `Unknown command: :${cmdName}` };
|
|
1672
|
+
}
|
|
1673
|
+
if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
|
|
1674
|
+
const intent = this.session.parseIntent(trimmed);
|
|
1675
|
+
if (intent) {
|
|
1676
|
+
this.session.defineIntent(intent);
|
|
1677
|
+
return { success: true, output: `Intent '${intent.name}' defined` };
|
|
1678
|
+
}
|
|
1679
|
+
return { success: false, error: "Failed to parse intent" };
|
|
1680
|
+
}
|
|
1681
|
+
return { success: false, error: "Unknown input" };
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
function startREPL(options) {
|
|
1685
|
+
const repl = new ISLREPL(options);
|
|
1686
|
+
repl.start();
|
|
1687
|
+
return repl;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// src/cli.ts
|
|
1691
|
+
function main() {
|
|
1692
|
+
const args = process.argv.slice(2);
|
|
1693
|
+
const options = {
|
|
1694
|
+
colors: !args.includes("--no-color"),
|
|
1695
|
+
verbose: args.includes("--verbose") || args.includes("-v")
|
|
1696
|
+
};
|
|
1697
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1698
|
+
console.log(`
|
|
1699
|
+
ISL REPL - Intent Specification Language Interactive Shell
|
|
1700
|
+
|
|
1701
|
+
Usage: isl-repl [options]
|
|
1702
|
+
|
|
1703
|
+
Options:
|
|
1704
|
+
--no-color Disable colored output
|
|
1705
|
+
-v, --verbose Enable verbose output
|
|
1706
|
+
-h, --help Show this help message
|
|
1707
|
+
|
|
1708
|
+
Inside the REPL:
|
|
1709
|
+
.help Show all commands
|
|
1710
|
+
.exit Exit the REPL
|
|
1711
|
+
:check Type check intents
|
|
1712
|
+
:gen Generate code
|
|
1713
|
+
:load Load ISL file
|
|
1714
|
+
:list List intents
|
|
1715
|
+
|
|
1716
|
+
Example:
|
|
1717
|
+
$ isl-repl
|
|
1718
|
+
isl> intent Greeting {
|
|
1719
|
+
...> pre: name.length > 0
|
|
1720
|
+
...> post: result.startsWith("Hello")
|
|
1721
|
+
...> }
|
|
1722
|
+
\u2713 Intent 'Greeting' defined (1 pre, 1 post)
|
|
1723
|
+
`);
|
|
1724
|
+
process.exit(0);
|
|
1725
|
+
}
|
|
1726
|
+
startREPL(options);
|
|
1727
|
+
}
|
|
1728
|
+
if (__require.main === module) {
|
|
1729
|
+
main();
|
|
1730
|
+
}
|
|
1731
|
+
export {
|
|
1732
|
+
main
|
|
1733
|
+
};
|
|
1734
|
+
//# sourceMappingURL=cli.js.map
|