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()
}