pulse-js-framework 1.10.0 → 1.10.3
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/compiler/parser/_extract.js +393 -0
- package/compiler/parser/blocks.js +361 -0
- package/compiler/parser/core.js +306 -0
- package/compiler/parser/expressions.js +386 -0
- package/compiler/parser/imports.js +108 -0
- package/compiler/parser/index.js +47 -0
- package/compiler/parser/state.js +155 -0
- package/compiler/parser/style.js +445 -0
- package/compiler/parser/view.js +632 -0
- package/compiler/parser.js +15 -2372
- package/compiler/parser.js.original +2376 -0
- package/package.json +2 -1
- package/runtime/a11y/announcements.js +213 -0
- package/runtime/a11y/contrast.js +125 -0
- package/runtime/a11y/focus.js +412 -0
- package/runtime/a11y/index.js +35 -0
- package/runtime/a11y/preferences.js +121 -0
- package/runtime/a11y/utils.js +164 -0
- package/runtime/a11y/validation.js +258 -0
- package/runtime/a11y/widgets.js +545 -0
- package/runtime/a11y.js +15 -1840
- package/runtime/a11y.js.original +1844 -0
- package/runtime/graphql/cache.js +69 -0
- package/runtime/graphql/client.js +563 -0
- package/runtime/graphql/hooks.js +492 -0
- package/runtime/graphql/index.js +62 -0
- package/runtime/graphql/subscriptions.js +241 -0
- package/runtime/graphql.js +12 -1322
- package/runtime/graphql.js.original +1326 -0
- package/runtime/router/core.js +956 -0
- package/runtime/router/guards.js +90 -0
- package/runtime/router/history.js +204 -0
- package/runtime/router/index.js +36 -0
- package/runtime/router/lazy.js +180 -0
- package/runtime/router/utils.js +226 -0
- package/runtime/router.js +12 -1600
- package/runtime/router.js.original +1605 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Parser - Expression Parsing
|
|
3
|
+
*
|
|
4
|
+
* Handles parsing of JavaScript expressions including assignments, conditionals,
|
|
5
|
+
* binary expressions, unary expressions, and primary expressions
|
|
6
|
+
*
|
|
7
|
+
* @module compiler/parser/expressions
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { TokenType } from '../lexer.js';
|
|
11
|
+
import { NodeType, ASTNode, Parser } from './core.js';
|
|
12
|
+
|
|
13
|
+
// ============================================================
|
|
14
|
+
// Expression Parsing
|
|
15
|
+
// ============================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parse expression - delegates to assignment expression
|
|
19
|
+
*/
|
|
20
|
+
Parser.prototype.parseExpression = function() {
|
|
21
|
+
return this.parseAssignmentExpression();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse assignment expression (a = b, a += b, a -= b, etc.)
|
|
26
|
+
*/
|
|
27
|
+
Parser.prototype.parseAssignmentExpression = function() {
|
|
28
|
+
const left = this.parseConditionalExpression();
|
|
29
|
+
|
|
30
|
+
// Check for assignment operators
|
|
31
|
+
const assignmentOps = [
|
|
32
|
+
TokenType.EQ, // =
|
|
33
|
+
TokenType.PLUS_ASSIGN, // +=
|
|
34
|
+
TokenType.MINUS_ASSIGN, // -=
|
|
35
|
+
TokenType.STAR_ASSIGN, // *=
|
|
36
|
+
TokenType.SLASH_ASSIGN, // /=
|
|
37
|
+
TokenType.AND_ASSIGN, // &&=
|
|
38
|
+
TokenType.OR_ASSIGN, // ||=
|
|
39
|
+
TokenType.NULLISH_ASSIGN // ??=
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
if (this.isAny(...assignmentOps)) {
|
|
43
|
+
const operator = this.advance().value;
|
|
44
|
+
const right = this.parseAssignmentExpression();
|
|
45
|
+
return new ASTNode(NodeType.AssignmentExpression, {
|
|
46
|
+
left,
|
|
47
|
+
right,
|
|
48
|
+
operator
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return left;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse conditional (ternary) expression
|
|
57
|
+
*/
|
|
58
|
+
Parser.prototype.parseConditionalExpression = function() {
|
|
59
|
+
const test = this.parseOrExpression();
|
|
60
|
+
|
|
61
|
+
if (this.is(TokenType.QUESTION)) {
|
|
62
|
+
this.advance();
|
|
63
|
+
const consequent = this.parseAssignmentExpression();
|
|
64
|
+
this.expect(TokenType.COLON);
|
|
65
|
+
const alternate = this.parseAssignmentExpression();
|
|
66
|
+
return new ASTNode(NodeType.ConditionalExpression, { test, consequent, alternate });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return test;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Binary operator precedence table (higher index = binds tighter)
|
|
74
|
+
*/
|
|
75
|
+
Parser.BINARY_OPS = [
|
|
76
|
+
{ ops: [TokenType.OR], name: 'or' },
|
|
77
|
+
{ ops: [TokenType.AND], name: 'and' },
|
|
78
|
+
{ ops: [TokenType.EQEQ, TokenType.EQEQEQ, TokenType.NEQ, TokenType.NEQEQ,
|
|
79
|
+
TokenType.LT, TokenType.GT, TokenType.LTE, TokenType.GTE], name: 'comparison' },
|
|
80
|
+
{ ops: [TokenType.PLUS, TokenType.MINUS], name: 'additive' },
|
|
81
|
+
{ ops: [TokenType.STAR, TokenType.SLASH, TokenType.PERCENT], name: 'multiplicative' }
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generic binary expression parser using precedence climbing
|
|
86
|
+
*/
|
|
87
|
+
Parser.prototype.parseBinaryExpr = function(level = 0) {
|
|
88
|
+
if (level >= Parser.BINARY_OPS.length) {
|
|
89
|
+
return this.parseUnaryExpression();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let left = this.parseBinaryExpr(level + 1);
|
|
93
|
+
const { ops } = Parser.BINARY_OPS[level];
|
|
94
|
+
|
|
95
|
+
while (this.isAny(...ops)) {
|
|
96
|
+
const operator = this.advance().value;
|
|
97
|
+
const right = this.parseBinaryExpr(level + 1);
|
|
98
|
+
left = new ASTNode(NodeType.BinaryExpression, { operator, left, right });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return left;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse OR expression (entry point for binary expressions)
|
|
106
|
+
*/
|
|
107
|
+
Parser.prototype.parseOrExpression = function() {
|
|
108
|
+
return this.parseBinaryExpr(0);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Parse unary expression
|
|
113
|
+
*/
|
|
114
|
+
Parser.prototype.parseUnaryExpression = function() {
|
|
115
|
+
if (this.is(TokenType.NOT)) {
|
|
116
|
+
this.advance();
|
|
117
|
+
const argument = this.parseUnaryExpression();
|
|
118
|
+
return new ASTNode(NodeType.UnaryExpression, { operator: '!', argument });
|
|
119
|
+
}
|
|
120
|
+
if (this.is(TokenType.MINUS)) {
|
|
121
|
+
this.advance();
|
|
122
|
+
const argument = this.parseUnaryExpression();
|
|
123
|
+
return new ASTNode(NodeType.UnaryExpression, { operator: '-', argument });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this.parsePostfixExpression();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Parse postfix expression (++, --)
|
|
131
|
+
*/
|
|
132
|
+
Parser.prototype.parsePostfixExpression = function() {
|
|
133
|
+
let expr = this.parsePrimaryExpression();
|
|
134
|
+
|
|
135
|
+
while (this.isAny(TokenType.PLUSPLUS, TokenType.MINUSMINUS)) {
|
|
136
|
+
const operator = this.advance().value;
|
|
137
|
+
expr = new ASTNode(NodeType.UpdateExpression, {
|
|
138
|
+
operator,
|
|
139
|
+
argument: expr,
|
|
140
|
+
prefix: false
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return expr;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Parse primary expression
|
|
149
|
+
*/
|
|
150
|
+
Parser.prototype.parsePrimaryExpression = function() {
|
|
151
|
+
// Check for arrow function: (params) => expr or () => expr
|
|
152
|
+
if (this.is(TokenType.LPAREN)) {
|
|
153
|
+
// Try to parse as arrow function by looking ahead
|
|
154
|
+
const savedPos = this.pos;
|
|
155
|
+
if (this.tryParseArrowFunction()) {
|
|
156
|
+
this.pos = savedPos;
|
|
157
|
+
return this.parseArrowFunction();
|
|
158
|
+
}
|
|
159
|
+
// Not an arrow function, parse as grouped expression
|
|
160
|
+
this.advance();
|
|
161
|
+
const expr = this.parseExpression();
|
|
162
|
+
this.expect(TokenType.RPAREN);
|
|
163
|
+
// Check if this grouped expression is actually arrow function params
|
|
164
|
+
if (this.is(TokenType.ARROW)) {
|
|
165
|
+
this.pos = savedPos;
|
|
166
|
+
return this.parseArrowFunction();
|
|
167
|
+
}
|
|
168
|
+
return expr;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Single param arrow function: x => expr
|
|
172
|
+
if ((this.is(TokenType.IDENT) || this.is(TokenType.SELECTOR)) && this.peek()?.type === TokenType.ARROW) {
|
|
173
|
+
return this.parseArrowFunction();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Array literal
|
|
177
|
+
if (this.is(TokenType.LBRACKET)) {
|
|
178
|
+
return this.parseArrayLiteralExpr();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Object literal in expression context
|
|
182
|
+
if (this.is(TokenType.LBRACE)) {
|
|
183
|
+
return this.parseObjectLiteralExpr();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Template literal
|
|
187
|
+
if (this.is(TokenType.TEMPLATE)) {
|
|
188
|
+
const token = this.advance();
|
|
189
|
+
return new ASTNode(NodeType.TemplateLiteral, { value: token.value, raw: token.raw });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Spread operator
|
|
193
|
+
if (this.is(TokenType.SPREAD)) {
|
|
194
|
+
this.advance();
|
|
195
|
+
const argument = this.parseAssignmentExpression();
|
|
196
|
+
return new ASTNode(NodeType.SpreadElement, { argument });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Try parsing a literal (NUMBER, STRING, TRUE, FALSE, NULL)
|
|
200
|
+
const literal = this.tryParseLiteral();
|
|
201
|
+
if (literal) return literal;
|
|
202
|
+
|
|
203
|
+
// In expressions, SELECTOR tokens should be treated as IDENT
|
|
204
|
+
// This happens when identifiers like 'selectedCategory' are followed by space in view context
|
|
205
|
+
if (this.is(TokenType.IDENT) || this.is(TokenType.SELECTOR)) {
|
|
206
|
+
return this.parseIdentifierOrExpression();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
throw this.createError(
|
|
210
|
+
`Unexpected token ${this.current()?.type} in expression`
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Try to determine if we're looking at an arrow function
|
|
216
|
+
*/
|
|
217
|
+
Parser.prototype.tryParseArrowFunction = function() {
|
|
218
|
+
if (!this.is(TokenType.LPAREN)) return false;
|
|
219
|
+
|
|
220
|
+
let depth = 0;
|
|
221
|
+
let i = 0;
|
|
222
|
+
|
|
223
|
+
while (this.peek(i)) {
|
|
224
|
+
const token = this.peek(i);
|
|
225
|
+
if (token.type === TokenType.LPAREN) depth++;
|
|
226
|
+
else if (token.type === TokenType.RPAREN) {
|
|
227
|
+
depth--;
|
|
228
|
+
if (depth === 0) {
|
|
229
|
+
// Check if next token is =>
|
|
230
|
+
const next = this.peek(i + 1);
|
|
231
|
+
return next?.type === TokenType.ARROW;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
i++;
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Parse arrow function: (params) => expr or param => expr
|
|
241
|
+
*/
|
|
242
|
+
Parser.prototype.parseArrowFunction = function() {
|
|
243
|
+
const params = [];
|
|
244
|
+
|
|
245
|
+
// Single param without parens: x => expr
|
|
246
|
+
if ((this.is(TokenType.IDENT) || this.is(TokenType.SELECTOR)) && this.peek()?.type === TokenType.ARROW) {
|
|
247
|
+
params.push(this.advance().value);
|
|
248
|
+
} else {
|
|
249
|
+
// Params in parens: (a, b) => expr or () => expr
|
|
250
|
+
this.expect(TokenType.LPAREN);
|
|
251
|
+
while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
|
|
252
|
+
if (this.is(TokenType.SPREAD)) {
|
|
253
|
+
this.advance();
|
|
254
|
+
params.push('...' + this.expect(TokenType.IDENT).value);
|
|
255
|
+
} else {
|
|
256
|
+
params.push(this.expect(TokenType.IDENT).value);
|
|
257
|
+
}
|
|
258
|
+
if (this.is(TokenType.COMMA)) {
|
|
259
|
+
this.advance();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
this.expect(TokenType.RPAREN);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
this.expect(TokenType.ARROW);
|
|
266
|
+
|
|
267
|
+
// Body can be expression or block
|
|
268
|
+
let body;
|
|
269
|
+
if (this.is(TokenType.LBRACE)) {
|
|
270
|
+
// Block body - collect tokens
|
|
271
|
+
this.advance();
|
|
272
|
+
body = this.parseFunctionBody();
|
|
273
|
+
this.expect(TokenType.RBRACE);
|
|
274
|
+
return new ASTNode(NodeType.ArrowFunction, { params, body, block: true });
|
|
275
|
+
} else {
|
|
276
|
+
// Expression body
|
|
277
|
+
body = this.parseAssignmentExpression();
|
|
278
|
+
return new ASTNode(NodeType.ArrowFunction, { params, body, block: false });
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Parse array literal in expression context
|
|
284
|
+
*/
|
|
285
|
+
Parser.prototype.parseArrayLiteralExpr = function() {
|
|
286
|
+
this.expect(TokenType.LBRACKET);
|
|
287
|
+
const elements = [];
|
|
288
|
+
|
|
289
|
+
while (!this.is(TokenType.RBRACKET) && !this.is(TokenType.EOF)) {
|
|
290
|
+
if (this.is(TokenType.SPREAD)) {
|
|
291
|
+
this.advance();
|
|
292
|
+
elements.push(new ASTNode(NodeType.SpreadElement, {
|
|
293
|
+
argument: this.parseAssignmentExpression()
|
|
294
|
+
}));
|
|
295
|
+
} else {
|
|
296
|
+
elements.push(this.parseAssignmentExpression());
|
|
297
|
+
}
|
|
298
|
+
if (this.is(TokenType.COMMA)) {
|
|
299
|
+
this.advance();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this.expect(TokenType.RBRACKET);
|
|
304
|
+
return new ASTNode(NodeType.ArrayLiteral, { elements });
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Parse object literal in expression context
|
|
309
|
+
*/
|
|
310
|
+
Parser.prototype.parseObjectLiteralExpr = function() {
|
|
311
|
+
this.expect(TokenType.LBRACE);
|
|
312
|
+
const properties = [];
|
|
313
|
+
|
|
314
|
+
while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
315
|
+
if (this.is(TokenType.SPREAD)) {
|
|
316
|
+
this.advance();
|
|
317
|
+
properties.push(new ASTNode(NodeType.SpreadElement, {
|
|
318
|
+
argument: this.parseAssignmentExpression()
|
|
319
|
+
}));
|
|
320
|
+
} else {
|
|
321
|
+
const key = this.expect(TokenType.IDENT);
|
|
322
|
+
if (this.is(TokenType.COLON)) {
|
|
323
|
+
this.advance();
|
|
324
|
+
const value = this.parseAssignmentExpression();
|
|
325
|
+
properties.push(new ASTNode(NodeType.Property, { name: key.value, value }));
|
|
326
|
+
} else {
|
|
327
|
+
// Shorthand property: { x } is same as { x: x }
|
|
328
|
+
properties.push(new ASTNode(NodeType.Property, {
|
|
329
|
+
name: key.value,
|
|
330
|
+
value: new ASTNode(NodeType.Identifier, { name: key.value }),
|
|
331
|
+
shorthand: true
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (this.is(TokenType.COMMA)) {
|
|
336
|
+
this.advance();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.expect(TokenType.RBRACE);
|
|
341
|
+
return new ASTNode(NodeType.ObjectLiteral, { properties });
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Parse identifier with possible member access and calls
|
|
346
|
+
*/
|
|
347
|
+
Parser.prototype.parseIdentifierOrExpression = function() {
|
|
348
|
+
// Accept both IDENT and SELECTOR (selector tokens can be identifiers in expression context)
|
|
349
|
+
const token = this.advance();
|
|
350
|
+
let expr = new ASTNode(NodeType.Identifier, { name: token.value });
|
|
351
|
+
|
|
352
|
+
while (true) {
|
|
353
|
+
if (this.is(TokenType.DOT)) {
|
|
354
|
+
this.advance();
|
|
355
|
+
const property = this.expect(TokenType.IDENT);
|
|
356
|
+
expr = new ASTNode(NodeType.MemberExpression, {
|
|
357
|
+
object: expr,
|
|
358
|
+
property: property.value
|
|
359
|
+
});
|
|
360
|
+
} else if (this.is(TokenType.LBRACKET)) {
|
|
361
|
+
this.advance();
|
|
362
|
+
const property = this.parseExpression();
|
|
363
|
+
this.expect(TokenType.RBRACKET);
|
|
364
|
+
expr = new ASTNode(NodeType.MemberExpression, {
|
|
365
|
+
object: expr,
|
|
366
|
+
property,
|
|
367
|
+
computed: true
|
|
368
|
+
});
|
|
369
|
+
} else if (this.is(TokenType.LPAREN)) {
|
|
370
|
+
this.advance();
|
|
371
|
+
const args = [];
|
|
372
|
+
while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
|
|
373
|
+
args.push(this.parseExpression());
|
|
374
|
+
if (this.is(TokenType.COMMA)) {
|
|
375
|
+
this.advance();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
this.expect(TokenType.RPAREN);
|
|
379
|
+
expr = new ASTNode(NodeType.CallExpression, { callee: expr, arguments: args });
|
|
380
|
+
} else {
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return expr;
|
|
386
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Parser - Import/Page/Route Declaration Parsing
|
|
3
|
+
*
|
|
4
|
+
* Handles parsing of import statements and @page/@route decorators
|
|
5
|
+
*
|
|
6
|
+
* @module compiler/parser/imports
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { TokenType } from '../lexer.js';
|
|
10
|
+
import { NodeType, ASTNode, Parser } from './core.js';
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// Import Declaration Parsing
|
|
14
|
+
// ============================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse import declaration
|
|
18
|
+
* Supports:
|
|
19
|
+
* import Component from './Component.pulse'
|
|
20
|
+
* import { helper, util } from './utils.pulse'
|
|
21
|
+
* import { helper as h } from './utils.pulse'
|
|
22
|
+
* import * as Utils from './utils.pulse'
|
|
23
|
+
*/
|
|
24
|
+
Parser.prototype.parseImportDeclaration = function() {
|
|
25
|
+
const startToken = this.expect(TokenType.IMPORT);
|
|
26
|
+
const specifiers = [];
|
|
27
|
+
let source = null;
|
|
28
|
+
|
|
29
|
+
// import * as Name from '...'
|
|
30
|
+
if (this.is(TokenType.STAR)) {
|
|
31
|
+
this.advance();
|
|
32
|
+
this.expect(TokenType.AS);
|
|
33
|
+
const local = this.expect(TokenType.IDENT);
|
|
34
|
+
specifiers.push(new ASTNode(NodeType.ImportSpecifier, {
|
|
35
|
+
type: 'namespace',
|
|
36
|
+
local: local.value,
|
|
37
|
+
imported: '*'
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
// import { a, b } from '...'
|
|
41
|
+
else if (this.is(TokenType.LBRACE)) {
|
|
42
|
+
this.advance();
|
|
43
|
+
while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
44
|
+
const imported = this.expect(TokenType.IDENT);
|
|
45
|
+
let local = imported.value;
|
|
46
|
+
|
|
47
|
+
// Handle 'as' alias
|
|
48
|
+
if (this.is(TokenType.AS)) {
|
|
49
|
+
this.advance();
|
|
50
|
+
local = this.expect(TokenType.IDENT).value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
specifiers.push(new ASTNode(NodeType.ImportSpecifier, {
|
|
54
|
+
type: 'named',
|
|
55
|
+
local,
|
|
56
|
+
imported: imported.value
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
if (this.is(TokenType.COMMA)) {
|
|
60
|
+
this.advance();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.expect(TokenType.RBRACE);
|
|
64
|
+
}
|
|
65
|
+
// import DefaultExport from '...'
|
|
66
|
+
else if (this.is(TokenType.IDENT)) {
|
|
67
|
+
const name = this.advance();
|
|
68
|
+
specifiers.push(new ASTNode(NodeType.ImportSpecifier, {
|
|
69
|
+
type: 'default',
|
|
70
|
+
local: name.value,
|
|
71
|
+
imported: 'default'
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// from '...'
|
|
76
|
+
this.expect(TokenType.FROM);
|
|
77
|
+
const sourceToken = this.expect(TokenType.STRING);
|
|
78
|
+
source = sourceToken.value;
|
|
79
|
+
|
|
80
|
+
return new ASTNode(NodeType.ImportDeclaration, {
|
|
81
|
+
specifiers,
|
|
82
|
+
source,
|
|
83
|
+
line: startToken.line,
|
|
84
|
+
column: startToken.column
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// ============================================================
|
|
89
|
+
// Page/Route Declaration Parsing
|
|
90
|
+
// ============================================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse @page declaration
|
|
94
|
+
*/
|
|
95
|
+
Parser.prototype.parsePageDeclaration = function() {
|
|
96
|
+
this.expect(TokenType.PAGE);
|
|
97
|
+
const name = this.expect(TokenType.IDENT);
|
|
98
|
+
return new ASTNode(NodeType.PageDeclaration, { name: name.value });
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Parse @route declaration
|
|
103
|
+
*/
|
|
104
|
+
Parser.prototype.parseRouteDeclaration = function() {
|
|
105
|
+
this.expect(TokenType.ROUTE);
|
|
106
|
+
const path = this.expect(TokenType.STRING);
|
|
107
|
+
return new ASTNode(NodeType.RouteDeclaration, { path: path.value });
|
|
108
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Parser - Main Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Barrel export for all parser modules
|
|
5
|
+
* Assembles the complete Parser class by importing all method modules
|
|
6
|
+
*
|
|
7
|
+
* @module compiler/parser
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { tokenize } from '../lexer.js';
|
|
11
|
+
import { NodeType, ASTNode, Parser } from './core.js';
|
|
12
|
+
|
|
13
|
+
// Import all parser method modules (these add methods to Parser.prototype)
|
|
14
|
+
import './imports.js'; // parseImportDeclaration, parsePageDeclaration, parseRouteDeclaration
|
|
15
|
+
import './state.js'; // parsePropsBlock, parseStateBlock, parseValue, etc.
|
|
16
|
+
import './view.js'; // parseViewBlock, parseElement, parseDirective, etc.
|
|
17
|
+
import './expressions.js'; // parseExpression, parseAssignmentExpression, etc.
|
|
18
|
+
import './blocks.js'; // parseActionsBlock, parseRouterBlock, parseStoreBlock, etc.
|
|
19
|
+
import './style.js'; // parseStyleBlock, parseStyleRule, parseStyleProperty
|
|
20
|
+
|
|
21
|
+
// ============================================================
|
|
22
|
+
// Main Parse Function
|
|
23
|
+
// ============================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse .pulse source code into an AST
|
|
27
|
+
* @param {string} source - Source code to parse
|
|
28
|
+
* @returns {ASTNode} Program AST node
|
|
29
|
+
*/
|
|
30
|
+
export function parse(source) {
|
|
31
|
+
const tokens = tokenize(source);
|
|
32
|
+
const parser = new Parser(tokens);
|
|
33
|
+
return parser.parse();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================
|
|
37
|
+
// Exports
|
|
38
|
+
// ============================================================
|
|
39
|
+
|
|
40
|
+
export { NodeType, ASTNode, Parser };
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
NodeType,
|
|
44
|
+
ASTNode,
|
|
45
|
+
Parser,
|
|
46
|
+
parse
|
|
47
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Parser - Props/State Block Parsing
|
|
3
|
+
*
|
|
4
|
+
* Handles parsing of props and state blocks with their properties
|
|
5
|
+
*
|
|
6
|
+
* @module compiler/parser/state
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { TokenType } from '../lexer.js';
|
|
10
|
+
import { NodeType, ASTNode, Parser } from './core.js';
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// Props Block Parsing
|
|
14
|
+
// ============================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse props block
|
|
18
|
+
* props {
|
|
19
|
+
* label: "Default"
|
|
20
|
+
* disabled: false
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
Parser.prototype.parsePropsBlock = function() {
|
|
24
|
+
this.expect(TokenType.PROPS);
|
|
25
|
+
this.expect(TokenType.LBRACE);
|
|
26
|
+
|
|
27
|
+
const properties = [];
|
|
28
|
+
while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
29
|
+
properties.push(this.parsePropsProperty());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.expect(TokenType.RBRACE);
|
|
33
|
+
return new ASTNode(NodeType.PropsBlock, { properties });
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse a props property (name: defaultValue)
|
|
38
|
+
*/
|
|
39
|
+
Parser.prototype.parsePropsProperty = function() {
|
|
40
|
+
const name = this.expect(TokenType.IDENT);
|
|
41
|
+
this.expect(TokenType.COLON);
|
|
42
|
+
const value = this.parseValue();
|
|
43
|
+
return new ASTNode(NodeType.Property, { name: name.value, value });
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ============================================================
|
|
47
|
+
// State Block Parsing
|
|
48
|
+
// ============================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse state block
|
|
52
|
+
*/
|
|
53
|
+
Parser.prototype.parseStateBlock = function() {
|
|
54
|
+
this.expect(TokenType.STATE);
|
|
55
|
+
this.expect(TokenType.LBRACE);
|
|
56
|
+
|
|
57
|
+
const properties = [];
|
|
58
|
+
while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
59
|
+
properties.push(this.parseStateProperty());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.expect(TokenType.RBRACE);
|
|
63
|
+
return new ASTNode(NodeType.StateBlock, { properties });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse a state property
|
|
68
|
+
*/
|
|
69
|
+
Parser.prototype.parseStateProperty = function() {
|
|
70
|
+
const name = this.expect(TokenType.IDENT);
|
|
71
|
+
this.expect(TokenType.COLON);
|
|
72
|
+
const value = this.parseValue();
|
|
73
|
+
return new ASTNode(NodeType.Property, { name: name.value, value });
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ============================================================
|
|
77
|
+
// Value Parsing Utilities
|
|
78
|
+
// ============================================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Try to parse a literal token (STRING, NUMBER, TRUE, FALSE, NULL)
|
|
82
|
+
* Returns the AST node or null if not a literal
|
|
83
|
+
*/
|
|
84
|
+
Parser.prototype.tryParseLiteral = function() {
|
|
85
|
+
const token = this.current();
|
|
86
|
+
if (!token) return null;
|
|
87
|
+
|
|
88
|
+
const literalMap = {
|
|
89
|
+
[TokenType.STRING]: () => new ASTNode(NodeType.Literal, { value: this.advance().value, raw: token.raw }),
|
|
90
|
+
[TokenType.NUMBER]: () => new ASTNode(NodeType.Literal, { value: this.advance().value }),
|
|
91
|
+
[TokenType.TRUE]: () => (this.advance(), new ASTNode(NodeType.Literal, { value: true })),
|
|
92
|
+
[TokenType.FALSE]: () => (this.advance(), new ASTNode(NodeType.Literal, { value: false })),
|
|
93
|
+
[TokenType.NULL]: () => (this.advance(), new ASTNode(NodeType.Literal, { value: null }))
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return literalMap[token.type]?.() || null;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Parse a value (literal, object, array, etc.)
|
|
101
|
+
*/
|
|
102
|
+
Parser.prototype.parseValue = function() {
|
|
103
|
+
if (this.is(TokenType.LBRACE)) return this.parseObjectLiteral();
|
|
104
|
+
if (this.is(TokenType.LBRACKET)) return this.parseArrayLiteral();
|
|
105
|
+
|
|
106
|
+
const literal = this.tryParseLiteral();
|
|
107
|
+
if (literal) return literal;
|
|
108
|
+
|
|
109
|
+
if (this.is(TokenType.IDENT)) return this.parseIdentifierOrExpression();
|
|
110
|
+
|
|
111
|
+
throw this.createError(
|
|
112
|
+
`Unexpected token ${this.current()?.type} in value`
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Parse object literal
|
|
118
|
+
*/
|
|
119
|
+
Parser.prototype.parseObjectLiteral = function() {
|
|
120
|
+
this.expect(TokenType.LBRACE);
|
|
121
|
+
const properties = [];
|
|
122
|
+
|
|
123
|
+
while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
124
|
+
const name = this.expect(TokenType.IDENT);
|
|
125
|
+
this.expect(TokenType.COLON);
|
|
126
|
+
const value = this.parseValue();
|
|
127
|
+
properties.push(new ASTNode(NodeType.Property, { name: name.value, value }));
|
|
128
|
+
|
|
129
|
+
if (this.is(TokenType.COMMA)) {
|
|
130
|
+
this.advance();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.expect(TokenType.RBRACE);
|
|
135
|
+
return new ASTNode(NodeType.ObjectLiteral, { properties });
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Parse array literal
|
|
140
|
+
*/
|
|
141
|
+
Parser.prototype.parseArrayLiteral = function() {
|
|
142
|
+
this.expect(TokenType.LBRACKET);
|
|
143
|
+
const elements = [];
|
|
144
|
+
|
|
145
|
+
while (!this.is(TokenType.RBRACKET) && !this.is(TokenType.EOF)) {
|
|
146
|
+
elements.push(this.parseValue());
|
|
147
|
+
|
|
148
|
+
if (this.is(TokenType.COMMA)) {
|
|
149
|
+
this.advance();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.expect(TokenType.RBRACKET);
|
|
154
|
+
return new ASTNode(NodeType.ArrayLiteral, { elements });
|
|
155
|
+
};
|