From d373bceb703cafc5057acc4abf35012bfd62617b Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Wed, 31 Jan 2024 19:52:48 +0200 Subject: [PATCH 1/8] feat: producing kin's ast --- src/main.ts | 54 -------------------------------------------- src/parser/parser.ts | 5 ++++ 2 files changed, 5 insertions(+), 54 deletions(-) delete mode 100644 src/main.ts diff --git a/src/main.ts b/src/main.ts deleted file mode 100644 index a09e3aa..0000000 --- a/src/main.ts +++ /dev/null @@ -1,54 +0,0 @@ -/********************************************** - * Kin Programming Language * - * * - * Author: Copyright (c) MURANGWA Pacifique * - * and affiliates. * - * Description: Write computer programs in * - * Kinyarwanda. * - * License: Apache License 2.0 * - *********************************************/ -import Lexer from './lexer/lexer'; -import { LogError, LogMessage } from './utils/log'; - -import * as readline from 'readline/promises'; -import { readFileSync } from 'fs'; - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, -}); - -const file = process.argv[2]; - -if (file) { - run(file); -} else { - repl(); -} - -async function run(filename: string): Promise { - const input: string = readFileSync(filename, 'utf-8'); - const lexer = new Lexer(input); - LogMessage(lexer.tokenize()); -} - -async function repl() { - LogMessage('Kin Repl v0.0'); - - while (true) { - const input = await rl.question('> '); - - // check for no user input or exit keyword. - if (input === '.exit' || input === '.quit' || input === '.q') { - process.exit(1); - } - - try { - const lexer = new Lexer(input); - LogMessage(lexer.tokenize()); - } catch (error: unknown) { - const err = error as Error; - LogError(err.message); - } - } -} diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 5d3796a..d1b5ebd 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,3 +1,8 @@ +/**************************************** + * Parser * + * Product Kin's AST * + ****************************************/ + import Lexer, { Token } from '../lexer/lexer'; import TokenType from '../lexer/tokens'; import { LogError } from '../utils/log'; From 4a32370895859d7917c220ce54d56d00fed2f058 Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Wed, 31 Jan 2024 22:02:47 +0200 Subject: [PATCH 2/8] feat: parsing statements in block of codes --- src/parser/parser.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/parser/parser.ts b/src/parser/parser.ts index d1b5ebd..c3d5fcf 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -72,6 +72,24 @@ export default class Parser { } } + private parse_block_statement(): Stmt[] { + this.expect( + TokenType.OPEN_CURLY_BRACES, + `"Expected { while parsing code block"`, + ); + const body: Stmt[] = []; + while (this.not_eof() && this.at().type != TokenType.CLOSE_CURLY_BRACES) { + body.push(this.parse_stmt()); + } + + this.expect( + TokenType.CLOSE_CURLY_BRACES, + `"Expected } while parsing code block"`, + ); + + return body; + } + private parse_var_declaration(): Stmt { const isConstant = this.eat().type == TokenType.NTAHINDUKA; const identifier = this.expect( From 4504ebac4f7694c50c21620e473c5d221196aa75 Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Thu, 1 Feb 2024 21:16:57 +0200 Subject: [PATCH 3/8] feat: parsing conditional statements --- .gitignore | 4 ++++ src/parser/parser.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/.gitignore b/.gitignore index 858afed..d2ea176 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,7 @@ coverage/ # npm *.npm-debug.log npm-debug.log + + +# Custom +local.todo.md \ No newline at end of file diff --git a/src/parser/parser.ts b/src/parser/parser.ts index c3d5fcf..4d8bac4 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -7,6 +7,7 @@ import Lexer, { Token } from '../lexer/lexer'; import TokenType from '../lexer/tokens'; import { LogError } from '../utils/log'; import { + ConditionalStmt, Expr, Identifier, NumericLiteral, @@ -67,6 +68,10 @@ export default class Parser { case TokenType.REKA: case TokenType.NTAHINDUKA: return this.parse_var_declaration(); + case TokenType.NIBA: + return this.parse_if_statement(); + case TokenType.SUBIRAMO_NIBA: + return this.parse_loop_statement(); default: return this.parse_expr(); } @@ -246,4 +251,27 @@ export default class Parser { return { kind: 'ObjectLiteral', properties } as ObjectLiteral; } + + private parse_if_statement(): Stmt { + this.eat(); // advance past niba or nanone_niba + this.expect(TokenType.OPEN_PARANTHESES, `"Expected ( after niba"`); + const condition: Stmt = this.parse_expr(); + this.expect(TokenType.CLOSE_PARANTHESES, `"Expected ) after condition"`); + const body: Stmt[] = this.parse_block_statement(); + let alternate: Stmt[] = []; + + if (this.at().type == TokenType.NANONE_NIBA) { + alternate = [this.parse_if_statement()]; + } else if (this.at().type == TokenType.NIBA_BYANZE) { + this.eat(); // advance past niba_byanze + alternate = this.parse_block_statement(); + } + + return { + kind: 'ConditionalStatement', + body, + condition, + alternate, + } as ConditionalStmt; + } } From 9f2b16c2c5bf04c84e99572651ddcec8307584b0 Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Thu, 1 Feb 2024 21:22:04 +0200 Subject: [PATCH 4/8] feat: parsing loop statement --- src/parser/parser.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 4d8bac4..2f1c06f 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -10,6 +10,7 @@ import { ConditionalStmt, Expr, Identifier, + LoopStatement, NumericLiteral, ObjectLiteral, Program, @@ -274,4 +275,17 @@ export default class Parser { alternate, } as ConditionalStmt; } + + private parse_loop_statement(): Stmt { + this.eat(); // advance past subiramo_niba + this.expect(TokenType.OPEN_PARANTHESES, `"Expected ( after subiramo_niba"`); + const condition: Stmt = this.parse_expr(); + const body: Stmt[] = this.parse_block_statement(); + + return { + kind: 'LoopStatement', + body, + condition, + } as LoopStatement; + } } From 80c73471943a63c00bb6fcd28d478a48332d0404 Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Thu, 1 Feb 2024 22:00:09 +0200 Subject: [PATCH 5/8] feat: parsing function declaration --- src/parser/parser.ts | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 2f1c06f..530e0af 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -9,6 +9,7 @@ import { LogError } from '../utils/log'; import { ConditionalStmt, Expr, + FunctionDeclaration, Identifier, LoopStatement, NumericLiteral, @@ -73,6 +74,8 @@ export default class Parser { return this.parse_if_statement(); case TokenType.SUBIRAMO_NIBA: return this.parse_loop_statement(); + case TokenType.POROGARAMU_NTOYA: + return this.parse_function_declaration(); default: return this.parse_expr(); } @@ -288,4 +291,59 @@ export default class Parser { condition, } as LoopStatement; } + + private parse_function_declaration(): Stmt { + this.eat(); // eat porogaramu_ntoya keywork + const name = this.expect( + TokenType.IDENTIFIER, + `"Expected function name following porogaramu_ntoya keyword"`, + ).lexeme; + + const args = this.parse_args(); + const params: string[] = new Array(); + + for (const arg of args) { + if (arg.kind != 'Identifier') { + LogError( + `On line ${this.at().line}: Kin Error: Expected identifier for function parameter`, + ); + process.exit(1); + } + + params.push((arg as Identifier).symbol); + } + + const body = this.parse_block_statement(); + + return { + kind: 'FunctionDeclaration', + name, + parameters: params, + body, + } as FunctionDeclaration; + } + + private parse_args(): Expr[] { + this.expect(TokenType.OPEN_PARANTHESES, `"Expected ( after function name"`); + const args = + this.at().type == TokenType.CLOSE_PARANTHESES + ? new Array() + : this.parse_args_list(); + + this.expect( + TokenType.CLOSE_PARANTHESES, + `"Expected ) after function parameters"`, + ); + + return args; + } + + private parse_args_list() { + const args: Expr[] = [this.parse_expr()]; //TODO: assignment expr + + while (this.at().type == TokenType.COMMA && this.eat()) { + args.push(this.parse_expr()); // TODO: assignment expr + } + return args; + } } From 0b5f19f4ed0984d43096bc0350008b3d663c2f88 Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Fri, 2 Feb 2024 13:57:08 +0200 Subject: [PATCH 6/8] fix: include tests in compiled paths --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 89fc597..305c868 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "moduleResolution": "NodeNext", "declaration": true, "outDir": "dist", - "rootDir": "src", + "rootDir": "./", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, From 0b568b0ef6f8e181b2179401bb530a8cb7664ca1 Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Fri, 2 Feb 2024 20:26:43 +0200 Subject: [PATCH 7/8] feat: parsing kin programs --- examples/factorial.kin | 10 +- examples/fibonacci.kin | 10 +- examples/fizzbuzz.kin | 14 +-- src/lexer/lexer.ts | 32 +----- src/lexer/tokens.ts | 18 +--- src/parser/ast.ts | 16 ++- src/parser/parser.ts | 219 +++++++++++++++++++++++++++++++++++------ 7 files changed, 225 insertions(+), 94 deletions(-) diff --git a/examples/factorial.kin b/examples/factorial.kin index d71e39c..eeac7d0 100644 --- a/examples/factorial.kin +++ b/examples/factorial.kin @@ -5,11 +5,11 @@ porogaramu_ntoya factorial(nbr) { niba (nbr == 1) { - tanga 1; + tanga 1 } - tanga nbr * factorial(nbr - 1); + tanga nbr * factorial(nbr - 1) } -reka input_nbr = injiza_amakuru("Enter a number: "); -reka nbr_factorial = factorial(nbr); -tangaza_amakuru(nbr_factorial); +reka input_nbr = injiza_amakuru("Enter a number: ") +reka nbr_factorial = factorial(nbr) +tangaza_amakuru(nbr_factorial) diff --git a/examples/fibonacci.kin b/examples/fibonacci.kin index 2f10518..0b7eace 100644 --- a/examples/fibonacci.kin +++ b/examples/fibonacci.kin @@ -6,11 +6,11 @@ porogaramu_ntoya fibonacci(nbr) { niba (nbr == 1 || nbr == 0) { - tanga 1; + tanga 1 } - tanga fibonacci(nbr - 1) + fibonacci(nbr - 2); + tanga fibonacci(nbr - 1) + fibonacci(nbr - 2) } -reka input_nbr = injiza_amakuru("Enter a number: "); -reka nbr_fibonacci = fibonacci(nbr); -tangaza_amakuru(nbr_fibonacci); +reka input_nbr = injiza_amakuru("Enter a number: ") +reka nbr_fibonacci = fibonacci(nbr) +tangaza_amakuru(nbr_fibonacci) diff --git a/examples/fizzbuzz.kin b/examples/fizzbuzz.kin index 2676e98..8798fde 100644 --- a/examples/fizzbuzz.kin +++ b/examples/fizzbuzz.kin @@ -4,21 +4,21 @@ # -reka limit = injiza_amakuru("Enter the limit (must be a number): "); +reka limit = injiza_amakuru("Enter the limit (must be a number): ") reka response; -reka i = 1; +reka i = 1 subiramo_niba(i <= limit) { niba (i%5 == 0 && i%3 == 0) { - response = "FizzBuzz"; + response = "FizzBuzz" } nanone_niba (i%5 == 0) { - response = "Fizz"; + response = "Fizz" } nanone_niba (i%3 == 0) { - response = "Buzz"; + response = "Buzz" } niba_byanze { - response = i; + response = i } - tangaza_amakuru(response); + tangaza_amakuru(response) } diff --git a/src/lexer/lexer.ts b/src/lexer/lexer.ts index 10c49a9..0b8372e 100644 --- a/src/lexer/lexer.ts +++ b/src/lexer/lexer.ts @@ -139,54 +139,26 @@ class Lexer { const lexeme: string = this.sourceCodes.slice(start, this.currentPos); /* Check if lexeme is a keywork */ - if (lexeme === 'ubusa') - return this.makeTokenWithLexeme(TokenType.UBUSA, lexeme); if (lexeme === 'niba') return this.makeTokenWithLexeme(TokenType.NIBA, lexeme); - if (lexeme === 'nibyo') - return this.makeTokenWithLexeme(TokenType.NIBYO, lexeme); - if (lexeme === 'sibyo') - return this.makeTokenWithLexeme(TokenType.SIBYO, lexeme); if (lexeme === 'nanone_niba') return this.makeTokenWithLexeme(TokenType.NANONE_NIBA, lexeme); if (lexeme === 'umubare') return this.makeTokenWithLexeme(TokenType.UMUBARE, lexeme); if (lexeme === 'umubare_wibice') return this.makeTokenWithLexeme(TokenType.UMUBARE_WIBICE, lexeme); - if (lexeme === 'ijambo') - return this.makeTokenWithLexeme(TokenType.IJAMBO, lexeme); if (lexeme === 'niba_byanze') return this.makeTokenWithLexeme(TokenType.NIBA_BYANZE, lexeme); - if (lexeme === 'subiramo_NIBA') + if (lexeme === 'subiramo_niba') return this.makeTokenWithLexeme(TokenType.SUBIRAMO_NIBA, lexeme); if (lexeme === 'tanga') return this.makeTokenWithLexeme(TokenType.TANGA, lexeme); if (lexeme === 'porogaramu_ntoya') return this.makeTokenWithLexeme(TokenType.POROGARAMU_NTOYA, lexeme); - if (lexeme === 'tangaza_amakuru') - return this.makeTokenWithLexeme(TokenType.TANGAZA_AMAKURU, lexeme); - if (lexeme === 'injiza_amakuru') - return this.makeTokenWithLexeme(TokenType.INJIZA_AMAKURU, lexeme); - if (lexeme === 'komeza') - return this.makeTokenWithLexeme(TokenType.KOMEZA, lexeme); - if (lexeme === 'hagarara') - return this.makeTokenWithLexeme(TokenType.HAGARARA, lexeme); - if (lexeme === 'ubwoko') - return this.makeTokenWithLexeme(TokenType.UBWOKO, lexeme); if (lexeme === 'reka') return this.makeTokenWithLexeme(TokenType.REKA, lexeme); if (lexeme === 'ntahinduka') return this.makeTokenWithLexeme(TokenType.NTAHINDUKA, lexeme); - if (lexeme === 'soma_inyandiko') - return this.makeTokenWithLexeme(TokenType.SOMA_INYANDIKO, lexeme); - if (lexeme === 'andika_inyandiko') - return this.makeTokenWithLexeme(TokenType.ANDIKA_INYANDIKO, lexeme); - if (lexeme === 'vugurura_inyandiko') - return this.makeTokenWithLexeme(TokenType.KUVUGURURA_INYANDIKO, lexeme); - if (lexeme === 'kin_hagarara') - return this.makeTokenWithLexeme(TokenType.KIN_HAGARARA, lexeme); - if (lexeme === 'sisitemu') - return this.makeTokenWithLexeme(TokenType.SISITEMU, lexeme); /* Not a keywork, it's an identifier */ return this.makeTokenWithLexeme(TokenType.IDENTIFIER, lexeme); @@ -260,7 +232,7 @@ class Lexer { } case ';': this.advance(); - return this.makeTokenWithLexeme(TokenType.END_OF_LINE, ';'); + return this.makeTokenWithLexeme(TokenType.SEMI_COLON, ';'); case ']': this.advance(); return this.makeTokenWithLexeme(TokenType.CLOSE_BRACKET, ']'); diff --git a/src/lexer/tokens.ts b/src/lexer/tokens.ts index 3dd5c3f..72ed105 100644 --- a/src/lexer/tokens.ts +++ b/src/lexer/tokens.ts @@ -5,6 +5,7 @@ enum TokenType { /* One-character tokens */ + DOT, MINUS, PLUS, STAR, @@ -13,7 +14,7 @@ enum TokenType { MODULO, AMPERSAND, NEGATION, - END_OF_LINE, + SEMI_COLON, OPEN_PARANTHESES, CLOSE_PARANTHESES, OPEN_BRACKET, @@ -45,10 +46,7 @@ enum TokenType { LESS_THAN_OR_EQUAL, /* Keywords */ - UBUSA, NIBA, - NIBYO, - SIBYO, NTAHINDUKA, NANONE_NIBA, UMUBARE, @@ -57,19 +55,7 @@ enum TokenType { SUBIRAMO_NIBA, TANGA, POROGARAMU_NTOYA, - TANGAZA_AMAKURU, - INJIZA_AMAKURU, - KOMEZA, - HAGARARA, - UBWOKO, - ERROR, - KIN_HAGARARA, REKA, - SOMA_INYANDIKO, - ANDIKA_INYANDIKO, - KUVUGURURA_INYANDIKO, - SISITEMU, - IJAMBO, EOF, } diff --git a/src/parser/ast.ts b/src/parser/ast.ts index 0cb4483..5a720a3 100644 --- a/src/parser/ast.ts +++ b/src/parser/ast.ts @@ -16,6 +16,7 @@ export type NodeType = | 'MemberExpression' | 'CallExpression' | 'BinaryExpr' + | 'ReturnExpr' // Literals | 'ObjectLiteral' @@ -46,6 +47,14 @@ export interface Program extends Stmt { body: Stmt[]; } +/** + * Defines a return statement + */ +export interface ReturnExpr extends Expr { + kind: 'ReturnExpr'; + value?: Expr; +} + /** * Defines a variable declaration */ @@ -87,6 +96,11 @@ export interface FunctionDeclaration extends Stmt { body: Stmt[]; } +export interface ReturnExpr extends Expr { + kind: 'ReturnExpr'; + value?: Expr; +} + /** * Defines a binary expression */ @@ -100,7 +114,7 @@ export interface BinaryExpr extends Expr { // foo["bar"]() should work export interface CallExpr extends Expr { kind: 'CallExpression'; - callee: Expr; + caller: Expr; args: Expr[]; } diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 530e0af..d1848f1 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -7,15 +7,20 @@ import Lexer, { Token } from '../lexer/lexer'; import TokenType from '../lexer/tokens'; import { LogError } from '../utils/log'; import { + AssignmentExpr, + BinaryExpr, + CallExpr, ConditionalStmt, Expr, FunctionDeclaration, Identifier, LoopStatement, + MemberExpr, NumericLiteral, ObjectLiteral, Program, Property, + ReturnExpr, Stmt, StringLiteral, VariableDeclaration, @@ -84,7 +89,7 @@ export default class Parser { private parse_block_statement(): Stmt[] { this.expect( TokenType.OPEN_CURLY_BRACES, - `"Expected { while parsing code block"`, + `"Expected { starting a code block"`, ); const body: Stmt[] = []; while (this.not_eof() && this.at().type != TokenType.CLOSE_CURLY_BRACES) { @@ -107,7 +112,7 @@ export default class Parser { ).lexeme; // Un initialized variable - if (this.at().type == TokenType.END_OF_LINE) { + if (this.at().type == TokenType.SEMI_COLON) { this.eat(); if (isConstant) throw 'Constant variables must be assigned a value'; @@ -130,51 +135,49 @@ export default class Parser { value: this.parse_expr(), } as VariableDeclaration; - this.expect( - TokenType.END_OF_LINE, - `"Expected semicolon at the end of line"`, - ); - return declaration; } private parse_expr(): Expr { - return this.parse_primary_expr(); + return this.parse_assignment_expr(); } private parse_primary_expr(): Expr { - switch (this.at().type) { + const tk = this.at().type; + switch (tk) { case TokenType.IDENTIFIER: - return { kind: 'Identifier', symbol: this.eat().lexeme } as Identifier; + const identifier_expr = { + kind: 'Identifier', + symbol: this.eat().lexeme, + } as Identifier; + + return identifier_expr; case TokenType.INTEGER: - return { - kind: 'NumericLiteral', - value: parseInt(this.eat().lexeme), - } as NumericLiteral; - case TokenType.FLOAT: - return { + const nbr_literal: Expr = { kind: 'NumericLiteral', value: parseFloat(this.eat().lexeme), } as NumericLiteral; + + return nbr_literal; case TokenType.STRING: - return { + const string_literal = { kind: 'StringLiteral', value: this.eat().lexeme, } as StringLiteral; - case TokenType.OPEN_CURLY_BRACES: - this.eat(); // advance past { - return this.parse_object_expr(); - case TokenType.OPEN_BRACKET: - this.eat(); // advance past [ - return this.parse_array_expr(); + + return string_literal; case TokenType.OPEN_PARANTHESES: - this.eat(); // advance past ( - const value: Expr = this.parse_expr(); + this.eat(); // eat the opening paren + const value = this.parse_expr(); + this.expect( - TokenType.END_OF_LINE, - `Expected semicolon at the end of line`, - ); + TokenType.CLOSE_PARANTHESES, + 'Unexpected token (?) found while parsing arguments.', + ); // closing paren + return value; + case TokenType.TANGA: + return this.parse_function_return(); default: LogError( `On line ${this.at().line}: Kin Error: Unexpected token ${this.at().lexeme}`, @@ -211,11 +214,138 @@ export default class Parser { return { kind: 'ObjectLiteral', properties } as ObjectLiteral; } + private parse_and_statement(): Expr { + let left = this.parse_additive_expr(); + + if (['&&', '||'].includes(this.at().lexeme)) { + const operator = this.eat().lexeme; + const right = this.parse_additive_expr(); + + left = { + kind: 'BinaryExpr', + left, + right, + operator, + } as BinaryExpr; + } + + return left; + } + + private parse_additive_expr(): Expr { + let left = this.parse_multiplicative_expr(); + + while ( + ['+', '-', '==', '!=', '<', '>', '<=', '>='].includes(this.at().lexeme) + ) { + const operator = this.eat().lexeme; + const right = this.parse_multiplicative_expr(); + left = { + kind: 'BinaryExpr', + left, + right, + operator, + } as BinaryExpr; + } + + return left; + } + + // foo.x() + private parse_call_member_expr(): Expr { + const member = this.parse_member_expr(); + + if (this.at().type == TokenType.OPEN_PARANTHESES) { + return this.parse_call_expr(member); + } + + return member; + } + + private parse_call_expr(caller: Expr): Expr { + let call_expr: Expr = { + kind: 'CallExpression', + caller, + args: this.parse_args(), + } as CallExpr; + + // allow chaining: foo.x()() + if (this.at().type == TokenType.OPEN_PARANTHESES) { + call_expr = this.parse_call_expr(call_expr); + } + + return call_expr; + } + + private parse_member_expr(): Expr { + let object = this.parse_primary_expr(); + + while ( + this.at().type == TokenType.DOT || + this.at().type == TokenType.OPEN_BRACKET + ) { + const operator = this.eat(); + let property: Expr; + let computed: boolean; + + // non-computed values (obj.expr) + if (operator.type == TokenType.DOT) { + computed = false; + // get identifier + property = this.parse_primary_expr(); + + if (property.kind !== 'Identifier') { + throw 'Dot operator (".") is illegal without right-hand-side (<-) being an Identifier.'; + } + } // computed values (obj[computedVal]) + else { + computed = true; + property = this.parse_expr(); + + this.expect( + TokenType.CLOSE_BRACKET, + 'Closing bracket ("}") expected following "computed value" in "Member" expression.', + ); + } + + object = { + kind: 'MemberExpression', + object, + property, + computed, + } as MemberExpr; + } + + return object; + } + + private parse_multiplicative_expr(): Expr { + let left = this.parse_call_member_expr(); + + while (['/', '*', '%'].includes(this.at().lexeme)) { + const operator = this.eat().lexeme; + const right = this.parse_call_member_expr(); + left = { + kind: 'BinaryExpr', + left, + right, + operator, + } as BinaryExpr; + } + + return left; + } + private parse_object_expr(): Expr { + if (this.at().type !== TokenType.OPEN_CURLY_BRACES) { + return this.parse_and_statement(); + } + const properties = new Array(); while (this.not_eof() && this.at().type != TokenType.CLOSE_CURLY_BRACES) { // {key: val, key2: val} + this.eat(); // advance past { const key = this.expect( TokenType.IDENTIFIER, `"Identifier Expected for object key"`, @@ -283,6 +413,7 @@ export default class Parser { this.eat(); // advance past subiramo_niba this.expect(TokenType.OPEN_PARANTHESES, `"Expected ( after subiramo_niba"`); const condition: Stmt = this.parse_expr(); + this.expect(TokenType.CLOSE_PARANTHESES, `"Expected ) after condition"`); const body: Stmt[] = this.parse_block_statement(); return { @@ -339,11 +470,39 @@ export default class Parser { } private parse_args_list() { - const args: Expr[] = [this.parse_expr()]; //TODO: assignment expr + const args: Expr[] = [this.parse_assignment_expr()]; while (this.at().type == TokenType.COMMA && this.eat()) { - args.push(this.parse_expr()); // TODO: assignment expr + args.push(this.parse_assignment_expr()); } return args; } + + private parse_assignment_expr(): Expr { + const left = this.parse_object_expr(); + if (this.at().type == TokenType.EQUAL) { + this.eat(); // advance past the equals + const value = this.parse_assignment_expr(); + + return { + kind: 'AssignmentExpression', + value, + assigne: left, + } as AssignmentExpr; + } + + return left; + } + + private parse_function_return(): Expr { + this.eat(); // advance past tanga + if (this.at().type == TokenType.SEMI_COLON) { + return { kind: 'ReturnExpr', value: undefined } as Expr; + } + const value = this.parse_expr(); + return { + kind: 'ReturnExpr', + value, + } as ReturnExpr; + } } From f265de8aacaa191834c48784849224123adde02d Mon Sep 17 00:00:00 2001 From: MURANGWA Pacifique Date: Fri, 2 Feb 2024 20:27:26 +0200 Subject: [PATCH 8/8] fix: semi-colons required on uninitialized var declarations only --- tests/lexer.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/lexer.test.ts b/tests/lexer.test.ts index 99f561c..72aa016 100644 --- a/tests/lexer.test.ts +++ b/tests/lexer.test.ts @@ -24,7 +24,7 @@ describe('Lexer', () => { }); test('should tokenize variable assignment correctly', () => { - const lexer = new Lexer('reka x = 42;'); + const lexer = new Lexer('reka x = 42'); const tokens = lexer.tokenize(); const expectedTokens = [ @@ -32,7 +32,6 @@ describe('Lexer', () => { { line: 1, type: TokenType.IDENTIFIER, lexeme: 'x' }, { line: 1, type: TokenType.EQUAL, lexeme: '=' }, { line: 1, type: TokenType.INTEGER, lexeme: '42' }, - { line: 1, type: TokenType.END_OF_LINE, lexeme: ';' }, { line: 1, type: TokenType.EOF, lexeme: 'EOF' }, ]; expect(tokens).toEqual(expectedTokens); @@ -51,7 +50,7 @@ describe('Lexer', () => { test('should tokenize comments and ignore whitespace', () => { const lexer = new Lexer(` # This is a comment - reka a = 10; # Another comment + reka a = 10 # Another comment `); const tokens = lexer.tokenize(); @@ -60,7 +59,6 @@ describe('Lexer', () => { { line: 3, type: TokenType.IDENTIFIER, lexeme: 'a' }, { line: 3, type: TokenType.EQUAL, lexeme: '=' }, { line: 3, type: TokenType.INTEGER, lexeme: '10' }, - { line: 3, type: TokenType.END_OF_LINE, lexeme: ';' }, { line: 4, type: TokenType.EOF, lexeme: 'EOF' }, ]; expect(tokens).toEqual(expectedTokens);