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
package/dist/parser.js
ADDED
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pear Language Parser
|
|
3
|
+
// Recursive descent parser producing an AST
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.Parser = exports.PEAR_TYPE_MAP = void 0;
|
|
6
|
+
exports.parse = parse;
|
|
7
|
+
const lexer_1 = require("./lexer");
|
|
8
|
+
// ─── Parser ──────────────────────────────────────────────────────────────────
|
|
9
|
+
const TYPE_KEYWORDS = new Set([
|
|
10
|
+
lexer_1.TokenType.I8, lexer_1.TokenType.I16, lexer_1.TokenType.I32, lexer_1.TokenType.I64,
|
|
11
|
+
lexer_1.TokenType.U8, lexer_1.TokenType.U16, lexer_1.TokenType.U32, lexer_1.TokenType.U64,
|
|
12
|
+
lexer_1.TokenType.F32, lexer_1.TokenType.F64,
|
|
13
|
+
lexer_1.TokenType.V, lexer_1.TokenType.C, lexer_1.TokenType.B, lexer_1.TokenType.SZ,
|
|
14
|
+
]);
|
|
15
|
+
const PEAR_TYPE_MAP = {
|
|
16
|
+
i8: 'int8_t',
|
|
17
|
+
i16: 'int16_t',
|
|
18
|
+
i32: 'int32_t',
|
|
19
|
+
i64: 'int64_t',
|
|
20
|
+
u8: 'uint8_t',
|
|
21
|
+
u16: 'uint16_t',
|
|
22
|
+
u32: 'uint32_t',
|
|
23
|
+
u64: 'uint64_t',
|
|
24
|
+
f32: 'float',
|
|
25
|
+
f64: 'double',
|
|
26
|
+
v: 'void',
|
|
27
|
+
c: 'char',
|
|
28
|
+
b: 'bool',
|
|
29
|
+
sz: 'size_t',
|
|
30
|
+
};
|
|
31
|
+
exports.PEAR_TYPE_MAP = PEAR_TYPE_MAP;
|
|
32
|
+
class Parser {
|
|
33
|
+
constructor(tokens) {
|
|
34
|
+
this.pos = 0;
|
|
35
|
+
// Filter newlines for the parser (they're only relevant for preprocessor which we handle specially)
|
|
36
|
+
this.tokens = tokens.filter(t => t.type !== lexer_1.TokenType.NEWLINE);
|
|
37
|
+
}
|
|
38
|
+
peek(offset = 0) {
|
|
39
|
+
const idx = this.pos + offset;
|
|
40
|
+
return this.tokens[idx] ?? { type: lexer_1.TokenType.EOF, value: '', line: 0, col: 0 };
|
|
41
|
+
}
|
|
42
|
+
advance() {
|
|
43
|
+
const tok = this.tokens[this.pos];
|
|
44
|
+
this.pos++;
|
|
45
|
+
return tok;
|
|
46
|
+
}
|
|
47
|
+
check(...types) {
|
|
48
|
+
return types.includes(this.peek().type);
|
|
49
|
+
}
|
|
50
|
+
eat(type) {
|
|
51
|
+
const tok = this.peek();
|
|
52
|
+
if (tok.type !== type) {
|
|
53
|
+
throw new Error(`Expected ${type} but got ${tok.type} ('${tok.value}') at line ${tok.line}:${tok.col}`);
|
|
54
|
+
}
|
|
55
|
+
return this.advance();
|
|
56
|
+
}
|
|
57
|
+
tryEat(...types) {
|
|
58
|
+
if (types.includes(this.peek().type))
|
|
59
|
+
return this.advance();
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
// Accept any token that can serve as an identifier name
|
|
63
|
+
// This handles cases like parameter named 'b', 'c', 'v', 'sz', etc.
|
|
64
|
+
eatIdent() {
|
|
65
|
+
const tok = this.peek();
|
|
66
|
+
// Allow IDENT plus any short keyword/type that might be used as a name
|
|
67
|
+
const identLike = new Set([
|
|
68
|
+
lexer_1.TokenType.IDENT,
|
|
69
|
+
lexer_1.TokenType.B, lexer_1.TokenType.C, lexer_1.TokenType.V, lexer_1.TokenType.SZ,
|
|
70
|
+
lexer_1.TokenType.I8, lexer_1.TokenType.I16, lexer_1.TokenType.I32, lexer_1.TokenType.I64,
|
|
71
|
+
lexer_1.TokenType.U8, lexer_1.TokenType.U16, lexer_1.TokenType.U32, lexer_1.TokenType.U64,
|
|
72
|
+
lexer_1.TokenType.F32, lexer_1.TokenType.F64,
|
|
73
|
+
// Also allow Pear keywords that might appear as identifiers in context
|
|
74
|
+
lexer_1.TokenType.IN, // 'in' can be a variable name
|
|
75
|
+
]);
|
|
76
|
+
if (identLike.has(tok.type))
|
|
77
|
+
return this.advance();
|
|
78
|
+
throw new Error(`Expected identifier but got ${tok.type} ('${tok.value}') at line ${tok.line}:${tok.col}`);
|
|
79
|
+
}
|
|
80
|
+
// ─── Type Parsing ──────────────────────────────────────────────────────────
|
|
81
|
+
parseQualifiers() {
|
|
82
|
+
let isConst = false, isVolatile = false, isStatic = false, isExtern = false, isInline = false;
|
|
83
|
+
while (true) {
|
|
84
|
+
if (this.tryEat(lexer_1.TokenType.CN)) {
|
|
85
|
+
isConst = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (this.tryEat(lexer_1.TokenType.VL)) {
|
|
89
|
+
isVolatile = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (this.tryEat(lexer_1.TokenType.SC)) {
|
|
93
|
+
isStatic = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (this.tryEat(lexer_1.TokenType.EX)) {
|
|
97
|
+
isExtern = true;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (this.tryEat(lexer_1.TokenType.IN)) {
|
|
101
|
+
isInline = true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
return { isConst, isVolatile, isStatic, isExtern, isInline };
|
|
107
|
+
}
|
|
108
|
+
parseType() {
|
|
109
|
+
const quals = this.parseQualifiers();
|
|
110
|
+
// Additional qualifiers after the first
|
|
111
|
+
let isConst = quals.isConst;
|
|
112
|
+
let isVolatile = quals.isVolatile;
|
|
113
|
+
// Pear supports *T syntax for pointer-to-T (prefix stars)
|
|
114
|
+
let prefixPointers = 0;
|
|
115
|
+
while (this.check(lexer_1.TokenType.STAR)) {
|
|
116
|
+
this.advance();
|
|
117
|
+
prefixPointers++;
|
|
118
|
+
}
|
|
119
|
+
let base = '';
|
|
120
|
+
if (TYPE_KEYWORDS.has(this.peek().type)) {
|
|
121
|
+
const tok = this.advance();
|
|
122
|
+
base = PEAR_TYPE_MAP[tok.value] ?? tok.value;
|
|
123
|
+
}
|
|
124
|
+
else if (this.check(lexer_1.TokenType.ST)) {
|
|
125
|
+
this.advance();
|
|
126
|
+
const name = this.peek().type === lexer_1.TokenType.IDENT ? this.advance().value : '';
|
|
127
|
+
base = 'struct ' + name;
|
|
128
|
+
}
|
|
129
|
+
else if (this.check(lexer_1.TokenType.UN)) {
|
|
130
|
+
this.advance();
|
|
131
|
+
const name = this.peek().type === lexer_1.TokenType.IDENT ? this.advance().value : '';
|
|
132
|
+
base = 'union ' + name;
|
|
133
|
+
}
|
|
134
|
+
else if (this.check(lexer_1.TokenType.EN)) {
|
|
135
|
+
this.advance();
|
|
136
|
+
const name = this.peek().type === lexer_1.TokenType.IDENT ? this.advance().value : '';
|
|
137
|
+
base = 'enum ' + name;
|
|
138
|
+
}
|
|
139
|
+
else if (this.check(lexer_1.TokenType.IDENT)) {
|
|
140
|
+
base = this.advance().value;
|
|
141
|
+
}
|
|
142
|
+
else if (prefixPointers > 0) {
|
|
143
|
+
// e.g. *v means void pointer — base type already consumed
|
|
144
|
+
throw new Error(`Expected base type after '*' at line ${this.peek().line}:${this.peek().col}, got '${this.peek().value}'`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
throw new Error(`Expected type at line ${this.peek().line}:${this.peek().col}, got '${this.peek().value}'`);
|
|
148
|
+
}
|
|
149
|
+
// Count trailing pointer levels (C-style, after base type)
|
|
150
|
+
let pointers = prefixPointers;
|
|
151
|
+
while (this.check(lexer_1.TokenType.STAR)) {
|
|
152
|
+
this.advance();
|
|
153
|
+
pointers++;
|
|
154
|
+
// const after * applies to pointer
|
|
155
|
+
if (this.check(lexer_1.TokenType.CN)) {
|
|
156
|
+
this.advance();
|
|
157
|
+
isConst = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
base,
|
|
162
|
+
pointers,
|
|
163
|
+
isConst,
|
|
164
|
+
isVolatile,
|
|
165
|
+
isStatic: quals.isStatic,
|
|
166
|
+
isExtern: quals.isExtern,
|
|
167
|
+
isInline: quals.isInline,
|
|
168
|
+
arraySize: null,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// ─── Top Level ─────────────────────────────────────────────────────────────
|
|
172
|
+
parse() {
|
|
173
|
+
const body = [];
|
|
174
|
+
while (!this.check(lexer_1.TokenType.EOF)) {
|
|
175
|
+
const node = this.parseTopLevel();
|
|
176
|
+
if (node)
|
|
177
|
+
body.push(node);
|
|
178
|
+
}
|
|
179
|
+
return { kind: 'Program', body };
|
|
180
|
+
}
|
|
181
|
+
parseTopLevel() {
|
|
182
|
+
// Raw preprocessor (from # tokens)
|
|
183
|
+
if (this.check(lexer_1.TokenType.HASH)) {
|
|
184
|
+
return { kind: 'RawPreprocessor', text: this.advance().value };
|
|
185
|
+
}
|
|
186
|
+
// im <path> or im "path"
|
|
187
|
+
if (this.check(lexer_1.TokenType.IM)) {
|
|
188
|
+
return this.parseInclude();
|
|
189
|
+
}
|
|
190
|
+
// df NAME value
|
|
191
|
+
if (this.check(lexer_1.TokenType.DF)) {
|
|
192
|
+
return this.parseDefine();
|
|
193
|
+
}
|
|
194
|
+
// nd NAME (include guard)
|
|
195
|
+
if (this.check(lexer_1.TokenType.ND)) {
|
|
196
|
+
this.advance();
|
|
197
|
+
const name = this.eat(lexer_1.TokenType.IDENT).value;
|
|
198
|
+
return { kind: 'IncludeGuardStart', name };
|
|
199
|
+
}
|
|
200
|
+
// dn (#endif)
|
|
201
|
+
if (this.check(lexer_1.TokenType.DN)) {
|
|
202
|
+
this.advance();
|
|
203
|
+
return { kind: 'IncludeGuardEnd' };
|
|
204
|
+
}
|
|
205
|
+
// pr value
|
|
206
|
+
if (this.check(lexer_1.TokenType.PR)) {
|
|
207
|
+
this.advance();
|
|
208
|
+
let val = '';
|
|
209
|
+
while (!this.check(lexer_1.TokenType.EOF) && !this.check(lexer_1.TokenType.NEWLINE)) {
|
|
210
|
+
val += this.advance().value + ' ';
|
|
211
|
+
}
|
|
212
|
+
return { kind: 'PragmaDirective', value: val.trim() };
|
|
213
|
+
}
|
|
214
|
+
// tp (typedef)
|
|
215
|
+
if (this.check(lexer_1.TokenType.TP)) {
|
|
216
|
+
return this.parseTypedef();
|
|
217
|
+
}
|
|
218
|
+
// st / un / en at top level (struct/union/enum definition)
|
|
219
|
+
if (this.check(lexer_1.TokenType.ST)) {
|
|
220
|
+
return this.parseStructDecl(false);
|
|
221
|
+
}
|
|
222
|
+
if (this.check(lexer_1.TokenType.UN)) {
|
|
223
|
+
return this.parseUnionDecl(false);
|
|
224
|
+
}
|
|
225
|
+
if (this.check(lexer_1.TokenType.EN)) {
|
|
226
|
+
return this.parseEnumDecl(false);
|
|
227
|
+
}
|
|
228
|
+
// fn (function)
|
|
229
|
+
if (this.check(lexer_1.TokenType.FN)) {
|
|
230
|
+
return this.parseFunctionDecl();
|
|
231
|
+
}
|
|
232
|
+
// Variable declaration at top level (qualifiers + type + name : type)
|
|
233
|
+
// or just a type followed by name
|
|
234
|
+
return this.parseVarOrFuncDecl();
|
|
235
|
+
}
|
|
236
|
+
parseInclude() {
|
|
237
|
+
this.eat(lexer_1.TokenType.IM);
|
|
238
|
+
// path token was already consumed by the lexer and placed as STRING
|
|
239
|
+
const pathTok = this.eat(lexer_1.TokenType.STRING);
|
|
240
|
+
return { kind: 'IncludeDirective', path: pathTok.value };
|
|
241
|
+
}
|
|
242
|
+
parseDefine() {
|
|
243
|
+
this.eat(lexer_1.TokenType.DF);
|
|
244
|
+
const name = this.advance().value; // macro name
|
|
245
|
+
let params = null;
|
|
246
|
+
let body = '';
|
|
247
|
+
// function-like macro: df NAME(a,b) body
|
|
248
|
+
if (this.check(lexer_1.TokenType.LPAREN)) {
|
|
249
|
+
this.advance();
|
|
250
|
+
params = [];
|
|
251
|
+
while (!this.check(lexer_1.TokenType.RPAREN) && !this.check(lexer_1.TokenType.EOF)) {
|
|
252
|
+
if (this.check(lexer_1.TokenType.ELLIPSIS)) {
|
|
253
|
+
params.push('...');
|
|
254
|
+
this.advance();
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
params.push(this.advance().value);
|
|
258
|
+
}
|
|
259
|
+
this.tryEat(lexer_1.TokenType.COMMA);
|
|
260
|
+
}
|
|
261
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
262
|
+
}
|
|
263
|
+
// rest of line is body
|
|
264
|
+
// We collect tokens until EOF or newline (but newlines were filtered — collect until semicolon or next keyword that starts a declaration)
|
|
265
|
+
// Actually define body can be complex; we gather remaining tokens on the "logical line"
|
|
266
|
+
// Since newlines are filtered, we use a heuristic: collect everything that could be part of an expression
|
|
267
|
+
const bodyToks = [];
|
|
268
|
+
// Gather tokens that are likely part of the define body
|
|
269
|
+
// Stop at top-level declaration starters
|
|
270
|
+
while (!this.check(lexer_1.TokenType.EOF) &&
|
|
271
|
+
!this.check(lexer_1.TokenType.FN) &&
|
|
272
|
+
!this.check(lexer_1.TokenType.ST) &&
|
|
273
|
+
!this.check(lexer_1.TokenType.UN) &&
|
|
274
|
+
!this.check(lexer_1.TokenType.EN) &&
|
|
275
|
+
!this.check(lexer_1.TokenType.TP) &&
|
|
276
|
+
!this.check(lexer_1.TokenType.IM) &&
|
|
277
|
+
!this.check(lexer_1.TokenType.DF) &&
|
|
278
|
+
!this.check(lexer_1.TokenType.ND) &&
|
|
279
|
+
!this.check(lexer_1.TokenType.DN) &&
|
|
280
|
+
!this.check(lexer_1.TokenType.PR) &&
|
|
281
|
+
!this.check(lexer_1.TokenType.HASH)) {
|
|
282
|
+
bodyToks.push(this.advance());
|
|
283
|
+
}
|
|
284
|
+
// Smart join: don't put spaces around punctuation/operators
|
|
285
|
+
const noSpaceBefore = new Set(['(', ')', '[', ']', ',', ';', '.', '->', '++', '--']);
|
|
286
|
+
const noSpaceAfter = new Set(['(', '[', '.', '->']);
|
|
287
|
+
body = bodyToks.map((t, i) => {
|
|
288
|
+
const prev = bodyToks[i - 1];
|
|
289
|
+
if (!prev)
|
|
290
|
+
return t.value;
|
|
291
|
+
if (noSpaceBefore.has(t.value) || noSpaceAfter.has(prev.value))
|
|
292
|
+
return t.value;
|
|
293
|
+
// identifiers and numbers need a space before them
|
|
294
|
+
if (t.type === lexer_1.TokenType.IDENT || t.type === lexer_1.TokenType.NUMBER ||
|
|
295
|
+
t.type === lexer_1.TokenType.STRING)
|
|
296
|
+
return ' ' + t.value;
|
|
297
|
+
return t.value;
|
|
298
|
+
}).join('').trim();
|
|
299
|
+
return { kind: 'DefineDirective', name, params, body };
|
|
300
|
+
}
|
|
301
|
+
parseTypedef() {
|
|
302
|
+
this.eat(lexer_1.TokenType.TP);
|
|
303
|
+
if (this.check(lexer_1.TokenType.ST)) {
|
|
304
|
+
const st = this.parseStructDecl(true);
|
|
305
|
+
return st;
|
|
306
|
+
}
|
|
307
|
+
if (this.check(lexer_1.TokenType.UN)) {
|
|
308
|
+
const un = this.parseUnionDecl(true);
|
|
309
|
+
return un;
|
|
310
|
+
}
|
|
311
|
+
if (this.check(lexer_1.TokenType.EN)) {
|
|
312
|
+
const en = this.parseEnumDecl(true);
|
|
313
|
+
return en;
|
|
314
|
+
}
|
|
315
|
+
// tp i32 MyInt
|
|
316
|
+
const type = this.parseType();
|
|
317
|
+
const alias = this.eat(lexer_1.TokenType.IDENT).value;
|
|
318
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
319
|
+
return { kind: 'TypedefDecl', inner: { kind: 'VarDecl', name: '', type, init: null, qualifiers: [] }, alias };
|
|
320
|
+
}
|
|
321
|
+
parseStructDecl(isTypedef) {
|
|
322
|
+
this.eat(lexer_1.TokenType.ST);
|
|
323
|
+
let name = null;
|
|
324
|
+
if (this.check(lexer_1.TokenType.IDENT))
|
|
325
|
+
name = this.advance().value;
|
|
326
|
+
const fields = [];
|
|
327
|
+
if (this.check(lexer_1.TokenType.LBRACE)) {
|
|
328
|
+
this.eat(lexer_1.TokenType.LBRACE);
|
|
329
|
+
while (!this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
330
|
+
fields.push(this.parseFieldDecl());
|
|
331
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
332
|
+
}
|
|
333
|
+
this.eat(lexer_1.TokenType.RBRACE);
|
|
334
|
+
}
|
|
335
|
+
let typedefName = null;
|
|
336
|
+
if (isTypedef) {
|
|
337
|
+
typedefName = this.eat(lexer_1.TokenType.IDENT).value;
|
|
338
|
+
}
|
|
339
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
340
|
+
return { kind: 'StructDecl', name, fields, isTypedef, typedefName };
|
|
341
|
+
}
|
|
342
|
+
parseUnionDecl(isTypedef) {
|
|
343
|
+
this.eat(lexer_1.TokenType.UN);
|
|
344
|
+
let name = null;
|
|
345
|
+
if (this.check(lexer_1.TokenType.IDENT))
|
|
346
|
+
name = this.advance().value;
|
|
347
|
+
const fields = [];
|
|
348
|
+
if (this.check(lexer_1.TokenType.LBRACE)) {
|
|
349
|
+
this.eat(lexer_1.TokenType.LBRACE);
|
|
350
|
+
while (!this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
351
|
+
fields.push(this.parseFieldDecl());
|
|
352
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
353
|
+
}
|
|
354
|
+
this.eat(lexer_1.TokenType.RBRACE);
|
|
355
|
+
}
|
|
356
|
+
let typedefName = null;
|
|
357
|
+
if (isTypedef)
|
|
358
|
+
typedefName = this.eat(lexer_1.TokenType.IDENT).value;
|
|
359
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
360
|
+
return { kind: 'UnionDecl', name, fields, isTypedef, typedefName };
|
|
361
|
+
}
|
|
362
|
+
parseFieldDecl() {
|
|
363
|
+
const name = this.eatIdent().value;
|
|
364
|
+
this.eat(lexer_1.TokenType.COLON);
|
|
365
|
+
const type = this.parseType();
|
|
366
|
+
// array field
|
|
367
|
+
if (this.check(lexer_1.TokenType.LBRACKET)) {
|
|
368
|
+
this.advance();
|
|
369
|
+
if (!this.check(lexer_1.TokenType.RBRACKET)) {
|
|
370
|
+
type.arraySize = this.parseExpr();
|
|
371
|
+
}
|
|
372
|
+
this.eat(lexer_1.TokenType.RBRACKET);
|
|
373
|
+
}
|
|
374
|
+
// bitfield
|
|
375
|
+
let bitfield;
|
|
376
|
+
if (this.check(lexer_1.TokenType.COLON)) {
|
|
377
|
+
this.advance();
|
|
378
|
+
bitfield = this.parseExpr();
|
|
379
|
+
}
|
|
380
|
+
return { name, type, bitfield };
|
|
381
|
+
}
|
|
382
|
+
parseEnumDecl(isTypedef) {
|
|
383
|
+
this.eat(lexer_1.TokenType.EN);
|
|
384
|
+
let name = null;
|
|
385
|
+
if (this.check(lexer_1.TokenType.IDENT))
|
|
386
|
+
name = this.advance().value;
|
|
387
|
+
const members = [];
|
|
388
|
+
if (this.check(lexer_1.TokenType.LBRACE)) {
|
|
389
|
+
this.eat(lexer_1.TokenType.LBRACE);
|
|
390
|
+
while (!this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
391
|
+
const mname = this.advance().value;
|
|
392
|
+
let value = null;
|
|
393
|
+
if (this.tryEat(lexer_1.TokenType.ASSIGN)) {
|
|
394
|
+
value = this.parseExpr();
|
|
395
|
+
}
|
|
396
|
+
members.push({ name: mname, value });
|
|
397
|
+
if (!this.tryEat(lexer_1.TokenType.COMMA))
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
this.eat(lexer_1.TokenType.RBRACE);
|
|
401
|
+
}
|
|
402
|
+
let typedefName = null;
|
|
403
|
+
if (isTypedef)
|
|
404
|
+
typedefName = this.eat(lexer_1.TokenType.IDENT).value;
|
|
405
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
406
|
+
return { kind: 'EnumDecl', name, members, isTypedef, typedefName };
|
|
407
|
+
}
|
|
408
|
+
parseFunctionDecl() {
|
|
409
|
+
this.eat(lexer_1.TokenType.FN);
|
|
410
|
+
const name = this.eat(lexer_1.TokenType.IDENT).value;
|
|
411
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
412
|
+
const params = [];
|
|
413
|
+
while (!this.check(lexer_1.TokenType.RPAREN) && !this.check(lexer_1.TokenType.EOF)) {
|
|
414
|
+
if (this.check(lexer_1.TokenType.ELLIPSIS)) {
|
|
415
|
+
params.push({ name: '...', type: { base: '', pointers: 0, isConst: false, isVolatile: false, isStatic: false, isExtern: false, isInline: false, arraySize: null }, isVararg: true });
|
|
416
|
+
this.advance();
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
const pname = this.eatIdent().value;
|
|
420
|
+
this.eat(lexer_1.TokenType.COLON);
|
|
421
|
+
const ptype = this.parseType();
|
|
422
|
+
// array param
|
|
423
|
+
if (this.check(lexer_1.TokenType.LBRACKET)) {
|
|
424
|
+
this.advance();
|
|
425
|
+
if (!this.check(lexer_1.TokenType.RBRACKET))
|
|
426
|
+
ptype.arraySize = this.parseExpr();
|
|
427
|
+
this.eat(lexer_1.TokenType.RBRACKET);
|
|
428
|
+
ptype.pointers = Math.max(ptype.pointers, 1);
|
|
429
|
+
}
|
|
430
|
+
params.push({ name: pname, type: ptype });
|
|
431
|
+
if (!this.tryEat(lexer_1.TokenType.COMMA))
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
435
|
+
let returnType = { base: 'void', pointers: 0, isConst: false, isVolatile: false, isStatic: false, isExtern: false, isInline: false, arraySize: null };
|
|
436
|
+
// Optional return type
|
|
437
|
+
if (this.check(lexer_1.TokenType.ARROW)) {
|
|
438
|
+
this.advance();
|
|
439
|
+
returnType = this.parseType();
|
|
440
|
+
}
|
|
441
|
+
// Body or semicolon (forward decl)
|
|
442
|
+
let body = null;
|
|
443
|
+
if (this.check(lexer_1.TokenType.LBRACE)) {
|
|
444
|
+
body = this.parseBlock();
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
448
|
+
}
|
|
449
|
+
return { kind: 'FunctionDecl', name, params, returnType, body, qualifiers: [] };
|
|
450
|
+
}
|
|
451
|
+
parseVarOrFuncDecl() {
|
|
452
|
+
// Handle qualifiers
|
|
453
|
+
const qualifiers = [];
|
|
454
|
+
while (this.check(lexer_1.TokenType.SC, lexer_1.TokenType.EX, lexer_1.TokenType.IN, lexer_1.TokenType.CN, lexer_1.TokenType.VL)) {
|
|
455
|
+
qualifiers.push(this.advance().value);
|
|
456
|
+
}
|
|
457
|
+
// name:type[=init] or just type-based declarations
|
|
458
|
+
// In Pear, declarations are: name:type[=init]
|
|
459
|
+
// But we might also have identifiers being used as statements
|
|
460
|
+
if (this.isIdentLike(this.peek()) && this.peek(1).type === lexer_1.TokenType.COLON) {
|
|
461
|
+
return this.parseVarDecl(qualifiers);
|
|
462
|
+
}
|
|
463
|
+
// Expression statement
|
|
464
|
+
const expr = this.parseExpr();
|
|
465
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
466
|
+
return { kind: 'ExprStmt', expr };
|
|
467
|
+
}
|
|
468
|
+
parseVarDecl(qualifiers = []) {
|
|
469
|
+
const name = this.eatIdent().value;
|
|
470
|
+
this.eat(lexer_1.TokenType.COLON);
|
|
471
|
+
const type = this.parseType();
|
|
472
|
+
// Array declaration: name:type[size]
|
|
473
|
+
if (this.check(lexer_1.TokenType.LBRACKET)) {
|
|
474
|
+
this.advance();
|
|
475
|
+
if (!this.check(lexer_1.TokenType.RBRACKET)) {
|
|
476
|
+
type.arraySize = this.parseExpr();
|
|
477
|
+
}
|
|
478
|
+
this.eat(lexer_1.TokenType.RBRACKET);
|
|
479
|
+
}
|
|
480
|
+
let init = null;
|
|
481
|
+
if (this.tryEat(lexer_1.TokenType.ASSIGN)) {
|
|
482
|
+
if (this.check(lexer_1.TokenType.LBRACE)) {
|
|
483
|
+
init = this.parseInitList();
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
init = this.parseExpr();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
490
|
+
return { kind: 'VarDecl', name, type, init, qualifiers };
|
|
491
|
+
}
|
|
492
|
+
// ─── Statements ────────────────────────────────────────────────────────────
|
|
493
|
+
parseStatement() {
|
|
494
|
+
if (this.check(lexer_1.TokenType.HASH)) {
|
|
495
|
+
return { kind: 'RawPreprocessor', text: this.advance().value };
|
|
496
|
+
}
|
|
497
|
+
if (this.check(lexer_1.TokenType.LBRACE))
|
|
498
|
+
return this.parseBlock();
|
|
499
|
+
if (this.check(lexer_1.TokenType.IF))
|
|
500
|
+
return this.parseIf();
|
|
501
|
+
if (this.check(lexer_1.TokenType.LP))
|
|
502
|
+
return this.parseFor();
|
|
503
|
+
if (this.check(lexer_1.TokenType.WH))
|
|
504
|
+
return this.parseWhile();
|
|
505
|
+
if (this.check(lexer_1.TokenType.DW))
|
|
506
|
+
return this.parseDoWhile();
|
|
507
|
+
if (this.check(lexer_1.TokenType.SW))
|
|
508
|
+
return this.parseSwitch();
|
|
509
|
+
if (this.check(lexer_1.TokenType.RT))
|
|
510
|
+
return this.parseReturn();
|
|
511
|
+
if (this.check(lexer_1.TokenType.BK)) {
|
|
512
|
+
this.advance();
|
|
513
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
514
|
+
return { kind: 'BreakStmt' };
|
|
515
|
+
}
|
|
516
|
+
if (this.check(lexer_1.TokenType.CT)) {
|
|
517
|
+
this.advance();
|
|
518
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
519
|
+
return { kind: 'ContinueStmt' };
|
|
520
|
+
}
|
|
521
|
+
if (this.check(lexer_1.TokenType.GT))
|
|
522
|
+
return this.parseGoto();
|
|
523
|
+
if (this.check(lexer_1.TokenType.ST))
|
|
524
|
+
return this.parseStructDecl(false);
|
|
525
|
+
if (this.check(lexer_1.TokenType.UN))
|
|
526
|
+
return this.parseUnionDecl(false);
|
|
527
|
+
if (this.check(lexer_1.TokenType.EN))
|
|
528
|
+
return this.parseEnumDecl(false);
|
|
529
|
+
if (this.check(lexer_1.TokenType.TP))
|
|
530
|
+
return this.parseTypedef();
|
|
531
|
+
if (this.check(lexer_1.TokenType.DF))
|
|
532
|
+
return this.parseDefine();
|
|
533
|
+
if (this.check(lexer_1.TokenType.IM))
|
|
534
|
+
return this.parseInclude();
|
|
535
|
+
if (this.check(lexer_1.TokenType.ND)) {
|
|
536
|
+
this.advance();
|
|
537
|
+
const name = this.eat(lexer_1.TokenType.IDENT).value;
|
|
538
|
+
return { kind: 'IncludeGuardStart', name };
|
|
539
|
+
}
|
|
540
|
+
if (this.check(lexer_1.TokenType.DN)) {
|
|
541
|
+
this.advance();
|
|
542
|
+
return { kind: 'IncludeGuardEnd' };
|
|
543
|
+
}
|
|
544
|
+
// Qualifiers
|
|
545
|
+
if (this.check(lexer_1.TokenType.SC, lexer_1.TokenType.EX, lexer_1.TokenType.IN, lexer_1.TokenType.VL)) {
|
|
546
|
+
return this.parseVarOrFuncDecl();
|
|
547
|
+
}
|
|
548
|
+
// cn might be a qualifier or part of something else
|
|
549
|
+
if (this.check(lexer_1.TokenType.CN)) {
|
|
550
|
+
return this.parseVarOrFuncDecl();
|
|
551
|
+
}
|
|
552
|
+
// var decl: name:type
|
|
553
|
+
// name can be IDENT or a short keyword used as identifier (b, c, v, sz, i32, etc.)
|
|
554
|
+
if (this.isIdentLike(this.peek()) && this.peek(1).type === lexer_1.TokenType.COLON) {
|
|
555
|
+
// Determine if this is a label or a variable declaration.
|
|
556
|
+
// It's a label only if:
|
|
557
|
+
// 1. The token is a plain IDENT (not a type keyword)
|
|
558
|
+
// 2. After the colon there's NO type (no type keyword, no IDENT, no STAR)
|
|
559
|
+
// 3. After the colon is a statement keyword or structural token
|
|
560
|
+
const afterColon = this.peek(2);
|
|
561
|
+
const isLabel = this.check(lexer_1.TokenType.IDENT) &&
|
|
562
|
+
!this.isTypeToken(afterColon) &&
|
|
563
|
+
!this.isTypeStartToken(afterColon) &&
|
|
564
|
+
afterColon.type !== lexer_1.TokenType.STAR &&
|
|
565
|
+
afterColon.type !== lexer_1.TokenType.IDENT; // IDENT after colon = typedef'd type name → var decl
|
|
566
|
+
if (isLabel) {
|
|
567
|
+
const label = this.advance().value;
|
|
568
|
+
this.advance(); // colon
|
|
569
|
+
const body = this.parseStatement();
|
|
570
|
+
return { kind: 'LabelStmt', label, body };
|
|
571
|
+
}
|
|
572
|
+
return this.parseVarDecl();
|
|
573
|
+
}
|
|
574
|
+
// cs (case)
|
|
575
|
+
if (this.check(lexer_1.TokenType.CS)) {
|
|
576
|
+
this.advance();
|
|
577
|
+
const val = this.parseExpr();
|
|
578
|
+
this.eat(lexer_1.TokenType.COLON);
|
|
579
|
+
const body = [];
|
|
580
|
+
while (!this.check(lexer_1.TokenType.CS) && !this.check(lexer_1.TokenType.DV) && !this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
581
|
+
body.push(this.parseStatement());
|
|
582
|
+
}
|
|
583
|
+
return { kind: 'CaseStmt', value: val, body };
|
|
584
|
+
}
|
|
585
|
+
// dv (default)
|
|
586
|
+
if (this.check(lexer_1.TokenType.DV)) {
|
|
587
|
+
this.advance();
|
|
588
|
+
this.eat(lexer_1.TokenType.COLON);
|
|
589
|
+
const body = [];
|
|
590
|
+
while (!this.check(lexer_1.TokenType.CS) && !this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
591
|
+
body.push(this.parseStatement());
|
|
592
|
+
}
|
|
593
|
+
return { kind: 'DefaultStmt', body };
|
|
594
|
+
}
|
|
595
|
+
// expression statement
|
|
596
|
+
const expr = this.parseExpr();
|
|
597
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
598
|
+
return { kind: 'ExprStmt', expr };
|
|
599
|
+
}
|
|
600
|
+
isIdentLike(tok) {
|
|
601
|
+
return tok.type === lexer_1.TokenType.IDENT ||
|
|
602
|
+
tok.type === lexer_1.TokenType.B || tok.type === lexer_1.TokenType.C ||
|
|
603
|
+
tok.type === lexer_1.TokenType.V || tok.type === lexer_1.TokenType.SZ ||
|
|
604
|
+
tok.type === lexer_1.TokenType.I8 || tok.type === lexer_1.TokenType.I16 ||
|
|
605
|
+
tok.type === lexer_1.TokenType.I32 || tok.type === lexer_1.TokenType.I64 ||
|
|
606
|
+
tok.type === lexer_1.TokenType.U8 || tok.type === lexer_1.TokenType.U16 ||
|
|
607
|
+
tok.type === lexer_1.TokenType.U32 || tok.type === lexer_1.TokenType.U64 ||
|
|
608
|
+
tok.type === lexer_1.TokenType.F32 || tok.type === lexer_1.TokenType.F64 ||
|
|
609
|
+
tok.type === lexer_1.TokenType.IN;
|
|
610
|
+
}
|
|
611
|
+
isTypeToken(tok) {
|
|
612
|
+
return TYPE_KEYWORDS.has(tok.type) || tok.type === lexer_1.TokenType.ST || tok.type === lexer_1.TokenType.UN || tok.type === lexer_1.TokenType.EN;
|
|
613
|
+
}
|
|
614
|
+
isTypeStartToken(tok) {
|
|
615
|
+
if (this.isTypeToken(tok))
|
|
616
|
+
return true;
|
|
617
|
+
if (tok.type === lexer_1.TokenType.CN || tok.type === lexer_1.TokenType.VL ||
|
|
618
|
+
tok.type === lexer_1.TokenType.SC || tok.type === lexer_1.TokenType.EX ||
|
|
619
|
+
tok.type === lexer_1.TokenType.IN)
|
|
620
|
+
return true;
|
|
621
|
+
// Could also be a typedef'd name (IDENT) — assume it is if followed by reasonable stuff
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
parseBlock() {
|
|
625
|
+
this.eat(lexer_1.TokenType.LBRACE);
|
|
626
|
+
const body = [];
|
|
627
|
+
while (!this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
628
|
+
body.push(this.parseStatement());
|
|
629
|
+
}
|
|
630
|
+
this.eat(lexer_1.TokenType.RBRACE);
|
|
631
|
+
return { kind: 'BlockStmt', body };
|
|
632
|
+
}
|
|
633
|
+
parseIf() {
|
|
634
|
+
this.eat(lexer_1.TokenType.IF);
|
|
635
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
636
|
+
const condition = this.parseExpr();
|
|
637
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
638
|
+
const consequent = this.parseStatement();
|
|
639
|
+
let alternate = null;
|
|
640
|
+
if (this.check(lexer_1.TokenType.EI)) {
|
|
641
|
+
this.advance();
|
|
642
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
643
|
+
const eicond = this.parseExpr();
|
|
644
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
645
|
+
const eibody = this.parseStatement();
|
|
646
|
+
alternate = { kind: 'IfStmt', condition: eicond, consequent: eibody, alternate: null };
|
|
647
|
+
// chain else-ifs
|
|
648
|
+
let cur = alternate;
|
|
649
|
+
while (this.check(lexer_1.TokenType.EI)) {
|
|
650
|
+
this.advance();
|
|
651
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
652
|
+
const c2 = this.parseExpr();
|
|
653
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
654
|
+
const b2 = this.parseStatement();
|
|
655
|
+
const next = { kind: 'IfStmt', condition: c2, consequent: b2, alternate: null };
|
|
656
|
+
cur.alternate = next;
|
|
657
|
+
cur = next;
|
|
658
|
+
}
|
|
659
|
+
if (this.check(lexer_1.TokenType.EL)) {
|
|
660
|
+
this.advance();
|
|
661
|
+
cur.alternate = this.parseStatement();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
else if (this.check(lexer_1.TokenType.EL)) {
|
|
665
|
+
this.advance();
|
|
666
|
+
alternate = this.parseStatement();
|
|
667
|
+
}
|
|
668
|
+
return { kind: 'IfStmt', condition, consequent, alternate };
|
|
669
|
+
}
|
|
670
|
+
parseFor() {
|
|
671
|
+
this.eat(lexer_1.TokenType.LP);
|
|
672
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
673
|
+
let init = null;
|
|
674
|
+
if (!this.check(lexer_1.TokenType.SEMICOLON)) {
|
|
675
|
+
if (this.isIdentLike(this.peek()) && this.peek(1).type === lexer_1.TokenType.COLON) {
|
|
676
|
+
// var decl without trailing semicolon eaten
|
|
677
|
+
const name = this.advance().value;
|
|
678
|
+
this.advance(); // colon
|
|
679
|
+
const type = this.parseType();
|
|
680
|
+
let initExpr = null;
|
|
681
|
+
if (this.tryEat(lexer_1.TokenType.ASSIGN)) {
|
|
682
|
+
initExpr = this.parseExpr();
|
|
683
|
+
}
|
|
684
|
+
init = { kind: 'VarDecl', name, type, init: initExpr, qualifiers: [] };
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
const expr = this.parseExpr();
|
|
688
|
+
init = { kind: 'ExprStmt', expr };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
this.eat(lexer_1.TokenType.SEMICOLON);
|
|
692
|
+
let condition = null;
|
|
693
|
+
if (!this.check(lexer_1.TokenType.SEMICOLON)) {
|
|
694
|
+
condition = this.parseExpr();
|
|
695
|
+
}
|
|
696
|
+
this.eat(lexer_1.TokenType.SEMICOLON);
|
|
697
|
+
let update = null;
|
|
698
|
+
if (!this.check(lexer_1.TokenType.RPAREN)) {
|
|
699
|
+
update = this.parseExpr();
|
|
700
|
+
}
|
|
701
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
702
|
+
const body = this.parseStatement();
|
|
703
|
+
return { kind: 'ForStmt', init, condition, update, body };
|
|
704
|
+
}
|
|
705
|
+
parseWhile() {
|
|
706
|
+
this.eat(lexer_1.TokenType.WH);
|
|
707
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
708
|
+
const condition = this.parseExpr();
|
|
709
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
710
|
+
const body = this.parseStatement();
|
|
711
|
+
return { kind: 'WhileStmt', condition, body };
|
|
712
|
+
}
|
|
713
|
+
parseDoWhile() {
|
|
714
|
+
this.eat(lexer_1.TokenType.DW);
|
|
715
|
+
const body = this.parseStatement();
|
|
716
|
+
this.eat(lexer_1.TokenType.WH);
|
|
717
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
718
|
+
const condition = this.parseExpr();
|
|
719
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
720
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
721
|
+
return { kind: 'DoWhileStmt', body, condition };
|
|
722
|
+
}
|
|
723
|
+
parseSwitch() {
|
|
724
|
+
this.eat(lexer_1.TokenType.SW);
|
|
725
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
726
|
+
const expr = this.parseExpr();
|
|
727
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
728
|
+
const body = this.parseBlock();
|
|
729
|
+
return { kind: 'SwitchStmt', expr, body };
|
|
730
|
+
}
|
|
731
|
+
parseReturn() {
|
|
732
|
+
this.eat(lexer_1.TokenType.RT);
|
|
733
|
+
let value = null;
|
|
734
|
+
if (!this.check(lexer_1.TokenType.SEMICOLON) && !this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
735
|
+
value = this.parseExpr();
|
|
736
|
+
}
|
|
737
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
738
|
+
return { kind: 'ReturnStmt', value };
|
|
739
|
+
}
|
|
740
|
+
parseGoto() {
|
|
741
|
+
this.eat(lexer_1.TokenType.GT);
|
|
742
|
+
const label = this.eat(lexer_1.TokenType.IDENT).value;
|
|
743
|
+
this.tryEat(lexer_1.TokenType.SEMICOLON);
|
|
744
|
+
return { kind: 'GotoStmt', label };
|
|
745
|
+
}
|
|
746
|
+
parseInitList() {
|
|
747
|
+
this.eat(lexer_1.TokenType.LBRACE);
|
|
748
|
+
const elements = [];
|
|
749
|
+
while (!this.check(lexer_1.TokenType.RBRACE) && !this.check(lexer_1.TokenType.EOF)) {
|
|
750
|
+
if (this.check(lexer_1.TokenType.LBRACE)) {
|
|
751
|
+
elements.push(this.parseInitList());
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
elements.push(this.parseExpr());
|
|
755
|
+
}
|
|
756
|
+
if (!this.tryEat(lexer_1.TokenType.COMMA))
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
this.eat(lexer_1.TokenType.RBRACE);
|
|
760
|
+
return { kind: 'InitListExpr', elements };
|
|
761
|
+
}
|
|
762
|
+
// ─── Expression Parsing (Pratt-style) ──────────────────────────────────────
|
|
763
|
+
parseExpr(minPrec = 0) {
|
|
764
|
+
return this.parseAssignment();
|
|
765
|
+
}
|
|
766
|
+
parseAssignment() {
|
|
767
|
+
const left = this.parseTernary();
|
|
768
|
+
const assignOps = [
|
|
769
|
+
lexer_1.TokenType.ASSIGN, lexer_1.TokenType.PLUS_ASSIGN, lexer_1.TokenType.MINUS_ASSIGN,
|
|
770
|
+
lexer_1.TokenType.STAR_ASSIGN, lexer_1.TokenType.SLASH_ASSIGN, lexer_1.TokenType.PERCENT_ASSIGN,
|
|
771
|
+
lexer_1.TokenType.AMP_ASSIGN, lexer_1.TokenType.PIPE_ASSIGN, lexer_1.TokenType.CARET_ASSIGN,
|
|
772
|
+
lexer_1.TokenType.LSHIFT_ASSIGN, lexer_1.TokenType.RSHIFT_ASSIGN,
|
|
773
|
+
];
|
|
774
|
+
if (assignOps.includes(this.peek().type)) {
|
|
775
|
+
const op = this.advance().value;
|
|
776
|
+
const right = this.parseAssignment();
|
|
777
|
+
return { kind: 'AssignExpr', op, left, right };
|
|
778
|
+
}
|
|
779
|
+
return left;
|
|
780
|
+
}
|
|
781
|
+
parseTernary() {
|
|
782
|
+
const cond = this.parseOr();
|
|
783
|
+
if (this.tryEat(lexer_1.TokenType.QUESTION)) {
|
|
784
|
+
const consequent = this.parseExpr();
|
|
785
|
+
this.eat(lexer_1.TokenType.COLON);
|
|
786
|
+
const alternate = this.parseTernary();
|
|
787
|
+
return { kind: 'TernaryExpr', condition: cond, consequent, alternate };
|
|
788
|
+
}
|
|
789
|
+
return cond;
|
|
790
|
+
}
|
|
791
|
+
parseOr() {
|
|
792
|
+
let left = this.parseAnd();
|
|
793
|
+
while (this.check(lexer_1.TokenType.OR)) {
|
|
794
|
+
const op = this.advance().value;
|
|
795
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseAnd() };
|
|
796
|
+
}
|
|
797
|
+
return left;
|
|
798
|
+
}
|
|
799
|
+
parseAnd() {
|
|
800
|
+
let left = this.parseBitOr();
|
|
801
|
+
while (this.check(lexer_1.TokenType.AND)) {
|
|
802
|
+
const op = this.advance().value;
|
|
803
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseBitOr() };
|
|
804
|
+
}
|
|
805
|
+
return left;
|
|
806
|
+
}
|
|
807
|
+
parseBitOr() {
|
|
808
|
+
let left = this.parseBitXor();
|
|
809
|
+
while (this.check(lexer_1.TokenType.PIPE)) {
|
|
810
|
+
const op = this.advance().value;
|
|
811
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseBitXor() };
|
|
812
|
+
}
|
|
813
|
+
return left;
|
|
814
|
+
}
|
|
815
|
+
parseBitXor() {
|
|
816
|
+
let left = this.parseBitAnd();
|
|
817
|
+
while (this.check(lexer_1.TokenType.CARET)) {
|
|
818
|
+
const op = this.advance().value;
|
|
819
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseBitAnd() };
|
|
820
|
+
}
|
|
821
|
+
return left;
|
|
822
|
+
}
|
|
823
|
+
parseBitAnd() {
|
|
824
|
+
let left = this.parseEquality();
|
|
825
|
+
while (this.check(lexer_1.TokenType.AMP)) {
|
|
826
|
+
const op = this.advance().value;
|
|
827
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseEquality() };
|
|
828
|
+
}
|
|
829
|
+
return left;
|
|
830
|
+
}
|
|
831
|
+
parseEquality() {
|
|
832
|
+
let left = this.parseRelational();
|
|
833
|
+
while (this.check(lexer_1.TokenType.EQ) || this.check(lexer_1.TokenType.NEQ)) {
|
|
834
|
+
const op = this.advance().value;
|
|
835
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseRelational() };
|
|
836
|
+
}
|
|
837
|
+
return left;
|
|
838
|
+
}
|
|
839
|
+
parseRelational() {
|
|
840
|
+
let left = this.parseShift();
|
|
841
|
+
while (this.check(lexer_1.TokenType.LT) || this.check(lexer_1.TokenType.GT_OP) || this.check(lexer_1.TokenType.LTE) || this.check(lexer_1.TokenType.GTE)) {
|
|
842
|
+
const op = this.advance().value;
|
|
843
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseShift() };
|
|
844
|
+
}
|
|
845
|
+
return left;
|
|
846
|
+
}
|
|
847
|
+
parseShift() {
|
|
848
|
+
let left = this.parseAddSub();
|
|
849
|
+
while (this.check(lexer_1.TokenType.LSHIFT) || this.check(lexer_1.TokenType.RSHIFT)) {
|
|
850
|
+
const op = this.advance().value;
|
|
851
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseAddSub() };
|
|
852
|
+
}
|
|
853
|
+
return left;
|
|
854
|
+
}
|
|
855
|
+
parseAddSub() {
|
|
856
|
+
let left = this.parseMulDiv();
|
|
857
|
+
while (this.check(lexer_1.TokenType.PLUS) || this.check(lexer_1.TokenType.MINUS)) {
|
|
858
|
+
const op = this.advance().value;
|
|
859
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseMulDiv() };
|
|
860
|
+
}
|
|
861
|
+
return left;
|
|
862
|
+
}
|
|
863
|
+
parseMulDiv() {
|
|
864
|
+
let left = this.parseUnary();
|
|
865
|
+
while (this.check(lexer_1.TokenType.STAR) || this.check(lexer_1.TokenType.SLASH) || this.check(lexer_1.TokenType.PERCENT)) {
|
|
866
|
+
const op = this.advance().value;
|
|
867
|
+
left = { kind: 'BinaryExpr', op, left, right: this.parseUnary() };
|
|
868
|
+
}
|
|
869
|
+
return left;
|
|
870
|
+
}
|
|
871
|
+
parseUnary() {
|
|
872
|
+
// Prefix operators
|
|
873
|
+
if (this.check(lexer_1.TokenType.MINUS)) {
|
|
874
|
+
this.advance();
|
|
875
|
+
return { kind: 'UnaryExpr', op: '-', operand: this.parseUnary(), prefix: true };
|
|
876
|
+
}
|
|
877
|
+
if (this.check(lexer_1.TokenType.NOT)) {
|
|
878
|
+
this.advance();
|
|
879
|
+
return { kind: 'UnaryExpr', op: '!', operand: this.parseUnary(), prefix: true };
|
|
880
|
+
}
|
|
881
|
+
if (this.check(lexer_1.TokenType.TILDE)) {
|
|
882
|
+
this.advance();
|
|
883
|
+
return { kind: 'UnaryExpr', op: '~', operand: this.parseUnary(), prefix: true };
|
|
884
|
+
}
|
|
885
|
+
if (this.check(lexer_1.TokenType.INC)) {
|
|
886
|
+
this.advance();
|
|
887
|
+
return { kind: 'UnaryExpr', op: '++', operand: this.parseUnary(), prefix: true };
|
|
888
|
+
}
|
|
889
|
+
if (this.check(lexer_1.TokenType.DEC)) {
|
|
890
|
+
this.advance();
|
|
891
|
+
return { kind: 'UnaryExpr', op: '--', operand: this.parseUnary(), prefix: true };
|
|
892
|
+
}
|
|
893
|
+
// Address-of & and dereference *
|
|
894
|
+
if (this.check(lexer_1.TokenType.AMP)) {
|
|
895
|
+
this.advance();
|
|
896
|
+
return { kind: 'UnaryExpr', op: '&', operand: this.parseUnary(), prefix: true };
|
|
897
|
+
}
|
|
898
|
+
if (this.check(lexer_1.TokenType.STAR)) {
|
|
899
|
+
this.advance();
|
|
900
|
+
return { kind: 'UnaryExpr', op: '*', operand: this.parseUnary(), prefix: true };
|
|
901
|
+
}
|
|
902
|
+
if (this.check(lexer_1.TokenType.PLUS)) {
|
|
903
|
+
this.advance();
|
|
904
|
+
return { kind: 'UnaryExpr', op: '+', operand: this.parseUnary(), prefix: true };
|
|
905
|
+
}
|
|
906
|
+
// sizeof
|
|
907
|
+
if (this.check(lexer_1.TokenType.SO)) {
|
|
908
|
+
this.advance();
|
|
909
|
+
this.eat(lexer_1.TokenType.LPAREN);
|
|
910
|
+
// Check if it's a type or expression
|
|
911
|
+
if (this.isTypeStartToken(this.peek()) || TYPE_KEYWORDS.has(this.peek().type)) {
|
|
912
|
+
const t = this.parseType();
|
|
913
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
914
|
+
return { kind: 'SizeofExpr', operand: t, isType: true };
|
|
915
|
+
}
|
|
916
|
+
const expr = this.parseExpr();
|
|
917
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
918
|
+
return { kind: 'SizeofExpr', operand: expr, isType: false };
|
|
919
|
+
}
|
|
920
|
+
return this.parsePostfix();
|
|
921
|
+
}
|
|
922
|
+
parsePostfix() {
|
|
923
|
+
let expr = this.parsePrimary();
|
|
924
|
+
while (true) {
|
|
925
|
+
if (this.check(lexer_1.TokenType.INC)) {
|
|
926
|
+
this.advance();
|
|
927
|
+
expr = { kind: 'PostfixExpr', op: '++', operand: expr };
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
if (this.check(lexer_1.TokenType.DEC)) {
|
|
931
|
+
this.advance();
|
|
932
|
+
expr = { kind: 'PostfixExpr', op: '--', operand: expr };
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (this.check(lexer_1.TokenType.LBRACKET)) {
|
|
936
|
+
this.advance();
|
|
937
|
+
const idx = this.parseExpr();
|
|
938
|
+
this.eat(lexer_1.TokenType.RBRACKET);
|
|
939
|
+
expr = { kind: 'IndexExpr', object: expr, index: idx };
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (this.check(lexer_1.TokenType.LPAREN)) {
|
|
943
|
+
this.advance();
|
|
944
|
+
const args = [];
|
|
945
|
+
while (!this.check(lexer_1.TokenType.RPAREN) && !this.check(lexer_1.TokenType.EOF)) {
|
|
946
|
+
args.push(this.parseExpr());
|
|
947
|
+
if (!this.tryEat(lexer_1.TokenType.COMMA))
|
|
948
|
+
break;
|
|
949
|
+
}
|
|
950
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
951
|
+
expr = { kind: 'CallExpr', callee: expr, args };
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (this.check(lexer_1.TokenType.DOT)) {
|
|
955
|
+
this.advance();
|
|
956
|
+
const member = this.advance().value;
|
|
957
|
+
expr = { kind: 'MemberExpr', object: expr, member, arrow: false };
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
if (this.check(lexer_1.TokenType.ARROW)) {
|
|
961
|
+
this.advance();
|
|
962
|
+
const member = this.advance().value;
|
|
963
|
+
expr = { kind: 'MemberExpr', object: expr, member, arrow: true };
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
return expr;
|
|
969
|
+
}
|
|
970
|
+
parsePrimary() {
|
|
971
|
+
const tok = this.peek();
|
|
972
|
+
if (tok.type === lexer_1.TokenType.NUMBER) {
|
|
973
|
+
this.advance();
|
|
974
|
+
return { kind: 'NumberLiteral', value: tok.value };
|
|
975
|
+
}
|
|
976
|
+
if (tok.type === lexer_1.TokenType.STRING) {
|
|
977
|
+
this.advance();
|
|
978
|
+
// Handle adjacent string literal concatenation
|
|
979
|
+
let val = tok.value;
|
|
980
|
+
while (this.check(lexer_1.TokenType.STRING)) {
|
|
981
|
+
val = val.slice(0, -1) + this.advance().value.slice(1);
|
|
982
|
+
}
|
|
983
|
+
return { kind: 'StringLiteral', value: val };
|
|
984
|
+
}
|
|
985
|
+
if (tok.type === lexer_1.TokenType.CHAR) {
|
|
986
|
+
this.advance();
|
|
987
|
+
return { kind: 'CharLiteral', value: tok.value };
|
|
988
|
+
}
|
|
989
|
+
// Parenthesized expression or cast
|
|
990
|
+
if (tok.type === lexer_1.TokenType.LPAREN) {
|
|
991
|
+
this.advance();
|
|
992
|
+
// Check for cast: (type)expr
|
|
993
|
+
if (this.isTypeStartToken(this.peek()) || TYPE_KEYWORDS.has(this.peek().type)) {
|
|
994
|
+
// Could be cast or just grouped expr
|
|
995
|
+
const savedPos = this.pos;
|
|
996
|
+
try {
|
|
997
|
+
const type = this.parseType();
|
|
998
|
+
if (this.check(lexer_1.TokenType.RPAREN)) {
|
|
999
|
+
this.advance();
|
|
1000
|
+
// Check it's followed by something that can be an expression
|
|
1001
|
+
if (!this.check(lexer_1.TokenType.SEMICOLON) && !this.check(lexer_1.TokenType.COMMA) &&
|
|
1002
|
+
!this.check(lexer_1.TokenType.RPAREN) && !this.check(lexer_1.TokenType.RBRACKET) &&
|
|
1003
|
+
!this.check(lexer_1.TokenType.EOF)) {
|
|
1004
|
+
const expr = this.parseUnary();
|
|
1005
|
+
return { kind: 'CastExpr', targetType: type, expr };
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
catch {
|
|
1010
|
+
// not a cast
|
|
1011
|
+
}
|
|
1012
|
+
this.pos = savedPos;
|
|
1013
|
+
}
|
|
1014
|
+
const expr = this.parseExpr();
|
|
1015
|
+
this.eat(lexer_1.TokenType.RPAREN);
|
|
1016
|
+
return expr;
|
|
1017
|
+
}
|
|
1018
|
+
// Identifiers (including keywords used as identifiers in context)
|
|
1019
|
+
if (tok.type === lexer_1.TokenType.IDENT ||
|
|
1020
|
+
tok.type === lexer_1.TokenType.B || // 'b' as a type used in expression context might just be an identifier
|
|
1021
|
+
tok.type === lexer_1.TokenType.C ||
|
|
1022
|
+
tok.type === lexer_1.TokenType.V) {
|
|
1023
|
+
this.advance();
|
|
1024
|
+
if (tok.value === 'NULL')
|
|
1025
|
+
return { kind: 'NullLiteral' };
|
|
1026
|
+
if (tok.value === 'true')
|
|
1027
|
+
return { kind: 'BoolLiteral', value: true };
|
|
1028
|
+
if (tok.value === 'false')
|
|
1029
|
+
return { kind: 'BoolLiteral', value: false };
|
|
1030
|
+
return { kind: 'Identifier', name: tok.value };
|
|
1031
|
+
}
|
|
1032
|
+
// Any keyword that might be used as an identifier
|
|
1033
|
+
if (tok.type !== lexer_1.TokenType.EOF) {
|
|
1034
|
+
this.advance();
|
|
1035
|
+
return { kind: 'Identifier', name: tok.value };
|
|
1036
|
+
}
|
|
1037
|
+
throw new Error(`Unexpected token '${tok.value}' (${tok.type}) at line ${tok.line}:${tok.col}`);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
exports.Parser = Parser;
|
|
1041
|
+
function parse(source) {
|
|
1042
|
+
const lexer = new lexer_1.Lexer(source);
|
|
1043
|
+
const tokens = lexer.tokenize();
|
|
1044
|
+
const parser = new Parser(tokens);
|
|
1045
|
+
return parser.parse();
|
|
1046
|
+
}
|
|
1047
|
+
//# sourceMappingURL=parser.js.map
|