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 +73 -1
- package/dist/index.js +167 -40
- package/package.json +1 -1
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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.
|
|
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",
|