exprify 1.0.0 → 1.0.1

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.
@@ -1,48 +1,38 @@
1
- const isValidNumberPair = (a, b) =>
2
- (typeof a === typeof b) &&
3
- (typeof a === 'number' || typeof a === 'bigint');
4
-
5
- export const mathOperations = Object.freeze({
6
-
7
- operator_precedence: {
8
- '^': 4,
9
- '*': 3,
10
- '/': 3,
11
- '%': 3,
12
- '+': 1,
13
- '-': 1,
14
- },
15
-
16
- power: function(a, b) {
17
- if (isValidNumberPair(a, b)) return a ** b;
18
- throw new Error("Invalid types for ^");
19
- },
20
-
21
- multiply: function(a, b) {
22
- if (isValidNumberPair(a, b)) return a * b;
23
- throw new Error("Invalid types for *");
24
- },
25
-
26
- divide: function(a, b) {
27
- if (isValidNumberPair(a, b)) {
28
- if (b === 0) throw new Error("Division by zero");
29
- return a / b;
30
- }
31
- throw new Error("Invalid types for /");
32
- },
33
-
34
- add: function(a, b) {
35
- if (isValidNumberPair(a, b)) return a + b;
36
- if (typeof a === 'string' && typeof b === 'string') return a + b;
37
- throw new Error("Invalid types for +");
38
- },
39
- subtract: function(a, b) {
40
- if (isValidNumberPair(a, b)) return a - b;
41
- throw new Error("Invalid types for -");
42
- },
43
-
44
- modulus: function(a, b) {
45
- if (isValidNumberPair(a, b)) return a % b;
46
- throw new Error("Invalid types for %");
47
- }
1
+ const isValidNumberPair = (a, b) =>
2
+ (typeof a === typeof b) &&
3
+ (typeof a === 'number' || typeof a === 'bigint');
4
+
5
+ export const mathOperations = Object.freeze({
6
+ power: function(a, b) {
7
+ if (isValidNumberPair(a, b)) return a ** b;
8
+ throw new Error("Invalid types for ^");
9
+ },
10
+
11
+ multiply: function(a, b) {
12
+ if (isValidNumberPair(a, b)) return a * b;
13
+ throw new Error("Invalid types for *");
14
+ },
15
+
16
+ divide: function(a, b) {
17
+ if (isValidNumberPair(a, b)) {
18
+ if (b === 0) throw new Error("Division by zero");
19
+ return a / b;
20
+ }
21
+ throw new Error("Invalid types for /");
22
+ },
23
+
24
+ add: function(a, b) {
25
+ if (isValidNumberPair(a, b)) return a + b;
26
+ if (typeof a === 'string' && typeof b === 'string') return a + b;
27
+ throw new Error("Invalid types for +");
28
+ },
29
+ subtract: function(a, b) {
30
+ if (isValidNumberPair(a, b)) return a - b;
31
+ throw new Error("Invalid types for -");
32
+ },
33
+
34
+ modulus: function(a, b) {
35
+ if (isValidNumberPair(a, b)) return a % b;
36
+ throw new Error("Invalid types for %");
37
+ }
48
38
  });
@@ -0,0 +1,508 @@
1
+ export function buildAST(tokens) {
2
+ let current = 0;
3
+
4
+ const peek = () => tokens[current];
5
+ const consume = () => tokens[current++];
6
+
7
+ const match = (type, value) => {
8
+ const t = peek();
9
+ if (!t) return false;
10
+
11
+ if (t.type !== type) return false;
12
+
13
+ if (value !== undefined && t.value !== value) return false;
14
+
15
+ current++;
16
+ return true;
17
+ };
18
+
19
+ const parseSliceOrIndex = () => {
20
+ let start = null;
21
+
22
+ if (!(peek()?.type === "Colon" || peek()?.type === "Comma" || peek()?.type === "ArrayEnd")) {
23
+ start = parseExpression();
24
+ }
25
+
26
+ if (match("Colon")) {
27
+ let end = null;
28
+
29
+ if (!(peek()?.type === "Comma" || peek()?.type === "ArrayEnd")) {
30
+ end = parseExpression();
31
+ }
32
+
33
+ return {
34
+ type: "SliceExpression",
35
+ start,
36
+ end
37
+ };
38
+ }
39
+
40
+ return start;
41
+ };
42
+
43
+ /* ================= PRIMARY ================= */
44
+ function parsePrimary() {
45
+ const token = consume();
46
+ if (!token) throw new Error("Unexpected end of input");
47
+
48
+ switch (token.type) {
49
+ case "Number":
50
+ case "BigInt":
51
+ case "Boolean":
52
+ case "String":
53
+ return { type: "Literal", value: token.value };
54
+
55
+ case "ImaginaryLiteral":
56
+ return { type: "ImaginaryLiteral", value: token.value };
57
+
58
+ case "NumberWithUnit":
59
+ return {
60
+ type: "UnitLiteral",
61
+ value: token.value,
62
+ unit: token.unit
63
+ };
64
+
65
+ case "Identifier":
66
+ return { type: "Identifier", name: token.name };
67
+
68
+ case "Function": // 🔥 ADD THIS
69
+ return {
70
+ type: "Identifier",
71
+ name: token.name
72
+ };
73
+
74
+ case "Parenthesis":
75
+ if (token.value === "(") {
76
+ const expr = parseExpression();
77
+
78
+ if (!match("Parenthesis", ")")) {
79
+ throw new Error(`Expected ')'`);
80
+ }
81
+
82
+ return expr;
83
+ }
84
+
85
+ case "ArrayStart": {
86
+ const rows = [];
87
+ let currentRow = [];
88
+
89
+ if (!match("ArrayEnd")) {
90
+ while (true) {
91
+ currentRow.push(parseExpression());
92
+
93
+ if (match("Comma")) {
94
+ continue;
95
+ }
96
+
97
+ if (match("Semicolon")) {
98
+ rows.push(currentRow);
99
+ currentRow = [];
100
+ continue;
101
+ }
102
+
103
+ if (match("ArrayEnd")) {
104
+ rows.push(currentRow);
105
+ break;
106
+ }
107
+
108
+ throw new Error(`Expected ',', ';', or ']' at ${current}`);
109
+ }
110
+ }
111
+
112
+ if (!rows.length) {
113
+ return { type: "ArrayExpression", elements: [] };
114
+ }
115
+
116
+ if (rows.length === 1) {
117
+ return { type: "ArrayExpression", elements: rows[0] };
118
+ }
119
+
120
+ return {
121
+ type: "ArrayExpression",
122
+ elements: rows.map((elements) => ({
123
+ type: "ArrayExpression",
124
+ elements
125
+ }))
126
+ };
127
+ }
128
+
129
+ case "BlockStart": {
130
+ const properties = [];
131
+
132
+ if (!match("BlockEnd")) {
133
+ do {
134
+ const keyToken = consume();
135
+
136
+ if (
137
+ keyToken.type !== "Identifier" &&
138
+ keyToken.type !== "String"
139
+ ) {
140
+ throw new Error("Invalid object key");
141
+ }
142
+
143
+ if (!match("Colon")) {
144
+ throw new Error("Expected ':' after key");
145
+ }
146
+
147
+ const value = parseExpression();
148
+
149
+ properties.push({
150
+ key: keyToken.value,
151
+ value
152
+ });
153
+
154
+ } while (match("Comma"));
155
+
156
+ if (!match("BlockEnd")) {
157
+ throw new Error(`Expected '}' at ${current}`);
158
+ }
159
+ }
160
+
161
+ return { type: "ObjectExpression", properties };
162
+ }
163
+ }
164
+
165
+ throw new Error(`Unexpected token: ${JSON.stringify(token)}`);
166
+ }
167
+
168
+ /* ================= MEMBER ================= */
169
+ function parseMember() {
170
+ let object = parsePrimary();
171
+
172
+ while (true) {
173
+ if (match("ArrayStart")) {
174
+ const selectors = [];
175
+
176
+ if (!match("ArrayEnd")) {
177
+ do {
178
+ selectors.push(parseSliceOrIndex());
179
+ } while (match("Comma"));
180
+
181
+ if (!match("ArrayEnd")) {
182
+ throw new Error(`Expected ']' at ${current}`);
183
+ }
184
+ }
185
+
186
+ object = {
187
+ type: "IndexExpression",
188
+ object,
189
+ selectors
190
+ };
191
+ continue;
192
+ }
193
+
194
+ if (match("Dot")) {
195
+ const property = consume();
196
+
197
+ if (property.type !== "Identifier") {
198
+ throw new Error("Expected property after '.'");
199
+ }
200
+
201
+ object = {
202
+ type: "MemberExpression",
203
+ object,
204
+ property: { type: "Identifier", name: property.value },
205
+ optional: false
206
+ };
207
+ continue;
208
+ }
209
+
210
+ if (match("Operator", "?.")) {
211
+ const property = consume();
212
+
213
+ object = {
214
+ type: "MemberExpression",
215
+ object,
216
+ property: { type: "Identifier", name: property.value },
217
+ optional: true
218
+ };
219
+ continue;
220
+ }
221
+
222
+ break;
223
+ }
224
+
225
+ return object;
226
+ }
227
+
228
+ /* ================= CALL ================= */
229
+ function parseCallChain() {
230
+ let expr = parseMember();
231
+
232
+ while (peek()?.type === "Parenthesis" && peek()?.value === "(") {
233
+ consume(); // '('
234
+
235
+ const args = [];
236
+
237
+ if (!(peek()?.type === "Parenthesis" && peek()?.value === ")")) {
238
+ do {
239
+ args.push(parseExpression());
240
+ } while (match("Comma"));
241
+ }
242
+
243
+ if (!match("Parenthesis", ")")) {
244
+ throw new Error(`Expected ')' at ${current}`);
245
+ }
246
+
247
+ expr = {
248
+ type: "CallExpression",
249
+ callee: expr,
250
+ arguments: args
251
+ };
252
+ }
253
+
254
+ return expr;
255
+ }
256
+
257
+ /* ================= UNARY ================= */
258
+ function parseUnary() {
259
+ if (match("UnaryOperator")) {
260
+ const operator = tokens[current - 1].value;
261
+
262
+ return {
263
+ type: "UnaryExpression",
264
+ operator,
265
+ argument: parseUnary()
266
+ };
267
+ }
268
+
269
+ return parseCallChain();
270
+ }
271
+
272
+ /* ================= POWER ================= */
273
+ function parsePower() {
274
+ let left = parseUnary();
275
+
276
+ if (match("Operator", "^")) {
277
+ const right = parsePower();
278
+ return {
279
+ type: "BinaryExpression",
280
+ operator: "^",
281
+ left,
282
+ right
283
+ };
284
+ }
285
+
286
+ return left;
287
+ }
288
+
289
+ /* ================= MULT ================= */
290
+ function parseMultiplication() {
291
+ let left = parsePower();
292
+
293
+ while (
294
+ match("Operator", "*") ||
295
+ match("Operator", "/") ||
296
+ match("Operator", "%")
297
+ ) {
298
+ const operator = tokens[current - 1].value;
299
+ const right = parsePower();
300
+
301
+ left = {
302
+ type: "BinaryExpression",
303
+ operator,
304
+ left,
305
+ right
306
+ };
307
+ }
308
+
309
+ return left;
310
+ }
311
+
312
+ /* ================= ADD ================= */
313
+ function parseAddition() {
314
+ let left = parseMultiplication();
315
+
316
+ while (match("Operator", "+") || match("Operator", "-")) {
317
+ const operator = tokens[current - 1].value;
318
+ const right = parseMultiplication();
319
+
320
+ left = {
321
+ type: "BinaryExpression",
322
+ operator,
323
+ left,
324
+ right
325
+ };
326
+ }
327
+
328
+ return left;
329
+ }
330
+
331
+ /* ================= UNIT CONVERSION ================= */
332
+ function parseUnitConversion() {
333
+ let left = parseAddition();
334
+
335
+ const nextKeyword = peek();
336
+ if (nextKeyword?.type === "Keyword" && ["to", "in"].includes(nextKeyword.value)) {
337
+ consume();
338
+ const next = consume();
339
+
340
+ if (!next || next.type !== "Unit") {
341
+ throw new Error(`Expected unit after '${nextKeyword.value}'`);
342
+ }
343
+
344
+ return {
345
+ type: "UnitConversion",
346
+ from: left,
347
+ to: next.value
348
+ };
349
+ }
350
+
351
+ return left;
352
+ }
353
+
354
+ /* ================= COMPARISON ================= */
355
+ function parseComparison() {
356
+ let left = parseUnitConversion();
357
+
358
+ while (
359
+ match("Operator", ">") ||
360
+ match("Operator", "<") ||
361
+ match("Operator", ">=") ||
362
+ match("Operator", "<=") ||
363
+ match("Operator", "==")
364
+ ) {
365
+ const operator = tokens[current - 1].value;
366
+ const right = parseUnitConversion();
367
+
368
+ left = {
369
+ type: "BinaryExpression",
370
+ operator,
371
+ left,
372
+ right
373
+ };
374
+ }
375
+
376
+ return left;
377
+ }
378
+
379
+ /* ================= LOGICAL ================= */
380
+ function parseLogical() {
381
+ let left = parseComparison();
382
+
383
+ while (
384
+ match("Operator", "&&") ||
385
+ match("Operator", "||")
386
+ ) {
387
+ const operator = tokens[current - 1].value;
388
+ const right = parseComparison();
389
+
390
+ left = {
391
+ type: "LogicalExpression",
392
+ operator,
393
+ left,
394
+ right
395
+ };
396
+ }
397
+
398
+ return left;
399
+ }
400
+
401
+ /* ================= NULLISH ================= */
402
+ function parseNullish() {
403
+ let left = parseLogical();
404
+
405
+ while (match("Operator", "??")) {
406
+ const right = parseLogical();
407
+
408
+ left = {
409
+ type: "LogicalExpression",
410
+ operator: "??",
411
+ left,
412
+ right
413
+ };
414
+ }
415
+
416
+ return left;
417
+ }
418
+
419
+ /* ================= TERNARY ================= */
420
+ function parseTernary() {
421
+ let test = parseNullish();
422
+
423
+ if (match("Ternary", "?")) {
424
+ const consequent = parseExpression();
425
+
426
+ if (!match("Ternary", ":")) {
427
+ throw new Error("Expected ':' in ternary");
428
+ }
429
+
430
+ const alternate = parseExpression();
431
+
432
+ return {
433
+ type: "ConditionalExpression",
434
+ test,
435
+ consequent,
436
+ alternate
437
+ };
438
+ }
439
+
440
+ return test;
441
+ }
442
+
443
+ /* ================= PIPELINE ================= */
444
+ function parsePipeline() {
445
+ let left = parseTernary();
446
+
447
+ while (match("Operator", "|>")) {
448
+ const right = parseTernary();
449
+
450
+ left = {
451
+ type: "PipelineExpression",
452
+ left,
453
+ right
454
+ };
455
+ }
456
+
457
+ return left;
458
+ }
459
+
460
+ /* ================= ASSIGNMENT ================= */
461
+ function parseAssignment() {
462
+ let left = parsePipeline();
463
+
464
+ if (
465
+ match("Operator", "=") ||
466
+ match("Operator", "+=") ||
467
+ match("Operator", "-=") ||
468
+ match("Operator", "*=") ||
469
+ match("Operator", "/=")
470
+ ) {
471
+ const operator = tokens[current - 1].value;
472
+
473
+ if (
474
+ left.type !== "Identifier" &&
475
+ left.type !== "MemberExpression" &&
476
+ left.type !== "IndexExpression"
477
+ ) {
478
+ throw new Error("Invalid assignment target");
479
+ }
480
+
481
+ const right = parseAssignment();
482
+
483
+ return {
484
+ type: "AssignmentExpression",
485
+ operator,
486
+ left,
487
+ right
488
+ };
489
+ }
490
+
491
+ return left;
492
+ }
493
+
494
+ /* ================= ENTRY ================= */
495
+ function parseExpression() {
496
+ return parseAssignment();
497
+ }
498
+
499
+ const ast = parseExpression();
500
+
501
+ if (current < tokens.length) {
502
+ throw new Error(
503
+ `Unexpected token at end: ${JSON.stringify(peek())}`
504
+ );
505
+ }
506
+
507
+ return ast;
508
+ }