littlewing 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/dist/index.js ADDED
@@ -0,0 +1,673 @@
1
+ import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+
13
+ // src/ast.ts
14
+ var exports_ast = {};
15
+ __export(exports_ast, {
16
+ unaryOp: () => unaryOp,
17
+ subtract: () => subtract,
18
+ number: () => number,
19
+ negate: () => negate,
20
+ multiply: () => multiply,
21
+ modulo: () => modulo,
22
+ identifier: () => identifier,
23
+ functionCall: () => functionCall,
24
+ exponentiate: () => exponentiate,
25
+ divide: () => divide,
26
+ binaryOp: () => binaryOp,
27
+ assign: () => assign,
28
+ add: () => add
29
+ });
30
+ function number(value) {
31
+ return {
32
+ type: "NumberLiteral",
33
+ value
34
+ };
35
+ }
36
+ function identifier(name) {
37
+ return {
38
+ type: "Identifier",
39
+ name
40
+ };
41
+ }
42
+ function binaryOp(left, operator, right) {
43
+ return {
44
+ type: "BinaryOp",
45
+ left,
46
+ operator,
47
+ right
48
+ };
49
+ }
50
+ function unaryOp(argument) {
51
+ return {
52
+ type: "UnaryOp",
53
+ operator: "-",
54
+ argument
55
+ };
56
+ }
57
+ function functionCall(name, args = []) {
58
+ return {
59
+ type: "FunctionCall",
60
+ name,
61
+ arguments: args
62
+ };
63
+ }
64
+ function assign(name, value) {
65
+ return {
66
+ type: "Assignment",
67
+ name,
68
+ value
69
+ };
70
+ }
71
+ function add(left, right) {
72
+ return binaryOp(left, "+", right);
73
+ }
74
+ function subtract(left, right) {
75
+ return binaryOp(left, "-", right);
76
+ }
77
+ function multiply(left, right) {
78
+ return binaryOp(left, "*", right);
79
+ }
80
+ function divide(left, right) {
81
+ return binaryOp(left, "/", right);
82
+ }
83
+ function modulo(left, right) {
84
+ return binaryOp(left, "%", right);
85
+ }
86
+ function exponentiate(left, right) {
87
+ return binaryOp(left, "^", right);
88
+ }
89
+ function negate(argument) {
90
+ return unaryOp(argument);
91
+ }
92
+ // src/defaults.ts
93
+ var defaultContext = {
94
+ functions: {
95
+ abs: Math.abs,
96
+ ceil: Math.ceil,
97
+ floor: Math.floor,
98
+ round: Math.round,
99
+ sqrt: Math.sqrt,
100
+ min: Math.min,
101
+ max: Math.max,
102
+ sin: Math.sin,
103
+ cos: Math.cos,
104
+ tan: Math.tan,
105
+ log: Math.log,
106
+ log10: Math.log10,
107
+ exp: Math.exp,
108
+ now: () => new Date,
109
+ date: (...args) => new Date(...args),
110
+ milliseconds: (ms) => ms,
111
+ seconds: (s) => s * 1000,
112
+ minutes: (m) => m * 60 * 1000,
113
+ hours: (h) => h * 60 * 60 * 1000,
114
+ days: (d) => d * 24 * 60 * 60 * 1000
115
+ }
116
+ };
117
+ // src/types.ts
118
+ var TokenType;
119
+ ((TokenType2) => {
120
+ TokenType2["NUMBER"] = "NUMBER";
121
+ TokenType2["STRING"] = "STRING";
122
+ TokenType2["IDENTIFIER"] = "IDENTIFIER";
123
+ TokenType2["PLUS"] = "PLUS";
124
+ TokenType2["MINUS"] = "MINUS";
125
+ TokenType2["STAR"] = "STAR";
126
+ TokenType2["SLASH"] = "SLASH";
127
+ TokenType2["PERCENT"] = "PERCENT";
128
+ TokenType2["CARET"] = "CARET";
129
+ TokenType2["LPAREN"] = "LPAREN";
130
+ TokenType2["RPAREN"] = "RPAREN";
131
+ TokenType2["EQUALS"] = "EQUALS";
132
+ TokenType2["COMMA"] = "COMMA";
133
+ TokenType2["EOF"] = "EOF";
134
+ })(TokenType ||= {});
135
+
136
+ // src/lexer.ts
137
+ class Lexer {
138
+ source;
139
+ position = 0;
140
+ constructor(source) {
141
+ this.source = source;
142
+ }
143
+ tokenize() {
144
+ const tokens = [];
145
+ while (true) {
146
+ const token = this.nextToken();
147
+ tokens.push(token);
148
+ if (token.type === "EOF" /* EOF */) {
149
+ break;
150
+ }
151
+ }
152
+ return tokens;
153
+ }
154
+ nextToken() {
155
+ this.skipWhitespaceAndComments();
156
+ if (this.position >= this.source.length) {
157
+ return { type: "EOF" /* EOF */, value: "", position: this.position };
158
+ }
159
+ const char = this.getCharAt(this.position);
160
+ const start = this.position;
161
+ if (this.isDigit(char)) {
162
+ return this.readNumber();
163
+ }
164
+ if (char === "'") {
165
+ return this.readString();
166
+ }
167
+ if (this.isLetter(char) || char === "_") {
168
+ return this.readIdentifier();
169
+ }
170
+ switch (char) {
171
+ case "+":
172
+ this.position++;
173
+ return { type: "PLUS" /* PLUS */, value: "+", position: start };
174
+ case "-":
175
+ this.position++;
176
+ return { type: "MINUS" /* MINUS */, value: "-", position: start };
177
+ case "*":
178
+ this.position++;
179
+ return { type: "STAR" /* STAR */, value: "*", position: start };
180
+ case "/":
181
+ this.position++;
182
+ return { type: "SLASH" /* SLASH */, value: "/", position: start };
183
+ case "%":
184
+ this.position++;
185
+ return { type: "PERCENT" /* PERCENT */, value: "%", position: start };
186
+ case "^":
187
+ this.position++;
188
+ return { type: "CARET" /* CARET */, value: "^", position: start };
189
+ case "(":
190
+ this.position++;
191
+ return { type: "LPAREN" /* LPAREN */, value: "(", position: start };
192
+ case ")":
193
+ this.position++;
194
+ return { type: "RPAREN" /* RPAREN */, value: ")", position: start };
195
+ case "=":
196
+ this.position++;
197
+ return { type: "EQUALS" /* EQUALS */, value: "=", position: start };
198
+ case ",":
199
+ this.position++;
200
+ return { type: "COMMA" /* COMMA */, value: ",", position: start };
201
+ case ";":
202
+ this.position++;
203
+ return this.nextToken();
204
+ default:
205
+ throw new Error(`Unexpected character '${char}' at position ${start}`);
206
+ }
207
+ }
208
+ skipWhitespaceAndComments() {
209
+ while (this.position < this.source.length) {
210
+ const char = this.getCharAt(this.position);
211
+ if (this.isWhitespace(char)) {
212
+ this.position++;
213
+ continue;
214
+ }
215
+ if (char === "/" && this.peek() === "/") {
216
+ while (this.position < this.source.length && this.getCharAt(this.position) !== `
217
+ `) {
218
+ this.position++;
219
+ }
220
+ continue;
221
+ }
222
+ break;
223
+ }
224
+ }
225
+ readNumber() {
226
+ const start = this.position;
227
+ let hasDecimal = false;
228
+ while (this.position < this.source.length) {
229
+ const char = this.getCharAt(this.position);
230
+ if (this.isDigit(char)) {
231
+ this.position++;
232
+ } else if (char === "." && !hasDecimal) {
233
+ hasDecimal = true;
234
+ this.position++;
235
+ } else {
236
+ break;
237
+ }
238
+ }
239
+ const value = parseFloat(this.source.slice(start, this.position));
240
+ return { type: "NUMBER" /* NUMBER */, value, position: start };
241
+ }
242
+ readIdentifier() {
243
+ const start = this.position;
244
+ while (this.position < this.source.length) {
245
+ const char = this.getCharAt(this.position);
246
+ if (this.isLetter(char) || this.isDigit(char) || char === "_") {
247
+ this.position++;
248
+ } else {
249
+ break;
250
+ }
251
+ }
252
+ const name = this.source.slice(start, this.position);
253
+ return { type: "IDENTIFIER" /* IDENTIFIER */, value: name, position: start };
254
+ }
255
+ readString() {
256
+ const start = this.position;
257
+ this.position++;
258
+ let value = "";
259
+ while (this.position < this.source.length) {
260
+ const char = this.getCharAt(this.position);
261
+ if (char === "'") {
262
+ this.position++;
263
+ break;
264
+ }
265
+ if (char === "\\" && this.position + 1 < this.source.length) {
266
+ this.position++;
267
+ const escaped = this.getCharAt(this.position);
268
+ switch (escaped) {
269
+ case "n":
270
+ value += `
271
+ `;
272
+ break;
273
+ case "t":
274
+ value += "\t";
275
+ break;
276
+ case "r":
277
+ value += "\r";
278
+ break;
279
+ case "'":
280
+ value += "'";
281
+ break;
282
+ case "\\":
283
+ value += "\\";
284
+ break;
285
+ default:
286
+ value += escaped;
287
+ }
288
+ this.position++;
289
+ } else {
290
+ value += char;
291
+ this.position++;
292
+ }
293
+ }
294
+ return { type: "STRING" /* STRING */, value, position: start };
295
+ }
296
+ getCharAt(pos) {
297
+ return pos < this.source.length ? this.source[pos] || "" : "";
298
+ }
299
+ peek() {
300
+ return this.getCharAt(this.position + 1);
301
+ }
302
+ isDigit(char) {
303
+ return char >= "0" && char <= "9";
304
+ }
305
+ isLetter(char) {
306
+ return char >= "a" && char <= "z" || char >= "A" && char <= "Z";
307
+ }
308
+ isWhitespace(char) {
309
+ return char === " " || char === "\t" || char === `
310
+ ` || char === "\r";
311
+ }
312
+ }
313
+
314
+ // src/parser.ts
315
+ class Parser {
316
+ tokens;
317
+ current = 0;
318
+ constructor(tokens) {
319
+ this.tokens = tokens;
320
+ }
321
+ parse() {
322
+ const statements = [];
323
+ while (this.peek().type !== "EOF" /* EOF */) {
324
+ statements.push(this.parseExpression(0));
325
+ }
326
+ if (statements.length === 0) {
327
+ throw new Error("Empty program");
328
+ }
329
+ if (statements.length === 1) {
330
+ const stmt = statements[0];
331
+ if (stmt === undefined) {
332
+ throw new Error("Unexpected undefined statement");
333
+ }
334
+ return stmt;
335
+ }
336
+ return {
337
+ type: "Program",
338
+ statements
339
+ };
340
+ }
341
+ parseExpression(minPrecedence) {
342
+ let left = this.parsePrefix();
343
+ while (true) {
344
+ const token = this.peek();
345
+ const precedence = this.getPrecedence(token.type);
346
+ if (precedence < minPrecedence) {
347
+ break;
348
+ }
349
+ if (token.type === "EQUALS" /* EQUALS */) {
350
+ if (left.type !== "Identifier") {
351
+ throw new Error("Invalid assignment target");
352
+ }
353
+ const identName = left.name;
354
+ this.advance();
355
+ const value = this.parseExpression(precedence + 1);
356
+ left = {
357
+ type: "Assignment",
358
+ name: identName,
359
+ value
360
+ };
361
+ } else if (this.isBinaryOperator(token.type)) {
362
+ const operator = token.value;
363
+ this.advance();
364
+ const right = this.parseExpression(precedence + 1);
365
+ left = {
366
+ type: "BinaryOp",
367
+ left,
368
+ operator,
369
+ right
370
+ };
371
+ } else {
372
+ break;
373
+ }
374
+ }
375
+ return left;
376
+ }
377
+ parsePrefix() {
378
+ const token = this.peek();
379
+ if (token.type === "MINUS" /* MINUS */) {
380
+ this.advance();
381
+ const argument = this.parseExpression(this.getUnaryPrecedence());
382
+ return {
383
+ type: "UnaryOp",
384
+ operator: "-",
385
+ argument
386
+ };
387
+ }
388
+ if (token.type === "LPAREN" /* LPAREN */) {
389
+ this.advance();
390
+ const expr = this.parseExpression(0);
391
+ if (this.peek().type !== "RPAREN" /* RPAREN */) {
392
+ throw new Error("Expected closing parenthesis");
393
+ }
394
+ this.advance();
395
+ return expr;
396
+ }
397
+ if (token.type === "NUMBER" /* NUMBER */) {
398
+ this.advance();
399
+ return {
400
+ type: "NumberLiteral",
401
+ value: token.value
402
+ };
403
+ }
404
+ if (token.type === "STRING" /* STRING */) {
405
+ this.advance();
406
+ return {
407
+ type: "StringLiteral",
408
+ value: token.value
409
+ };
410
+ }
411
+ if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
412
+ const name = token.value;
413
+ this.advance();
414
+ if (this.peek().type === "LPAREN" /* LPAREN */) {
415
+ this.advance();
416
+ const args = this.parseFunctionArguments();
417
+ if (this.peek().type !== "RPAREN" /* RPAREN */) {
418
+ throw new Error("Expected closing parenthesis");
419
+ }
420
+ this.advance();
421
+ return {
422
+ type: "FunctionCall",
423
+ name,
424
+ arguments: args
425
+ };
426
+ }
427
+ return {
428
+ type: "Identifier",
429
+ name
430
+ };
431
+ }
432
+ throw new Error(`Unexpected token: ${token.value}`);
433
+ }
434
+ parseFunctionArguments() {
435
+ const args = [];
436
+ if (this.peek().type === "RPAREN" /* RPAREN */) {
437
+ return args;
438
+ }
439
+ args.push(this.parseExpression(0));
440
+ while (this.peek().type === "COMMA" /* COMMA */) {
441
+ this.advance();
442
+ args.push(this.parseExpression(0));
443
+ }
444
+ return args;
445
+ }
446
+ getPrecedence(type) {
447
+ switch (type) {
448
+ case "EQUALS" /* EQUALS */:
449
+ return 1;
450
+ case "PLUS" /* PLUS */:
451
+ case "MINUS" /* MINUS */:
452
+ return 2;
453
+ case "STAR" /* STAR */:
454
+ case "SLASH" /* SLASH */:
455
+ case "PERCENT" /* PERCENT */:
456
+ return 3;
457
+ case "CARET" /* CARET */:
458
+ return 4;
459
+ default:
460
+ return 0;
461
+ }
462
+ }
463
+ getUnaryPrecedence() {
464
+ return 5;
465
+ }
466
+ isBinaryOperator(type) {
467
+ return type === "PLUS" /* PLUS */ || type === "MINUS" /* MINUS */ || type === "STAR" /* STAR */ || type === "SLASH" /* SLASH */ || type === "PERCENT" /* PERCENT */ || type === "CARET" /* CARET */;
468
+ }
469
+ peek() {
470
+ if (this.current >= this.tokens.length) {
471
+ return { type: "EOF" /* EOF */, value: "", position: -1 };
472
+ }
473
+ const token = this.tokens[this.current];
474
+ if (token === undefined) {
475
+ return { type: "EOF" /* EOF */, value: "", position: -1 };
476
+ }
477
+ return token;
478
+ }
479
+ advance() {
480
+ this.current++;
481
+ }
482
+ }
483
+ function parseSource(source) {
484
+ const lexer = new Lexer(source);
485
+ const tokens = lexer.tokenize();
486
+ const parser = new Parser(tokens);
487
+ return parser.parse();
488
+ }
489
+
490
+ // src/executor.ts
491
+ class Executor {
492
+ context;
493
+ variables;
494
+ constructor(context = {}) {
495
+ this.context = context;
496
+ this.variables = new Map(Object.entries(context.variables || {}));
497
+ }
498
+ execute(node) {
499
+ switch (node.type) {
500
+ case "Program":
501
+ return this.executeProgram(node);
502
+ case "NumberLiteral":
503
+ return this.executeNumberLiteral(node);
504
+ case "StringLiteral":
505
+ return this.executeStringLiteral(node);
506
+ case "Identifier":
507
+ return this.executeIdentifier(node);
508
+ case "BinaryOp":
509
+ return this.executeBinaryOp(node);
510
+ case "UnaryOp":
511
+ return this.executeUnaryOp(node);
512
+ case "FunctionCall":
513
+ return this.executeFunctionCall(node);
514
+ case "Assignment":
515
+ return this.executeAssignment(node);
516
+ default:
517
+ throw new Error(`Unknown node type`);
518
+ }
519
+ }
520
+ executeProgram(node) {
521
+ let result;
522
+ for (const statement of node.statements) {
523
+ result = this.execute(statement);
524
+ }
525
+ return result;
526
+ }
527
+ executeNumberLiteral(node) {
528
+ return node.value;
529
+ }
530
+ executeStringLiteral(node) {
531
+ return node.value;
532
+ }
533
+ executeIdentifier(node) {
534
+ const value = this.variables.get(node.name);
535
+ if (value === undefined) {
536
+ throw new Error(`Undefined variable: ${node.name}`);
537
+ }
538
+ return value;
539
+ }
540
+ executeBinaryOp(node) {
541
+ const left = this.execute(node.left);
542
+ const right = this.execute(node.right);
543
+ switch (node.operator) {
544
+ case "+":
545
+ return this.add(left, right);
546
+ case "-":
547
+ return this.subtract(left, right);
548
+ case "*":
549
+ return this.multiply(left, right);
550
+ case "/":
551
+ return this.divide(left, right);
552
+ case "%":
553
+ return this.modulo(left, right);
554
+ case "^":
555
+ return this.exponentiate(left, right);
556
+ default:
557
+ throw new Error(`Unknown operator: ${node.operator}`);
558
+ }
559
+ }
560
+ executeUnaryOp(node) {
561
+ const arg = this.execute(node.argument);
562
+ if (node.operator === "-") {
563
+ if (typeof arg === "number") {
564
+ return -arg;
565
+ }
566
+ if (arg instanceof Date) {
567
+ return new Date(-arg.getTime());
568
+ }
569
+ throw new Error(`Cannot negate ${typeof arg}`);
570
+ }
571
+ throw new Error(`Unknown unary operator: ${node.operator}`);
572
+ }
573
+ executeFunctionCall(node) {
574
+ const fn = this.context.functions?.[node.name];
575
+ if (fn === undefined) {
576
+ throw new Error(`Undefined function: ${node.name}`);
577
+ }
578
+ if (typeof fn !== "function") {
579
+ throw new Error(`${node.name} is not a function`);
580
+ }
581
+ const args = node.arguments.map((arg) => this.execute(arg));
582
+ return fn(...args);
583
+ }
584
+ executeAssignment(node) {
585
+ const value = this.execute(node.value);
586
+ if (typeof value !== "number" && !(value instanceof Date)) {
587
+ throw new Error(`Cannot assign ${typeof value} to variable. Only numbers and Dates are allowed.`);
588
+ }
589
+ this.variables.set(node.name, value);
590
+ return value;
591
+ }
592
+ add(left, right) {
593
+ if (typeof left === "number" && typeof right === "number") {
594
+ return left + right;
595
+ }
596
+ if (left instanceof Date && typeof right === "number") {
597
+ return new Date(left.getTime() + right);
598
+ }
599
+ if (typeof left === "number" && right instanceof Date) {
600
+ return new Date(right.getTime() + left);
601
+ }
602
+ if (left instanceof Date && right instanceof Date) {
603
+ return new Date(left.getTime() + right.getTime());
604
+ }
605
+ throw new Error(`Cannot add ${this.typeOf(left)} and ${this.typeOf(right)}`);
606
+ }
607
+ subtract(left, right) {
608
+ if (typeof left === "number" && typeof right === "number") {
609
+ return left - right;
610
+ }
611
+ if (left instanceof Date && typeof right === "number") {
612
+ return new Date(left.getTime() - right);
613
+ }
614
+ if (typeof left === "number" && right instanceof Date) {
615
+ return new Date(left - right.getTime());
616
+ }
617
+ if (left instanceof Date && right instanceof Date) {
618
+ return left.getTime() - right.getTime();
619
+ }
620
+ throw new Error(`Cannot subtract ${this.typeOf(right)} from ${this.typeOf(left)}`);
621
+ }
622
+ multiply(left, right) {
623
+ if (typeof left === "number" && typeof right === "number") {
624
+ return left * right;
625
+ }
626
+ throw new Error(`Cannot multiply ${this.typeOf(left)} and ${this.typeOf(right)}`);
627
+ }
628
+ divide(left, right) {
629
+ if (typeof left === "number" && typeof right === "number") {
630
+ if (right === 0) {
631
+ throw new Error("Division by zero");
632
+ }
633
+ return left / right;
634
+ }
635
+ throw new Error(`Cannot divide ${this.typeOf(left)} by ${this.typeOf(right)}`);
636
+ }
637
+ modulo(left, right) {
638
+ if (typeof left === "number" && typeof right === "number") {
639
+ if (right === 0) {
640
+ throw new Error("Division by zero");
641
+ }
642
+ return left % right;
643
+ }
644
+ throw new Error(`Cannot compute ${this.typeOf(left)} modulo ${this.typeOf(right)}`);
645
+ }
646
+ exponentiate(left, right) {
647
+ if (typeof left === "number" && typeof right === "number") {
648
+ return left ** right;
649
+ }
650
+ throw new Error(`Cannot exponentiate ${this.typeOf(left)} by ${this.typeOf(right)}`);
651
+ }
652
+ typeOf(value) {
653
+ if (value instanceof Date) {
654
+ return "Date";
655
+ }
656
+ return typeof value;
657
+ }
658
+ }
659
+ function execute(source, context) {
660
+ const ast = parseSource(source);
661
+ const executor = new Executor(context);
662
+ return executor.execute(ast);
663
+ }
664
+ export {
665
+ parseSource,
666
+ execute,
667
+ defaultContext,
668
+ exports_ast as ast,
669
+ TokenType,
670
+ Parser,
671
+ Lexer,
672
+ Executor
673
+ };