diff --git "a/docs/Gram\303\241tica.txt" "b/docs/Gram\303\241tica.txt" new file mode 100644 index 0000000..e5574b3 --- /dev/null +++ "b/docs/Gram\303\241tica.txt" @@ -0,0 +1,33 @@ +bloco_codigo: LBRACE expressão* RBRACE + +expressão : FUNCTION IDENTIFIER LPAREN (IDENTIFIER (COMMA IDENTIFIER)*)? RPAREN bloco_codigo + : VAR|CONST IDENTIFIER EQ expressão + : expressão_co ((AND|OR) expressão_co)* + +expressão_co: NOT expressão_co + : expressão_ar ((IGUAL|N_IGUAL|ME_QUE|MA_QUE|ME_IG_QUE|MA_IG_QUE) expressão_ar)* + +expressão_ar: termo ((MAIS|MENOS) termo)* + +termo : fator ((MULTIPLICAR|DIVIDIR) fator)* + +fator : (MAIS|MENOS) fator + : power + +power : call (POWER fator)* + +call : atom (LPAREN (expressão (COMMA expressão)*)? RPAREN)? + +atom : INT|FLOAT|STRING|IDENTIFIER + : LPAREN expressão RPAREN + : expressão_if + : expressão_fo + : expressão_wh + +expressão_if: IF LPAREN expressão RPAREN bloco_codigo + (ELSEIF LPAREN expressão RPAREN bloco_codigo)* + (ELSE bloco_codigo)? + +expressão_fo: FOR LPAREN IDENTIFIER EQ expressão TO expressão STEP expressão RPAREN bloco_codigo + +expressão_wh: WHILE LPAREN expressão RPAREN bloco_codigo \ No newline at end of file diff --git a/src/_examples/ProfMedia.txt b/src/_examples/ProfMedia.txt new file mode 100644 index 0000000..90d9de5 --- /dev/null +++ b/src/_examples/ProfMedia.txt @@ -0,0 +1,19 @@ +{ + var valor1 = 0 + var valor2 = 0 + + print("Digite o valor 1 de um número inteiro") + read(valor1) + + print("Digite o valor 2 de um número inteiro") + read(valor2) + + //Processamento do programa + const resultado = valor1 + valor2 + + if(resultado >= 10) { + print("O valor é maior ou igual a 10. O resultado é " + resultado) + } else { + print("O valor é menor que 10. O resultado é " + resultado) + } +} \ No newline at end of file diff --git a/src/_examples/ProfTabuada.txt b/src/_examples/ProfTabuada.txt new file mode 100644 index 0000000..d14a9cc --- /dev/null +++ b/src/_examples/ProfTabuada.txt @@ -0,0 +1,25 @@ +{ + var tabuada = 0 + var nome = "" + + escreva("Digite o seu nome") + leia(nome) + + escreva("Tabuada que deseja calcular") + leia(tabuada) + + resolverTabuada(tabuada) + + escreva(nome + ". Foi resolvida a tabuada do " + tabuada) + + function resolverTabuada(tabuada) { + var resto = 0 + + for(i = 0 to 20 step 1) { + if(resto == 0) { + escreva(tabuada + " X " + i + " = " + tabuada * i) + escreva(tabuada + " X " + i + " = " + tabuada * i) + } + } + } +} \ No newline at end of file diff --git a/src/examples/SomaDoisNumeros.txt b/src/examples/SomaDoisNumeros.txt deleted file mode 100644 index 7212efe..0000000 --- a/src/examples/SomaDoisNumeros.txt +++ /dev/null @@ -1,16 +0,0 @@ -var valor1 = 0; -var valor2 = 0; - -print("Digite o valor 1 de um número inteiro"); -read(valor1); - -print("Digite o valor 2 de um número inteiro"); -read(valor2); - -//Processamento do programa -const resultado = valor1 + valor2; - -if(resultado >= 10) - print("O valor é maior ou igual a 10. O resultado é " + resultado); -else - print("O valor é menor que 10. O resultado é " + resultado); \ No newline at end of file diff --git a/src/examples/teste.txt b/src/examples/teste.txt deleted file mode 100644 index 6aca5c7..0000000 --- a/src/examples/teste.txt +++ /dev/null @@ -1,9 +0,0 @@ -{ - //Primeiro comentário awuiiiii - const abc= 1345.65;var vfd="meu email é 1800935@escolas.anchieta.br"; - if(abc === 1) abc++; - else abc--; - const finalSum = abc*2; - - //Segundo comentário aqui!!!! -} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index a6146ce..778f5b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,12 @@ -import { Lexer } from "./models/Lexer"; -import { Token } from "./models/Token"; +import { ParserNode } from './parser/nodes/ParserNode'; +import { CodeBlockNode } from './parser/nodes/CodeBlockNode'; +import { Lexer } from "./lexer/Lexer"; +import { Token } from "./lexer/Token"; import fs from 'fs'; import path from 'path'; +import { Parser } from "./parser/Parser"; -const FILENAME: string = 'examples/SomaDoisNumeros.txt'; +const FILENAME: string = '_examples/ProfMedia.txt'; const filePath: string = path.join(__dirname, FILENAME); @@ -12,7 +15,14 @@ fs.readFile(filePath, {encoding: 'utf-8'}, (err, data) => { const lexer: Lexer = new Lexer(data); const tokens: Token[] = lexer.findTokens(); + console.log('Análise Léxica:'); console.table(tokens); + + const parser: Parser = new Parser(tokens); + const codeBlock: ParserNode = parser.makeCodeBlock(); + + console.log('Análise Sintática:'); + if (codeBlock != null) console.log(codeBlock.representation); } else { console.error(err); } diff --git a/src/models/Lexer.ts b/src/lexer/Lexer.ts similarity index 86% rename from src/models/Lexer.ts rename to src/lexer/Lexer.ts index 10e2214..8859173 100644 --- a/src/models/Lexer.ts +++ b/src/lexer/Lexer.ts @@ -10,13 +10,14 @@ const CHARS: string[] = [...STARTING_CHARS, ...STARTING_DIGITS, '_']; const STRING_INIT_SYMBOL: string = '"'; const STRING_STOP_SYMBOL: string = '"'; const COMMENT_INIT_SYMBOL: string = '//'; -const COMMENT_STOP_SYMBOL: string = '\n'; +const COMMENT_STOP_SYMBOL: string = '' +const END_OF_LINE_SYMBOL: string = '\n'; -const SYMBOLS: string[] = ['\'', '*', '-', '+', '/', ';', '{', '}', '(', ')', '=', '|', '&', '%', '!', '<', '>']; +const SYMBOLS: string[] = ['\'', '*', '-', '+', '/', ';', '{', '}', '(', ')', '=', '|', '&', '%', '!', '<', '>', '^']; const COMPOUND_SYMBOLS: string[] = [ '--', '++', '==', '===', '!=', '<=', '>=', '||', '&&']; -const KEYWORDS: string[] = ['break','return','continue','for','while', 'var', 'const', 'if', 'else', 'elseif', 'print', 'read']; -const IGNORED_TOKENS: string[] = ['\n', '\t', '\r', ' ']; +const KEYWORDS: string[] = ['break','return','continue','for', 'to', 'step', 'while', 'var', 'const', 'function', 'if', 'else', 'elseif']; +const IGNORED_TOKENS: string[] = ['\t', '\r', ' ']; export class Lexer { @@ -52,6 +53,13 @@ export class Lexer continue; } + if(END_OF_LINE_SYMBOL === this.currentChar) + { + tokens.push(new Token(TokenType.END_OF_LINE, null)); + this.advance(); + continue; + } + if(IGNORED_TOKENS.includes(this.currentChar)) { this.advance(); @@ -85,6 +93,8 @@ export class Lexer this.error(LexerErrors.ILLEGAL_CHAR, `${this.currentChar} at position ${this.currentPosition}`); } + tokens.push(new Token(TokenType.END_OF_FILE, null)); + return tokens; } @@ -113,16 +123,10 @@ export class Lexer private makeComment(): void { - this.advance(); - - while(this.currentChar) + do { - if(this.currentChar === COMMENT_STOP_SYMBOL) break; - this.advance(); - } - - if(this.currentChar === COMMENT_STOP_SYMBOL) this.advance(); + } while(this.currentChar && this.currentChar !== END_OF_LINE_SYMBOL); } private makeIdentifier(): Token @@ -211,6 +215,10 @@ export class Lexer case '&': return SymbolType.AMPERSAND; case '%': return SymbolType.PERCENTAGE; case '!': return SymbolType.EXCLAMATION; + case '^': return SymbolType.POWER; + case ',': return SymbolType.COMMA; + case '>': return SymbolType.MORE_THAN; + case '<': return SymbolType.LESS_THAN; case '==': return SymbolType.EQUALS; case '===': return SymbolType.EXACTLY_EQUALS; @@ -234,9 +242,12 @@ export class Lexer case 'return': return KeywordType.RETURN; case 'continue': return KeywordType.CONTINUE; case 'for': return KeywordType.FOR; + case 'to': return KeywordType.TO; + case 'step': return KeywordType.STEP; case 'while': return KeywordType.WHILE; case 'var': return KeywordType.VAR; case 'const': return KeywordType.CONST; + case 'function': return KeywordType.FUNCTION; case 'if': return KeywordType.IF; case 'else': return KeywordType.ELSE; case 'elseif': return KeywordType.ELSEIF; diff --git a/src/models/LexerError.ts b/src/lexer/LexerError.ts similarity index 87% rename from src/models/LexerError.ts rename to src/lexer/LexerError.ts index 593d3ca..9113b9c 100644 --- a/src/models/LexerError.ts +++ b/src/lexer/LexerError.ts @@ -11,7 +11,7 @@ export class LexerError public asString(): string { - return ` ${this.name}: ${this.details}`; + return `[LEXER] ${this.name}: ${this.details}`; } } diff --git a/src/models/Token.ts b/src/lexer/Token.ts similarity index 82% rename from src/models/Token.ts rename to src/lexer/Token.ts index f88641e..0038788 100644 --- a/src/models/Token.ts +++ b/src/lexer/Token.ts @@ -10,6 +10,11 @@ export class Token this.type = type; this.subType = subType || null; } + + public get deepType(): string + { + return this.subType ? this.subType : this.type; + } } export class TokenType @@ -21,6 +26,8 @@ export class TokenType public static readonly FLOAT: string = 'FLOAT'; public static readonly STRING: string = 'STRING'; public static readonly IDENTIFIER: string = 'IDENTIFIER'; + public static readonly END_OF_LINE: string = 'EOL'; + public static readonly END_OF_FILE: string = 'EOF'; } export class SymbolType @@ -40,6 +47,10 @@ export class SymbolType public static readonly AMPERSAND: string = 'SYMBOL_AMPERSAND'; public static readonly PERCENTAGE: string = 'SYMBOL_PERCENTAGE'; public static readonly EXCLAMATION: string = 'SYMBOL_EXCLAMATION'; + public static readonly POWER: string = 'SYMBOL_POWER'; + public static readonly COMMA: string = 'SYMBOL_COMMA'; + public static readonly MORE_THAN: string = 'SYMBOL_MORE_THAN'; + public static readonly LESS_THAN: string = 'SYMBOL_LESS_THAN'; public static readonly EQUALS: string = 'SYMBOL_EQUALS'; public static readonly EXACTLY_EQUALS: string = 'SYMBOL_EXACTLY_EQUALS'; @@ -58,12 +69,13 @@ export class KeywordType public static readonly RETURN: string = 'KEYWORD_RETURN'; public static readonly CONTINUE: string = 'KEYWORD_CONTINUE'; public static readonly FOR: string = 'KEYWORD_FOR'; + public static readonly TO: string = 'KEYWORD_TO'; + public static readonly STEP: string = 'KEYWORD_STEP'; public static readonly WHILE: string = 'KEYWORD_WHILE'; public static readonly VAR: string = 'KEYWORD_VAR'; public static readonly CONST: string = 'KEYWORD_CONST'; + public static readonly FUNCTION: string = 'KEYWORD_FUNCTION'; public static readonly IF: string = 'KEYWORD_IF'; public static readonly ELSE: string = 'KEYWORD_ELSE'; public static readonly ELSEIF: string = 'KEYWORD_ELSEIF'; - public static readonly PRINT: string = 'KEYWORD_PRINT'; - public static readonly READ: string = 'KEYWORD_READ'; } \ No newline at end of file diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts new file mode 100644 index 0000000..0e6d11a --- /dev/null +++ b/src/parser/Parser.ts @@ -0,0 +1,473 @@ +import { ForNode } from './nodes/ForNode'; +import { IfNode } from './nodes/IfNode'; +import { IfCase } from './nodes/IfCase'; +import { VariableAccessNode } from './nodes/VariableAccessNode'; +import { CallingNode } from './nodes/CallingNode'; +import { StringNode } from './nodes/StringNode'; +import { ParserError, ParserErrors } from './ParserError'; +import { UnaryOperationNode } from './nodes/UnaryOperationNode'; +import { ParserNode } from './nodes/ParserNode'; +import { BinaryOperationNode } from './nodes/BinaryOperationNode'; +import { KeywordType, SymbolType, Token, TokenType } from "../lexer/Token"; +import { NumberNode } from "./nodes/NumberNode"; +import { VariableAssignementNode } from './nodes/VariableAssignementNode'; +import { WhileNode } from './nodes/WhileNode'; +import { FunctionAssignementNode } from './nodes/FunctionAssignementNode'; +import { CodeBlockNode } from './nodes/CodeBlockNode'; + +//BY PRIORITY +const ATOMS: string[][] = [ + [TokenType.INTEGER, TokenType.FLOAT], + [TokenType.STRING] +]; + +const FACTORS: string[][] = [ + [SymbolType.PLUS, SymbolType.MINUS], +]; + +const TERMS: string[] = [SymbolType.MULTIPLY, SymbolType.DIVIDE]; +const EXPRESSIONS: string[][] = [ + [KeywordType.VAR, KeywordType.CONST], + [SymbolType.AND, SymbolType.OR] +]; + +export class Parser +{ + tokens: Token[]; + currentPosition: number; + + constructor(tokens: Token[]) + { + this.tokens = tokens; + this.currentPosition = 0; + } + + private get currentToken(): Token + { + return this.tokens[this.currentPosition] + } + + private get currentType(): string + { + return this.currentToken ? this.currentToken.deepType : ''; + } + + private advance(): void + { + this.currentPosition++; + } + + private makeWhile(): ParserNode + { + if (this.currentType !== SymbolType.LEFT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.LEFT_PARENTHESES); + + this.advance(); + + const condition: ParserNode = this.makeExpression(); + + if (this.currentType !== SymbolType.RIGHT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_PARENTHESES); + + this.advance(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + const body: ParserNode = this.makeCodeBlock(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + return new WhileNode(condition, body); + } + + private makeFor(): ParserNode + { + if (this.currentType !== SymbolType.LEFT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.LEFT_PARENTHESES); + + this.advance(); + + if (this.currentType !== TokenType.IDENTIFIER) throw new ParserError(ParserErrors.TOKEN_EXPECTED, TokenType.IDENTIFIER); + + const identifier: Token = this.currentToken; + + this.advance(); + + if (this.currentType !== SymbolType.ATTRIBUTION) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.ATTRIBUTION); + + this.advance(); + + const initialValue: ParserNode = this.makeExpression(); + + if (this.currentType !== KeywordType.TO) throw new ParserError(ParserErrors.TOKEN_EXPECTED, KeywordType.TO); + + this.advance(); + + const targetValue: ParserNode = this.makeExpression(); + + if (this.currentType !== KeywordType.STEP) throw new ParserError(ParserErrors.TOKEN_EXPECTED, KeywordType.STEP); + + this.advance(); + + const step: ParserNode = this.makeExpression(); + + if (this.currentType !== SymbolType.RIGHT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_PARENTHESES); + + this.advance(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + const body: ParserNode = this.makeCodeBlock(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + return new ForNode(identifier, initialValue, targetValue, step, body); + } + + private makeIf(): ParserNode + { + const cases: IfCase[] = []; + let elseCase: ParserNode | null = null; + + if (this.currentType !== SymbolType.LEFT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.LEFT_PARENTHESES); + + this.advance(); + + const condition: ParserNode = this.makeExpression(); + + if (this.currentType !== SymbolType.RIGHT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_PARENTHESES); + + this.advance(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + const body: ParserNode = this.makeCodeBlock(); + + cases.push(new IfCase(condition, body)); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + while (this.currentType === KeywordType.ELSEIF) + { + this.advance(); + + if (this.currentType !== SymbolType.LEFT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.LEFT_PARENTHESES); + + this.advance(); + + const condition: ParserNode = this.makeExpression(); + + if (this.currentType !== SymbolType.RIGHT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_PARENTHESES); + + this.advance(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + const body: ParserNode = this.makeCodeBlock(); + + cases.push(new IfCase(condition, body)); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + } + + if (this.currentType === KeywordType.ELSE) + { + this.advance(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + elseCase = this.makeCodeBlock(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + } + + return new IfNode(cases, elseCase); + } + + private makeAtom(): ParserNode + { + let node: ParserNode | null = null; + + if (this.currentToken) + { + if (ATOMS[0].includes(this.currentType)) + { + node = new NumberNode(this.currentToken); + this.advance(); + } + else if (ATOMS[1].includes(this.currentType)) + { + node = new StringNode(this.currentToken); + this.advance(); + } + else if (this.currentType === TokenType.IDENTIFIER) + { + node = new VariableAccessNode(this.currentToken); + this.advance(); + } + else if (this.currentType === SymbolType.LEFT_PARENTHESES) + { + this.advance(); + const expression: ParserNode = this.makeExpression(); + + if (this.currentType === SymbolType.RIGHT_PARENTHESES) + { + this.advance(); + node = expression; + } + else throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_PARENTHESES); + } + else if (this.currentType === KeywordType.IF) + { + this.advance(); + node = this.makeIf(); + } + else if (this.currentType === KeywordType.FOR) + { + this.advance(); + node = this.makeFor(); + } + else if (this.currentType === KeywordType.WHILE) + { + this.advance(); + node = this.makeWhile(); + } + } + + if (!node) throw new ParserError(ParserErrors.TOKEN_EXPECTED, [...ATOMS[0], ...ATOMS[1], ...FACTORS[0], KeywordType.IF, TokenType.IDENTIFIER, SymbolType.LEFT_PARENTHESES].toString()); + + return node; + } + + private makeCall(): ParserNode + { + let node: ParserNode = this.makeAtom(); + + if (this.currentType === SymbolType.LEFT_PARENTHESES) + { + this.advance(); + const argumentsNodes: ParserNode[] = []; + + if (this.currentType === SymbolType.RIGHT_PARENTHESES) + { + this.advance(); + } + else + { + argumentsNodes.push(this.makeExpression()); + + while (this.currentType === SymbolType.COMMA) + { + this.advance(); + + argumentsNodes.push(this.makeExpression()); + } + + if (this.currentType !== SymbolType.RIGHT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_PARENTHESES); + + this.advance(); + } + + return new CallingNode(node, argumentsNodes); + } + + return node; + } + + private makePower(): ParserNode + { + let node: ParserNode = this.makeCall(); + + while (this.currentType === SymbolType.POWER) + { + const operator: Token = this.currentToken; + this.advance(); + + const rightFactor: ParserNode = this.makeFactor(); + node = new BinaryOperationNode(node, operator, rightFactor); + } + + return node; + } + + private makeFactor(): ParserNode + { + let node: ParserNode | null = null; + + if (FACTORS[0].includes(this.currentType)) + { + const cacheToken = this.currentToken; + this.advance(); + + node = new UnaryOperationNode(cacheToken, this.makeFactor()); + } + else + { + node = this.makePower(); + } + + if (!node) throw new ParserError(ParserErrors.TOKEN_EXPECTED, [...FACTORS[0]].toString()); + + return node; + } + + private makeTerm(): ParserNode + { + let node: ParserNode = this.makeFactor(); + + while (TERMS.includes(this.currentType)) + { + const operator: Token = this.currentToken; + this.advance(); + + const rightFactor: ParserNode = this.makeFactor(); + node = new BinaryOperationNode(node, operator, rightFactor); + } + + return node; + } + + private makeArithmeticExpression(): ParserNode + { + let node: ParserNode = this.makeTerm(); + + while ([SymbolType.PLUS, SymbolType.MINUS].includes(this.currentType)) + { + const operator: Token = this.currentToken; + this.advance(); + + const right: ParserNode = this.makeTerm(); + node = new BinaryOperationNode(node, operator, right); + } + + return node; + } + + private makeComparisonExpression(): ParserNode + { + if (this.currentType === SymbolType.EXCLAMATION) + { + const operation: Token = this.currentToken; + this.advance(); + + const node: ParserNode = this.makeComparisonExpression(); + + return new UnaryOperationNode(operation, node); + } + + let node: ParserNode = this.makeArithmeticExpression(); + + while ([SymbolType.EQUALS, SymbolType.NOT_EQUAL, SymbolType.MORE_THAN, SymbolType.LESS_THAN, SymbolType.MORE_OR_EQUALS_TO, SymbolType.LESS_OR_EQUALS_TO].includes(this.currentType)) + { + const operator: Token = this.currentToken; + this.advance(); + + const right: ParserNode = this.makeArithmeticExpression(); + node = new BinaryOperationNode(node, operator, right); + } + + return node; + } + + private makeExpression(): ParserNode + { + if (this.currentType === KeywordType.FUNCTION) + { + this.advance(); + + if (this.currentType !== TokenType.IDENTIFIER) throw new ParserError(ParserErrors.TOKEN_EXPECTED, TokenType.IDENTIFIER); + + const identifier: Token = this.currentToken; + this.advance(); + + if (this.currentType !== SymbolType.LEFT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.LEFT_PARENTHESES); + + this.advance(); + + const parameters: Token[] = []; + + if (this.currentType === TokenType.IDENTIFIER) + { + parameters.push(this.currentToken); + this.advance(); + + while (this.currentType === SymbolType.COMMA) + { + this.advance(); + + if (this.currentType !== TokenType.IDENTIFIER) throw new ParserError(ParserErrors.TOKEN_EXPECTED, TokenType.IDENTIFIER); + + parameters.push(this.currentToken); + + this.advance(); + } + } + + if (this.currentType !== SymbolType.RIGHT_PARENTHESES) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_PARENTHESES); + + this.advance(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + const body: ParserNode = this.makeCodeBlock(); + + while (this.currentType === TokenType.END_OF_LINE) this.advance(); + + return new FunctionAssignementNode(identifier, parameters, body); + } + + if (EXPRESSIONS[0].includes(this.currentType)) + { + this.advance(); + + if (this.currentType !== TokenType.IDENTIFIER) throw new ParserError(ParserErrors.TOKEN_EXPECTED, TokenType.IDENTIFIER); + + const identifier: Token = this.currentToken; + this.advance(); + + if (this.currentType !== SymbolType.ATTRIBUTION) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.ATTRIBUTION); + + this.advance(); + + const expression: ParserNode = this.makeExpression(); + + return new VariableAssignementNode(identifier, expression); + } + + let node: ParserNode = this.makeComparisonExpression(); + + while (EXPRESSIONS[1].includes(this.currentType)) + { + const operator: Token = this.currentToken; + this.advance(); + + const right: ParserNode = this.makeComparisonExpression(); + node = new BinaryOperationNode(node, operator, right); + } + + return node; + } + + public makeCodeBlock(): ParserNode + { + const nodes: ParserNode[] = []; + + if (this.currentType !== SymbolType.LEFT_BRACE) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.LEFT_BRACE); + + this.advance(); + + while(this.currentType !== SymbolType.RIGHT_BRACE) + { + if (this.currentType === TokenType.END_OF_LINE) + { + this.advance(); + continue; + } + + nodes.push(this.makeExpression()); + } + + if (this.currentType !== SymbolType.RIGHT_BRACE) throw new ParserError(ParserErrors.TOKEN_EXPECTED, SymbolType.RIGHT_BRACE); + + this.advance(); + + return new CodeBlockNode(nodes); + } +} \ No newline at end of file diff --git a/src/parser/ParserError.ts b/src/parser/ParserError.ts new file mode 100644 index 0000000..c8c3ac6 --- /dev/null +++ b/src/parser/ParserError.ts @@ -0,0 +1,21 @@ +export class ParserError +{ + name: string; + details: string; + + constructor(name: string, details: string) + { + this.name = name; + this.details = details; + } + + public asString(): string + { + return `[PARSER] ${this.name}: ${this.details}`; + } +} + +export class ParserErrors +{ + public static readonly TOKEN_EXPECTED: string = 'TOKEN_EXPECTED'; +} \ No newline at end of file diff --git a/src/parser/ParserResult.ts b/src/parser/ParserResult.ts new file mode 100644 index 0000000..9b71624 --- /dev/null +++ b/src/parser/ParserResult.ts @@ -0,0 +1,13 @@ +import { ParserNode } from './nodes/ParserNode'; + +export class ParserResult +{ + node: ParserNode | null = null; + + public success(result: ParserResult): ParserResult + { + this.node = result.node; + + return this; + } +} \ No newline at end of file diff --git a/src/parser/nodes/BinaryOperationNode.ts b/src/parser/nodes/BinaryOperationNode.ts new file mode 100644 index 0000000..5a9019f --- /dev/null +++ b/src/parser/nodes/BinaryOperationNode.ts @@ -0,0 +1,22 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class BinaryOperationNode extends ParserNode +{ + leftNode: ParserNode; + operationToken: Token; + rightNode: ParserNode; + + constructor(leftNode: ParserNode, operationToken: Token, rightNode: ParserNode) + { + super(); + this.leftNode = leftNode; + this.operationToken = operationToken; + this.rightNode = rightNode; + } + + public get representation(): string + { + return `BinaryOperation: (${this.leftNode.representation}, ${this.operationToken.value}, ${this.rightNode.representation})\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/CallingNode.ts b/src/parser/nodes/CallingNode.ts new file mode 100644 index 0000000..c116fe1 --- /dev/null +++ b/src/parser/nodes/CallingNode.ts @@ -0,0 +1,23 @@ +import { ParserNode } from "./ParserNode"; + +export class CallingNode extends ParserNode +{ + identifierNode: ParserNode; + argumentsNodes: ParserNode[]; + + constructor(identifierNode: ParserNode, argumentsNodes: ParserNode[]) + { + super(); + this.identifierNode = identifierNode; + this.argumentsNodes = argumentsNodes; + } + + public get representation(): string + { + var argumentsReps: string[] = []; + + for(const argument of this.argumentsNodes) argumentsReps.push(argument.representation); + + return `Call: ${this.identifierNode.representation}(${argumentsReps.join(',')})\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/CodeBlockNode.ts b/src/parser/nodes/CodeBlockNode.ts new file mode 100644 index 0000000..4579fea --- /dev/null +++ b/src/parser/nodes/CodeBlockNode.ts @@ -0,0 +1,21 @@ +import { ParserNode } from "./ParserNode"; + +export class CodeBlockNode extends ParserNode +{ + expressions: ParserNode[]; + + constructor(expressions: ParserNode[]) + { + super(); + this.expressions = expressions; + } + + public get representation(): string + { + const expressionsReps: string[] = []; + + for (const expression of this.expressions) expressionsReps.push(expression.representation); + + return `CodeBlock: (${expressionsReps.join(',')})\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/ForNode.ts b/src/parser/nodes/ForNode.ts new file mode 100644 index 0000000..ab05103 --- /dev/null +++ b/src/parser/nodes/ForNode.ts @@ -0,0 +1,25 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class ForNode +{ + identifier: Token; + initialValue: ParserNode; + targetValue: ParserNode; + step: ParserNode; + body: ParserNode; + + constructor(identifier: Token, initialValue: ParserNode, targetValue: ParserNode, step: ParserNode, body: ParserNode) + { + this.identifier = identifier; + this.initialValue = initialValue; + this.targetValue = targetValue; + this.step = step; + this.body = body; + } + + public get representation(): string + { + return `For: (Variable: ${this.identifier.value}, Params: (${this.initialValue.representation} TO ${this.targetValue.representation} STEP ${this.step.representation}), Body: (${this.body.representation}))\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/FunctionAssignementNode.ts b/src/parser/nodes/FunctionAssignementNode.ts new file mode 100644 index 0000000..e638bb0 --- /dev/null +++ b/src/parser/nodes/FunctionAssignementNode.ts @@ -0,0 +1,26 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class FunctionAssignementNode extends ParserNode +{ + variableToken: Token; + parametersTokens: Token[]; + body: ParserNode; + + constructor(variableToken: Token, parametersTokens: Token[], body: ParserNode) + { + super(); + this.variableToken = variableToken; + this.parametersTokens = parametersTokens; + this.body = body; + } + + public get representation(): string + { + var paramsReps: string[] = []; + + for(const parameter of this.parametersTokens) paramsReps.push(parameter.value); + + return `FunctionAssignement: (${this.variableToken.value}, Parameters: (${paramsReps.join(',')}), Body: (${this.body.representation}))\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/IfCase.ts b/src/parser/nodes/IfCase.ts new file mode 100644 index 0000000..1538ec4 --- /dev/null +++ b/src/parser/nodes/IfCase.ts @@ -0,0 +1,18 @@ +import { ParserNode } from "./ParserNode"; + +export class IfCase +{ + conditionNode: ParserNode; + expressionNode: ParserNode; + + constructor(conditionNode: ParserNode, expressionNode: ParserNode) + { + this.conditionNode = conditionNode; + this.expressionNode = expressionNode; + } + + public get representation(): string + { + return `Case: Condition: (${this.conditionNode.representation}), Expression: (${this.expressionNode.representation})\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/IfNode.ts b/src/parser/nodes/IfNode.ts new file mode 100644 index 0000000..a456a79 --- /dev/null +++ b/src/parser/nodes/IfNode.ts @@ -0,0 +1,24 @@ +import { IfCase } from './IfCase'; +import { ParserNode } from "./ParserNode"; + +export class IfNode extends ParserNode +{ + cases: IfCase[]; + elseCase: ParserNode | null; + + constructor(cases: IfCase[], elseCase: ParserNode | null) + { + super(); + this.cases = cases; + this.elseCase = elseCase; + } + + public get representation(): string + { + var casesReps: string[] = []; + + for(const casex of this.cases) casesReps.push(casex.representation); + + return `If: (${casesReps.join(',')}, Else: (${this.elseCase ? this.elseCase.representation : 'None'}))\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/NumberNode.ts b/src/parser/nodes/NumberNode.ts new file mode 100644 index 0000000..719fb2b --- /dev/null +++ b/src/parser/nodes/NumberNode.ts @@ -0,0 +1,18 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class NumberNode extends ParserNode +{ + token: Token; + + constructor(token: Token) + { + super(); + this.token = token; + } + + public get representation(): string + { + return `Number: ${this.token.value}\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/ParserNode.ts b/src/parser/nodes/ParserNode.ts new file mode 100644 index 0000000..1c44ffb --- /dev/null +++ b/src/parser/nodes/ParserNode.ts @@ -0,0 +1,7 @@ +export class ParserNode +{ + public get representation(): string + { + return ''; + } +} \ No newline at end of file diff --git a/src/parser/nodes/StringNode.ts b/src/parser/nodes/StringNode.ts new file mode 100644 index 0000000..031e519 --- /dev/null +++ b/src/parser/nodes/StringNode.ts @@ -0,0 +1,18 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class StringNode extends ParserNode +{ + token: Token; + + constructor(token: Token) + { + super(); + this.token = token; + } + + public get representation(): string + { + return `String: "${this.token.value}"\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/UnaryOperationNode.ts b/src/parser/nodes/UnaryOperationNode.ts new file mode 100644 index 0000000..e4e9c49 --- /dev/null +++ b/src/parser/nodes/UnaryOperationNode.ts @@ -0,0 +1,20 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class UnaryOperationNode extends ParserNode +{ + operationToken: Token; + node: ParserNode; + + constructor(operationToken: Token, node: ParserNode) + { + super(); + this.operationToken = operationToken; + this.node = node; + } + + public get representation(): string + { + return `UnaryOperation: (${this.operationToken.value}, ${this.node.representation})\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/VariableAccessNode.ts b/src/parser/nodes/VariableAccessNode.ts new file mode 100644 index 0000000..9e67e8f --- /dev/null +++ b/src/parser/nodes/VariableAccessNode.ts @@ -0,0 +1,18 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class VariableAccessNode extends ParserNode +{ + variableToken: Token; + + constructor(variableToken: Token) + { + super(); + this.variableToken = variableToken; + } + + public get representation(): string + { + return `VariableAccess: ${this.variableToken.value}\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/VariableAssignementNode.ts b/src/parser/nodes/VariableAssignementNode.ts new file mode 100644 index 0000000..af2bc2e --- /dev/null +++ b/src/parser/nodes/VariableAssignementNode.ts @@ -0,0 +1,20 @@ +import { Token } from "../../lexer/Token"; +import { ParserNode } from "./ParserNode"; + +export class VariableAssignementNode extends ParserNode +{ + variableToken: Token; + node: ParserNode; + + constructor(variableToken: Token, node: ParserNode) + { + super(); + this.variableToken = variableToken; + this.node = node; + } + + public get representation(): string + { + return `VariableAssignement: (${this.variableToken.value}, ${this.node.representation})\n`; + } +} \ No newline at end of file diff --git a/src/parser/nodes/WhileNode.ts b/src/parser/nodes/WhileNode.ts new file mode 100644 index 0000000..b8ac203 --- /dev/null +++ b/src/parser/nodes/WhileNode.ts @@ -0,0 +1,18 @@ +import { ParserNode } from "./ParserNode"; + +export class WhileNode +{ + conditionNode: ParserNode; + body: ParserNode; + + constructor(conditionNode: ParserNode, body: ParserNode) + { + this.conditionNode = conditionNode; + this.body = body; + } + + public get representation(): string + { + return `While: (Condition: (${this.conditionNode.representation}), Body: (${this.body.representation}))\n`; + } +} \ No newline at end of file