Skip to content

Commit 999637b

Browse files
Refactor enhance type safety and improve code structure
- Convert TokenType to use Symbols for better type safety - Introduce ASTNodeType enum for clearer AST node type definitions - Make TokenType and ASTNodeType immutable using Object.freeze() - Refactor Interpreter's visit method to use switch statement for better readability
1 parent e8167cc commit 999637b

File tree

1 file changed

+67
-89
lines changed

1 file changed

+67
-89
lines changed

safe-template-parser.js

Lines changed: 67 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
1-
class TokenType {
2-
static NUMBER = "NUMBER";
3-
static IDENTIFIER = "IDENTIFIER";
4-
static OPERATOR = "OPERATOR";
5-
static LEFT_PAREN = "LEFT_PAREN";
6-
static RIGHT_PAREN = "RIGHT_PAREN";
7-
static LEFT_BRACKET = "LEFT_BRACKET";
8-
static RIGHT_BRACKET = "RIGHT_BRACKET";
9-
static COMMA = "COMMA";
10-
static DOT = "DOT";
11-
static EOF = "EOF";
12-
}
1+
const TokenType = Object.freeze({
2+
NUMBER: Symbol('NUMBER'),
3+
IDENTIFIER: Symbol('IDENTIFIER'),
4+
OPERATOR: Symbol('OPERATOR'),
5+
LEFT_PAREN: Symbol('LEFT_PAREN'),
6+
RIGHT_PAREN: Symbol('RIGHT_PAREN'),
7+
LEFT_BRACKET: Symbol('LEFT_BRACKET'),
8+
RIGHT_BRACKET: Symbol('RIGHT_BRACKET'),
9+
COMMA: Symbol('COMMA'),
10+
DOT: Symbol('DOT'),
11+
EOF: Symbol('EOF')
12+
});
13+
14+
const ASTNodeType = Object.freeze({
15+
NUMBER: Symbol('NUMBER'),
16+
VARIABLE: Symbol('VARIABLE'),
17+
BINARY_OP: Symbol('BINARY_OP'),
18+
UNARY_OP: Symbol('UNARY_OP'),
19+
FUNCTION_CALL: Symbol('FUNCTION_CALL'),
20+
ARRAY_ACCESS: Symbol('ARRAY_ACCESS'),
21+
PROPERTY_ACCESS: Symbol('PROPERTY_ACCESS')
22+
});
1323

1424
class Token {
1525
constructor(type, value) {
@@ -32,25 +42,16 @@ class Lexer {
3242

3343
const char = this.input[this.position];
3444

35-
if (this.isDigit(char)) {
36-
return this.readNumber();
37-
} else if (this.isAlpha(char)) {
38-
return this.readIdentifier();
39-
} else if (this.isOperator(char)) {
40-
return new Token(TokenType.OPERATOR, this.advance());
41-
} else if (char === "(") {
42-
return new Token(TokenType.LEFT_PAREN, this.advance());
43-
} else if (char === ")") {
44-
return new Token(TokenType.RIGHT_PAREN, this.advance());
45-
} else if (char === "[") {
46-
return new Token(TokenType.LEFT_BRACKET, this.advance());
47-
} else if (char === "]") {
48-
return new Token(TokenType.RIGHT_BRACKET, this.advance());
49-
} else if (char === ",") {
50-
return new Token(TokenType.COMMA, this.advance());
51-
} else if (char === ".") {
52-
return new Token(TokenType.DOT, this.advance());
53-
}
45+
if (this.isDigit(char)) {return this.readNumber();}
46+
else if (this.isAlpha(char)) {return this.readIdentifier();}
47+
else if (this.isOperator(char)) {return new Token(TokenType.OPERATOR, this.advance());}
48+
else if (char === "(") {return new Token(TokenType.LEFT_PAREN, this.advance());}
49+
else if (char === ")") {return new Token(TokenType.RIGHT_PAREN, this.advance());}
50+
else if (char === "[") {return new Token(TokenType.LEFT_BRACKET, this.advance());}
51+
else if (char === "]") {return new Token(TokenType.RIGHT_BRACKET, this.advance());}
52+
else if (char === ",") {return new Token(TokenType.COMMA, this.advance());}
53+
else if (char === ".") {return new Token(TokenType.DOT, this.advance());
54+
}
5455

5556
throw new Error(`Unexpected character: ${char}`);
5657
}
@@ -118,7 +119,7 @@ class Parser {
118119
while (this.currentToken.type === TokenType.OPERATOR && (this.currentToken.value === "+" || this.currentToken.value === "-")) {
119120
const token = this.currentToken;
120121
this.eat(TokenType.OPERATOR);
121-
node = { type: "BinaryOp", left: node, op: token.value, right: this.multiplicative() };
122+
node = { type: ASTNodeType.BINARY_OP, left: node, op: token.value, right: this.multiplicative() };
122123
}
123124

124125
return node;
@@ -130,7 +131,7 @@ class Parser {
130131
while (this.currentToken.type === TokenType.OPERATOR && (this.currentToken.value === "*" || this.currentToken.value === "/" || this.currentToken.value === "%")) {
131132
const token = this.currentToken;
132133
this.eat(TokenType.OPERATOR);
133-
node = { type: "BinaryOp", left: node, op: token.value, right: this.exponential() };
134+
node = { type: ASTNodeType.BINARY_OP, left: node, op: token.value, right: this.exponential() };
134135
}
135136

136137
return node;
@@ -142,7 +143,7 @@ class Parser {
142143
while (this.currentToken.type === TokenType.OPERATOR && this.currentToken.value === "^") {
143144
const token = this.currentToken;
144145
this.eat(TokenType.OPERATOR);
145-
node = { type: "BinaryOp", left: node, op: token.value, right: this.unary() };
146+
node = { type: ASTNodeType.BINARY_OP, left: node, op: token.value, right: this.unary() };
146147
}
147148

148149
return node;
@@ -152,7 +153,7 @@ class Parser {
152153
if (this.currentToken.type === TokenType.OPERATOR && (this.currentToken.value === "+" || this.currentToken.value === "-")) {
153154
const token = this.currentToken;
154155
this.eat(TokenType.OPERATOR);
155-
return { type: "UnaryOp", op: token.value, expr: this.unary() };
156+
return { type: ASTNodeType.UNARY_OP, op: token.value, expr: this.unary() };
156157
}
157158
return this.primary();
158159
}
@@ -161,7 +162,7 @@ class Parser {
161162
if (this.currentToken.type === TokenType.NUMBER) {
162163
const token = this.currentToken;
163164
this.eat(TokenType.NUMBER);
164-
return { type: "Number", value: token.value };
165+
return { type: ASTNodeType.NUMBER, value: token.value };
165166
} else if (this.currentToken.type === TokenType.IDENTIFIER) {
166167
return this.variable();
167168
} else if (this.currentToken.type === TokenType.LEFT_PAREN) {
@@ -175,7 +176,7 @@ class Parser {
175176
}
176177

177178
variable() {
178-
let node = { type: "Variable", name: this.currentToken.value };
179+
let node = { type: ASTNodeType.VARIABLE, name: this.currentToken.value };
179180
this.eat(TokenType.IDENTIFIER);
180181

181182
if (this.currentToken.type === TokenType.LEFT_PAREN) {
@@ -194,7 +195,7 @@ class Parser {
194195
throw new Error("Expected property name after '.'");
195196
}
196197
node = {
197-
type: "PropertyAccess",
198+
type: ASTNodeType.PROPERTY_ACCESS,
198199
object: node,
199200
property: this.currentToken.value
200201
};
@@ -209,14 +210,14 @@ class Parser {
209210
this.eat(TokenType.LEFT_PAREN);
210211
const args = this.argumentList();
211212
this.eat(TokenType.RIGHT_PAREN);
212-
return { type: "FunctionCall", name: name, arguments: args };
213+
return { type: ASTNodeType.FUNCTION_CALL, name: name, arguments: args };
213214
}
214215

215216
arrayAccess(node) {
216217
this.eat(TokenType.LEFT_BRACKET);
217218
const index = this.expression();
218219
this.eat(TokenType.RIGHT_BRACKET);
219-
return { type: "ArrayAccess", array: node, index: index };
220+
return { type: ASTNodeType.ARRAY_ACCESS, array: node, index: index };
220221
}
221222

222223
argumentList() {
@@ -251,22 +252,16 @@ class Interpreter {
251252
}
252253

253254
visit(node) {
254-
if (node.type === "Number") {
255-
return node.value;
256-
} else if (node.type === "Variable") {
257-
return this.getValueFromData(node.name, this.data);
258-
} else if (node.type === "BinaryOp") {
259-
return this.visitBinaryOp(node);
260-
} else if (node.type === "UnaryOp") {
261-
return this.visitUnaryOp(node);
262-
} else if (node.type === "FunctionCall") {
263-
return this.visitFunctionCall(node);
264-
} else if (node.type === "ArrayAccess") {
265-
return this.visitArrayAccess(node);
266-
} else if (node.type === "PropertyAccess") {
267-
return this.visitPropertyAccess(node);
255+
switch (node.type) {
256+
case ASTNodeType.NUMBER: return node.value;
257+
case ASTNodeType.VARIABLE: return this.getValueFromData(node.name, this.data);
258+
case ASTNodeType.BINARY_OP: return this.visitBinaryOp(node);
259+
case ASTNodeType.UNARY_OP: return this.visitUnaryOp(node);
260+
case ASTNodeType.FUNCTION_CALL: return this.visitFunctionCall(node);
261+
case ASTNodeType.ARRAY_ACCESS: return this.visitArrayAccess(node);
262+
case ASTNodeType.PROPERTY_ACCESS: return this.visitPropertyAccess(node);
263+
default: throw new Error(`Unknown node type: ${node.type}`);
268264
}
269-
throw new Error(`Unknown node type: ${node.type}`);
270265
}
271266

272267
visitArrayAccess(node) {
@@ -293,52 +288,35 @@ class Interpreter {
293288
const left = this.visit(node.left);
294289
const right = this.visit(node.right);
295290
switch (node.op) {
296-
case "+":
297-
return left + right;
298-
case "-":
299-
return left - right;
300-
case "*":
301-
return left * right;
302-
case "/":
303-
return left / right;
304-
case "%":
305-
return left % right;
306-
case "^":
307-
return Math.pow(left, right);
308-
default:
309-
throw new Error(`Unknown operator: ${node.op}`);
291+
case "+": return left + right;
292+
case "-": return left - right;
293+
case "*": return left * right;
294+
case "/": return left / right;
295+
case "%": return left % right;
296+
case "^": return Math.pow(left, right);
297+
default: throw new Error(`Unknown operator: ${node.op}`);
310298
}
311299
}
312300

313301
visitUnaryOp(node) {
314302
const expr = this.visit(node.expr);
315303
switch (node.op) {
316-
case "+":
317-
return +expr;
318-
case "-":
319-
return -expr;
320-
default:
321-
throw new Error(`Unknown unary operator: ${node.op}`);
304+
case "+": return +expr;
305+
case "-": return -expr;
306+
default: throw new Error(`Unknown unary operator: ${node.op}`);
322307
}
323308
}
324309

325310
visitFunctionCall(node) {
326311
const args = node.arguments.map((arg) => this.visit(arg));
327312
switch (node.name.toLowerCase()) {
328-
case "min":
329-
return Math.min(...args);
330-
case "max":
331-
return Math.max(...args);
332-
case "abs":
333-
return Math.abs(args[0]);
334-
case "round":
335-
return Math.round(args[0]);
336-
case "floor":
337-
return Math.floor(args[0]);
338-
case "ceil":
339-
return Math.ceil(args[0]);
340-
default:
341-
throw new Error(`Unknown function: ${node.name}`);
313+
case "min": return Math.min(...args);
314+
case "max": return Math.max(...args);
315+
case "abs": return Math.abs(args[0]);
316+
case "round": return Math.round(args[0]);
317+
case "floor": return Math.floor(args[0]);
318+
case "ceil": return Math.ceil(args[0]);
319+
default: throw new Error(`Unknown function: ${node.name}`);
342320
}
343321
}
344322

0 commit comments

Comments
 (0)