diff --git a/src/ast/compiler/ast.interface.ts b/src/ast/compiler/ast.interface.ts index 995280a..2ddf984 100644 --- a/src/ast/compiler/ast.interface.ts +++ b/src/ast/compiler/ast.interface.ts @@ -1,4 +1,5 @@ -/** Abstract Syntax Tree Node Types (Expressions) +/** ## Abstract Syntax Tree Node Types + * ### Defines the structure of our language AST * @description * - expression is not a statement * - expression example -> 5 * 10 @@ -6,7 +7,14 @@ * - statements will not return a value * - statement example -> let x = 45 */ -export type NodeType = 'Program' | 'NumericLiteral' | 'Identifier' | 'BinaryExpr' +export type NodeType = + // STATEMENTS + | 'Program' + | 'VarDeclaration' + // EXPRESSIONS + | 'NumericLiteral' + | 'Identifier' + | 'BinaryExpr' /** Abstract Statement Interface that include statement kind * @description @@ -25,6 +33,17 @@ export interface IProgram extends IStatement { body: IStatement[] } +/** Variable Declaration Node Type + * @example + * let x; // x is undefined + */ +export interface IVarDeclaration extends IStatement { + kind: 'VarDeclaration' + constant: boolean + identifier: string + value?: IExpression +} + /** Expression interface that extends `IStatement` */ export interface IExpression extends IStatement {} diff --git a/src/ast/compiler/parser.ts b/src/ast/compiler/parser.ts index 251b3a7..7b5f86a 100644 --- a/src/ast/compiler/parser.ts +++ b/src/ast/compiler/parser.ts @@ -8,6 +8,7 @@ import { IBinaryExpression, INumericLiteral, IIdentifierExpression, + IVarDeclaration, } from './ast.interface' /** @@ -42,15 +43,58 @@ export class Parser { /** entry point of the parser*/ private parseSTMT(): IStatement { - return this.parseExpr() + switch (this.at().type) { + case TokenType.Let: + case TokenType.Const: + return this.parseVarDeclaration() + default: + return this.parseExpr() + } + } + + /** parse variable declaration + * @example + * - LET IDENTIFIER; + * - ( LET | CONST ) IDENTIFIER = EXPR; + */ + private parseVarDeclaration(): IStatement { + const isConstant = this.eat().type == TokenType.Const + const identifier = this.expect( + TokenType.Identifier, + 'Expected identifier name following let | const keywords' + ).value + + if (this.at().type == TokenType.Semicolon) { + this.eat() // expect semicolon + if (isConstant) { + throw 'Must assign value to constant expression, no value provided.' + } + return { + kind: 'VarDeclaration', + identifier, + constant: false, + } as IVarDeclaration + } + + this.expect(TokenType.Equals, 'Expected equals token following identifier in var declaration. ') + + const declaration = { + kind: 'VarDeclaration', + value: this.parseExpr(), + constant: isConstant, + } as IVarDeclaration + + this.expect(TokenType.Semicolon, 'Variable declaration statement must end with semicolon.') // force end with semicolon + + return declaration } - /** parse expression */ + /** ## parse expression */ private parseExpr(): IExpression { return this.parseAdditiveExpr() } - /** parse additive expression + /** ### parse additive expression * @description
* - Additive Expression has left <-> hand presidence * @example @@ -76,7 +120,7 @@ export class Parser { return left } - /** parse multiplicative expression + /** ### parse multiplicative expression * @description parse `*`, `/` and `%` */ private parseMultiplicitaveExpr(): IExpression { @@ -120,8 +164,8 @@ export class Parser { } } - /** fnc to return tokens index position */ private at(): IToken { + /** fnc to return tokens index position */ return this.tokens[0] } @@ -136,16 +180,16 @@ export class Parser { return prev } - /** check token is EOF or not */ - private notEOF(): boolean { - return this.tokens[0].type !== TokenType.EOF - } - /** shift the token */ private eat(): IToken { const prev = this.tokens.shift() return prev as IToken } + + /** check token is EOF or not */ + private notEOF(): boolean { + return this.tokens[0].type !== TokenType.EOF + } } /** diff --git a/src/lexer/lexer.interface.ts b/src/lexer/lexer.interface.ts index 0d9994b..f7cfa29 100644 --- a/src/lexer/lexer.interface.ts +++ b/src/lexer/lexer.interface.ts @@ -1,5 +1,5 @@ /** - * Token Type Enum + * # Token Type Enum * @example * let x = 45 + (foo * bar) * [letToken, IdentifierToken, EqualsToken, NumberToken] @@ -14,12 +14,16 @@ export enum TokenType { //? ------ Keywords /** let to declare variable */ Let, + /** const to declare variable */ + Const, //? ------ Grouping * Operators /** binary operator */ BinaryOperator, /** equal */ Equals, + /** semicolon */ + Semicolon, /** open parenthesis */ OpenParen, /** close parenthesis */ @@ -39,4 +43,5 @@ export interface IToken { /** Reserved Keywords */ export const KEYWORDS: Record = { let: TokenType.Let, + const: TokenType.Const, } diff --git a/src/lexer/lexer.ts b/src/lexer/lexer.ts index 20c8b93..118e5de 100644 --- a/src/lexer/lexer.ts +++ b/src/lexer/lexer.ts @@ -25,6 +25,8 @@ export class Lexer { tokens.push(this.token(src.shift(), TokenType.BinaryOperator)) } else if (src[0] == '=') { tokens.push(this.token(src.shift(), TokenType.Equals)) + } else if (src[0] == ';') { + tokens.push(this.token(src.shift(), TokenType.Semicolon)) } else { // handling multi character tokens if (this.isInt(src[0])) { @@ -35,7 +37,7 @@ export class Lexer { tokens.push(this.token(num, TokenType.Number)) } else if (this.isAlpha(src[0])) { - let ident = '' // foo let + let ident = '' // foo let (identifier) while (src.length > 0 && this.isAlpha(src[0])) { ident += src.shift() }