littlewing 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ A minimal, high-performance arithmetic expression language with a complete lexer
8
8
  - 📦 **Tiny Bundle** - 3.61 KB gzipped, zero dependencies
9
9
  - 🌐 **Browser Ready** - 100% ESM, no Node.js APIs
10
10
  - 🔒 **Type-Safe** - Strict TypeScript with full type coverage
11
- - ✅ **Thoroughly Tested** - 71 tests, 97.66% coverage
11
+ - ✅ **Thoroughly Tested** - 106 tests, 97.66% coverage
12
12
  - 📐 **Math Expressions** - Numbers, dates, operators, functions, variables
13
13
  - 🎯 **Clean API** - Intuitive dual API (class-based + functional)
14
14
  - 📝 **Well Documented** - Complete JSDoc and examples
@@ -183,6 +183,24 @@ const ast = parseSource("2 + 3 * 4");
183
183
  // Returns: BinaryOp(+, NumberLiteral(2), BinaryOp(*, ...))
184
184
  ```
185
185
 
186
+ #### `generate(node: ASTNode): string`
187
+
188
+ Convert an AST node back to source code. Intelligently adds parentheses only when necessary to preserve semantics.
189
+
190
+ ```typescript
191
+ import { generate, ast } from "littlewing";
192
+
193
+ // From AST builders
194
+ const expr = ast.multiply(ast.add(ast.number(2), ast.number(3)), ast.number(4));
195
+ generate(expr); // → "(2 + 3) * 4"
196
+
197
+ // Round-trip: parse → generate → parse
198
+ const code = "2 + 3 * 4";
199
+ const tree = parseSource(code);
200
+ const regenerated = generate(tree); // → "2 + 3 * 4"
201
+ parseSource(regenerated); // Same AST structure
202
+ ```
203
+
186
204
  ### Classes
187
205
 
188
206
  #### `Lexer`
@@ -219,6 +237,17 @@ const executor = new Executor(context);
219
237
  const result = executor.execute(ast);
220
238
  ```
221
239
 
240
+ #### `CodeGenerator`
241
+
242
+ Convert AST nodes back to source code. Handles operator precedence and associativity automatically.
243
+
244
+ ```typescript
245
+ import { CodeGenerator } from "littlewing";
246
+
247
+ const generator = new CodeGenerator();
248
+ const code = generator.generate(ast);
249
+ ```
250
+
222
251
  ### AST Builders
223
252
 
224
253
  The `ast` namespace provides convenient functions for building AST nodes:
@@ -256,37 +285,6 @@ import { defaultContext } from "littlewing";
256
285
  (milliseconds, seconds, minutes, hours, days);
257
286
  ```
258
287
 
259
- ## Type Definitions
260
-
261
- ### ExecutionContext
262
-
263
- ```typescript
264
- interface ExecutionContext {
265
- functions?: Record<string, (...args: any[]) => number | Date>;
266
- variables?: Record<string, number | Date>;
267
- }
268
- ```
269
-
270
- ### RuntimeValue
271
-
272
- ```typescript
273
- type RuntimeValue = number | Date | unknown;
274
- ```
275
-
276
- ### ASTNode
277
-
278
- ```typescript
279
- type ASTNode =
280
- | Program
281
- | NumberLiteral
282
- | StringLiteral
283
- | Identifier
284
- | BinaryOp
285
- | UnaryOp
286
- | FunctionCall
287
- | Assignment;
288
- ```
289
-
290
288
  ## Examples
291
289
 
292
290
  ### Calculator
@@ -362,14 +360,14 @@ execute("kilometers(5)", context); // → 8.0467
362
360
 
363
361
  ### Bundle Size
364
362
 
365
- - Raw: 16.70 KB
366
- - Gzipped: **3.61 KB**
363
+ - Raw: 21.06 KB
364
+ - Gzipped: **4.26 KB**
367
365
  - Zero dependencies
368
366
 
369
367
  ### Test Coverage
370
368
 
371
- - 71 comprehensive tests
372
- - 97.66% code coverage
369
+ - 106 comprehensive tests
370
+ - 95.99% code coverage
373
371
  - All edge cases handled
374
372
 
375
373
  ## Type Safety
@@ -408,7 +406,3 @@ MIT
408
406
  ## Contributing
409
407
 
410
408
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
411
-
412
- ---
413
-
414
- Made with ❤️ by the littlewing team
package/dist/index.d.ts CHANGED
@@ -176,6 +176,67 @@ declare function exponentiate(left: ASTNode, right: ASTNode): BinaryOp;
176
176
  */
177
177
  declare function negate(argument: ASTNode): UnaryOp;
178
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
+ /**
179
240
  * Default execution context with common Math functions and date utilities
180
241
  * Users can use this as-is or spread it into their own context
181
242
  *
@@ -368,4 +429,4 @@ declare class Parser {
368
429
  * Parse source code string into AST
369
430
  */
370
431
  declare function parseSource(source: string): ASTNode;
371
- export { parseSource, isUnaryOp, isStringLiteral, isProgram, isNumberLiteral, isIdentifier, isFunctionCall, isBinaryOp, isAssignment, 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,31 +89,6 @@ function exponentiate(left, right) {
89
89
  function negate(argument) {
90
90
  return unaryOp(argument);
91
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
92
  // src/types.ts
118
93
  var TokenType;
119
94
  ((TokenType2) => {
@@ -157,6 +132,127 @@ function isProgram(node) {
157
132
  return node.type === "Program";
158
133
  }
159
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
+ }
231
+ // src/defaults.ts
232
+ var defaultContext = {
233
+ functions: {
234
+ abs: Math.abs,
235
+ ceil: Math.ceil,
236
+ floor: Math.floor,
237
+ round: Math.round,
238
+ sqrt: Math.sqrt,
239
+ min: Math.min,
240
+ max: Math.max,
241
+ sin: Math.sin,
242
+ cos: Math.cos,
243
+ tan: Math.tan,
244
+ log: Math.log,
245
+ log10: Math.log10,
246
+ exp: Math.exp,
247
+ now: () => new Date,
248
+ date: (...args) => new Date(...args),
249
+ milliseconds: (ms) => ms,
250
+ seconds: (s) => s * 1000,
251
+ minutes: (m) => m * 60 * 1000,
252
+ hours: (h) => h * 60 * 60 * 1000,
253
+ days: (d) => d * 24 * 60 * 60 * 1000
254
+ }
255
+ };
160
256
  // src/lexer.ts
161
257
  class Lexer {
162
258
  source;
@@ -692,11 +788,13 @@ export {
692
788
  isFunctionCall,
693
789
  isBinaryOp,
694
790
  isAssignment,
791
+ generate,
695
792
  execute,
696
793
  defaultContext,
697
794
  exports_ast as ast,
698
795
  TokenType,
699
796
  Parser,
700
797
  Lexer,
701
- Executor
798
+ Executor,
799
+ CodeGenerator
702
800
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlewing",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
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",