From 56f2770493414c03eb54d05b7178e745d5cf7fbc Mon Sep 17 00:00:00 2001 From: thutasann Date: Tue, 21 May 2024 23:43:28 +0800 Subject: [PATCH] feat: parse binary expressions --- README.md | 3 +- src/ast/compiler/ast.interface.ts | 2 +- src/ast/compiler/parser.ts | 87 +++++++++++++++++++++++++++++-- src/ast/main.test.ts | 2 +- src/lexer/lexer.ts | 2 +- 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4fc234c..e8bf512 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Building Custom Scripting Language -This is the repo how Custom Scripting language is created from scratch with typescript. +This is the repo where Custom Scripting language is created from scratch with typescript. ## Topics - Lexer - AST Definitions +- Parser ## Scripts diff --git a/src/ast/compiler/ast.interface.ts b/src/ast/compiler/ast.interface.ts index e4f8bf0..5736f31 100644 --- a/src/ast/compiler/ast.interface.ts +++ b/src/ast/compiler/ast.interface.ts @@ -21,7 +21,7 @@ export interface IProgram extends IStatement { body: IStatement[] } -/** Expression interface*/ +/** Expression interface that extends `IStatement` */ export interface IExpression extends IStatement {} /** Binary Expression Node Type diff --git a/src/ast/compiler/parser.ts b/src/ast/compiler/parser.ts index 5ab6477..84b479b 100644 --- a/src/ast/compiler/parser.ts +++ b/src/ast/compiler/parser.ts @@ -26,7 +26,6 @@ export class Parser { public produceAST(sourceCode: string): IProgram { const _lexer = new Lexer() this.tokens = _lexer.tokenize(sourceCode) - Logger.log('tokens -> ', this.tokens) const program: IProgram = { kind: 'Program', @@ -43,16 +42,62 @@ export class Parser { /** entry point of the parser*/ private parseSTMT(): IStatement { - // skip to parseExpr return this.parseExpr() } /** parse expression */ private parseExpr(): IExpression { - return this.parsePrimaryExpr() + return this.parseAdditiveExpr() } - /** parse primary expressions */ + /** parse additive expression + * @description
+ * - Additive Expression has left <-> hand presidence + * @example + * - (10 + 5) - 5 + * - (10 + (10 - fooBar)) - 5 + * - 10 + 5 * 3 + * - wrap with additive expression with `()` + */ + private parseAdditiveExpr(): IExpression { + let left = this.parseMultiplicitaveExpr() + + while (this.at().value == '+' || this.at().value == '-') { + const operator = this.eat().value + const right = this.parseMultiplicitaveExpr() + left = { + kind: 'BinaryExpr', + left, + right, + operator, + } as IBinaryExpression + } + + return left + } + + /** parse multiplicative expression + * @description parse `*`, `/` and `%` + */ + private parseMultiplicitaveExpr(): IExpression { + let left = this.parsePrimaryExpr() + while (this.at().value == '*' || this.at().value == '/' || this.at().value == '%') { + const operator = this.eat().value + const right = this.parseMultiplicitaveExpr() + left = { + kind: 'BinaryExpr', + left, + right, + operator, + } as IBinaryExpression + } + + return left + } + + /** ### primary parse expressions fnc + * @description this is the primary fnc of parse expression + */ private parsePrimaryExpr(): IExpression { const tokenType = this.at().type @@ -61,6 +106,14 @@ export class Parser { return { kind: 'Identifier', symbol: this.eat().value } as IIdentifierExpression case TokenType.Number: return { kind: 'NumericLiteral', value: parseFloat(this.eat().value) } as INumericLiteral + case TokenType.OpenParen: + this.eat() // eat open paren + const value = this.parseExpr() + this.expect( + TokenType.CloseParen, + 'Unexpected token found inside parenthesised expression. Expected closing parenthesis', + ) // eat close paren + return value default: console.error('Unexpected token found during parsing :', this.at()) process.exit(1) @@ -72,6 +125,17 @@ export class Parser { return this.tokens[0] } + /** expect fnc */ + private expect(type: TokenType, err: any): IToken { + const prev = this.tokens.shift() as IToken + if (!prev || prev.type == type) { + Logger.error('Parse Error:\n', err, prev, ' - Expecting: ', type) + process.exit(1) + } + + return prev + } + /** check token is EOF or not */ private notEOF(): boolean { return this.tokens[0].type !== TokenType.EOF @@ -83,3 +147,18 @@ export class Parser { return prev as IToken } } + +/** + * ## Orders of Prescidence + * - AssignmentExpr + * - MemberExpr + * - FunctionCall + * - LogicalExpr + * - ComparisionExpr + * - AdditiveExpr ✅ + * - MultiplicationExpr + * - UnaryExpr + * - PrimaryExpr + * @description This is just the Note + */ +class OrdersOfPrescidence {} diff --git a/src/ast/main.test.ts b/src/ast/main.test.ts index ed6de0b..f520a8e 100644 --- a/src/ast/main.test.ts +++ b/src/ast/main.test.ts @@ -10,7 +10,7 @@ const r1 = readline.createInterface({ /** test parser */ ;(async function repl() { const parser = new Parser() - Logger.info('\nRepl v0.1') + Logger.log('Repl v0.1') r1.question('> ', (input) => { if (!input || input.includes('exit')) { diff --git a/src/lexer/lexer.ts b/src/lexer/lexer.ts index cd46db3..3de8d63 100644 --- a/src/lexer/lexer.ts +++ b/src/lexer/lexer.ts @@ -21,7 +21,7 @@ export class Lexer { tokens.push(this.token(src.shift(), TokenType.OpenParen)) } else if (src[0] == ')') { tokens.push(this.token(src.shift(), TokenType.CloseParen)) - } else if (src[0] == '+' || src[0] == '-' || src[0] == '*' || src[0] == '/') { + } else if (src[0] == '+' || src[0] == '-' || src[0] == '*' || src[0] == '/' || src[0] == '%') { tokens.push(this.token(src.shift(), TokenType.BinaryOperator)) } else if (src[0] == '=') { tokens.push(this.token(src.shift(), TokenType.Equals))