arc-lang 0.5.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.
@@ -0,0 +1,668 @@
1
+ // Arc Language Tree-Walking Interpreter
2
+ class Env {
3
+ parent;
4
+ vars = new Map();
5
+ _depth;
6
+ constructor(parent) {
7
+ this.parent = parent;
8
+ this._depth = parent ? parent._depth + 1 : 0;
9
+ }
10
+ get(name) {
11
+ const v = this.vars.get(name);
12
+ if (v !== undefined)
13
+ return v.value;
14
+ if (this.parent)
15
+ return this.parent.get(name);
16
+ throw new Error(`Undefined variable: ${name}`);
17
+ }
18
+ // Fast lookup that returns the entry directly (avoids repeated Map lookups in hot paths)
19
+ getEntry(name) {
20
+ const v = this.vars.get(name);
21
+ if (v !== undefined)
22
+ return v;
23
+ if (this.parent)
24
+ return this.parent.getEntry(name);
25
+ return undefined;
26
+ }
27
+ set(name, value, mutable = false) {
28
+ this.vars.set(name, { value, mutable });
29
+ }
30
+ assign(name, value) {
31
+ const v = this.vars.get(name);
32
+ if (v) {
33
+ if (!v.mutable)
34
+ throw new Error(`Cannot reassign immutable variable: ${name}`);
35
+ v.value = value;
36
+ return;
37
+ }
38
+ if (this.parent) {
39
+ this.parent.assign(name, value);
40
+ return;
41
+ }
42
+ throw new Error(`Undefined variable: ${name}`);
43
+ }
44
+ has(name) {
45
+ return this.vars.has(name) || (this.parent?.has(name) ?? false);
46
+ }
47
+ }
48
+ function isTruthy(v) {
49
+ if (v === null || v === false || v === 0 || v === "")
50
+ return false;
51
+ return true;
52
+ }
53
+ function toStr(v) {
54
+ if (v === null)
55
+ return "nil";
56
+ if (typeof v === "boolean")
57
+ return v ? "true" : "false";
58
+ if (typeof v === "number" || typeof v === "string")
59
+ return String(v);
60
+ if (Array.isArray(v))
61
+ return "[" + v.map(toStr).join(", ") + "]";
62
+ if (v && typeof v === "object" && "__map" in v) {
63
+ const entries = [...v.entries.entries()].map(([k, val]) => `${k}: ${toStr(val)}`);
64
+ return "{" + entries.join(", ") + "}";
65
+ }
66
+ if (v && typeof v === "object" && "__fn" in v)
67
+ return `<fn ${v.name}>`;
68
+ if (v && typeof v === "object" && "__async" in v)
69
+ return `<async>`;
70
+ return String(v);
71
+ }
72
+ function resolveAsync(v) {
73
+ if (v && typeof v === "object" && "__async" in v) {
74
+ return v.thunk();
75
+ }
76
+ return v;
77
+ }
78
+ function makePrelude(env) {
79
+ const fns = {
80
+ print: (...args) => { console.log(args.map(toStr).join(" ")); return null; },
81
+ len: (v) => {
82
+ if (typeof v === "string")
83
+ return v.length;
84
+ if (Array.isArray(v))
85
+ return v.length;
86
+ if (v && typeof v === "object" && "__map" in v)
87
+ return v.entries.size;
88
+ return 0;
89
+ },
90
+ map: (list, fn) => {
91
+ if (!Array.isArray(list))
92
+ throw new Error("map expects a list");
93
+ return list.map(item => callFn(fn, [item]));
94
+ },
95
+ filter: (list, fn) => {
96
+ if (!Array.isArray(list))
97
+ throw new Error("filter expects a list");
98
+ return list.filter(item => isTruthy(callFn(fn, [item])));
99
+ },
100
+ reduce: (list, fn, init) => {
101
+ if (!Array.isArray(list))
102
+ throw new Error("reduce expects a list");
103
+ let acc = init ?? list[0];
104
+ const start = init !== undefined ? 0 : 1;
105
+ for (let i = start; i < list.length; i++) {
106
+ acc = callFn(fn, [acc, list[i]]);
107
+ }
108
+ return acc;
109
+ },
110
+ sort: (list) => {
111
+ if (!Array.isArray(list))
112
+ throw new Error("sort expects a list");
113
+ return [...list].sort((a, b) => {
114
+ if (typeof a === "number" && typeof b === "number")
115
+ return a - b;
116
+ return String(a).localeCompare(String(b));
117
+ });
118
+ },
119
+ take: (list, n) => Array.isArray(list) ? list.slice(0, n) : null,
120
+ drop: (list, n) => Array.isArray(list) ? list.slice(n) : null,
121
+ find: (list, fn) => {
122
+ if (!Array.isArray(list))
123
+ return null;
124
+ return list.find(item => isTruthy(callFn(fn, [item]))) ?? null;
125
+ },
126
+ any: (list, fn) => {
127
+ if (!Array.isArray(list))
128
+ return false;
129
+ return list.some(item => isTruthy(callFn(fn, [item])));
130
+ },
131
+ all: (list, fn) => {
132
+ if (!Array.isArray(list))
133
+ return false;
134
+ return list.every(item => isTruthy(callFn(fn, [item])));
135
+ },
136
+ sum: (list) => {
137
+ if (!Array.isArray(list))
138
+ return 0;
139
+ return list.reduce((a, b) => a + (typeof b === "number" ? b : 0), 0);
140
+ },
141
+ flat: (list) => Array.isArray(list) ? list.flat() : list,
142
+ zip: (a, b) => {
143
+ if (!Array.isArray(a) || !Array.isArray(b))
144
+ return [];
145
+ return a.map((v, i) => [v, b[i]]);
146
+ },
147
+ enumerate: (list) => {
148
+ if (!Array.isArray(list))
149
+ return [];
150
+ return list.map((v, i) => [i, v]);
151
+ },
152
+ trim: (s) => typeof s === "string" ? s.trim() : s,
153
+ split: (s, sep) => typeof s === "string" ? s.split(sep) : [],
154
+ join: (list, sep) => Array.isArray(list) ? list.map(toStr).join(sep) : "",
155
+ upper: (s) => typeof s === "string" ? s.toUpperCase() : s,
156
+ lower: (s) => typeof s === "string" ? s.toLowerCase() : s,
157
+ replace: (s, from, to) => typeof s === "string" ? s.replaceAll(from, to) : s,
158
+ contains: (s, sub) => {
159
+ if (typeof s === "string")
160
+ return s.includes(sub);
161
+ if (Array.isArray(s))
162
+ return s.includes(sub);
163
+ return false;
164
+ },
165
+ starts: (s, pre) => typeof s === "string" ? s.startsWith(pre) : false,
166
+ ends: (s, suf) => typeof s === "string" ? s.endsWith(suf) : false,
167
+ int: (v) => typeof v === "string" ? parseInt(v) : typeof v === "number" ? Math.floor(v) : 0,
168
+ float: (v) => typeof v === "string" ? parseFloat(v) : typeof v === "number" ? v : 0,
169
+ str: (v) => toStr(v),
170
+ bool: (v) => isTruthy(v),
171
+ min: (...args) => {
172
+ const vals = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
173
+ return Math.min(...vals.map(v => typeof v === "number" ? v : Infinity));
174
+ },
175
+ max: (...args) => {
176
+ const vals = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
177
+ return Math.max(...vals.map(v => typeof v === "number" ? v : -Infinity));
178
+ },
179
+ abs: (v) => typeof v === "number" ? Math.abs(v) : 0,
180
+ round: (v) => typeof v === "number" ? Math.round(v) : 0,
181
+ assert: (cond, msg) => {
182
+ if (!isTruthy(cond))
183
+ throw new Error(`Assertion failed: ${msg !== null && msg !== undefined ? toStr(msg) : "assertion failed"}`);
184
+ return true;
185
+ },
186
+ sleep: (_ms) => null,
187
+ timeout: (_ms, expr) => expr ?? null,
188
+ type_of: (v) => {
189
+ if (v === null)
190
+ return "nil";
191
+ if (typeof v === "boolean")
192
+ return "bool";
193
+ if (typeof v === "number")
194
+ return Number.isInteger(v) ? "int" : "float";
195
+ if (typeof v === "string")
196
+ return "string";
197
+ if (Array.isArray(v))
198
+ return "list";
199
+ if (v && typeof v === "object" && "__map" in v)
200
+ return "map";
201
+ if (v && typeof v === "object" && "__fn" in v)
202
+ return "fn";
203
+ return "unknown";
204
+ },
205
+ head: (list) => Array.isArray(list) && list.length > 0 ? list[0] : null,
206
+ tail: (list) => Array.isArray(list) ? list.slice(1) : [],
207
+ last: (list) => Array.isArray(list) && list.length > 0 ? list[list.length - 1] : null,
208
+ reverse: (list) => Array.isArray(list) ? [...list].reverse() : list,
209
+ range: (a, b) => { const r = []; for (let i = a; i < b; i++)
210
+ r.push(i); return r; },
211
+ keys: (m) => m && typeof m === "object" && "__map" in m ? [...m.entries.keys()] : [],
212
+ values: (m) => m && typeof m === "object" && "__map" in m ? [...m.entries.values()] : [],
213
+ push: (list, item) => Array.isArray(list) ? [...list, item] : list,
214
+ concat: (a, b) => {
215
+ if (Array.isArray(a) && Array.isArray(b))
216
+ return [...a, ...b];
217
+ return toStr(a) + toStr(b);
218
+ },
219
+ chars: (s) => typeof s === "string" ? s.split("") : [],
220
+ repeat: (s, n) => typeof s === "string" ? s.repeat(n) : s,
221
+ slice: (v, start, end) => {
222
+ if (Array.isArray(v))
223
+ return v.slice(start, end ?? undefined);
224
+ if (typeof v === "string")
225
+ return v.slice(start, end ?? undefined);
226
+ return null;
227
+ },
228
+ };
229
+ function callFn(fn, args) {
230
+ if (fn && typeof fn === "object" && "__fn" in fn) {
231
+ const f = fn;
232
+ const fnEnv = new Env(f.closure);
233
+ f.params.forEach((p, i) => fnEnv.set(p, args[i] ?? null));
234
+ return evalExpr(f.body, fnEnv);
235
+ }
236
+ // It might be a native function stored as a special wrapper
237
+ if (typeof fn === "function")
238
+ return fn(...args);
239
+ throw new Error(`Not a function: ${toStr(fn)}`);
240
+ }
241
+ // Register prelude fns as special callable values
242
+ for (const [name, fn] of Object.entries(fns)) {
243
+ env.set(name, fn);
244
+ }
245
+ }
246
+ // Evaluate expression in tail position — returns TCOSignal for self-recursive tail calls
247
+ function evalExprTCO(expr, env, fnName) {
248
+ // Only handle tail-position expressions specially
249
+ switch (expr.kind) {
250
+ case "IfExpr": {
251
+ const cond = evalExpr(expr.condition, env);
252
+ if (isTruthy(cond))
253
+ return evalExprTCO(expr.then, env, fnName);
254
+ if (expr.else_)
255
+ return evalExprTCO(expr.else_, env, fnName);
256
+ return null;
257
+ }
258
+ case "CallExpr": {
259
+ // Check if this is a self-recursive tail call
260
+ if (expr.callee.kind === "Identifier" && expr.callee.name === fnName) {
261
+ const args = expr.args.map(a => evalExpr(a, env));
262
+ return { __tco: true, args };
263
+ }
264
+ return evalExpr(expr, env);
265
+ }
266
+ case "BlockExpr": {
267
+ const blockEnv = new Env(env);
268
+ let result = null;
269
+ for (let i = 0; i < expr.stmts.length; i++) {
270
+ if (i === expr.stmts.length - 1 && expr.stmts[i].kind === "ExprStmt") {
271
+ return evalExprTCO(expr.stmts[i].expr, blockEnv, fnName);
272
+ }
273
+ result = evalStmt(expr.stmts[i], blockEnv);
274
+ }
275
+ return result;
276
+ }
277
+ case "MatchExpr": {
278
+ const subject = evalExpr(expr.subject, env);
279
+ for (const arm of expr.arms) {
280
+ const matchEnv = new Env(env);
281
+ if (matchPattern(arm.pattern, subject, matchEnv)) {
282
+ if (arm.guard && !isTruthy(evalExpr(arm.guard, matchEnv)))
283
+ continue;
284
+ return evalExprTCO(arm.body, matchEnv, fnName);
285
+ }
286
+ }
287
+ return null;
288
+ }
289
+ default:
290
+ return evalExpr(expr, env);
291
+ }
292
+ }
293
+ function evalExpr(expr, env) {
294
+ switch (expr.kind) {
295
+ case "IntLiteral": return expr.value;
296
+ case "FloatLiteral": return expr.value;
297
+ case "BoolLiteral": return expr.value;
298
+ case "NilLiteral": return null;
299
+ case "StringLiteral": return expr.value;
300
+ case "StringInterp": {
301
+ return expr.parts.map(p => typeof p === "string" ? p : toStr(evalExpr(p, env))).join("");
302
+ }
303
+ case "Identifier": return env.get(expr.name);
304
+ case "BinaryExpr": {
305
+ // Short-circuit for logical operators
306
+ if (expr.op === "and") {
307
+ const left = evalExpr(expr.left, env);
308
+ return isTruthy(left) ? evalExpr(expr.right, env) : left;
309
+ }
310
+ if (expr.op === "or") {
311
+ const left = evalExpr(expr.left, env);
312
+ return isTruthy(left) ? left : evalExpr(expr.right, env);
313
+ }
314
+ const left = evalExpr(expr.left, env);
315
+ const right = evalExpr(expr.right, env);
316
+ switch (expr.op) {
317
+ case "+": return left + right;
318
+ case "-": return left - right;
319
+ case "*": return left * right;
320
+ case "/": return left / right;
321
+ case "%": return left % right;
322
+ case "**": return Math.pow(left, right);
323
+ case "==": return left === right;
324
+ case "!=": return left !== right;
325
+ case "<": return left < right;
326
+ case ">": return left > right;
327
+ case "<=": return left <= right;
328
+ case ">=": return left >= right;
329
+ case "++": {
330
+ if (Array.isArray(left) && Array.isArray(right))
331
+ return [...left, ...right];
332
+ return toStr(left) + toStr(right);
333
+ }
334
+ default: throw new Error(`Unknown operator: ${expr.op} at line ${expr.loc.line}`);
335
+ }
336
+ }
337
+ case "UnaryExpr": {
338
+ const operand = evalExpr(expr.operand, env);
339
+ if (expr.op === "-")
340
+ return -operand;
341
+ if (expr.op === "not")
342
+ return !isTruthy(operand);
343
+ throw new Error(`Unknown unary op: ${expr.op}`);
344
+ }
345
+ case "CallExpr": {
346
+ const callee = evalExpr(expr.callee, env);
347
+ let args = expr.args.map(a => evalExpr(a, env));
348
+ let result;
349
+ if (typeof callee === "function") {
350
+ result = callee(...args);
351
+ }
352
+ else if (callee && typeof callee === "object" && "__fn" in callee) {
353
+ let fn = callee;
354
+ // Tail call optimization loop: if the function body resolves to
355
+ // a tail call back to itself, reuse the frame instead of recursing
356
+ tailLoop: while (true) {
357
+ const fnEnv = new Env(fn.closure);
358
+ fn.params.forEach((p, i) => fnEnv.set(p, args[i] ?? null));
359
+ const bodyResult = evalExprTCO(fn.body, fnEnv, fn.name);
360
+ if (bodyResult && typeof bodyResult === "object" && "__tco" in bodyResult) {
361
+ const tco = bodyResult;
362
+ args = tco.args;
363
+ // fn stays the same — it's a self-recursive tail call
364
+ continue tailLoop;
365
+ }
366
+ result = bodyResult;
367
+ break;
368
+ }
369
+ }
370
+ else {
371
+ throw new Error(`Not callable: ${toStr(callee)} at line ${expr.loc.line}`);
372
+ }
373
+ // Auto-await async function results
374
+ return resolveAsync(result);
375
+ }
376
+ case "MemberExpr": {
377
+ const obj = evalExpr(expr.object, env);
378
+ if (obj && typeof obj === "object" && "__map" in obj) {
379
+ return obj.entries.get(expr.property) ?? null;
380
+ }
381
+ throw new Error(`Cannot access property '${expr.property}' on ${toStr(obj)}`);
382
+ }
383
+ case "IndexExpr": {
384
+ const obj = evalExpr(expr.object, env);
385
+ const idx = evalExpr(expr.index, env);
386
+ if (Array.isArray(obj) && typeof idx === "number")
387
+ return obj[idx] ?? null;
388
+ if (obj && typeof obj === "object" && "__map" in obj && typeof idx === "string") {
389
+ return obj.entries.get(idx) ?? null;
390
+ }
391
+ return null;
392
+ }
393
+ case "PipelineExpr": {
394
+ const left = evalExpr(expr.left, env);
395
+ // The right side should be a function or call expression
396
+ // If it's an identifier, call it with left as first arg
397
+ // If it's a call, prepend left to args
398
+ if (expr.right.kind === "Identifier") {
399
+ const fn = env.get(expr.right.name);
400
+ if (typeof fn === "function")
401
+ return fn(left);
402
+ if (fn && typeof fn === "object" && "__fn" in fn) {
403
+ const f = fn;
404
+ const fnEnv = new Env(f.closure);
405
+ f.params.forEach((p, i) => fnEnv.set(p, i === 0 ? left : null));
406
+ return evalExpr(f.body, fnEnv);
407
+ }
408
+ }
409
+ if (expr.right.kind === "CallExpr") {
410
+ const callee = evalExpr(expr.right.callee, env);
411
+ const args = [left, ...expr.right.args.map(a => evalExpr(a, env))];
412
+ if (typeof callee === "function")
413
+ return callee(...args);
414
+ if (callee && typeof callee === "object" && "__fn" in callee) {
415
+ const fn = callee;
416
+ const fnEnv = new Env(fn.closure);
417
+ fn.params.forEach((p, i) => fnEnv.set(p, args[i] ?? null));
418
+ return evalExpr(fn.body, fnEnv);
419
+ }
420
+ }
421
+ throw new Error(`Pipeline target must be a function at line ${expr.loc.line}`);
422
+ }
423
+ case "IfExpr": {
424
+ const cond = evalExpr(expr.condition, env);
425
+ if (isTruthy(cond))
426
+ return evalExpr(expr.then, env);
427
+ if (expr.else_)
428
+ return evalExpr(expr.else_, env);
429
+ return null;
430
+ }
431
+ case "MatchExpr": {
432
+ const subject = evalExpr(expr.subject, env);
433
+ for (const arm of expr.arms) {
434
+ const matchEnv = new Env(env);
435
+ if (matchPattern(arm.pattern, subject, matchEnv)) {
436
+ if (arm.guard && !isTruthy(evalExpr(arm.guard, matchEnv)))
437
+ continue;
438
+ return evalExpr(arm.body, matchEnv);
439
+ }
440
+ }
441
+ return null;
442
+ }
443
+ case "LambdaExpr": {
444
+ return { __fn: true, name: "<lambda>", params: expr.params, body: expr.body, closure: env };
445
+ }
446
+ case "ListLiteral": return expr.elements.map(e => evalExpr(e, env));
447
+ case "MapLiteral": {
448
+ const m = new Map();
449
+ for (const entry of expr.entries) {
450
+ const key = typeof entry.key === "string" ? entry.key : toStr(evalExpr(entry.key, env));
451
+ m.set(key, evalExpr(entry.value, env));
452
+ }
453
+ return { __map: true, entries: m };
454
+ }
455
+ case "ListComprehension": {
456
+ const iterable = evalExpr(expr.iterable, env);
457
+ if (!Array.isArray(iterable))
458
+ throw new Error(`Comprehension requires iterable at line ${expr.loc.line}`);
459
+ const result = [];
460
+ for (const item of iterable) {
461
+ const iterEnv = new Env(env);
462
+ iterEnv.set(expr.variable, item);
463
+ if (expr.filter && !isTruthy(evalExpr(expr.filter, iterEnv)))
464
+ continue;
465
+ result.push(evalExpr(expr.expr, iterEnv));
466
+ }
467
+ return result;
468
+ }
469
+ case "RangeExpr": {
470
+ const start = evalExpr(expr.start, env);
471
+ const end = evalExpr(expr.end, env);
472
+ const result = [];
473
+ for (let i = start; i < end; i++)
474
+ result.push(i);
475
+ return result;
476
+ }
477
+ case "ToolCallExpr": {
478
+ const method = expr.method.toUpperCase();
479
+ const arg = evalExpr(expr.arg, env);
480
+ const url = toStr(arg);
481
+ // Mock HTTP tool calls
482
+ if (["GET", "POST", "PUT", "DELETE", "PATCH"].includes(method)) {
483
+ console.log(`[mock ${method} ${url}]`);
484
+ if (expr.body) {
485
+ const body = evalExpr(expr.body, env);
486
+ return { __map: true, entries: new Map([["status", 200], ["method", method], ["url", url], ["body", body]]) };
487
+ }
488
+ return { __map: true, entries: new Map([["status", 200], ["method", method], ["url", url], ["data", `mock-data-from-${url}`]]) };
489
+ }
490
+ // Custom tool call
491
+ console.log(`[mock tool @${expr.method}(${url})]`);
492
+ return `mock-result-from-${expr.method}`;
493
+ }
494
+ case "AsyncExpr": {
495
+ const capturedEnv = env;
496
+ const body = expr.body;
497
+ return { __async: true, thunk: () => evalExpr(body, new Env(capturedEnv)) };
498
+ }
499
+ case "AwaitExpr": {
500
+ const val = evalExpr(expr.expr, env);
501
+ return resolveAsync(val);
502
+ }
503
+ case "FetchExpr": {
504
+ return expr.targets.map(t => {
505
+ const val = evalExpr(t, env);
506
+ return resolveAsync(val);
507
+ });
508
+ }
509
+ case "BlockExpr": {
510
+ const blockEnv = new Env(env);
511
+ let result = null;
512
+ for (const stmt of expr.stmts) {
513
+ result = evalStmt(stmt, blockEnv);
514
+ }
515
+ return result;
516
+ }
517
+ default:
518
+ throw new Error(`Unknown expression kind: ${expr.kind}`);
519
+ }
520
+ }
521
+ function matchPattern(pattern, value, env) {
522
+ switch (pattern.kind) {
523
+ case "WildcardPattern": return true;
524
+ case "LiteralPattern": return pattern.value === value;
525
+ case "BindingPattern":
526
+ env.set(pattern.name, value);
527
+ return true;
528
+ case "ArrayPattern": {
529
+ if (!Array.isArray(value))
530
+ return false;
531
+ if (pattern.elements.length !== value.length)
532
+ return false;
533
+ return pattern.elements.every((p, i) => matchPattern(p, value[i], env));
534
+ }
535
+ case "OrPattern":
536
+ return pattern.patterns.some(p => matchPattern(p, value, env));
537
+ default: return false;
538
+ }
539
+ }
540
+ function evalStmt(stmt, env) {
541
+ switch (stmt.kind) {
542
+ case "LetStmt": {
543
+ const value = evalExpr(stmt.value, env);
544
+ if (typeof stmt.name === "string") {
545
+ env.set(stmt.name, value, stmt.mutable);
546
+ }
547
+ else {
548
+ // Destructuring
549
+ const target = stmt.name;
550
+ if (target.type === "object" && value && typeof value === "object" && "__map" in value) {
551
+ const m = value.entries;
552
+ for (const n of target.names)
553
+ env.set(n, m.get(n) ?? null, stmt.mutable);
554
+ }
555
+ else if (target.type === "array" && Array.isArray(value)) {
556
+ target.names.forEach((n, i) => env.set(n, value[i] ?? null, stmt.mutable));
557
+ }
558
+ }
559
+ return value;
560
+ }
561
+ case "FnStmt": {
562
+ const fn = { __fn: true, name: stmt.name, params: stmt.params, body: stmt.body, closure: env };
563
+ env.set(stmt.name, fn);
564
+ return fn;
565
+ }
566
+ case "ForStmt": {
567
+ const iterable = evalExpr(stmt.iterable, env);
568
+ if (!Array.isArray(iterable))
569
+ throw new Error(`For loop requires iterable at line ${stmt.loc.line}`);
570
+ let result = null;
571
+ for (const item of iterable) {
572
+ const loopEnv = new Env(env);
573
+ loopEnv.set(stmt.variable, item);
574
+ result = evalExpr(stmt.body, loopEnv);
575
+ }
576
+ return result;
577
+ }
578
+ case "DoStmt": {
579
+ let result = null;
580
+ do {
581
+ result = evalExpr(stmt.body, env);
582
+ const cond = evalExpr(stmt.condition, env);
583
+ if (stmt.isWhile && !isTruthy(cond))
584
+ break;
585
+ if (!stmt.isWhile && isTruthy(cond))
586
+ break;
587
+ } while (true);
588
+ return result;
589
+ }
590
+ case "AssignStmt": {
591
+ const value = evalExpr(stmt.value, env);
592
+ env.assign(stmt.target, value);
593
+ return value;
594
+ }
595
+ case "MemberAssignStmt": {
596
+ const obj = evalExpr(stmt.object, env);
597
+ const value = evalExpr(stmt.value, env);
598
+ if (obj && typeof obj === "object" && "__map" in obj) {
599
+ obj.entries.set(stmt.property, value);
600
+ return value;
601
+ }
602
+ throw new Error(`Cannot assign property '${stmt.property}' on ${toStr(obj)}`);
603
+ }
604
+ case "IndexAssignStmt": {
605
+ const obj = evalExpr(stmt.object, env);
606
+ const index = evalExpr(stmt.index, env);
607
+ const value = evalExpr(stmt.value, env);
608
+ if (Array.isArray(obj) && typeof index === "number") {
609
+ obj[index] = value;
610
+ return value;
611
+ }
612
+ if (obj && typeof obj === "object" && "__map" in obj && typeof index === "string") {
613
+ obj.entries.set(index, value);
614
+ return value;
615
+ }
616
+ throw new Error(`Cannot assign index on ${toStr(obj)}`);
617
+ }
618
+ case "ExprStmt": return evalExpr(stmt.expr, env);
619
+ case "UseStmt": {
620
+ // Module imports handled by interpretWithFile; no-op if no file context
621
+ return null;
622
+ }
623
+ case "TypeStmt": {
624
+ // Store type definitions in the environment for runtime validation
625
+ const typeDef = stmt;
626
+ env.set(`__type__${typeDef.name}`, typeDef.def);
627
+ return null;
628
+ }
629
+ default:
630
+ throw new Error(`Unknown statement kind: ${stmt.kind}`);
631
+ }
632
+ }
633
+ export function createEnv() {
634
+ const env = new Env();
635
+ makePrelude(env);
636
+ return env;
637
+ }
638
+ export function runStmt(stmt, env) {
639
+ return evalStmt(stmt, env);
640
+ }
641
+ export function runExpr(expr, env) {
642
+ return evalExpr(expr, env);
643
+ }
644
+ export function interpret(program, onUse) {
645
+ const env = createEnv();
646
+ for (const stmt of program.stmts) {
647
+ if (stmt.kind === "UseStmt" && onUse) {
648
+ onUse(stmt, env);
649
+ }
650
+ else {
651
+ evalStmt(stmt, env);
652
+ }
653
+ }
654
+ }
655
+ export function interpretWithEnv(program, env, onUse) {
656
+ let result = null;
657
+ for (const stmt of program.stmts) {
658
+ if (stmt.kind === "UseStmt" && onUse) {
659
+ onUse(stmt, env);
660
+ result = null;
661
+ }
662
+ else {
663
+ result = evalStmt(stmt, env);
664
+ }
665
+ }
666
+ return result;
667
+ }
668
+ export { Env, toStr };