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.
- package/README.md +146 -0
- package/dist/codegen.d.ts +29 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +644 -0
- package/dist/codegen.js.map +1 -0
- package/dist/compiler.d.ts +23 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +370 -0
- package/dist/compiler.js.map +1 -0
- package/dist/decompiler.d.ts +25 -0
- package/dist/decompiler.d.ts.map +1 -0
- package/dist/decompiler.js +294 -0
- package/dist/decompiler.js.map +1 -0
- package/dist/interpreter.d.ts +72 -0
- package/dist/interpreter.d.ts.map +1 -0
- package/dist/interpreter.js +2063 -0
- package/dist/interpreter.js.map +1 -0
- package/dist/lexer.d.ts +125 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +563 -0
- package/dist/lexer.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +228 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/parser.d.ts +307 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +1047 -0
- package/dist/parser.js.map +1 -0
- package/examples/hello.pr +2 -0
- package/examples/pointers.pr +32 -0
- package/examples/structs.pr +6 -0
- package/package.json +40 -0
- package/spec/pear.md +213 -0
|
@@ -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
|