pearc 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.
@@ -0,0 +1,2063 @@
1
+ "use strict";
2
+ // Pear Language Tree-Walking Interpreter
3
+ // Executes Pear code directly in Node.js without a C compiler
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.Interpreter = void 0;
6
+ exports.interpret = interpret;
7
+ const parser_1 = require("./parser");
8
+ const VOID = { tag: 'void' };
9
+ function mkInt(v) {
10
+ return { tag: 'i64', v: typeof v === 'bigint' ? v : BigInt(Math.trunc(v)) };
11
+ }
12
+ function mkFloat(v) { return { tag: 'f64', v }; }
13
+ function mkBool(v) { return { tag: 'bool', v }; }
14
+ function mkPtr(addr, pointee) { return { tag: 'ptr', addr, pointee }; }
15
+ function NULL_PTR() { return { tag: 'ptr', addr: 0 }; }
16
+ function isTruthy(v) {
17
+ switch (v.tag) {
18
+ case 'i64': return v.v !== 0n;
19
+ case 'f64': return v.v !== 0;
20
+ case 'bool': return v.v;
21
+ case 'ptr': return v.addr !== 0;
22
+ case 'str': return v.addr !== 0;
23
+ case 'void': return false;
24
+ case 'struct': return true;
25
+ case 'array': return true;
26
+ }
27
+ }
28
+ function toNumber(v) {
29
+ switch (v.tag) {
30
+ case 'i64': return Number(v.v);
31
+ case 'f64': return v.v;
32
+ case 'bool': return v.v ? 1 : 0;
33
+ case 'ptr': return v.addr;
34
+ case 'str': return v.addr;
35
+ case 'void': return 0;
36
+ default: return 0;
37
+ }
38
+ }
39
+ function toBigInt(v) {
40
+ switch (v.tag) {
41
+ case 'i64': return v.v;
42
+ case 'f64': return BigInt(Math.trunc(v.v));
43
+ case 'bool': return v.v ? 1n : 0n;
44
+ case 'ptr': return BigInt(v.addr);
45
+ case 'str': return BigInt(v.addr);
46
+ default: return 0n;
47
+ }
48
+ }
49
+ // ─── Control Flow Signals ─────────────────────────────────────────────────────
50
+ class ReturnSignal {
51
+ constructor(value) {
52
+ this.value = value;
53
+ }
54
+ }
55
+ class BreakSignal {
56
+ }
57
+ class ContinueSignal {
58
+ }
59
+ class GotoSignal {
60
+ constructor(label) {
61
+ this.label = label;
62
+ }
63
+ }
64
+ class ExitSignal {
65
+ constructor(code) {
66
+ this.code = code;
67
+ }
68
+ }
69
+ // ─── Scope / Environment ──────────────────────────────────────────────────────
70
+ class Scope {
71
+ constructor(parent = null) {
72
+ this.parent = parent;
73
+ this.vars = new Map();
74
+ }
75
+ get(name) {
76
+ const v = this.vars.get(name);
77
+ if (v !== undefined)
78
+ return v;
79
+ return this.parent?.get(name);
80
+ }
81
+ set(name, value) {
82
+ this.vars.set(name, value);
83
+ }
84
+ assign(name, value) {
85
+ if (this.vars.has(name)) {
86
+ this.vars.set(name, value);
87
+ return true;
88
+ }
89
+ return this.parent?.assign(name, value) ?? false;
90
+ }
91
+ has(name) {
92
+ return this.vars.has(name) || (this.parent?.has(name) ?? false);
93
+ }
94
+ }
95
+ class Heap {
96
+ constructor() {
97
+ this.blocks = new Map();
98
+ this.nextAddr = 0x10000;
99
+ // String pool: string content → address
100
+ this.stringPool = new Map();
101
+ // Address → string content (for pointer-to-string reads)
102
+ this.addrToStr = new Map();
103
+ // For struct/array storage as JS objects at addresses
104
+ this.structStore = new Map();
105
+ this.arrayStore = new Map();
106
+ }
107
+ allocate(size) {
108
+ const addr = this.nextAddr;
109
+ this.nextAddr += Math.max(size, 1) + 8; // 8 bytes alignment padding
110
+ const data = new Uint8Array(size);
111
+ this.blocks.set(addr, { data, size });
112
+ return addr;
113
+ }
114
+ reallocate(addr, newSize) {
115
+ const block = this.blocks.get(addr);
116
+ if (!block)
117
+ return this.allocate(newSize);
118
+ const newAddr = this.allocate(newSize);
119
+ const newBlock = this.blocks.get(newAddr);
120
+ newBlock.data.set(block.data.slice(0, Math.min(block.size, newSize)));
121
+ this.blocks.delete(addr);
122
+ return newAddr;
123
+ }
124
+ free(addr) {
125
+ this.blocks.delete(addr);
126
+ }
127
+ internString(s) {
128
+ const existing = this.stringPool.get(s);
129
+ if (existing !== undefined)
130
+ return existing;
131
+ const addr = this.nextAddr;
132
+ this.nextAddr += s.length + 1 + 4;
133
+ this.stringPool.set(s, addr);
134
+ this.addrToStr.set(addr, s);
135
+ return addr;
136
+ }
137
+ readString(addr) {
138
+ const s = this.addrToStr.get(addr);
139
+ if (s !== undefined)
140
+ return s;
141
+ // Try to find string in blocks
142
+ const block = this.blocks.get(addr);
143
+ if (block) {
144
+ let str = '';
145
+ for (let i = 0; i < block.size; i++) {
146
+ if (block.data[i] === 0)
147
+ break;
148
+ str += String.fromCharCode(block.data[i]);
149
+ }
150
+ return str;
151
+ }
152
+ return '';
153
+ }
154
+ writeString(addr, s) {
155
+ this.addrToStr.set(addr, s);
156
+ // Also write into block if exists
157
+ const block = this.blocks.get(addr);
158
+ if (block) {
159
+ for (let i = 0; i < s.length && i < block.size - 1; i++) {
160
+ block.data[i] = s.charCodeAt(i);
161
+ }
162
+ if (s.length < block.size)
163
+ block.data[s.length] = 0;
164
+ }
165
+ }
166
+ readByte(addr, offset) {
167
+ const block = this.blocks.get(addr);
168
+ if (block && offset < block.size)
169
+ return block.data[offset];
170
+ return 0;
171
+ }
172
+ writeByte(addr, offset, val) {
173
+ const block = this.blocks.get(addr);
174
+ if (block && offset < block.size)
175
+ block.data[offset] = val & 0xff;
176
+ }
177
+ storeStruct(addr, fields) {
178
+ this.structStore.set(addr, fields);
179
+ }
180
+ loadStruct(addr) {
181
+ return this.structStore.get(addr);
182
+ }
183
+ storeArray(addr, elements) {
184
+ this.arrayStore.set(addr, elements);
185
+ }
186
+ loadArray(addr) {
187
+ return this.arrayStore.get(addr);
188
+ }
189
+ }
190
+ // ─── Type Size Map ─────────────────────────────────────────────────────────────
191
+ function sizeofType(type) {
192
+ const base = typeof type === 'string' ? type : type.base;
193
+ const ptrs = typeof type === 'string' ? 0 : type.pointers;
194
+ if (ptrs > 0)
195
+ return 8;
196
+ switch (base) {
197
+ case 'int8_t':
198
+ case 'uint8_t':
199
+ case 'char':
200
+ case 'bool': return 1;
201
+ case 'int16_t':
202
+ case 'uint16_t': return 2;
203
+ case 'int32_t':
204
+ case 'uint32_t':
205
+ case 'float': return 4;
206
+ case 'int64_t':
207
+ case 'uint64_t':
208
+ case 'double':
209
+ case 'size_t': return 8;
210
+ case 'void': return 0;
211
+ default: return 8; // struct/other
212
+ }
213
+ }
214
+ // ─── printf format string parser ─────────────────────────────────────────────
215
+ function formatPrintf(fmt, args, heap) {
216
+ let result = '';
217
+ let argIdx = 0;
218
+ let i = 0;
219
+ while (i < fmt.length) {
220
+ if (fmt[i] !== '%') {
221
+ if (fmt[i] === '\\') {
222
+ i++;
223
+ switch (fmt[i]) {
224
+ case 'n':
225
+ result += '\n';
226
+ break;
227
+ case 't':
228
+ result += '\t';
229
+ break;
230
+ case 'r':
231
+ result += '\r';
232
+ break;
233
+ case '\\':
234
+ result += '\\';
235
+ break;
236
+ case '"':
237
+ result += '"';
238
+ break;
239
+ case '0':
240
+ result += '\0';
241
+ break;
242
+ default: result += '\\' + fmt[i];
243
+ }
244
+ i++;
245
+ continue;
246
+ }
247
+ result += fmt[i++];
248
+ continue;
249
+ }
250
+ i++; // consume %
251
+ if (fmt[i] === '%') {
252
+ result += '%';
253
+ i++;
254
+ continue;
255
+ }
256
+ // Parse flags
257
+ let flags = '';
258
+ while (i < fmt.length && '-+ #0'.includes(fmt[i]))
259
+ flags += fmt[i++];
260
+ // Width
261
+ let width = '';
262
+ if (fmt[i] === '*') {
263
+ width = String(toNumber(args[argIdx++] ?? mkInt(0)));
264
+ i++;
265
+ }
266
+ else
267
+ while (i < fmt.length && /[0-9]/.test(fmt[i]))
268
+ width += fmt[i++];
269
+ // Precision
270
+ let precision = '';
271
+ if (fmt[i] === '.') {
272
+ i++;
273
+ if (fmt[i] === '*') {
274
+ precision = String(toNumber(args[argIdx++] ?? mkInt(0)));
275
+ i++;
276
+ }
277
+ else
278
+ while (i < fmt.length && /[0-9]/.test(fmt[i]))
279
+ precision += fmt[i++];
280
+ }
281
+ // Length modifier
282
+ let length = '';
283
+ if (fmt[i] === 'h') {
284
+ length = 'h';
285
+ i++;
286
+ if (fmt[i] === 'h') {
287
+ length = 'hh';
288
+ i++;
289
+ }
290
+ }
291
+ else if (fmt[i] === 'l') {
292
+ length = 'l';
293
+ i++;
294
+ if (fmt[i] === 'l') {
295
+ length = 'll';
296
+ i++;
297
+ }
298
+ }
299
+ else if (fmt[i] === 'z') {
300
+ length = 'z';
301
+ i++;
302
+ }
303
+ else if (fmt[i] === 'j') {
304
+ length = 'j';
305
+ i++;
306
+ }
307
+ else if (fmt[i] === 't') {
308
+ length = 't';
309
+ i++;
310
+ }
311
+ const spec = fmt[i++];
312
+ const arg = args[argIdx++] ?? mkInt(0);
313
+ const w = width ? parseInt(width, 10) : 0;
314
+ const p = precision !== '' ? parseInt(precision, 10) : -1;
315
+ const leftAlign = flags.includes('-');
316
+ const zeroPad = flags.includes('0') && !leftAlign;
317
+ const showSign = flags.includes('+');
318
+ function pad(s, padChar = ' ') {
319
+ if (w <= 0 || s.length >= w)
320
+ return s;
321
+ const padding = padChar.repeat(w - s.length);
322
+ return leftAlign ? s + padding : padding + s;
323
+ }
324
+ switch (spec) {
325
+ case 'd':
326
+ case 'i': {
327
+ const n = Number(toBigInt(arg));
328
+ let s = String(n);
329
+ if (showSign && n >= 0)
330
+ s = '+' + s;
331
+ result += pad(s, zeroPad ? '0' : ' ');
332
+ break;
333
+ }
334
+ case 'u': {
335
+ const n = toBigInt(arg);
336
+ const u = n < 0n ? n + 0x100000000n : n;
337
+ result += pad(String(u), zeroPad ? '0' : ' ');
338
+ break;
339
+ }
340
+ case 'f': {
341
+ const n = toNumber(arg);
342
+ const prec = p >= 0 ? p : 6;
343
+ let s = n.toFixed(prec);
344
+ if (showSign && n >= 0)
345
+ s = '+' + s;
346
+ result += pad(s, zeroPad ? '0' : ' ');
347
+ break;
348
+ }
349
+ case 'e': {
350
+ const n = toNumber(arg);
351
+ const prec = p >= 0 ? p : 6;
352
+ result += pad(n.toExponential(prec), zeroPad ? '0' : ' ');
353
+ break;
354
+ }
355
+ case 'g': {
356
+ const n = toNumber(arg);
357
+ const prec = p >= 0 ? p : 6;
358
+ const s = prec === 0 ? n.toPrecision(1) : n.toPrecision(prec);
359
+ result += pad(s, zeroPad ? '0' : ' ');
360
+ break;
361
+ }
362
+ case 'x': {
363
+ const n = Number(toBigInt(arg) & 0xffffffffn);
364
+ let s = (n >>> 0).toString(16);
365
+ if (p >= 0)
366
+ s = s.padStart(p, '0');
367
+ result += pad(s, zeroPad ? '0' : ' ');
368
+ break;
369
+ }
370
+ case 'X': {
371
+ const n = Number(toBigInt(arg) & 0xffffffffn);
372
+ let s = (n >>> 0).toString(16).toUpperCase();
373
+ if (p >= 0)
374
+ s = s.padStart(p, '0');
375
+ result += pad(s, zeroPad ? '0' : ' ');
376
+ break;
377
+ }
378
+ case 'o': {
379
+ const n = Number(toBigInt(arg) & 0xffffffffn);
380
+ result += pad((n >>> 0).toString(8), zeroPad ? '0' : ' ');
381
+ break;
382
+ }
383
+ case 'c': {
384
+ const n = Number(toBigInt(arg));
385
+ result += pad(String.fromCharCode(n & 0xff));
386
+ break;
387
+ }
388
+ case 's': {
389
+ let s;
390
+ if (arg.tag === 'str')
391
+ s = arg.s;
392
+ else if (arg.tag === 'ptr')
393
+ s = heap.readString(arg.addr);
394
+ else
395
+ s = String(toNumber(arg));
396
+ if (p >= 0 && s.length > p)
397
+ s = s.slice(0, p);
398
+ result += pad(s);
399
+ break;
400
+ }
401
+ case 'p': {
402
+ const addr = arg.tag === 'ptr' ? arg.addr : toNumber(arg);
403
+ result += pad('0x' + addr.toString(16));
404
+ break;
405
+ }
406
+ case 'n':
407
+ // %n writes count to pointer - skip
408
+ break;
409
+ default:
410
+ result += '%' + spec;
411
+ }
412
+ }
413
+ return result;
414
+ }
415
+ // ─── Interpreter ──────────────────────────────────────────────────────────────
416
+ class Interpreter {
417
+ constructor(options = {}) {
418
+ this.functions = new Map();
419
+ this.structs = new Map();
420
+ this.enums = new Map();
421
+ this.defines = new Map();
422
+ this.typedefs = new Map();
423
+ this.stdoutBuf = '';
424
+ this.stderrBuf = '';
425
+ this.stdinPos = 0;
426
+ // Map: heap address → {scope, name} for scalar variable cells
427
+ this.addrToVar = new Map();
428
+ // Map: variable key (scope id + name) → heap address
429
+ this.varToAddr = new Map();
430
+ this.scopeIdCounter = 0;
431
+ this.scopeIds = new WeakMap();
432
+ this.currentVarargs = [];
433
+ this.globalScope = new Scope();
434
+ this.heap = new Heap();
435
+ this.captureOutput = options.captureOutput ?? false;
436
+ this.stdinData = options.stdin ?? '';
437
+ this.setupBuiltins();
438
+ }
439
+ writeStdout(s) {
440
+ if (this.captureOutput) {
441
+ this.stdoutBuf += s;
442
+ }
443
+ else {
444
+ process.stdout.write(s);
445
+ }
446
+ }
447
+ writeStderr(s) {
448
+ if (this.captureOutput) {
449
+ this.stderrBuf += s;
450
+ }
451
+ else {
452
+ process.stderr.write(s);
453
+ }
454
+ }
455
+ setupBuiltins() {
456
+ // NULL constant
457
+ this.globalScope.set('NULL', NULL_PTR());
458
+ this.globalScope.set('null', NULL_PTR());
459
+ this.globalScope.set('true', mkBool(true));
460
+ this.globalScope.set('false', mkBool(false));
461
+ this.globalScope.set('EOF', mkInt(-1));
462
+ this.globalScope.set('stdin', mkPtr(0, 'file'));
463
+ this.globalScope.set('stdout', mkPtr(1, 'file'));
464
+ this.globalScope.set('stderr', mkPtr(2, 'file'));
465
+ this.globalScope.set('INT_MAX', mkInt(2147483647));
466
+ this.globalScope.set('INT_MIN', mkInt(-2147483648));
467
+ this.globalScope.set('UINT_MAX', mkInt(4294967295));
468
+ this.globalScope.set('SIZE_MAX', mkInt(Number.MAX_SAFE_INTEGER));
469
+ this.globalScope.set('M_PI', mkFloat(Math.PI));
470
+ this.globalScope.set('M_E', mkFloat(Math.E));
471
+ this.globalScope.set('HUGE_VAL', mkFloat(Infinity));
472
+ }
473
+ run(pearSource, argv = []) {
474
+ let exitCode = 0;
475
+ try {
476
+ const ast = (0, parser_1.parse)(pearSource);
477
+ this.loadProgram(ast);
478
+ // Set up argc/argv in global scope
479
+ const argc = argv.length + 1; // +1 for program name
480
+ this.globalScope.set('argc', mkInt(argc));
481
+ // Call main
482
+ const mainFn = this.functions.get('main');
483
+ if (!mainFn)
484
+ throw new Error('No main function found');
485
+ const result = this.callFunction(mainFn, [mkInt(argc), NULL_PTR()], this.globalScope);
486
+ if (result.tag === 'i64')
487
+ exitCode = Number(result.v);
488
+ else if (result.tag === 'f64')
489
+ exitCode = Math.trunc(result.v);
490
+ }
491
+ catch (e) {
492
+ if (e instanceof ExitSignal) {
493
+ exitCode = e.code;
494
+ }
495
+ else if (e instanceof ReturnSignal) {
496
+ const v = e.value;
497
+ exitCode = v.tag === 'i64' ? Number(v.v) : 0;
498
+ }
499
+ else {
500
+ const msg = e instanceof Error ? e.message : String(e);
501
+ this.writeStderr(`Runtime error: ${msg}\n`);
502
+ exitCode = 1;
503
+ }
504
+ }
505
+ return { exitCode, stdout: this.stdoutBuf, stderr: this.stderrBuf };
506
+ }
507
+ loadProgram(ast) {
508
+ for (const node of ast.body) {
509
+ this.loadTopLevel(node);
510
+ }
511
+ }
512
+ loadTopLevel(node) {
513
+ switch (node.kind) {
514
+ case 'FunctionDecl':
515
+ if (node.body !== null) {
516
+ this.functions.set(node.name, node);
517
+ }
518
+ break;
519
+ case 'StructDecl':
520
+ if (node.name)
521
+ this.structs.set(node.name, node);
522
+ if (node.typedefName)
523
+ this.structs.set(node.typedefName, node);
524
+ break;
525
+ case 'UnionDecl':
526
+ if (node.name)
527
+ this.structs.set(node.name, node);
528
+ if (node.typedefName)
529
+ this.structs.set(node.typedefName, node);
530
+ break;
531
+ case 'EnumDecl': {
532
+ const members = new Map();
533
+ let counter = 0;
534
+ for (const m of node.members) {
535
+ if (m.value) {
536
+ const v = this.evalExpr(m.value, this.globalScope);
537
+ counter = Number(toBigInt(v));
538
+ }
539
+ members.set(m.name, counter);
540
+ this.globalScope.set(m.name, mkInt(counter));
541
+ counter++;
542
+ }
543
+ if (node.name)
544
+ this.enums.set(node.name, members);
545
+ break;
546
+ }
547
+ case 'VarDecl':
548
+ this.execVarDecl(node, this.globalScope);
549
+ break;
550
+ case 'DefineDirective': {
551
+ // Simple value defines only (not function-like)
552
+ if (!node.params) {
553
+ this.defines.set(node.name, node.body);
554
+ // Try to eval as number constant
555
+ if (/^-?\d+(\.\d+)?$/.test(node.body.trim())) {
556
+ this.globalScope.set(node.name, mkFloat(parseFloat(node.body.trim())));
557
+ }
558
+ else if (/^0x[0-9a-fA-F]+$/.test(node.body.trim())) {
559
+ this.globalScope.set(node.name, mkInt(parseInt(node.body.trim(), 16)));
560
+ }
561
+ else if (node.body.trim() === 'true') {
562
+ this.globalScope.set(node.name, mkBool(true));
563
+ }
564
+ else if (node.body.trim() === 'false') {
565
+ this.globalScope.set(node.name, mkBool(false));
566
+ }
567
+ else {
568
+ // store as string
569
+ this.defines.set(node.name, node.body);
570
+ }
571
+ }
572
+ break;
573
+ }
574
+ case 'TypedefDecl': {
575
+ if (node.inner && node.inner.kind === 'VarDecl') {
576
+ const vd = node.inner;
577
+ this.typedefs.set(node.alias, vd.type);
578
+ }
579
+ break;
580
+ }
581
+ // Directives we ignore
582
+ case 'IncludeDirective':
583
+ case 'PragmaDirective':
584
+ case 'RawPreprocessor':
585
+ case 'IncludeGuardStart':
586
+ case 'IncludeGuardEnd':
587
+ break;
588
+ default:
589
+ break;
590
+ }
591
+ }
592
+ // ─── Statement Execution ───────────────────────────────────────────────────
593
+ execNode(node, scope) {
594
+ switch (node.kind) {
595
+ case 'BlockStmt':
596
+ this.execBlock(node, scope);
597
+ break;
598
+ case 'VarDecl':
599
+ this.execVarDecl(node, scope);
600
+ break;
601
+ case 'IfStmt':
602
+ this.execIf(node, scope);
603
+ break;
604
+ case 'ForStmt':
605
+ this.execFor(node, scope);
606
+ break;
607
+ case 'WhileStmt':
608
+ this.execWhile(node, scope);
609
+ break;
610
+ case 'DoWhileStmt':
611
+ this.execDoWhile(node, scope);
612
+ break;
613
+ case 'SwitchStmt':
614
+ this.execSwitch(node, scope);
615
+ break;
616
+ case 'ReturnStmt':
617
+ throw new ReturnSignal(node.value ? this.evalExpr(node.value, scope) : VOID);
618
+ case 'BreakStmt': throw new BreakSignal();
619
+ case 'ContinueStmt': throw new ContinueSignal();
620
+ case 'GotoStmt': throw new GotoSignal(node.label);
621
+ case 'LabelStmt':
622
+ // Execute the labeled statement
623
+ this.execNode(node.body, scope);
624
+ break;
625
+ case 'ExprStmt':
626
+ this.evalExpr(node.expr, scope);
627
+ break;
628
+ // top-level things that can appear in blocks
629
+ case 'FunctionDecl':
630
+ if (node.body !== null)
631
+ this.functions.set(node.name, node);
632
+ break;
633
+ case 'StructDecl':
634
+ if (node.name)
635
+ this.structs.set(node.name, node);
636
+ if (node.typedefName)
637
+ this.structs.set(node.typedefName, node);
638
+ break;
639
+ case 'UnionDecl':
640
+ if (node.name)
641
+ this.structs.set(node.name, node);
642
+ if (node.typedefName)
643
+ this.structs.set(node.typedefName, node);
644
+ break;
645
+ case 'EnumDecl':
646
+ this.loadTopLevel(node);
647
+ break;
648
+ case 'TypedefDecl':
649
+ this.loadTopLevel(node);
650
+ break;
651
+ case 'DefineDirective':
652
+ this.loadTopLevel(node);
653
+ break;
654
+ case 'IncludeDirective':
655
+ case 'PragmaDirective':
656
+ case 'RawPreprocessor':
657
+ case 'IncludeGuardStart':
658
+ case 'IncludeGuardEnd':
659
+ break;
660
+ default:
661
+ break;
662
+ }
663
+ }
664
+ execBlock(block, parentScope) {
665
+ const scope = new Scope(parentScope);
666
+ this.execBlockInScope(block.body, scope);
667
+ }
668
+ execBlockInScope(stmts, scope) {
669
+ let i = 0;
670
+ while (i < stmts.length) {
671
+ try {
672
+ this.execNode(stmts[i], scope);
673
+ i++;
674
+ }
675
+ catch (e) {
676
+ if (e instanceof GotoSignal) {
677
+ // Scan for the label in this block
678
+ const labelIdx = stmts.findIndex(s => s.kind === 'LabelStmt' && s.label === e.label);
679
+ if (labelIdx >= 0) {
680
+ i = labelIdx;
681
+ continue;
682
+ }
683
+ throw e; // propagate upward
684
+ }
685
+ throw e;
686
+ }
687
+ }
688
+ }
689
+ execVarDecl(node, scope) {
690
+ // Handle array type: type with arraySize
691
+ if (node.type.arraySize) {
692
+ const sizeVal = this.evalExpr(node.type.arraySize, scope);
693
+ const size = Number(toBigInt(sizeVal));
694
+ let elements;
695
+ if (node.init && node.init.kind === 'InitListExpr') {
696
+ elements = [];
697
+ const initList = node.init;
698
+ for (let i = 0; i < size; i++) {
699
+ const initNode = initList.elements[i];
700
+ elements.push(initNode ? this.evalExpr(initNode, scope) : this.defaultValue(node.type));
701
+ }
702
+ }
703
+ else if (node.init) {
704
+ const v = this.evalExpr(node.init, scope);
705
+ if (v.tag === 'array') {
706
+ elements = v.elements;
707
+ }
708
+ else {
709
+ elements = new Array(size).fill(null).map(() => this.defaultValue(node.type));
710
+ }
711
+ }
712
+ else {
713
+ elements = new Array(size).fill(null).map(() => this.defaultValue(node.type));
714
+ }
715
+ const elemSize = sizeofType({ ...node.type, arraySize: null, pointers: node.type.pointers });
716
+ const addr = this.heap.allocate(size * Math.max(elemSize, 1));
717
+ this.heap.storeArray(addr, elements);
718
+ const arrVal = { tag: 'array', elements, addr };
719
+ scope.set(node.name, arrVal);
720
+ return;
721
+ }
722
+ let value;
723
+ if (node.init) {
724
+ value = this.evalExpr(node.init, scope);
725
+ // Handle struct initialization from InitListExpr
726
+ value = this.coerceInitForType(value, node.type, scope);
727
+ }
728
+ else {
729
+ value = this.defaultValue(node.type);
730
+ }
731
+ scope.set(node.name, value);
732
+ }
733
+ coerceInitForType(value, type, scope) {
734
+ if (type.pointers > 0) {
735
+ // Pointer type
736
+ if (value.tag === 'array')
737
+ return mkPtr(value.addr);
738
+ if (value.tag === 'str')
739
+ return mkPtr(value.addr);
740
+ if (value.tag === 'ptr')
741
+ return value;
742
+ if (value.tag === 'i64' && value.v === 0n)
743
+ return NULL_PTR();
744
+ return value;
745
+ }
746
+ // Check if we need struct coercion
747
+ const structDef = this.lookupStruct(type.base);
748
+ if (structDef && value.tag === 'array') {
749
+ // Convert positional InitList to struct
750
+ const fields = new Map();
751
+ for (let i = 0; i < structDef.fields.length; i++) {
752
+ const f = structDef.fields[i];
753
+ fields.set(f.name, value.elements[i] ?? this.defaultValue(f.type));
754
+ }
755
+ return { tag: 'struct', fields, typeName: type.base };
756
+ }
757
+ if (structDef && value.tag === 'void') {
758
+ return this.defaultValue(type);
759
+ }
760
+ return value;
761
+ }
762
+ lookupStruct(name) {
763
+ return this.structs.get(name) ||
764
+ this.structs.get(name.replace('struct ', '').replace('union ', '')) ||
765
+ null;
766
+ }
767
+ defaultValue(type) {
768
+ if (type.pointers > 0)
769
+ return NULL_PTR();
770
+ switch (type.base) {
771
+ case 'int8_t':
772
+ case 'int16_t':
773
+ case 'int32_t':
774
+ case 'int64_t':
775
+ case 'uint8_t':
776
+ case 'uint16_t':
777
+ case 'uint32_t':
778
+ case 'uint64_t':
779
+ case 'size_t':
780
+ case 'char':
781
+ return mkInt(0);
782
+ case 'float':
783
+ case 'double':
784
+ return mkFloat(0);
785
+ case 'bool':
786
+ return mkBool(false);
787
+ case 'void':
788
+ return VOID;
789
+ default: {
790
+ // Struct or typedef'd type
791
+ const structDef = this.structs.get(type.base) ||
792
+ this.structs.get(type.base.replace('struct ', '').replace('union ', ''));
793
+ if (structDef) {
794
+ const fields = new Map();
795
+ for (const f of structDef.fields) {
796
+ fields.set(f.name, this.defaultValue(f.type));
797
+ }
798
+ return { tag: 'struct', fields, typeName: type.base };
799
+ }
800
+ return mkInt(0);
801
+ }
802
+ }
803
+ }
804
+ execIf(node, scope) {
805
+ const cond = this.evalExpr(node.condition, scope);
806
+ if (isTruthy(cond)) {
807
+ this.execNode(node.consequent, scope);
808
+ }
809
+ else if (node.alternate) {
810
+ this.execNode(node.alternate, scope);
811
+ }
812
+ }
813
+ execFor(node, scope) {
814
+ const loopScope = new Scope(scope);
815
+ if (node.init)
816
+ this.execNode(node.init, loopScope);
817
+ while (true) {
818
+ if (node.condition) {
819
+ const cond = this.evalExpr(node.condition, loopScope);
820
+ if (!isTruthy(cond))
821
+ break;
822
+ }
823
+ try {
824
+ this.execNode(node.body, loopScope);
825
+ }
826
+ catch (e) {
827
+ if (e instanceof BreakSignal)
828
+ break;
829
+ if (e instanceof ContinueSignal) {
830
+ // fall through to update
831
+ }
832
+ else
833
+ throw e;
834
+ }
835
+ if (node.update)
836
+ this.evalExpr(node.update, loopScope);
837
+ }
838
+ }
839
+ execWhile(node, scope) {
840
+ while (true) {
841
+ const cond = this.evalExpr(node.condition, scope);
842
+ if (!isTruthy(cond))
843
+ break;
844
+ try {
845
+ this.execNode(node.body, scope);
846
+ }
847
+ catch (e) {
848
+ if (e instanceof BreakSignal)
849
+ break;
850
+ if (e instanceof ContinueSignal)
851
+ continue;
852
+ throw e;
853
+ }
854
+ }
855
+ }
856
+ execDoWhile(node, scope) {
857
+ do {
858
+ try {
859
+ this.execNode(node.body, scope);
860
+ }
861
+ catch (e) {
862
+ if (e instanceof BreakSignal)
863
+ break;
864
+ if (e instanceof ContinueSignal) {
865
+ const cond = this.evalExpr(node.condition, scope);
866
+ if (!isTruthy(cond))
867
+ return;
868
+ continue;
869
+ }
870
+ throw e;
871
+ }
872
+ } while (isTruthy(this.evalExpr(node.condition, scope)));
873
+ }
874
+ execSwitch(node, scope) {
875
+ const switchVal = this.evalExpr(node.expr, scope);
876
+ const switchNum = toBigInt(switchVal);
877
+ // The body should be a block with case/default statements
878
+ const body = node.body;
879
+ let stmts = [];
880
+ if (body.kind === 'BlockStmt')
881
+ stmts = body.body;
882
+ else
883
+ stmts = [body];
884
+ // Find matching case or default
885
+ let startIdx = -1;
886
+ let defaultIdx = -1;
887
+ for (let i = 0; i < stmts.length; i++) {
888
+ const s = stmts[i];
889
+ if (s.kind === 'CaseStmt') {
890
+ const caseVal = this.evalExpr(s.value, scope);
891
+ if (toBigInt(caseVal) === switchNum) {
892
+ startIdx = i;
893
+ break;
894
+ }
895
+ }
896
+ else if (s.kind === 'DefaultStmt') {
897
+ defaultIdx = i;
898
+ }
899
+ }
900
+ if (startIdx < 0)
901
+ startIdx = defaultIdx;
902
+ if (startIdx < 0)
903
+ return; // no match
904
+ // Execute from matched case onwards
905
+ const switchScope = new Scope(scope);
906
+ try {
907
+ for (let i = startIdx; i < stmts.length; i++) {
908
+ const s = stmts[i];
909
+ if (s.kind === 'CaseStmt') {
910
+ const cs = s;
911
+ for (const stmt of cs.body)
912
+ this.execNode(stmt, switchScope);
913
+ }
914
+ else if (s.kind === 'DefaultStmt') {
915
+ const ds = s;
916
+ for (const stmt of ds.body)
917
+ this.execNode(stmt, switchScope);
918
+ }
919
+ else {
920
+ this.execNode(s, switchScope);
921
+ }
922
+ }
923
+ }
924
+ catch (e) {
925
+ if (e instanceof BreakSignal)
926
+ return;
927
+ throw e;
928
+ }
929
+ }
930
+ // ─── Expression Evaluation ─────────────────────────────────────────────────
931
+ evalExpr(expr, scope) {
932
+ switch (expr.kind) {
933
+ case 'NumberLiteral': return this.evalNumber(expr);
934
+ case 'StringLiteral': return this.evalString(expr);
935
+ case 'CharLiteral': return this.evalChar(expr);
936
+ case 'BoolLiteral': return mkBool(expr.value);
937
+ case 'NullLiteral': return NULL_PTR();
938
+ case 'Identifier': return this.evalIdentifier(expr, scope);
939
+ case 'BinaryExpr': return this.evalBinary(expr, scope);
940
+ case 'UnaryExpr': return this.evalUnary(expr, scope);
941
+ case 'PostfixExpr': return this.evalPostfix(expr, scope);
942
+ case 'AssignExpr': return this.evalAssign(expr, scope);
943
+ case 'TernaryExpr': {
944
+ const cond = this.evalExpr(expr.condition, scope);
945
+ return isTruthy(cond)
946
+ ? this.evalExpr(expr.consequent, scope)
947
+ : this.evalExpr(expr.alternate, scope);
948
+ }
949
+ case 'CallExpr': return this.evalCall(expr, scope);
950
+ case 'IndexExpr': return this.evalIndex(expr, scope);
951
+ case 'MemberExpr': return this.evalMember(expr, scope);
952
+ case 'CastExpr': return this.evalCast(expr, scope);
953
+ case 'SizeofExpr': return this.evalSizeof(expr, scope);
954
+ case 'InitListExpr': return this.evalInitList(expr, scope);
955
+ case 'FuncPtrExpr': return mkPtr(0, expr.name);
956
+ default:
957
+ return VOID;
958
+ }
959
+ }
960
+ evalNumber(expr) {
961
+ let s = expr.value.replace(/_/g, '');
962
+ // Strip suffix
963
+ const suffix = s.match(/[uUlLfF]+$/)?.[0] ?? '';
964
+ s = s.slice(0, s.length - suffix.length);
965
+ const isFloat = suffix.toLowerCase().includes('f') || s.includes('.');
966
+ if (s.startsWith('0x') || s.startsWith('0X')) {
967
+ return mkInt(parseInt(s, 16));
968
+ }
969
+ if (isFloat || s.includes('.') || s.includes('e') || s.includes('E')) {
970
+ return mkFloat(parseFloat(s));
971
+ }
972
+ return mkInt(parseInt(s, 10));
973
+ }
974
+ evalString(expr) {
975
+ // Value is like '"Hello, World!\n"'
976
+ let raw = expr.value;
977
+ if (raw.startsWith('"'))
978
+ raw = raw.slice(1);
979
+ if (raw.endsWith('"'))
980
+ raw = raw.slice(0, -1);
981
+ const s = this.unescapeString(raw);
982
+ const addr = this.heap.internString(s);
983
+ return { tag: 'str', s, addr };
984
+ }
985
+ unescapeString(s) {
986
+ let result = '';
987
+ let i = 0;
988
+ while (i < s.length) {
989
+ if (s[i] === '\\') {
990
+ i++;
991
+ switch (s[i]) {
992
+ case 'n':
993
+ result += '\n';
994
+ break;
995
+ case 't':
996
+ result += '\t';
997
+ break;
998
+ case 'r':
999
+ result += '\r';
1000
+ break;
1001
+ case '0':
1002
+ result += '\0';
1003
+ break;
1004
+ case '\\':
1005
+ result += '\\';
1006
+ break;
1007
+ case '"':
1008
+ result += '"';
1009
+ break;
1010
+ case "'":
1011
+ result += "'";
1012
+ break;
1013
+ case 'a':
1014
+ result += '\x07';
1015
+ break;
1016
+ case 'b':
1017
+ result += '\x08';
1018
+ break;
1019
+ case 'f':
1020
+ result += '\x0c';
1021
+ break;
1022
+ case 'v':
1023
+ result += '\x0b';
1024
+ break;
1025
+ case 'x': {
1026
+ const hex = s.slice(i + 1, i + 3);
1027
+ result += String.fromCharCode(parseInt(hex, 16));
1028
+ i += 2;
1029
+ break;
1030
+ }
1031
+ default: result += s[i];
1032
+ }
1033
+ i++;
1034
+ }
1035
+ else {
1036
+ result += s[i++];
1037
+ }
1038
+ }
1039
+ return result;
1040
+ }
1041
+ evalChar(expr) {
1042
+ let raw = expr.value;
1043
+ if (raw.startsWith("'"))
1044
+ raw = raw.slice(1);
1045
+ if (raw.endsWith("'"))
1046
+ raw = raw.slice(0, -1);
1047
+ const unescaped = this.unescapeString(raw);
1048
+ return mkInt(unescaped.charCodeAt(0) || 0);
1049
+ }
1050
+ evalIdentifier(expr, scope) {
1051
+ const v = scope.get(expr.name);
1052
+ if (v !== undefined)
1053
+ return v;
1054
+ // Check enum
1055
+ for (const [, members] of this.enums) {
1056
+ if (members.has(expr.name))
1057
+ return mkInt(members.get(expr.name));
1058
+ }
1059
+ // Check if it's a function name (function pointer context)
1060
+ if (this.functions.has(expr.name)) {
1061
+ return mkPtr(0, expr.name);
1062
+ }
1063
+ return mkInt(0); // undefined → 0
1064
+ }
1065
+ evalBinary(expr, scope) {
1066
+ // Short-circuit for &&, ||
1067
+ if (expr.op === '&&') {
1068
+ const left = this.evalExpr(expr.left, scope);
1069
+ if (!isTruthy(left))
1070
+ return mkBool(false);
1071
+ const right = this.evalExpr(expr.right, scope);
1072
+ return mkBool(isTruthy(right));
1073
+ }
1074
+ if (expr.op === '||') {
1075
+ const left = this.evalExpr(expr.left, scope);
1076
+ if (isTruthy(left))
1077
+ return mkBool(true);
1078
+ const right = this.evalExpr(expr.right, scope);
1079
+ return mkBool(isTruthy(right));
1080
+ }
1081
+ const left = this.evalExpr(expr.left, scope);
1082
+ const right = this.evalExpr(expr.right, scope);
1083
+ // Float arithmetic if either operand is float
1084
+ if (left.tag === 'f64' || right.tag === 'f64') {
1085
+ const l = toNumber(left);
1086
+ const r = toNumber(right);
1087
+ switch (expr.op) {
1088
+ case '+': return mkFloat(l + r);
1089
+ case '-': return mkFloat(l - r);
1090
+ case '*': return mkFloat(l * r);
1091
+ case '/': return mkFloat(r === 0 ? (l >= 0 ? Infinity : -Infinity) : l / r);
1092
+ case '%': return mkFloat(l % r);
1093
+ case '<': return mkBool(l < r);
1094
+ case '>': return mkBool(l > r);
1095
+ case '<=': return mkBool(l <= r);
1096
+ case '>=': return mkBool(l >= r);
1097
+ case '==': return mkBool(l === r);
1098
+ case '!=': return mkBool(l !== r);
1099
+ default: return mkFloat(0);
1100
+ }
1101
+ }
1102
+ // Pointer arithmetic
1103
+ if (left.tag === 'ptr' || left.tag === 'str') {
1104
+ const addr = left.tag === 'ptr' ? left.addr : left.addr;
1105
+ const r = Number(toBigInt(right));
1106
+ switch (expr.op) {
1107
+ case '+': return mkPtr(addr + r * 8); // approximate stride
1108
+ case '-':
1109
+ if (right.tag === 'ptr' || right.tag === 'str') {
1110
+ return mkInt(Math.floor((addr - toNumber(right)) / 8));
1111
+ }
1112
+ return mkPtr(addr - r * 8);
1113
+ case '==': return mkBool(addr === toNumber(right));
1114
+ case '!=': return mkBool(addr !== toNumber(right));
1115
+ case '<': return mkBool(addr < toNumber(right));
1116
+ case '>': return mkBool(addr > toNumber(right));
1117
+ case '<=': return mkBool(addr <= toNumber(right));
1118
+ case '>=': return mkBool(addr >= toNumber(right));
1119
+ default: return mkPtr(addr);
1120
+ }
1121
+ }
1122
+ // Integer arithmetic
1123
+ const l = toBigInt(left);
1124
+ const r = toBigInt(right);
1125
+ switch (expr.op) {
1126
+ case '+': return mkInt(l + r);
1127
+ case '-': return mkInt(l - r);
1128
+ case '*': return mkInt(l * r);
1129
+ case '/': return r === 0n ? mkInt(0) : mkInt(l / r);
1130
+ case '%': return r === 0n ? mkInt(0) : mkInt(l % r);
1131
+ case '&': return mkInt(l & r);
1132
+ case '|': return mkInt(l | r);
1133
+ case '^': return mkInt(l ^ r);
1134
+ case '<<': return mkInt(l << (r & 63n));
1135
+ case '>>': return mkInt(l >> (r & 63n));
1136
+ case '<': return mkBool(l < r);
1137
+ case '>': return mkBool(l > r);
1138
+ case '<=': return mkBool(l <= r);
1139
+ case '>=': return mkBool(l >= r);
1140
+ case '==': return mkBool(l === r);
1141
+ case '!=': return mkBool(l !== r);
1142
+ default: return mkInt(0);
1143
+ }
1144
+ }
1145
+ evalUnary(expr, scope) {
1146
+ if (!expr.prefix) {
1147
+ // Postfix handled in PostfixExpr
1148
+ return this.evalExpr(expr.operand, scope);
1149
+ }
1150
+ switch (expr.op) {
1151
+ case '-': {
1152
+ const v = this.evalExpr(expr.operand, scope);
1153
+ if (v.tag === 'f64')
1154
+ return mkFloat(-v.v);
1155
+ return mkInt(-toBigInt(v));
1156
+ }
1157
+ case '+': return this.evalExpr(expr.operand, scope);
1158
+ case '!': {
1159
+ const v = this.evalExpr(expr.operand, scope);
1160
+ return mkBool(!isTruthy(v));
1161
+ }
1162
+ case '~': {
1163
+ const v = this.evalExpr(expr.operand, scope);
1164
+ return mkInt(~toBigInt(v));
1165
+ }
1166
+ case '*': {
1167
+ // Dereference
1168
+ const v = this.evalExpr(expr.operand, scope);
1169
+ return this.deref(v, scope);
1170
+ }
1171
+ case '&': {
1172
+ // Address-of: return a pointer that can be used for assignment
1173
+ return this.addressOf(expr.operand, scope);
1174
+ }
1175
+ case '++': {
1176
+ // Prefix increment
1177
+ const val = this.evalExpr(expr.operand, scope);
1178
+ const newVal = val.tag === 'f64' ? mkFloat(val.v + 1) : mkInt(toBigInt(val) + 1n);
1179
+ this.assignTo(expr.operand, newVal, scope);
1180
+ return newVal;
1181
+ }
1182
+ case '--': {
1183
+ const val = this.evalExpr(expr.operand, scope);
1184
+ const newVal = val.tag === 'f64' ? mkFloat(val.v - 1) : mkInt(toBigInt(val) - 1n);
1185
+ this.assignTo(expr.operand, newVal, scope);
1186
+ return newVal;
1187
+ }
1188
+ default:
1189
+ return this.evalExpr(expr.operand, scope);
1190
+ }
1191
+ }
1192
+ evalPostfix(expr, scope) {
1193
+ const val = this.evalExpr(expr.operand, scope);
1194
+ if (expr.op === '++') {
1195
+ const newVal = val.tag === 'f64' ? mkFloat(val.v + 1) : mkInt(toBigInt(val) + 1n);
1196
+ this.assignTo(expr.operand, newVal, scope);
1197
+ return val; // return OLD value
1198
+ }
1199
+ if (expr.op === '--') {
1200
+ const newVal = val.tag === 'f64' ? mkFloat(val.v - 1) : mkInt(toBigInt(val) - 1n);
1201
+ this.assignTo(expr.operand, newVal, scope);
1202
+ return val;
1203
+ }
1204
+ return val;
1205
+ }
1206
+ deref(ptr, scope) {
1207
+ if (ptr.tag === 'ptr') {
1208
+ // Check if address maps to a scope variable (read current value)
1209
+ const varRef = this.addrToVar.get(ptr.addr);
1210
+ if (varRef) {
1211
+ const v = varRef.scope.get(varRef.name);
1212
+ if (v !== undefined)
1213
+ return v;
1214
+ }
1215
+ // Check struct store
1216
+ const structFields = this.heap.loadStruct(ptr.addr);
1217
+ if (structFields)
1218
+ return { tag: 'struct', fields: structFields, typeName: ptr.pointee ?? '' };
1219
+ // Check array store (first element = scalar value)
1220
+ const arr = this.heap.loadArray(ptr.addr);
1221
+ if (arr)
1222
+ return arr[0] ?? mkInt(0);
1223
+ // String
1224
+ const s = this.heap.readString(ptr.addr);
1225
+ if (s.length > 0)
1226
+ return mkInt(s.charCodeAt(0));
1227
+ return mkInt(0);
1228
+ }
1229
+ if (ptr.tag === 'str') {
1230
+ return mkInt(ptr.s.charCodeAt(0));
1231
+ }
1232
+ return mkInt(0);
1233
+ }
1234
+ addressOf(expr, scope) {
1235
+ if (expr.kind === 'Identifier') {
1236
+ const v = scope.get(expr.name);
1237
+ if (v === undefined)
1238
+ return mkPtr(0);
1239
+ if (v.tag === 'array')
1240
+ return mkPtr(v.addr);
1241
+ if (v.tag === 'str')
1242
+ return mkPtr(v.addr);
1243
+ if (v.tag === 'struct') {
1244
+ // Store struct in heap and return pointer; keep a mapping so writes sync back
1245
+ const addr = this.heap.allocate(8);
1246
+ this.heap.storeStruct(addr, v.fields);
1247
+ // Track this struct's pointer so mutations sync back to the original value
1248
+ this.addrToVar.set(addr, { scope, name: expr.name });
1249
+ return mkPtr(addr, 'struct');
1250
+ }
1251
+ // Scalar: allocate a cell linked to this scope variable
1252
+ const cell = this.allocScalarCell(expr.name, v, scope);
1253
+ // Ensure cell reflects the current value
1254
+ const arr = this.heap.loadArray(cell);
1255
+ if (arr)
1256
+ arr[0] = v;
1257
+ return mkPtr(cell);
1258
+ }
1259
+ if (expr.kind === 'MemberExpr') {
1260
+ const obj = this.evalExpr(expr.object, scope);
1261
+ if (obj.tag === 'struct') {
1262
+ const addr = this.heap.allocate(8);
1263
+ const fieldVal = obj.fields.get(expr.member) ?? mkInt(0);
1264
+ // Store only this field in the heap cell, with pointee = field name
1265
+ this.heap.storeArray(addr, [fieldVal]);
1266
+ return mkPtr(addr, expr.member);
1267
+ }
1268
+ }
1269
+ if (expr.kind === 'UnaryExpr' && expr.op === '*') {
1270
+ return this.evalExpr(expr.operand, scope);
1271
+ }
1272
+ const v = this.evalExpr(expr, scope);
1273
+ if (v.tag === 'ptr')
1274
+ return v;
1275
+ if (v.tag === 'str')
1276
+ return mkPtr(v.addr);
1277
+ if (v.tag === 'array')
1278
+ return mkPtr(v.addr);
1279
+ const addr = this.heap.allocate(8);
1280
+ this.heap.storeArray(addr, [v]);
1281
+ return mkPtr(addr);
1282
+ }
1283
+ getScopeId(scope) {
1284
+ let id = this.scopeIds.get(scope);
1285
+ if (id === undefined) {
1286
+ id = this.scopeIdCounter++;
1287
+ this.scopeIds.set(scope, id);
1288
+ }
1289
+ return id;
1290
+ }
1291
+ allocScalarCell(name, v, scope) {
1292
+ // Walk scope chain to find where the variable is defined
1293
+ let defScope = scope;
1294
+ let s = scope;
1295
+ while (s) {
1296
+ if (s.vars.has(name)) {
1297
+ defScope = s;
1298
+ break;
1299
+ }
1300
+ s = s.parent;
1301
+ }
1302
+ const scopeId = this.getScopeId(defScope);
1303
+ const key = `${scopeId}:${name}`;
1304
+ if (this.varToAddr.has(key))
1305
+ return this.varToAddr.get(key);
1306
+ const addr = this.heap.allocate(8);
1307
+ this.varToAddr.set(key, addr);
1308
+ this.addrToVar.set(addr, { scope: defScope, name });
1309
+ this.heap.storeArray(addr, [v]);
1310
+ return addr;
1311
+ }
1312
+ assignTo(target, value, scope) {
1313
+ if (target.kind === 'Identifier') {
1314
+ if (!scope.assign(target.name, value)) {
1315
+ scope.set(target.name, value);
1316
+ }
1317
+ // Also update any scalar cell linked to this variable
1318
+ // Walk all addrToVar entries to find ones pointing to this scope+name
1319
+ // (lightweight: we just update the heap array directly)
1320
+ for (const [addr, ref] of this.addrToVar) {
1321
+ if (ref.name === target.name) {
1322
+ const arr = this.heap.loadArray(addr);
1323
+ if (arr)
1324
+ arr[0] = value;
1325
+ }
1326
+ }
1327
+ return;
1328
+ }
1329
+ if (target.kind === 'UnaryExpr' && target.op === '*') {
1330
+ // *ptr = value
1331
+ const ptr = this.evalExpr(target.operand, scope);
1332
+ this.writeToPtr(ptr, value);
1333
+ return;
1334
+ }
1335
+ if (target.kind === 'IndexExpr') {
1336
+ const obj = this.evalExpr(target.object, scope);
1337
+ const idx = Number(toBigInt(this.evalExpr(target.index, scope)));
1338
+ this.setArrayElement(obj, idx, value, scope, target.object);
1339
+ return;
1340
+ }
1341
+ if (target.kind === 'MemberExpr') {
1342
+ const obj = this.evalExpr(target.object, scope);
1343
+ if (obj.tag === 'struct') {
1344
+ obj.fields.set(target.member, value);
1345
+ // Write back to parent if it's an identifier
1346
+ if (target.object.kind === 'Identifier') {
1347
+ scope.assign(target.object.name, obj);
1348
+ }
1349
+ return;
1350
+ }
1351
+ if (obj.tag === 'ptr') {
1352
+ // Arrow access: ptr->field
1353
+ // Check if the pointer maps to a scope variable (struct)
1354
+ const varRef = this.addrToVar.get(obj.addr);
1355
+ if (varRef) {
1356
+ const sv = varRef.scope.get(varRef.name);
1357
+ if (sv && sv.tag === 'struct') {
1358
+ sv.fields.set(target.member, value);
1359
+ varRef.scope.assign(varRef.name, sv);
1360
+ // Also update heap store
1361
+ const sf = this.heap.loadStruct(obj.addr);
1362
+ if (sf)
1363
+ sf.set(target.member, value);
1364
+ return;
1365
+ }
1366
+ }
1367
+ const structFields = this.heap.loadStruct(obj.addr);
1368
+ if (structFields) {
1369
+ structFields.set(target.member, value);
1370
+ return;
1371
+ }
1372
+ // No struct store yet, create one
1373
+ const newFields = new Map();
1374
+ newFields.set(target.member, value);
1375
+ this.heap.storeStruct(obj.addr, newFields);
1376
+ }
1377
+ return;
1378
+ }
1379
+ }
1380
+ writeToPtr(ptr, value) {
1381
+ if (ptr.tag !== 'ptr')
1382
+ return;
1383
+ // Check if this address maps to a scope variable
1384
+ const varRef = this.addrToVar.get(ptr.addr);
1385
+ if (varRef) {
1386
+ varRef.scope.assign(varRef.name, value);
1387
+ const arr = this.heap.loadArray(ptr.addr);
1388
+ if (arr)
1389
+ arr[0] = value;
1390
+ return;
1391
+ }
1392
+ // Check if it's a heap array cell
1393
+ const arr = this.heap.loadArray(ptr.addr);
1394
+ if (arr) {
1395
+ arr[0] = value;
1396
+ return;
1397
+ }
1398
+ // Struct store
1399
+ const structFields = this.heap.loadStruct(ptr.addr);
1400
+ if (structFields) {
1401
+ if (value.tag === 'struct') {
1402
+ for (const [k, v] of value.fields)
1403
+ structFields.set(k, v);
1404
+ }
1405
+ else if (ptr.pointee) {
1406
+ structFields.set(ptr.pointee, value);
1407
+ }
1408
+ return;
1409
+ }
1410
+ // Write string
1411
+ if (value.tag === 'str') {
1412
+ this.heap.writeString(ptr.addr, value.s);
1413
+ return;
1414
+ }
1415
+ // Create a new cell
1416
+ this.heap.storeArray(ptr.addr, [value]);
1417
+ }
1418
+ setArrayElement(arr, idx, value, scope, objExpr) {
1419
+ if (arr.tag === 'array') {
1420
+ arr.elements[idx] = value;
1421
+ return;
1422
+ }
1423
+ if (arr.tag === 'ptr') {
1424
+ // Could be a heap array
1425
+ const heapArr = this.heap.loadArray(arr.addr);
1426
+ if (heapArr) {
1427
+ heapArr[idx] = value;
1428
+ return;
1429
+ }
1430
+ // Try to write at offset
1431
+ const targetAddr = arr.addr + idx * 8;
1432
+ const targetCell = this.heap.loadArray(targetAddr);
1433
+ if (targetCell) {
1434
+ targetCell[0] = value;
1435
+ return;
1436
+ }
1437
+ this.heap.storeArray(targetAddr, [value]);
1438
+ return;
1439
+ }
1440
+ }
1441
+ evalAssign(expr, scope) {
1442
+ const right = this.evalExpr(expr.right, scope);
1443
+ const left = this.evalExpr(expr.left, scope);
1444
+ let newVal;
1445
+ if (expr.op === '=') {
1446
+ newVal = right;
1447
+ }
1448
+ else {
1449
+ // Compound assignment
1450
+ const op = expr.op.slice(0, -1); // strip '='
1451
+ const combined = { kind: 'BinaryExpr', op, left: expr.left, right: expr.right };
1452
+ newVal = this.evalBinary(combined, scope);
1453
+ }
1454
+ this.assignTo(expr.left, newVal, scope);
1455
+ return newVal;
1456
+ }
1457
+ evalCall(expr, scope) {
1458
+ // Get function name
1459
+ let fnName = '';
1460
+ if (expr.callee.kind === 'Identifier') {
1461
+ fnName = expr.callee.name;
1462
+ }
1463
+ else if (expr.callee.kind === 'MemberExpr') {
1464
+ fnName = expr.callee.member;
1465
+ }
1466
+ else {
1467
+ const v = this.evalExpr(expr.callee, scope);
1468
+ if (v.tag === 'ptr' && v.pointee)
1469
+ fnName = v.pointee;
1470
+ }
1471
+ // Evaluate arguments
1472
+ const args = expr.args.map(a => this.evalExpr(a, scope));
1473
+ // Try built-in first
1474
+ const builtin = this.callBuiltin(fnName, args, expr.args, scope);
1475
+ if (builtin !== undefined)
1476
+ return builtin;
1477
+ // User-defined function
1478
+ const fn = this.functions.get(fnName);
1479
+ if (fn)
1480
+ return this.callFunction(fn, args, scope);
1481
+ // Unknown function — return 0
1482
+ return mkInt(0);
1483
+ }
1484
+ callFunction(fn, args, callerScope) {
1485
+ if (!fn.body)
1486
+ return VOID;
1487
+ const fnScope = new Scope(this.globalScope);
1488
+ // Bind parameters
1489
+ for (let i = 0; i < fn.params.length; i++) {
1490
+ const param = fn.params[i];
1491
+ if (param.isVararg)
1492
+ break;
1493
+ const argVal = args[i] ?? this.defaultValue(param.type);
1494
+ fnScope.set(param.name, argVal);
1495
+ }
1496
+ // Handle varargs: store remaining as __varargs
1497
+ const varargStart = fn.params.findIndex(p => p.isVararg);
1498
+ if (varargStart >= 0) {
1499
+ const varargs = args.slice(varargStart);
1500
+ this.currentVarargs = varargs;
1501
+ }
1502
+ try {
1503
+ this.execBlock(fn.body, fnScope);
1504
+ return VOID;
1505
+ }
1506
+ catch (e) {
1507
+ if (e instanceof ReturnSignal)
1508
+ return e.value;
1509
+ throw e;
1510
+ }
1511
+ }
1512
+ // ─── Built-in Functions ────────────────────────────────────────────────────
1513
+ callBuiltin(name, args, rawArgs, scope) {
1514
+ switch (name) {
1515
+ // I/O
1516
+ case 'printf': {
1517
+ const fmt = args[0];
1518
+ const fmtStr = fmt?.tag === 'str' ? fmt.s : (fmt?.tag === 'ptr' ? this.heap.readString(fmt.addr) : '');
1519
+ const rest = args.slice(1);
1520
+ const out = formatPrintf(fmtStr, rest, this.heap);
1521
+ this.writeStdout(out);
1522
+ return mkInt(out.length);
1523
+ }
1524
+ case 'fprintf': {
1525
+ // args[0] = FILE*, args[1] = fmt, rest = ...
1526
+ const filePtr = args[0];
1527
+ const fmt = args[1];
1528
+ const fmtStr = fmt?.tag === 'str' ? fmt.s : (fmt?.tag === 'ptr' ? this.heap.readString(fmt.addr) : '');
1529
+ const rest = args.slice(2);
1530
+ const out = formatPrintf(fmtStr, rest, this.heap);
1531
+ // Check if stderr (addr=2)
1532
+ const isStderr = filePtr?.tag === 'ptr' && filePtr.addr === 2;
1533
+ if (isStderr)
1534
+ this.writeStderr(out);
1535
+ else
1536
+ this.writeStdout(out);
1537
+ return mkInt(out.length);
1538
+ }
1539
+ case 'sprintf': {
1540
+ const bufPtr = args[0];
1541
+ const fmt = args[1];
1542
+ const fmtStr = fmt?.tag === 'str' ? fmt.s : (fmt?.tag === 'ptr' ? this.heap.readString(fmt.addr) : '');
1543
+ const rest = args.slice(2);
1544
+ const out = formatPrintf(fmtStr, rest, this.heap);
1545
+ if (bufPtr?.tag === 'ptr')
1546
+ this.heap.writeString(bufPtr.addr, out);
1547
+ return mkInt(out.length);
1548
+ }
1549
+ case 'snprintf': {
1550
+ // snprintf(buf, n, fmt, ...)
1551
+ const bufPtr = args[0];
1552
+ // args[1] = max size, args[2] = fmt
1553
+ const fmt = args[2];
1554
+ const fmtStr = fmt?.tag === 'str' ? fmt.s : (fmt?.tag === 'ptr' ? this.heap.readString(fmt.addr) : '');
1555
+ const rest = args.slice(3);
1556
+ const out = formatPrintf(fmtStr, rest, this.heap);
1557
+ if (bufPtr?.tag === 'ptr')
1558
+ this.heap.writeString(bufPtr.addr, out);
1559
+ return mkInt(out.length);
1560
+ }
1561
+ case 'puts': {
1562
+ const s = args[0];
1563
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '');
1564
+ this.writeStdout(str + '\n');
1565
+ return mkInt(str.length + 1);
1566
+ }
1567
+ case 'putchar': {
1568
+ const c = Number(toBigInt(args[0] ?? mkInt(0)));
1569
+ this.writeStdout(String.fromCharCode(c));
1570
+ return mkInt(c);
1571
+ }
1572
+ case 'putc':
1573
+ case 'fputc': {
1574
+ const c = Number(toBigInt(args[0] ?? mkInt(0)));
1575
+ this.writeStdout(String.fromCharCode(c));
1576
+ return mkInt(c);
1577
+ }
1578
+ case 'fputs': {
1579
+ const s = args[0];
1580
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '');
1581
+ this.writeStdout(str);
1582
+ return mkInt(str.length);
1583
+ }
1584
+ case 'getchar':
1585
+ case 'fgetc': {
1586
+ if (this.stdinPos < this.stdinData.length) {
1587
+ return mkInt(this.stdinData.charCodeAt(this.stdinPos++));
1588
+ }
1589
+ return mkInt(-1); // EOF
1590
+ }
1591
+ case 'scanf':
1592
+ case 'fscanf': return mkInt(0); // not supported
1593
+ case 'fflush': return mkInt(0);
1594
+ case 'fopen': return NULL_PTR();
1595
+ case 'fclose': return mkInt(0);
1596
+ case 'fread': return mkInt(0);
1597
+ case 'fwrite': return mkInt(0);
1598
+ case 'feof': return mkInt(1);
1599
+ case 'ferror': return mkInt(0);
1600
+ case 'rewind': return VOID;
1601
+ case 'fseek': return mkInt(0);
1602
+ case 'ftell': return mkInt(0);
1603
+ case 'perror': {
1604
+ const msg = args[0];
1605
+ const str = msg?.tag === 'str' ? msg.s : (msg?.tag === 'ptr' ? this.heap.readString(msg.addr) : '');
1606
+ this.writeStderr(str + ': error\n');
1607
+ return VOID;
1608
+ }
1609
+ // Memory
1610
+ case 'malloc': {
1611
+ const n = Number(toBigInt(args[0] ?? mkInt(0)));
1612
+ if (n === 0)
1613
+ return NULL_PTR();
1614
+ const addr = this.heap.allocate(n);
1615
+ return mkPtr(addr);
1616
+ }
1617
+ case 'calloc': {
1618
+ const n = Number(toBigInt(args[0] ?? mkInt(0)));
1619
+ const sz = Number(toBigInt(args[1] ?? mkInt(1)));
1620
+ const total = n * sz;
1621
+ if (total === 0)
1622
+ return NULL_PTR();
1623
+ const addr = this.heap.allocate(total);
1624
+ return mkPtr(addr);
1625
+ }
1626
+ case 'realloc': {
1627
+ const ptr = args[0];
1628
+ const n = Number(toBigInt(args[1] ?? mkInt(0)));
1629
+ if (!ptr || ptr.tag !== 'ptr' || ptr.addr === 0) {
1630
+ return mkPtr(this.heap.allocate(n));
1631
+ }
1632
+ const newAddr = this.heap.reallocate(ptr.addr, n);
1633
+ return mkPtr(newAddr);
1634
+ }
1635
+ case 'free': return VOID; // no-op
1636
+ // String operations
1637
+ case 'strlen': {
1638
+ const s = args[0];
1639
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '');
1640
+ return mkInt(str.length);
1641
+ }
1642
+ case 'strcpy': {
1643
+ const dst = args[0];
1644
+ const src = args[1];
1645
+ const str = src?.tag === 'str' ? src.s : (src?.tag === 'ptr' ? this.heap.readString(src.addr) : '');
1646
+ if (dst?.tag === 'ptr')
1647
+ this.heap.writeString(dst.addr, str);
1648
+ return dst ?? NULL_PTR();
1649
+ }
1650
+ case 'strncpy': {
1651
+ const dst = args[0];
1652
+ const src = args[1];
1653
+ const n = Number(toBigInt(args[2] ?? mkInt(0)));
1654
+ const str = (src?.tag === 'str' ? src.s : (src?.tag === 'ptr' ? this.heap.readString(src.addr) : '')).slice(0, n);
1655
+ if (dst?.tag === 'ptr')
1656
+ this.heap.writeString(dst.addr, str);
1657
+ return dst ?? NULL_PTR();
1658
+ }
1659
+ case 'strcmp': {
1660
+ const a = args[0];
1661
+ const b = args[1];
1662
+ const sa = a?.tag === 'str' ? a.s : (a?.tag === 'ptr' ? this.heap.readString(a.addr) : '');
1663
+ const sb = b?.tag === 'str' ? b.s : (b?.tag === 'ptr' ? this.heap.readString(b.addr) : '');
1664
+ if (sa < sb)
1665
+ return mkInt(-1);
1666
+ if (sa > sb)
1667
+ return mkInt(1);
1668
+ return mkInt(0);
1669
+ }
1670
+ case 'strncmp': {
1671
+ const a = args[0];
1672
+ const b = args[1];
1673
+ const n = Number(toBigInt(args[2] ?? mkInt(0)));
1674
+ const sa = (a?.tag === 'str' ? a.s : (a?.tag === 'ptr' ? this.heap.readString(a.addr) : '')).slice(0, n);
1675
+ const sb = (b?.tag === 'str' ? b.s : (b?.tag === 'ptr' ? this.heap.readString(b.addr) : '')).slice(0, n);
1676
+ if (sa < sb)
1677
+ return mkInt(-1);
1678
+ if (sa > sb)
1679
+ return mkInt(1);
1680
+ return mkInt(0);
1681
+ }
1682
+ case 'strcat': {
1683
+ const dst = args[0];
1684
+ const src = args[1];
1685
+ const sa = dst?.tag === 'ptr' ? this.heap.readString(dst.addr) : '';
1686
+ const sb = src?.tag === 'str' ? src.s : (src?.tag === 'ptr' ? this.heap.readString(src.addr) : '');
1687
+ const result = sa + sb;
1688
+ if (dst?.tag === 'ptr')
1689
+ this.heap.writeString(dst.addr, result);
1690
+ return dst ?? NULL_PTR();
1691
+ }
1692
+ case 'strncat': {
1693
+ const dst = args[0];
1694
+ const src = args[1];
1695
+ const n = Number(toBigInt(args[2] ?? mkInt(0)));
1696
+ const sa = dst?.tag === 'ptr' ? this.heap.readString(dst.addr) : '';
1697
+ const sb = (src?.tag === 'str' ? src.s : (src?.tag === 'ptr' ? this.heap.readString(src.addr) : '')).slice(0, n);
1698
+ const result = sa + sb;
1699
+ if (dst?.tag === 'ptr')
1700
+ this.heap.writeString(dst.addr, result);
1701
+ return dst ?? NULL_PTR();
1702
+ }
1703
+ case 'strchr': {
1704
+ const s = args[0];
1705
+ const c = Number(toBigInt(args[1] ?? mkInt(0)));
1706
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '');
1707
+ const idx = str.indexOf(String.fromCharCode(c));
1708
+ if (idx < 0)
1709
+ return NULL_PTR();
1710
+ const addr = this.heap.internString(str.slice(idx));
1711
+ return mkPtr(addr);
1712
+ }
1713
+ case 'strstr': {
1714
+ const haystack = args[0];
1715
+ const needle = args[1];
1716
+ const hs = haystack?.tag === 'str' ? haystack.s : (haystack?.tag === 'ptr' ? this.heap.readString(haystack.addr) : '');
1717
+ const nd = needle?.tag === 'str' ? needle.s : (needle?.tag === 'ptr' ? this.heap.readString(needle.addr) : '');
1718
+ const idx = hs.indexOf(nd);
1719
+ if (idx < 0)
1720
+ return NULL_PTR();
1721
+ const addr = this.heap.internString(hs.slice(idx));
1722
+ return mkPtr(addr);
1723
+ }
1724
+ case 'strtok': return NULL_PTR(); // not supported properly
1725
+ // Memory operations
1726
+ case 'memset': {
1727
+ const ptr = args[0];
1728
+ const val = Number(toBigInt(args[1] ?? mkInt(0)));
1729
+ const n = Number(toBigInt(args[2] ?? mkInt(0)));
1730
+ if (ptr?.tag === 'ptr') {
1731
+ for (let i = 0; i < n; i++) {
1732
+ this.heap.writeByte(ptr.addr, i, val);
1733
+ }
1734
+ }
1735
+ return ptr ?? NULL_PTR();
1736
+ }
1737
+ case 'memcpy': {
1738
+ const dst = args[0];
1739
+ const src = args[1];
1740
+ const n = Number(toBigInt(args[2] ?? mkInt(0)));
1741
+ if (dst?.tag === 'ptr' && src?.tag === 'ptr') {
1742
+ for (let i = 0; i < n; i++) {
1743
+ const b = this.heap.readByte(src.addr, i);
1744
+ this.heap.writeByte(dst.addr, i, b);
1745
+ }
1746
+ // Also copy string/array stores
1747
+ const srcStr = this.heap.readString(src.addr);
1748
+ if (srcStr)
1749
+ this.heap.writeString(dst.addr, srcStr);
1750
+ const srcArr = this.heap.loadArray(src.addr);
1751
+ if (srcArr)
1752
+ this.heap.storeArray(dst.addr, [...srcArr]);
1753
+ }
1754
+ return dst ?? NULL_PTR();
1755
+ }
1756
+ case 'memmove': return this.callBuiltin('memcpy', args, rawArgs, scope);
1757
+ case 'memcmp': {
1758
+ const a = args[0];
1759
+ const b = args[1];
1760
+ const n = Number(toBigInt(args[2] ?? mkInt(0)));
1761
+ for (let i = 0; i < n; i++) {
1762
+ const ba = a?.tag === 'ptr' ? this.heap.readByte(a.addr, i) : 0;
1763
+ const bb = b?.tag === 'ptr' ? this.heap.readByte(b.addr, i) : 0;
1764
+ if (ba !== bb)
1765
+ return mkInt(ba - bb);
1766
+ }
1767
+ return mkInt(0);
1768
+ }
1769
+ // Math
1770
+ case 'sqrt': return mkFloat(Math.sqrt(toNumber(args[0] ?? mkFloat(0))));
1771
+ case 'sqrtf': return mkFloat(Math.sqrt(toNumber(args[0] ?? mkFloat(0))));
1772
+ case 'abs': return mkInt(Math.abs(Number(toBigInt(args[0] ?? mkInt(0)))));
1773
+ case 'fabs':
1774
+ case 'fabsf': return mkFloat(Math.abs(toNumber(args[0] ?? mkFloat(0))));
1775
+ case 'pow':
1776
+ case 'powf': return mkFloat(Math.pow(toNumber(args[0] ?? mkFloat(0)), toNumber(args[1] ?? mkFloat(0))));
1777
+ case 'floor':
1778
+ case 'floorf': return mkFloat(Math.floor(toNumber(args[0] ?? mkFloat(0))));
1779
+ case 'ceil':
1780
+ case 'ceilf': return mkFloat(Math.ceil(toNumber(args[0] ?? mkFloat(0))));
1781
+ case 'round':
1782
+ case 'roundf': return mkFloat(Math.round(toNumber(args[0] ?? mkFloat(0))));
1783
+ case 'sin':
1784
+ case 'sinf': return mkFloat(Math.sin(toNumber(args[0] ?? mkFloat(0))));
1785
+ case 'cos':
1786
+ case 'cosf': return mkFloat(Math.cos(toNumber(args[0] ?? mkFloat(0))));
1787
+ case 'tan':
1788
+ case 'tanf': return mkFloat(Math.tan(toNumber(args[0] ?? mkFloat(0))));
1789
+ case 'asin':
1790
+ case 'asinf': return mkFloat(Math.asin(toNumber(args[0] ?? mkFloat(0))));
1791
+ case 'acos':
1792
+ case 'acosf': return mkFloat(Math.acos(toNumber(args[0] ?? mkFloat(0))));
1793
+ case 'atan':
1794
+ case 'atanf': return mkFloat(Math.atan(toNumber(args[0] ?? mkFloat(0))));
1795
+ case 'atan2':
1796
+ case 'atan2f': return mkFloat(Math.atan2(toNumber(args[0] ?? mkFloat(0)), toNumber(args[1] ?? mkFloat(0))));
1797
+ case 'log':
1798
+ case 'logf': return mkFloat(Math.log(toNumber(args[0] ?? mkFloat(0))));
1799
+ case 'log2':
1800
+ case 'log2f': return mkFloat(Math.log2(toNumber(args[0] ?? mkFloat(0))));
1801
+ case 'log10':
1802
+ case 'log10f': return mkFloat(Math.log10(toNumber(args[0] ?? mkFloat(0))));
1803
+ case 'exp':
1804
+ case 'expf': return mkFloat(Math.exp(toNumber(args[0] ?? mkFloat(0))));
1805
+ case 'fmod':
1806
+ case 'fmodf': return mkFloat(toNumber(args[0] ?? mkFloat(0)) % toNumber(args[1] ?? mkFloat(1)));
1807
+ case 'hypot':
1808
+ case 'hypotf': return mkFloat(Math.hypot(toNumber(args[0] ?? mkFloat(0)), toNumber(args[1] ?? mkFloat(0))));
1809
+ case 'max':
1810
+ case 'fmax': {
1811
+ const a = toNumber(args[0] ?? mkFloat(0));
1812
+ const b = toNumber(args[1] ?? mkFloat(0));
1813
+ return mkFloat(Math.max(a, b));
1814
+ }
1815
+ case 'min':
1816
+ case 'fmin': {
1817
+ const a = toNumber(args[0] ?? mkFloat(0));
1818
+ const b = toNumber(args[1] ?? mkFloat(0));
1819
+ return mkFloat(Math.min(a, b));
1820
+ }
1821
+ // Conversion
1822
+ case 'atoi': {
1823
+ const s = args[0];
1824
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '0');
1825
+ return mkInt(parseInt(str.trim(), 10) || 0);
1826
+ }
1827
+ case 'atol':
1828
+ case 'atoll': {
1829
+ const s = args[0];
1830
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '0');
1831
+ return mkInt(parseInt(str.trim(), 10) || 0);
1832
+ }
1833
+ case 'atof': {
1834
+ const s = args[0];
1835
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '0');
1836
+ return mkFloat(parseFloat(str.trim()) || 0);
1837
+ }
1838
+ case 'strtol':
1839
+ case 'strtoul':
1840
+ case 'strtoll':
1841
+ case 'strtoull': {
1842
+ const s = args[0];
1843
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '0');
1844
+ const base = Number(toBigInt(args[2] ?? mkInt(10)));
1845
+ return mkInt(parseInt(str.trim(), base || 10) || 0);
1846
+ }
1847
+ case 'strtod':
1848
+ case 'strtof': {
1849
+ const s = args[0];
1850
+ const str = s?.tag === 'str' ? s.s : (s?.tag === 'ptr' ? this.heap.readString(s.addr) : '0');
1851
+ return mkFloat(parseFloat(str.trim()) || 0);
1852
+ }
1853
+ case 'itoa': {
1854
+ const n = Number(toBigInt(args[0] ?? mkInt(0)));
1855
+ const buf = args[1];
1856
+ const base = Number(toBigInt(args[2] ?? mkInt(10)));
1857
+ const str = n.toString(base);
1858
+ if (buf?.tag === 'ptr')
1859
+ this.heap.writeString(buf.addr, str);
1860
+ return buf ?? NULL_PTR();
1861
+ }
1862
+ // Random
1863
+ case 'rand': return mkInt((Math.random() * 32767) | 0);
1864
+ case 'srand': return VOID;
1865
+ case 'random': return mkInt((Math.random() * 2147483647) | 0);
1866
+ case 'srandom': return VOID;
1867
+ // Process control
1868
+ case 'exit': {
1869
+ const code = Number(toBigInt(args[0] ?? mkInt(0)));
1870
+ if (this.captureOutput) {
1871
+ throw new ExitSignal(code);
1872
+ }
1873
+ else {
1874
+ process.exit(code);
1875
+ }
1876
+ }
1877
+ case 'abort': {
1878
+ this.writeStderr('Aborted\n');
1879
+ if (this.captureOutput)
1880
+ throw new ExitSignal(134);
1881
+ process.exit(134);
1882
+ }
1883
+ case 'assert': {
1884
+ const cond = args[0];
1885
+ if (!isTruthy(cond)) {
1886
+ this.writeStderr('Assertion failed\n');
1887
+ if (this.captureOutput)
1888
+ throw new ExitSignal(1);
1889
+ process.exit(1);
1890
+ }
1891
+ return VOID;
1892
+ }
1893
+ // Type checking / misc
1894
+ case 'isdigit': return mkBool(/[0-9]/.test(String.fromCharCode(toNumber(args[0] ?? mkInt(0)))));
1895
+ case 'isalpha': return mkBool(/[a-zA-Z]/.test(String.fromCharCode(toNumber(args[0] ?? mkInt(0)))));
1896
+ case 'isalnum': return mkBool(/[a-zA-Z0-9]/.test(String.fromCharCode(toNumber(args[0] ?? mkInt(0)))));
1897
+ case 'isspace': return mkBool(/\s/.test(String.fromCharCode(toNumber(args[0] ?? mkInt(0)))));
1898
+ case 'isupper': return mkBool(/[A-Z]/.test(String.fromCharCode(toNumber(args[0] ?? mkInt(0)))));
1899
+ case 'islower': return mkBool(/[a-z]/.test(String.fromCharCode(toNumber(args[0] ?? mkInt(0)))));
1900
+ case 'ispunct': return mkBool(/[^\w\s]/.test(String.fromCharCode(toNumber(args[0] ?? mkInt(0)))));
1901
+ case 'isprint': {
1902
+ const c = toNumber(args[0] ?? mkInt(0));
1903
+ return mkBool(c >= 32 && c < 127);
1904
+ }
1905
+ case 'toupper': {
1906
+ const c = toNumber(args[0] ?? mkInt(0));
1907
+ return mkInt(String.fromCharCode(c).toUpperCase().charCodeAt(0));
1908
+ }
1909
+ case 'tolower': {
1910
+ const c = toNumber(args[0] ?? mkInt(0));
1911
+ return mkInt(String.fromCharCode(c).toLowerCase().charCodeAt(0));
1912
+ }
1913
+ // Unused / not supported
1914
+ case 'va_start':
1915
+ case 'va_end':
1916
+ case 'va_arg':
1917
+ case 'va_copy': return VOID;
1918
+ case 'sizeof': {
1919
+ // shouldn't be called as a function but just in case
1920
+ return mkInt(8);
1921
+ }
1922
+ default:
1923
+ return undefined; // not a builtin
1924
+ }
1925
+ }
1926
+ evalIndex(expr, scope) {
1927
+ const obj = this.evalExpr(expr.object, scope);
1928
+ const idx = Number(toBigInt(this.evalExpr(expr.index, scope)));
1929
+ if (obj.tag === 'array') {
1930
+ return obj.elements[idx] ?? mkInt(0);
1931
+ }
1932
+ if (obj.tag === 'ptr') {
1933
+ // Check heap array
1934
+ const arr = this.heap.loadArray(obj.addr);
1935
+ if (arr)
1936
+ return arr[idx] ?? mkInt(0);
1937
+ // Could be offset-based: each element at addr + idx*stride
1938
+ const elemAddr = obj.addr + idx * 8;
1939
+ const elemArr = this.heap.loadArray(elemAddr);
1940
+ if (elemArr)
1941
+ return elemArr[0] ?? mkInt(0);
1942
+ // String indexing
1943
+ const s = this.heap.readString(obj.addr);
1944
+ if (s.length > 0)
1945
+ return mkInt(s.charCodeAt(idx) || 0);
1946
+ return mkInt(0);
1947
+ }
1948
+ if (obj.tag === 'str') {
1949
+ return mkInt(obj.s.charCodeAt(idx) || 0);
1950
+ }
1951
+ return mkInt(0);
1952
+ }
1953
+ evalMember(expr, scope) {
1954
+ const obj = this.evalExpr(expr.object, scope);
1955
+ if (expr.arrow) {
1956
+ // obj->member (obj is a pointer)
1957
+ if (obj.tag === 'ptr') {
1958
+ // Check if addr maps to a scope variable with a struct value
1959
+ const varRef = this.addrToVar.get(obj.addr);
1960
+ if (varRef) {
1961
+ const sv = varRef.scope.get(varRef.name);
1962
+ if (sv && sv.tag === 'struct')
1963
+ return sv.fields.get(expr.member) ?? mkInt(0);
1964
+ }
1965
+ const structFields = this.heap.loadStruct(obj.addr);
1966
+ if (structFields)
1967
+ return structFields.get(expr.member) ?? mkInt(0);
1968
+ return mkInt(0);
1969
+ }
1970
+ if (obj.tag === 'struct')
1971
+ return obj.fields.get(expr.member) ?? mkInt(0);
1972
+ return mkInt(0);
1973
+ }
1974
+ else {
1975
+ // obj.member
1976
+ if (obj.tag === 'struct')
1977
+ return obj.fields.get(expr.member) ?? mkInt(0);
1978
+ if (obj.tag === 'ptr') {
1979
+ const varRef = this.addrToVar.get(obj.addr);
1980
+ if (varRef) {
1981
+ const sv = varRef.scope.get(varRef.name);
1982
+ if (sv && sv.tag === 'struct')
1983
+ return sv.fields.get(expr.member) ?? mkInt(0);
1984
+ }
1985
+ const structFields = this.heap.loadStruct(obj.addr);
1986
+ if (structFields)
1987
+ return structFields.get(expr.member) ?? mkInt(0);
1988
+ }
1989
+ return mkInt(0);
1990
+ }
1991
+ }
1992
+ evalCast(expr, scope) {
1993
+ const v = this.evalExpr(expr.expr, scope);
1994
+ const target = expr.targetType;
1995
+ if (target.pointers > 0) {
1996
+ // Cast to pointer
1997
+ if (v.tag === 'ptr')
1998
+ return v;
1999
+ if (v.tag === 'str')
2000
+ return mkPtr(v.addr);
2001
+ if (v.tag === 'array')
2002
+ return mkPtr(v.addr);
2003
+ return mkPtr(toNumber(v));
2004
+ }
2005
+ switch (target.base) {
2006
+ case 'int8_t': return mkInt(Number(BigInt.asIntN(8, toBigInt(v))));
2007
+ case 'uint8_t': return mkInt(Number(BigInt.asUintN(8, toBigInt(v))));
2008
+ case 'int16_t': return mkInt(Number(BigInt.asIntN(16, toBigInt(v))));
2009
+ case 'uint16_t': return mkInt(Number(BigInt.asUintN(16, toBigInt(v))));
2010
+ case 'int32_t': return mkInt(Number(BigInt.asIntN(32, toBigInt(v))));
2011
+ case 'uint32_t': return mkInt(Number(BigInt.asUintN(32, toBigInt(v))));
2012
+ case 'int64_t': return mkInt(toBigInt(v));
2013
+ case 'uint64_t': return mkInt(BigInt.asUintN(64, toBigInt(v)));
2014
+ case 'float':
2015
+ case 'double': return mkFloat(toNumber(v));
2016
+ case 'char': return mkInt(Number(BigInt.asIntN(8, toBigInt(v))));
2017
+ case 'bool': return mkBool(isTruthy(v));
2018
+ case 'void': return VOID;
2019
+ default: return v;
2020
+ }
2021
+ }
2022
+ evalSizeof(expr, scope) {
2023
+ if (expr.isType) {
2024
+ const t = expr.operand;
2025
+ return mkInt(sizeofType(t));
2026
+ }
2027
+ // sizeof(expr) — evaluate type from expression
2028
+ const v = this.evalExpr(expr.operand, scope);
2029
+ switch (v.tag) {
2030
+ case 'i64': return mkInt(8);
2031
+ case 'f64': return mkInt(8);
2032
+ case 'ptr': return mkInt(8);
2033
+ case 'str': return mkInt(v.s.length + 1);
2034
+ case 'bool': return mkInt(1);
2035
+ case 'struct': return mkInt(v.fields.size * 8);
2036
+ case 'array': return mkInt(v.elements.length * 8);
2037
+ default: return mkInt(0);
2038
+ }
2039
+ }
2040
+ evalInitList(expr, scope) {
2041
+ // Used for struct/array initialization
2042
+ const elements = expr.elements.map(e => this.evalExpr(e, scope));
2043
+ // If it's being used in a struct context, we can't know the field names here
2044
+ // Return as an array; the VarDecl handler will match to struct fields
2045
+ const addr = this.heap.allocate(elements.length * 8);
2046
+ this.heap.storeArray(addr, elements);
2047
+ return { tag: 'array', elements, addr };
2048
+ }
2049
+ }
2050
+ exports.Interpreter = Interpreter;
2051
+ // ─── Handle struct initialization from InitList ────────────────────────────
2052
+ // We need to patch VarDecl execution to handle struct init lists
2053
+ // This is done in the Interpreter class above via the defaultValue + init logic
2054
+ // ─── Interpreter Instance Helper ──────────────────────────────────────────────
2055
+ // Override execVarDecl to handle struct init from init list
2056
+ // This is already handled in the Interpreter class.
2057
+ // ─── Public API ──────────────────────────────────────────────────────────────
2058
+ function interpret(pearSource, argv) {
2059
+ const interp = new Interpreter();
2060
+ const result = interp.run(pearSource, argv);
2061
+ return result.exitCode;
2062
+ }
2063
+ //# sourceMappingURL=interpreter.js.map