123 lines
4.3 KiB
TypeScript
123 lines
4.3 KiB
TypeScript
class Token {
|
|
constructor(public type: string, public value: string) {}
|
|
}
|
|
|
|
class Lexer {
|
|
private position = 0;
|
|
private tokens: Token[] = [];
|
|
|
|
constructor(private input: string) {}
|
|
|
|
tokenize(): Token[] {
|
|
const regex = /([a-zA-Z_][a-zA-Z0-9_]*)|([0-9]+)|([+\-*%!@^\/\\])|([()\[\]{};,])/g;
|
|
let match;
|
|
while ((match = regex.exec(this.input)) !== null) {
|
|
if (match[1]) this.tokens.push(new Token("IDENTIFIER", match[1]));
|
|
else if (match[2]) this.tokens.push(new Token("NUMBER", match[2]));
|
|
else if (match[3]) this.tokens.push(new Token("OPERATOR", match[3]));
|
|
else if (match[4]) this.tokens.push(new Token("SYMBOL", match[4]));
|
|
}
|
|
return this.tokens;
|
|
}
|
|
}
|
|
|
|
class Parser {
|
|
private position = 0;
|
|
|
|
constructor(private tokens: Token[]) {}
|
|
|
|
parseExpressions(): any[] {
|
|
let expressions = [];
|
|
while (this.position < this.tokens.length) {
|
|
expressions.push(this.parseExpression());
|
|
}
|
|
return expressions;
|
|
}
|
|
|
|
parseExpression(): any {
|
|
let token = this.tokens[this.position];
|
|
if (!token) return null;
|
|
if (token.type === "IDENTIFIER") {
|
|
this.position++;
|
|
if (this.match("SYMBOL", "[")) {
|
|
return this.parseIndexingOrFunctionCall(token);
|
|
}
|
|
return token;
|
|
} else if (token.type === "NUMBER") {
|
|
this.position++;
|
|
return token;
|
|
} else if (token.type === "OPERATOR") {
|
|
return this.parseOperatorOrUnary();
|
|
}
|
|
throw new Error(`Unexpected token: ${token.value}`);
|
|
}
|
|
|
|
parseIndexingOrFunctionCall(identifier: Token): any {
|
|
let args = [];
|
|
this.position++; // Consume "["
|
|
while (!this.match("SYMBOL", "]") && !this.match("SYMBOL", "}")) {
|
|
let expr = this.parseExpression();
|
|
if (!expr) throw new Error("Expected expression inside indexing or function call");
|
|
args.push(expr);
|
|
if (this.match("SYMBOL", ";")) this.position++;
|
|
}
|
|
let closingSymbol = this.tokens[this.position].value;
|
|
this.position++; // Consume closing bracket or brace
|
|
return { type: closingSymbol === "]" ? "FunctionCall" : "Indexing", name: identifier.value, args };
|
|
}
|
|
|
|
parseOperatorOrUnary(): any {
|
|
let operator = this.tokens[this.position];
|
|
this.position++;
|
|
if (this.isUnaryOperator(operator.value)) {
|
|
let operand = this.parseExpression();
|
|
return { type: "UnaryExpression", operator: operator.value, operand };
|
|
}
|
|
let left = this.parseExpression();
|
|
let right = this.parseExpression();
|
|
|
|
// Reverse arguments for non-commutative operators
|
|
if (this.isNonCommutative(operator.value)) {
|
|
return { type: "BinaryExpression", operator: operator.value, left: right, right: left };
|
|
}
|
|
|
|
return { type: "BinaryExpression", operator: operator.value, left, right };
|
|
}
|
|
|
|
isUnaryOperator(op: string): boolean {
|
|
return ["!", "#", "_", "$"].includes(op);
|
|
}
|
|
|
|
isNonCommutative(op: string): boolean {
|
|
return ["-", "%", "!", "^"].includes(op);
|
|
}
|
|
|
|
generateNGNK(ast: any): string {
|
|
if (!ast) return "";
|
|
switch (ast.type) {
|
|
case "FunctionCall":
|
|
return `${ast.name}[${ast.args.map(this.generateNGNK).join(";")}]`;
|
|
case "Indexing":
|
|
return `${ast.name}[${ast.args.map(this.generateNGNK).join(";")}]`;
|
|
case "BinaryExpression":
|
|
return `${this.generateNGNK(ast.left)} ${ast.operator} ${this.generateNGNK(ast.right)}`;
|
|
case "UnaryExpression":
|
|
return `${this.generateNGNK(ast.operand)} ${ast.operator}`;
|
|
case "NUMBER":
|
|
case "IDENTIFIER":
|
|
return ast.value;
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Example usage for Deno
|
|
if (import.meta.main) {
|
|
const input = "sum[1; 2; 3] + array{5} $[ x > y ; \"greater\" ; \"smaller\" ] while[ x < 10 ; x + 1 ] 'fold /+ [1;2;3] \\load \"file.pong\" x ! 15 # 20 _ \"abc\" $ 10 - 3 8 % 2 5 ! 2 3 ^ 4";
|
|
const lexer = new Lexer(input);
|
|
const tokens = lexer.tokenize();
|
|
const parser = new Parser(tokens);
|
|
const ast = parser.parseExpressions();
|
|
console.log(ast.map(parser.generateNGNK).join("; "));
|
|
} |