diff options
author | WlodekM <[email protected]> | 2025-03-31 19:27:55 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2025-03-31 19:27:55 +0300 |
commit | ef4e8c20719822eebd6318a878cc37902c2b85a5 (patch) | |
tree | c80cc67921c8b511f5a50ec68834b5b28deb05a1 /the_e_programming_language/ast.ts |
pc thing
Diffstat (limited to 'the_e_programming_language/ast.ts')
-rw-r--r-- | the_e_programming_language/ast.ts | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/the_e_programming_language/ast.ts b/the_e_programming_language/ast.ts new file mode 100644 index 0000000..80210d8 --- /dev/null +++ b/the_e_programming_language/ast.ts @@ -0,0 +1,357 @@ +import { Token, TokenType } from "./tokenizer.ts"; + +export interface ASTNode { + type: string; +} + +export interface VariableDeclarationNode extends ASTNode { + type: "VariableDeclaration"; + identifier: string; + value: ASTNode; + vtype: string; + length: number; +} + +export interface FunctionDeclarationNode extends ASTNode { + type: "FunctionDeclaration"; + name: string; + // params: string[]; + body: ASTNode[]; +} + +export interface AssignmentNode extends ASTNode { + type: "Assignment"; + identifier: IdentifierNode; + value: ASTNode; +} + +export interface BinaryExpressionNode extends ASTNode { + type: "BinaryExpression"; + operator: string; + left: ASTNode; + right: ASTNode; +} + +export interface LiteralNode extends ASTNode { + type: "Literal"; + value: string; +} + +export interface NumberNode extends ASTNode { + type: "Number"; + value: number; +} + +export interface IdentifierNode extends ASTNode { + type: "Identifier"; + name: string; + offset?: ASTNode; +} + +export interface FunctionCallNode extends ASTNode { + type: "FunctionCall"; + identifier: string; + args: ASTNode[]; +} + +// export interface BranchFunctionCallNode extends ASTNode { +// type: "BranchFunctionCall"; +// identifier: string; +// args: ASTNode[]; +// branches: ASTNode[][]; +// } + +// export interface StartBlockNode extends ASTNode { +// type: "StartBlock"; +// body: ASTNode[]; +// } + +export interface IfNode extends ASTNode { + type: "If"; + condition: ASTNode; + thenBranch: ASTNode[]; + elseBranch?: ASTNode[]; +} + +export interface WhileNode extends ASTNode { + type: "While"; + condition: ASTNode; + branch: ASTNode[]; +} + +// export interface ForNode extends ASTNode { +// type: "For"; +// times: ASTNode; +// varname: ASTNode; +// branch: ASTNode[]; +// } + +// export interface GreenFlagNode extends ASTNode { +// type: "GreenFlag"; +// branch: ASTNode[]; +// } + +// use 1 or 0 for boolean +// export interface BooleanNode extends ASTNode { +// type: "Boolean"; +// value: boolean; +// } + +// export interface IncludeNode extends ASTNode { +// type: "Include"; +// itype: string; +// path: string; +// } + +// export interface ListDeclarationNode extends ASTNode { +// type: "ListDeclaration"; +// identifier: string; +// value: ASTNode[]; +// vtype: 'list' | 'global' +// } + +export default class AST { + private tokens: Token[]; + position: number = 0; + + constructor(tokens: Token[]) { + this.tokens = tokens; + } + + private peek(ahead = 0): Token { + return this.tokens[this.position + ahead]; + } + + private advance(): Token { + return this.tokens[this.position++]; + } + + private match(...types: TokenType[]): boolean { + if (types.includes(this.peek().type)) { + this.advance(); + return true; + } + return false; + } + + private matchTk(types: TokenType[], token = this.peek()): boolean { + if (types.includes(token.type)) { + return true; + } + return false; + } + + private expect(type: TokenType, errorMessage: string): Token { + if (this.peek().type === type) { + return this.advance(); + } + console.error('trace: tokens', this.tokens, '\nIDX:', this.position); + throw new Error(errorMessage); + } + + parse(): ASTNode[] { + const nodes: ASTNode[] = []; + while (this.peek().type !== TokenType.EOF) { + nodes.push(this.parseStatement()); + } + return nodes; + } + + private parseStatement(): ASTNode { + if (this.matchTk([TokenType.TYPE])) { + const type = this.advance().value + let len = 1; + if (this.match(TokenType.LBRACKET)) { + len = Number(this.expect(TokenType.NUMBER, 'expected number after [').value); + this.expect(TokenType.RBRACKET, 'expected ] after length') + } + const identifier = this.expect(TokenType.IDENTIFIER, "expected var name after type (hint: functions dont have return types yet").value; + this.expect(TokenType.ASSIGN, "expected = after var name"); + const value = this.parseAssignment(false); + return { type: "VariableDeclaration", identifier, value, vtype: type, length: len } as VariableDeclarationNode; + } + + if (this.match(TokenType.FN_DECL)) { + const name = this.expect(TokenType.IDENTIFIER, "expected function name after fn").value; + // this.expect(TokenType.LPAREN, "Expected '(' after function name"); + // const params: string[] = []; + // if (!this.match(TokenType.RPAREN)) { + // do { + // params.push(this.expect(TokenType.IDENTIFIER, "Expected parameter name").value); + // } while (this.match(TokenType.COMMA)); + // this.expect(TokenType.RPAREN, "Expected ')' after parameters"); + // } + this.expect(TokenType.LBRACE, "expected '{' before function body"); + const body = this.parseBlock(); + return { type: "FunctionDeclaration", name, body } as FunctionDeclarationNode; + } + + if (this.match(TokenType.IF)) { + this.expect(TokenType.LPAREN, "Expected '(' after 'if'"); + const condition = this.parseAssignment(); + this.expect(TokenType.RPAREN, "Expected ')' after if condition"); + this.expect(TokenType.LBRACE, "Expected '{' after if condition"); + const thenBranch = this.parseBlock(); + let elseBranch: ASTNode[] | undefined; + if (this.match(TokenType.ELSE)) { + this.expect(TokenType.LBRACE, "Expected '{' after 'else'"); + elseBranch = this.parseBlock(); + } + return { type: "If", condition, thenBranch, elseBranch } as IfNode; + } + + if (this.match(TokenType.WHILE)) { + this.expect(TokenType.LPAREN, "Expected '(' after 'while'"); + const condition = this.parseAssignment(); + this.expect(TokenType.RPAREN, "Expected ')' after while condition"); + this.expect(TokenType.LBRACE, "Expected '{' after while condition"); + const branch = this.parseBlock(); + return { type: "While", condition, branch } as WhileNode; + } + + // if (this.match(TokenType.FOR)) { + // this.expect(TokenType.LPAREN, "Expected '(' after 'for'"); + // const varname = this.parseAssignment(); + // const of = this.expect(TokenType.IDENTIFIER, 'expected of'); + // if (of.value !== 'of') throw new Error('expected of'); + // const times = this.parseAssignment(); + // this.expect(TokenType.RPAREN, "Expected ')' after for"); + // this.expect(TokenType.LBRACE, "Expected '{' after for"); + // const branch = this.parseBlock(); + + // return { type: "For", varname, times, branch } as ForNode; + // } + + // if (this.match(TokenType.GREENFLAG)) { + // this.expect(TokenType.LBRACE, "Expected '{' after greenflag"); + // const branch = this.parseBlock(); + + // return { type: "GreenFlag", branch } as GreenFlagNode; + // } + + return this.parseAssignment(); + } + + private parseBlock(): ASTNode[] { + const nodes: ASTNode[] = []; + + while (!this.match(TokenType.RBRACE)) { + nodes.push(this.parseStatement()); + } + + return nodes; + } + + private parseAssignment(allowStuff = true): ASTNode { + + const expr = this.parseBinaryExpression(allowStuff); + if (this.match(TokenType.ASSIGN)) { + if (expr.type !== "Identifier") + throw new Error("invalid assignment target; expected an identifier"); + const value = allowStuff ? this.parseAssignment() : this.parsePrimary(false); + // let offset = undefined; + // if (this.match(TokenType.LBRACKET)) { + // offset = this.parseAssignment(); + // this.expect(TokenType.RBRACKET, 'expected ]') + // } + return { type: "Assignment", identifier: (expr as IdentifierNode), value } as AssignmentNode; + } + return expr; + } + + private parseBinaryExpression(allowStuff = false): ASTNode { + let left = this.parseCall(allowStuff); + + while (this.peek().type === TokenType.BINOP) { + const operator = this.advance().value; + const right = this.parseCall(); + left = { type: "BinaryExpression", operator, left, right } as BinaryExpressionNode; + } + return left; + } + + private parseCall(allowStuff = false): ASTNode { + let expr = this.parsePrimary(allowStuff); + + while (this.peek().type === TokenType.LPAREN) { + expr = this.finishCall(expr); + } + return expr; + } + + private finishCall(callee: ASTNode): ASTNode { + this.expect(TokenType.LPAREN, "Expected '(' after function name"); + //TODO - arguments + // const args: ASTNode[] = []; + // if (this.peek().type !== TokenType.RPAREN) { + // do { + // args.push(this.parseAssignment()); + // } while (this.match(TokenType.COMMA)); + // } + this.expect(TokenType.RPAREN, "Expected ')' after arguments"); + + + // if (this.peek().type === TokenType.LBRACE) { + // const branches: ASTNode[][] = []; + // do { + // this.expect(TokenType.LBRACE, "Expected '{' for branch block"); + // branches.push(this.parseBlock()); + // } while (this.peek().type === TokenType.LBRACE); + + // if (callee.type !== "Identifier") + // throw new Error("Branch function call expects an identifier"); + // return { + // type: "BranchFunctionCall", + // identifier: (callee as IdentifierNode).name, + // args, + // branches, + // } as BranchFunctionCallNode; + // } + + + if (callee.type !== "Identifier") + throw new Error("Function call expects an identifier"); + return { + type: "FunctionCall", + identifier: (callee as IdentifierNode).name, + // args, + } as FunctionCallNode; + } + + private parsePrimary(allowOther = true): ASTNode { + const token = this.peek(); + + if (this.match(TokenType.NUMBER)) { + return { type: "Number", value: Number(token.value) } as NumberNode; + } + + if (this.match(TokenType.LITERAL)) { + return { type: "Literal", value: token.value } as LiteralNode; + } + + if (this.match(TokenType.IDENTIFIER) && allowOther) { + + // if (["True", "true", "False", "false"].includes(token.value)) { + // return { + // type: "Boolean", + // value: token.value === "True" || token.value === "true" + // } as BooleanNode; + // } + let offset = undefined; + if (this.match(TokenType.LBRACKET)) { + offset = this.parseAssignment(); + this.expect(TokenType.RBRACKET, 'expected ]') + } + return { type: "Identifier", name: token.value, offset } as IdentifierNode; + } + + if (this.match(TokenType.LPAREN) && allowOther) { + const expr = this.parseAssignment(); + this.expect(TokenType.RPAREN, "Expected ')' after expression"); + return expr; + } + + throw new Error(`Unexpected token: ${token.type}`); + } + +} \ No newline at end of file |