littlewing 0.1.0 → 0.3.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.d.ts CHANGED
@@ -107,6 +107,17 @@ interface Assignment {
107
107
  value: ASTNode;
108
108
  }
109
109
  /**
110
+ * Type guard functions for discriminated union narrowing
111
+ */
112
+ declare function isNumberLiteral(node: ASTNode): node is NumberLiteral;
113
+ declare function isStringLiteral(node: ASTNode): node is StringLiteral;
114
+ declare function isIdentifier(node: ASTNode): node is Identifier;
115
+ declare function isBinaryOp(node: ASTNode): node is BinaryOp;
116
+ declare function isUnaryOp(node: ASTNode): node is UnaryOp;
117
+ declare function isFunctionCall(node: ASTNode): node is FunctionCall;
118
+ declare function isAssignment(node: ASTNode): node is Assignment;
119
+ declare function isProgram(node: ASTNode): node is Program;
120
+ /**
110
121
  * Builder functions for creating AST nodes manually
111
122
  */
112
123
  /**
@@ -165,6 +176,67 @@ declare function exponentiate(left: ASTNode, right: ASTNode): BinaryOp;
165
176
  */
166
177
  declare function negate(argument: ASTNode): UnaryOp;
167
178
  /**
179
+ * CodeGenerator - converts AST nodes back to source code
180
+ */
181
+ declare class CodeGenerator {
182
+ /**
183
+ * Generate source code from an AST node
184
+ */
185
+ generate(node: ASTNode): string;
186
+ /**
187
+ * Generate code for a program (multiple statements)
188
+ */
189
+ private generateProgram;
190
+ /**
191
+ * Generate code for a number literal
192
+ */
193
+ private generateNumberLiteral;
194
+ /**
195
+ * Generate code for a string literal
196
+ */
197
+ private generateStringLiteral;
198
+ /**
199
+ * Generate code for an identifier
200
+ */
201
+ private generateIdentifier;
202
+ /**
203
+ * Generate code for a binary operation
204
+ */
205
+ private generateBinaryOp;
206
+ /**
207
+ * Generate code for a unary operation
208
+ */
209
+ private generateUnaryOp;
210
+ /**
211
+ * Generate code for a function call
212
+ */
213
+ private generateFunctionCall;
214
+ /**
215
+ * Generate code for a variable assignment
216
+ */
217
+ private generateAssignment;
218
+ /**
219
+ * Check if left operand needs parentheses based on operator precedence
220
+ * - For left-associative operators: parens only if strictly lower precedence
221
+ * - For right-associative operators (^): parens if lower or equal precedence
222
+ */
223
+ private needsParensLeft;
224
+ /**
225
+ * Check if right operand needs parentheses based on operator precedence and associativity
226
+ * - For right-associative operators (^): parens if strictly lower precedence
227
+ * - For left-associative operators: parens if lower or equal precedence
228
+ */
229
+ private needsParensRight;
230
+ /**
231
+ * Get precedence of an operator (higher number = higher precedence)
232
+ */
233
+ private getPrecedence;
234
+ }
235
+ /**
236
+ * Generate source code from an AST node
237
+ */
238
+ declare function generate(node: ASTNode): string;
239
+ /**
168
240
  * Default execution context with common Math functions and date utilities
169
241
  * Users can use this as-is or spread it into their own context
170
242
  *
@@ -357,4 +429,4 @@ declare class Parser {
357
429
  * Parse source code string into AST
358
430
  */
359
431
  declare function parseSource(source: string): ASTNode;
360
- export { parseSource, execute, defaultContext, exports_ast as ast, TokenType, Token, RuntimeValue, Parser, Lexer, Executor, ExecutionContext, ASTNode };
432
+ export { parseSource, isUnaryOp, isStringLiteral, isProgram, isNumberLiteral, isIdentifier, isFunctionCall, isBinaryOp, isAssignment, generate, execute, defaultContext, exports_ast as ast, TokenType, Token, RuntimeValue, Parser, Lexer, Executor, ExecutionContext, CodeGenerator, ASTNode };
package/dist/index.js CHANGED
@@ -89,6 +89,145 @@ function exponentiate(left, right) {
89
89
  function negate(argument) {
90
90
  return unaryOp(argument);
91
91
  }
92
+ // src/types.ts
93
+ var TokenType;
94
+ ((TokenType2) => {
95
+ TokenType2["NUMBER"] = "NUMBER";
96
+ TokenType2["STRING"] = "STRING";
97
+ TokenType2["IDENTIFIER"] = "IDENTIFIER";
98
+ TokenType2["PLUS"] = "PLUS";
99
+ TokenType2["MINUS"] = "MINUS";
100
+ TokenType2["STAR"] = "STAR";
101
+ TokenType2["SLASH"] = "SLASH";
102
+ TokenType2["PERCENT"] = "PERCENT";
103
+ TokenType2["CARET"] = "CARET";
104
+ TokenType2["LPAREN"] = "LPAREN";
105
+ TokenType2["RPAREN"] = "RPAREN";
106
+ TokenType2["EQUALS"] = "EQUALS";
107
+ TokenType2["COMMA"] = "COMMA";
108
+ TokenType2["EOF"] = "EOF";
109
+ })(TokenType ||= {});
110
+ function isNumberLiteral(node) {
111
+ return node.type === "NumberLiteral";
112
+ }
113
+ function isStringLiteral(node) {
114
+ return node.type === "StringLiteral";
115
+ }
116
+ function isIdentifier(node) {
117
+ return node.type === "Identifier";
118
+ }
119
+ function isBinaryOp(node) {
120
+ return node.type === "BinaryOp";
121
+ }
122
+ function isUnaryOp(node) {
123
+ return node.type === "UnaryOp";
124
+ }
125
+ function isFunctionCall(node) {
126
+ return node.type === "FunctionCall";
127
+ }
128
+ function isAssignment(node) {
129
+ return node.type === "Assignment";
130
+ }
131
+ function isProgram(node) {
132
+ return node.type === "Program";
133
+ }
134
+
135
+ // src/codegen.ts
136
+ class CodeGenerator {
137
+ generate(node) {
138
+ if (isProgram(node))
139
+ return this.generateProgram(node);
140
+ if (isNumberLiteral(node))
141
+ return this.generateNumberLiteral(node);
142
+ if (isStringLiteral(node))
143
+ return this.generateStringLiteral(node);
144
+ if (isIdentifier(node))
145
+ return this.generateIdentifier(node);
146
+ if (isBinaryOp(node))
147
+ return this.generateBinaryOp(node);
148
+ if (isUnaryOp(node))
149
+ return this.generateUnaryOp(node);
150
+ if (isFunctionCall(node))
151
+ return this.generateFunctionCall(node);
152
+ if (isAssignment(node))
153
+ return this.generateAssignment(node);
154
+ throw new Error(`Unknown node type`);
155
+ }
156
+ generateProgram(node) {
157
+ return node.statements.map((stmt) => this.generate(stmt)).join("; ");
158
+ }
159
+ generateNumberLiteral(node) {
160
+ return String(node.value);
161
+ }
162
+ generateStringLiteral(node) {
163
+ return `'${node.value}'`;
164
+ }
165
+ generateIdentifier(node) {
166
+ return node.name;
167
+ }
168
+ generateBinaryOp(node) {
169
+ const left = this.generate(node.left);
170
+ const right = this.generate(node.right);
171
+ const leftNeedsParens = this.needsParensLeft(node.left, node.operator);
172
+ const leftCode = leftNeedsParens ? `(${left})` : left;
173
+ const rightNeedsParens = this.needsParensRight(node.right, node.operator);
174
+ const rightCode = rightNeedsParens ? `(${right})` : right;
175
+ return `${leftCode} ${node.operator} ${rightCode}`;
176
+ }
177
+ generateUnaryOp(node) {
178
+ const arg = this.generate(node.argument);
179
+ const needsParens = isBinaryOp(node.argument) || isAssignment(node.argument);
180
+ const argCode = needsParens ? `(${arg})` : arg;
181
+ return `-${argCode}`;
182
+ }
183
+ generateFunctionCall(node) {
184
+ const args = node.arguments.map((arg) => this.generate(arg)).join(", ");
185
+ return `${node.name}(${args})`;
186
+ }
187
+ generateAssignment(node) {
188
+ const value = this.generate(node.value);
189
+ return `${node.name} = ${value}`;
190
+ }
191
+ needsParensLeft(node, operator) {
192
+ if (!isBinaryOp(node))
193
+ return false;
194
+ const nodePrecedence = this.getPrecedence(node.operator);
195
+ const operatorPrecedence = this.getPrecedence(operator);
196
+ if (operator === "^") {
197
+ return nodePrecedence <= operatorPrecedence;
198
+ }
199
+ return nodePrecedence < operatorPrecedence;
200
+ }
201
+ needsParensRight(node, operator) {
202
+ if (!isBinaryOp(node))
203
+ return false;
204
+ const nodePrecedence = this.getPrecedence(node.operator);
205
+ const operatorPrecedence = this.getPrecedence(operator);
206
+ if (operator === "^") {
207
+ return nodePrecedence < operatorPrecedence;
208
+ }
209
+ return nodePrecedence <= operatorPrecedence;
210
+ }
211
+ getPrecedence(operator) {
212
+ switch (operator) {
213
+ case "^":
214
+ return 4;
215
+ case "*":
216
+ case "/":
217
+ case "%":
218
+ return 3;
219
+ case "+":
220
+ case "-":
221
+ return 2;
222
+ default:
223
+ return 0;
224
+ }
225
+ }
226
+ }
227
+ function generate(node) {
228
+ const generator = new CodeGenerator;
229
+ return generator.generate(node);
230
+ }
92
231
  // src/defaults.ts
93
232
  var defaultContext = {
94
233
  functions: {
@@ -114,25 +253,6 @@ var defaultContext = {
114
253
  days: (d) => d * 24 * 60 * 60 * 1000
115
254
  }
116
255
  };
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
256
  // src/lexer.ts
137
257
  class Lexer {
138
258
  source;
@@ -496,26 +616,23 @@ class Executor {
496
616
  this.variables = new Map(Object.entries(context.variables || {}));
497
617
  }
498
618
  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
- }
619
+ if (isProgram(node))
620
+ return this.executeProgram(node);
621
+ if (isNumberLiteral(node))
622
+ return this.executeNumberLiteral(node);
623
+ if (isStringLiteral(node))
624
+ return this.executeStringLiteral(node);
625
+ if (isIdentifier(node))
626
+ return this.executeIdentifier(node);
627
+ if (isBinaryOp(node))
628
+ return this.executeBinaryOp(node);
629
+ if (isUnaryOp(node))
630
+ return this.executeUnaryOp(node);
631
+ if (isFunctionCall(node))
632
+ return this.executeFunctionCall(node);
633
+ if (isAssignment(node))
634
+ return this.executeAssignment(node);
635
+ throw new Error(`Unknown node type`);
519
636
  }
520
637
  executeProgram(node) {
521
638
  let result;
@@ -663,11 +780,21 @@ function execute(source, context) {
663
780
  }
664
781
  export {
665
782
  parseSource,
783
+ isUnaryOp,
784
+ isStringLiteral,
785
+ isProgram,
786
+ isNumberLiteral,
787
+ isIdentifier,
788
+ isFunctionCall,
789
+ isBinaryOp,
790
+ isAssignment,
791
+ generate,
666
792
  execute,
667
793
  defaultContext,
668
794
  exports_ast as ast,
669
795
  TokenType,
670
796
  Parser,
671
797
  Lexer,
672
- Executor
798
+ Executor,
799
+ CodeGenerator
673
800
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlewing",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "A minimal, high-performance arithmetic expression language with lexer, parser, and executor. Optimized for browsers with zero dependencies and type-safe execution.",
5
5
  "keywords": [
6
6
  "arithmetic",