import * as Expressions from "./expressions"; import * as Commands from "./commands"; import * as Parsers from "../typeSystem/parsers"; import { Types } from "../typeSystem/types"; import { ArrayType } from "../typeSystem/array_type"; import { SourceInfo } from "./sourceInfo"; import { convertFromString } from "./operators"; import { SyntaxErrorFactory } from "./error/syntaxErrorFactory"; import { LanguageDefinedFunction } from "../processor/definedFunctions"; import { LanguageService } from "../services/languageService"; export class IVProgParser { static createParser (input, fill = true) { const lexer = LanguageService.getCurrentLexer(); const parser = new IVProgParser(input, lexer); if (fill) { parser.fill(); } return parser; } // static get BASE () { return 0; } static get FUNCTION () { return 1; } static get COMMAND () { return 2; } static get BREAKABLE () { return 4; } // // static get EOF_TOKEN () { return { text: null, line: -1, col: -1, value: null, offset: -1, lineBreaks: false, type: "EOF", }; } /** * @param {string} input * @param {IVProgLexer} ivprogLexer **/ constructor (input, ivprogLexer) { this.ivprogLexer = ivprogLexer; this.inputStream = input; this.lexer = ivprogLexer.lexer; this.tokenStream = []; this.pos = 0; this.ruleNames = ivprogLexer.getRules(); this.variableTypes = Object.entries(this.ivprogLexer.getTypeKeys()).map( ([key, _]) => key ); this.functionTypes = this.variableTypes.concat(this.ruleNames.RK_VOID); this.parsingArrayDimension = 0; this.scope = []; this.langFuncs = this.ivprogLexer.getLangFuncs(); this.definedFuncsNameList = []; this.definedVariablesStack = []; } fill (stream = null) { if (stream) { this.tokenStream = stream; return; } this.tokenStream = Array.from(this.lexer.reset(this.inputStream)); this.tokenStream = this.tokenStream.filter((token) => { // Throws an exception in case of invalid syntax if (token.type === this.ruleNames.ERROR) { let text = token.text; const line = token.line; const column = token.col; throw SyntaxErrorFactory.invalid_syntax(text, line, column); } // remove all whitespaces token and comments return ( token.type !== this.ruleNames.WHITESPACE && token.type !== this.ruleNames.COMMENTS ); }); } parseTree () { return this.parseProgram(); } /** * @param {number} index * @return {moo.Token} * */ getToken (index = this.pos) { // if(index === null) // index = this.pos; if (index >= this.tokenStream.length) { return IVProgParser.EOF_TOKEN; } return this.tokenStream[index]; } insideScope (scope) { if (this.scope.length <= 0) { return IVProgParser.BASE === scope; } else { return this.scope[this.scope.length - 1] === scope; } } pushScope (scope) { this.scope.push(scope); } pushVariableStack () { this.definedVariablesStack.push([]); } popScope () { return this.scope.pop(); } popVariableStack () { return this.definedVariablesStack.pop(); } getCurrentVariableStack () { return this.definedVariablesStack[this.definedVariablesStack.length - 1]; } isEOF () { return this.getToken(this.pos).type === IVProgParser.EOF_TOKEN.type; } parseProgram () { this.consumeNewLines(); const token = this.getToken(); let globalVars = []; let functions = []; if (this.ruleNames.RK_PROGRAM === token.type) { this.pos++; this.consumeNewLines(); this.checkOpenCurly(); this.pos++; this.pushVariableStack(); for (;;) { this.consumeNewLines(); const token = this.getToken(); if ( token.type === this.ruleNames.RK_CONST || this.isVariableType(token) ) { globalVars = globalVars.concat(this.parseGlobalVariables()); } else if (token.type === this.ruleNames.RK_FUNCTION) { this.pushVariableStack(); functions = functions.concat(this.parseFunction()); this.popVariableStack(); } else { break; } } this.consumeNewLines(); this.checkCloseCurly(); this.pos++; this.consumeNewLines(); if (!this.isEOF()) { console.log(this.getToken()); throw SyntaxErrorFactory.extra_lines(); } this.popVariableStack(); return { global: globalVars, functions: functions }; } else { throw SyntaxErrorFactory.token_missing_one( this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_PROGRAM], token ); } } checkOpenCurly (attempt = false) { const token = this.getToken(); if (this.ruleNames.OPEN_CURLY !== token.type) { if (!attempt) throw SyntaxErrorFactory.token_missing_one("{", token); else return false; } return true; } checkCloseCurly (attempt = false) { const token = this.getToken(); if (this.ruleNames.CLOSE_CURLY !== token.type) { if (!attempt) throw SyntaxErrorFactory.token_missing_one("}", token); else return false; } return true; } /* It checks if the current token at position pos is a ']'. * As a check function it doesn't increment pos. * * @params bool:attempt, indicates that the token is optional. Defaults: false * * @returns true if the attempt is true and current token is '[', * false is attempt is true and current token is not '[' **/ checkOpenBrace (attempt = false) { const token = this.getToken(); if (this.ruleNames.OPEN_BRACE !== token.type) { if (!attempt) { throw SyntaxErrorFactory.token_missing_one("[", token); } else { return false; } } return true; } checkCloseBrace (attempt = false) { const token = this.getToken(); if (this.ruleNames.CLOSE_BRACE !== token.type) { if (!attempt) { throw SyntaxErrorFactory.token_missing_one("]", token); } else { return false; } } return true; } checkOpenParenthesis (attempt = false) { const token = this.getToken(); if (this.ruleNames.OPEN_PARENTHESIS !== token.type) { if (!attempt) { throw SyntaxErrorFactory.token_missing_one("(", token); } else { return false; } } return true; } checkCloseParenthesis (attempt = false) { const token = this.getToken(); if (this.ruleNames.CLOSE_PARENTHESIS !== token.type) { if (!attempt) { throw SyntaxErrorFactory.token_missing_one(")", token); } else { return false; } } return true; } checkEOS (attempt = false) { const eosToken = this.getToken(); if (eosToken.type !== this.ruleNames.EOS) { if (!attempt) throw SyntaxErrorFactory.eos_missing(eosToken); else return false; } return true; } checkFunctionDuplicate (functionID, funcIDToken) { const id = functionID === null ? "$main" : functionID; const index = this.definedFuncsNameList.indexOf(id); if (index !== -1) { throw SyntaxErrorFactory.duplicate_function(funcIDToken); } this.definedFuncsNameList.push(id); } checkVariableDuplicate (variableID, sourceInfo) { const index = this.getCurrentVariableStack().indexOf(variableID); if (index !== -1) { throw SyntaxErrorFactory.duplicate_variable(sourceInfo); } this.getCurrentVariableStack().push(variableID); } consumeForSemiColon () { const eosToken = this.getToken(); if (eosToken.type === this.ruleNames.EOS && eosToken.text.match("^;$")) { this.pos++; return; } throw SyntaxErrorFactory.token_missing_one(";", eosToken); } parseGlobalVariables () { const decl = this.parseMaybeConst(); this.checkEOS(); this.pos++; return decl; } /* * Checks if the next token is PR_CONST. It's only available * at global variables declaration level * @returns Declararion(const, type, id, initVal?) **/ parseMaybeConst () { const constToken = this.getToken(); if (constToken.type === this.ruleNames.RK_CONST) { this.pos++; const typeString = this.parseType(); return this.parseDeclaration(typeString, true); } else if (this.isVariableType(constToken)) { const typeString = this.parseType(); return this.parseDeclaration(typeString); } else { throw SyntaxErrorFactory.token_missing_list( [this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_CONST]].concat( this.getTypeArray() ), constToken ); } } /** * Parses a declarion of the form: type --- id --- (= --- EAnd)? * @return {Commands.Declartion[]} a list of Declararion(const, type, id, initVal?) **/ parseDeclaration (typeString, isConst = false) { let initial = null; let dim1 = null; let dim2 = null; let dimensions = 0; const sourceInfo = SourceInfo.createSourceInfo(this.getToken()); const idString = this.parseID(); this.checkVariableDuplicate(idString, sourceInfo); // Check for array or vector // ID[int/IDi?][int/IDj?] if (this.checkOpenBrace(true)) { this.pos += 1; this.consumeNewLines(); dim1 = this.parseArrayDimension(); this.consumeNewLines(); this.checkCloseBrace(); this.pos += 1; dimensions += 1; if (this.checkOpenBrace(true)) { this.pos += 1; this.consumeNewLines(); dim2 = this.parseArrayDimension(); this.consumeNewLines(); this.checkCloseBrace(); this.pos += 1; dimensions += 1; } return this.parseArrayDeclaration( typeString, isConst, idString, sourceInfo, dimensions, dim1, dim2 ); } else { const assignmentToken = this.getToken(); if (isConst && assignmentToken.type !== this.ruleNames.ASSIGNMENT) { throw SyntaxErrorFactory.const_not_init(sourceInfo); } if (assignmentToken.type === this.ruleNames.ASSIGNMENT) { this.pos++; initial = this.parseExpressionOR(); } const declaration = new Commands.Declaration( idString, typeString, initial, isConst ); declaration.sourceInfo = sourceInfo; const commaToken = this.getToken(); if (commaToken.type === this.ruleNames.COMMA) { this.pos++; this.consumeNewLines(); return [declaration].concat(this.parseDeclaration(typeString, isConst)); } else { return [declaration]; } } } parseArrayDeclaration ( typeString, isConst, idString, sourceInfo, dimensions, dim1, dim2 ) { const assignmentToken = this.getToken(); let n_lines = dim1; let n_columns = dim2; let initial = null; let dim_is_id = false; if ( dim1 instanceof Expressions.VariableLiteral || dim2 instanceof Expressions.VariableLiteral ) { dim_is_id = true; if (dimensions > 1 && (dim1 == null || dim2 == null)) { throw SyntaxErrorFactory.invalid_matrix_id_dimension( SourceInfo.createSourceInfo(assignmentToken) ); } } if (isConst && assignmentToken.type !== this.ruleNames.ASSIGNMENT) { throw SyntaxErrorFactory.const_not_init(sourceInfo); } if (assignmentToken.type === this.ruleNames.ASSIGNMENT) { if (dim_is_id) { if (dimensions == 1) { throw SyntaxErrorFactory.invalid_vector_init( SourceInfo.createSourceInfo(assignmentToken) ); } else { throw SyntaxErrorFactory.invalid_matrix_init( SourceInfo.createSourceInfo(assignmentToken) ); } } this.pos += 1; initial = this.parseArrayLiteral(typeString); } if (initial == null && dim1 == null) { if (dimensions > 1) { throw SyntaxErrorFactory.cannot_infer_matrix_line(idString, sourceInfo); } throw SyntaxErrorFactory.cannot_infer_vector_size(idString, sourceInfo); } if (dimensions > 1) { if (initial == null && dim2 == null) { throw SyntaxErrorFactory.cannot_infer_matrix_column( idString, sourceInfo ); } } if (dimensions === 1 && initial != null && !initial.isVector) { const expString = initial.toString(); throw SyntaxErrorFactory.matrix_to_vector_literal_attr( idString, expString, initial.sourceInfo ); } else if (dimensions > 1 && initial != null && initial.isVector) { const expString = initial.toString(); throw SyntaxErrorFactory.vector_to_matrix_literal_attr( idString, expString, initial.sourceInfo ); } if (dim1 == null) { n_lines = new Expressions.IntLiteral(Parsers.toInt(initial.lines)); n_lines.sourceInfo = sourceInfo; } if (dimensions > 1) { if (dim2 == null) { n_columns = new Expressions.IntLiteral(Parsers.toInt(initial.columns)); n_columns.sourceInfo = sourceInfo; } } const declaration = new Commands.ArrayDeclaration( idString, new ArrayType(typeString, dimensions), n_lines, n_columns, initial, isConst ); declaration.sourceInfo = sourceInfo; const commaToken = this.getToken(); if (commaToken.type === this.ruleNames.COMMA) { this.pos++; this.consumeNewLines(); return [declaration].concat(this.parseDeclaration(typeString, isConst)); } else { return [declaration]; } } consumeNewLines () { let token = this.getToken(); while ( token && token.type === this.ruleNames.EOS && token.text.match("^[\r\n]+$") ) { this.pos++; token = this.getToken(); } } isVariableType (token) { return this.variableTypes.find((v) => v === token.type); } /** * Reads the next token of the stream to check if it is a Integer or an ID. * @returns Integer | ID **/ parseArrayDimension () { const dimToken = this.getToken(); if (dimToken.type === this.ruleNames.INTEGER) { //parse as int literal this.pos++; return this.getIntLiteral(dimToken); } else if (dimToken.type === this.ruleNames.ID) { //parse as variable this.pos++; return this.parseVariable(dimToken); } else if (dimToken.type === this.ruleNames.CLOSE_BRACE) { return null; } else { throw SyntaxErrorFactory.invalid_array_dimension( this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_INTEGER], dimToken ); } } /** * Returns an object {type: 'int', value: value}. * It checks for binary and hexadecimal integers. * @param {moo.Token} token * @return {Object} object with fields type and value **/ getIntLiteral (token) { const text = token.text; const sourceInfo = SourceInfo.createSourceInfo(token); const exp = new Expressions.IntLiteral(Parsers.toInt(text)); exp.sourceInfo = sourceInfo; return exp; } getRealLiteral (token) { const sourceInfo = SourceInfo.createSourceInfo(token); const exp = new Expressions.RealLiteral(Parsers.toReal(token.text)); exp.sourceInfo = sourceInfo; return exp; } getStringLiteral (token) { const text = token.text; const sourceInfo = SourceInfo.createSourceInfo(token); const exp = new Expressions.StringLiteral(Parsers.toString(text)); exp.sourceInfo = sourceInfo; return exp; } getCharLiteral (token) { const text = token.text; const exp = new Expressions.CharLiteral(Parsers.toChar(text)); exp.sourceInfo = SourceInfo.createSourceInfo(token); return exp; } getBoolLiteral (token) { const val = Parsers.toBool(token.text); const exp = new Expressions.BoolLiteral(val); exp.sourceInfo = SourceInfo.createSourceInfo(token); return exp; } parseArrayLiteral (typeString) { const openCurly = this.checkOpenCurly(true); if (!openCurly) { const invalid_token = this.getToken(); throw SyntaxErrorFactory.array_init_not_literal( SourceInfo.createSourceInfo(invalid_token) ); } const beginArray = this.getToken(); if (this.parsingArrayDimension >= 2) { throw SyntaxErrorFactory.array_exceeds_2d( SourceInfo.createSourceInfo(beginArray) ); } this.pos += 1; this.parsingArrayDimension += 1; this.consumeNewLines(); let data = null; const maybeCurlyOpen = this.checkOpenCurly(true); if (maybeCurlyOpen) { // This is potentially a list of vectors data = this.parseVectorList(typeString); } else { data = this.parseExpressionList(); } this.consumeNewLines(); this.checkCloseCurly(); const endArray = this.getToken(); this.pos += 1; this.parsingArrayDimension -= 1; const sourceInfo = SourceInfo.createSourceInfoFromList( beginArray, endArray ); let dataDim = 1; if (data[0] instanceof Expressions.ArrayLiteral) { dataDim += 1; } else if (data.length == 1) { console.log("Talvez uma variável seja uma melhor opção"); } const type = new ArrayType(typeString, dataDim); const exp = new Expressions.ArrayLiteral(type, data); exp.sourceInfo = sourceInfo; return exp; } /** * Returns a list of ArrayLiterals. Helper function for parsing matrices */ parseVectorList (typeString) { const list = []; let lastSize = null; for (;;) { this.checkOpenCurly(); const beginArray = this.getToken(); if (this.parsingArrayDimension >= 2) { throw SyntaxErrorFactory.array_exceeds_2d( SourceInfo.createSourceInfo(beginArray) ); } this.pos += 1; this.parsingArrayDimension += 1; this.consumeNewLines(); const data = this.parseExpressionList(); this.consumeNewLines(); this.checkCloseCurly(); const endArray = this.getToken(); this.pos += 1; this.parsingArrayDimension -= 1; const sourceInfo = SourceInfo.createSourceInfoFromList( beginArray, endArray ); if (lastSize == null) { lastSize = data.length; } else if (lastSize !== data.length) { const expString = this.inputStream.substring( beginArray.offset, endArray.offset + endArray.text.length ); throw SyntaxErrorFactory.invalid_matrix_literal_line( expString, sourceInfo ); } const type = new ArrayType(typeString, 1); const exp = new Expressions.ArrayLiteral(type, data); exp.sourceInfo = sourceInfo; list.push(exp); const commaToken = this.getToken(); if (commaToken.type !== this.ruleNames.COMMA) { break; } this.pos += 1; this.consumeNewLines(); } if (list.length == 1) { console.log("Talvez um vetor seja uma melhor opção"); } return list; } /* * Returns an object {type: 'variable', value: value}. * @returns object with fields type and value **/ parseVariable (token) { const sourceInfo = SourceInfo.createSourceInfo(token); const exp = new Expressions.VariableLiteral(token.text); exp.sourceInfo = sourceInfo; return exp; } /* * Returns an object representing a function. It has * four attributes: returnType, id, formalParams and block. * The block object has two attributes: declarations and commands **/ parseFunction () { this.pushScope(IVProgParser.FUNCTION); let formalParams = []; const token = this.getToken(); if (token.type !== this.ruleNames.RK_FUNCTION) { //throw SyntaxError.createError(this.lexer.literalNames[this.lexerClass.PR_FUNCAO], token); return null; } this.pos++; const funType = this.parseType(); let dimensions = 0; if (this.checkOpenBrace(true)) { this.pos++; this.checkCloseBrace(); this.pos++; dimensions++; if (this.checkOpenBrace(true)) { this.pos++; this.checkCloseBrace(); this.pos++; dimensions++; } } const funcIDToken = this.getToken(); const functionID = this.parseID(); this.checkFunctionDuplicate(functionID, funcIDToken); this.checkOpenParenthesis(); this.pos++; this.consumeNewLines(); if (!this.checkCloseParenthesis(true)) { formalParams = this.parseFormalParameters(); // formal parameters this.consumeNewLines(); this.checkCloseParenthesis(); this.pos++; } else { this.pos++; } this.consumeNewLines(); const commandsBlock = this.parseCommandBlock(); let returnType = funType; if (dimensions > 0) { returnType = new ArrayType(funType, dimensions); } const func = new Commands.Function( functionID, returnType, formalParams, commandsBlock ); if (functionID === null && !func.isMain) { throw SyntaxErrorFactory.invalid_main_return( LanguageDefinedFunction.getMainFunctionName(), this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_VOID], token.line ); } else if (func.isMain && formalParams.length !== 0) { throw SyntaxErrorFactory.main_parameters(); } this.popScope(); func.sourceInfo = SourceInfo.createSourceInfo(funcIDToken); return func; } /* * Parse the formal parameters of a function. * @returns a list of objects with the following attributes: type, id and dimensions. **/ parseFormalParameters () { const list = []; for (;;) { let dimensions = 0; let reference = false; const typeString = this.parseType(); let maybeIDToken = this.getToken(); if (maybeIDToken.type === this.ruleNames.RK_REFERENCE) { reference = true; this.pos += 1; maybeIDToken = this.getToken(); } const idString = this.parseID(); this.checkVariableDuplicate(idString, maybeIDToken); if (this.checkOpenBrace(true)) { this.pos += 1; dimensions += 1; this.checkCloseBrace(); this.pos += 1; if (this.checkOpenBrace(true)) { this.pos += 1; dimensions += 1; this.checkCloseBrace(); this.pos += 1; } } let type = null; if (dimensions > 0) { type = new ArrayType(typeString, dimensions); } else { type = typeString; } const parameter = new Commands.FormalParameter(type, idString, reference); parameter.sourceInfo = SourceInfo.createSourceInfo(maybeIDToken); list.push(parameter); const commaToken = this.getToken(); if (commaToken.type !== this.ruleNames.COMMA) break; this.pos++; this.consumeNewLines(); } return list; } parseID () { const token = this.getToken(); if (token.type !== this.ruleNames.ID) { throw SyntaxErrorFactory.id_missing(token); } this.pos++; if (this.insideScope(IVProgParser.FUNCTION)) { if (token.text === LanguageDefinedFunction.getMainFunctionName()) { return null; } } return token.text; } /** * @return {string} **/ parseMaybeLibID () { const token = this.getToken(); if (token.type !== this.ruleNames.ID) { throw SyntaxErrorFactory.id_missing(token); } const maybeDOT = this.getToken(this.pos + 1); if (maybeDOT.type === this.ruleNames.DOT) { this.pos += 2; const anotherID = this.getToken(); if (anotherID.type !== this.ruleNames.ID) { throw SyntaxErrorFactory.id_missing(anotherID); } this.pos++; return `${token.text}.${anotherID.text}`; } this.pos++; return token.text; } parseType () { const token = this.getToken(); if ( token.type === this.ruleNames.ID && this.insideScope(IVProgParser.FUNCTION) ) { return Types.VOID; } else if ( token.type === this.ruleNames.RK_VOID && this.insideScope(IVProgParser.FUNCTION) ) { this.pos++; return Types.VOID; } else if (this.isVariableType(token)) { this.pos++; switch (token.type) { case this.ruleNames.RK_INTEGER: return Types.INTEGER; case this.ruleNames.RK_BOOLEAN: return Types.BOOLEAN; case this.ruleNames.RK_REAL: return Types.REAL; case this.ruleNames.RK_STRING: return Types.STRING; case this.ruleNames.RK_CHARACTER: return Types.CHAR; default: break; } } throw SyntaxErrorFactory.invalid_type(this.getTypeArray(), token); } parseCommandBlock (optionalCurly = false) { let variablesDecl = []; const commands = []; let hasOpen = false; if (this.checkOpenCurly(optionalCurly)) { this.pos++; hasOpen = true; } this.consumeNewLines(); let parsedCommand = false; for (;;) { const cmd = this.parseCommand(); if (cmd === null) break; if (cmd !== -1) { if (cmd instanceof Array) { if (parsedCommand) { const lastToken = this.getToken(this.pos - 1); throw SyntaxErrorFactory.invalid_var_declaration(lastToken); } variablesDecl = variablesDecl.concat(cmd); } else { parsedCommand = true; commands.push(cmd); } } } this.consumeNewLines(); if (hasOpen) { this.checkCloseCurly(); this.pos++; this.consumeNewLines(); } return new Commands.CommandBlock(variablesDecl, commands); } parseCommand () { const token = this.getToken(); if (this.isVariableType(token)) { if (!this.insideScope(IVProgParser.FUNCTION)) { throw SyntaxErrorFactory.invalid_var_declaration(token); } this.pushScope(IVProgParser.BASE); const varType = this.parseType(); this.popScope(); const cmd = this.parseDeclaration(varType); this.checkEOS(); this.pos++; return cmd; } else if (token.type === this.ruleNames.ID) { return this.parseIDCommand(); } else if (token.type === this.ruleNames.DOT) { // TODO Check if this is relevant since DOT is a replacement for antlr4 LIB_ID :=> ID . ID throw SyntaxErrorFactory.invalid_syntax( token.text, token.line, token.col ); } else if (token.type === this.ruleNames.RK_RETURN) { return this.parseReturn(); } else if ( token.type === this.ruleNames.RK_WHILE || token.type === this.ruleNames.RK_WHILE_ALT ) { return this.parseWhile(); } else if ( token.type === this.ruleNames.RK_FOR || token.type === this.ruleNames.RK_FOR_ALT ) { return this.parseFor(); } else if (token.type === this.ruleNames.RK_BREAK) { if (!this.insideScope(IVProgParser.BREAKABLE)) { throw SyntaxErrorFactory.invalid_break_command( this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_BREAK], token ); } return this.parseBreak(); } else if (token.type === this.ruleNames.RK_SWITCH) { return this.parseSwitchCase(); } else if (token.type === this.ruleNames.RK_DO) { return this.parseRepeatUntil(); } else if (token.type === this.ruleNames.RK_IF) { return this.parseIfThenElse(); } else if (this.checkEOS(true)) { this.pos++; return -1; } else { return null; } } parseSwitchCase () { const token = this.getToken(); this.pushScope(IVProgParser.BREAKABLE); this.pos++; this.checkOpenParenthesis(); this.pos++; this.consumeNewLines(); const exp = this.parseExpressionOR(); this.consumeNewLines(); this.checkCloseParenthesis(); this.pos++; this.consumeNewLines(); this.checkOpenCurly(); this.pos++; this.consumeNewLines(); const casesList = this.parseCases(); this.consumeNewLines(); this.checkCloseCurly(); this.pos++; this.consumeNewLines(); this.popScope(); const command = new Commands.Switch(exp, casesList); command.sourceInfo = SourceInfo.createSourceInfo(token); return command; } parseRepeatUntil () { const token = this.getToken(); this.pos++; this.consumeNewLines(); this.pushScope(IVProgParser.BREAKABLE); const commandsBlock = this.parseCommandBlock(); this.consumeNewLines(); //Maybe not... const whileToken = this.getToken(); if (whileToken.type !== this.ruleNames.RK_DO_UNTIL) { throw SyntaxErrorFactory.token_missing_one( this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_DO_UNTIL], whileToken ); } this.pos++; this.checkOpenParenthesis(); this.pos++; this.consumeNewLines(); const condition = this.parseExpressionOR(); this.consumeNewLines(); this.checkCloseParenthesis(); this.pos++; this.checkEOS(); this.popScope(); const command = new Commands.RepeatUntil(condition, commandsBlock); command.sourceInfo = SourceInfo.createSourceInfo(token); return command; } parseIfThenElse () { if (this.insideScope(IVProgParser.BREAKABLE)) { this.pushScope(IVProgParser.BREAKABLE); } else { this.pushScope(IVProgParser.COMMAND); } const token = this.getToken(); this.pos++; this.checkOpenParenthesis(); this.pos++; this.consumeNewLines(); const logicalExpression = this.parseExpressionOR(); this.consumeNewLines(); this.checkCloseParenthesis(); this.pos++; this.consumeNewLines(); const cmdBlocks = this.parseCommandBlock(); const maybeElse = this.getToken(); if (maybeElse.type === this.ruleNames.RK_ELSE) { this.pos++; this.consumeNewLines(); const maybeIf = this.getToken(); let elseBlock = null; if (this.checkOpenCurly(true)) { elseBlock = this.parseCommandBlock(); } else if (maybeIf.type === this.ruleNames.RK_IF) { elseBlock = this.parseIfThenElse(); } else { throw SyntaxErrorFactory.token_missing_list( [this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_IF], "{"], maybeIf ); } this.popScope(); const cmd = new Commands.IfThenElse( logicalExpression, cmdBlocks, elseBlock ); cmd.sourceInfo = SourceInfo.createSourceInfo(token); return cmd; } this.popScope(); const cmd = new Commands.IfThenElse(logicalExpression, cmdBlocks, null); cmd.sourceInfo = SourceInfo.createSourceInfo(token); return cmd; } parseFor () { this.pushScope(IVProgParser.BREAKABLE); const for_token = this.getToken(); this.pos += 1; // parse ID const id_token = this.getToken(); const id = this.parseID(); const for_id = new Expressions.VariableLiteral(id); for_id.sourceInfo = SourceInfo.createSourceInfo(id_token); // END parse ID const for_from = this.parseForParameters(this.ruleNames.RK_FOR_FROM); const for_to = this.parseForParameters(this.ruleNames.RK_FOR_TO); const maybePass = this.parseForParameters(this.ruleNames.RK_FOR_PASS); this.consumeNewLines(); const commandsBlock = this.parseCommandBlock(); this.popScope(); const cmd = new Commands.For( for_id, for_from, for_to, maybePass, commandsBlock ); cmd.sourceInfo = SourceInfo.createSourceInfo(for_token); return cmd; } parseWhile () { this.pushScope(IVProgParser.BREAKABLE); const token = this.getToken(); this.pos++; this.checkOpenParenthesis(); this.pos++; this.consumeNewLines(); const logicalExpression = this.parseExpressionOR(); this.consumeNewLines(); this.checkCloseParenthesis(); this.pos++; this.consumeNewLines(); const cmdBlocks = this.parseCommandBlock(); this.popScope(); const cmd = new Commands.While(logicalExpression, cmdBlocks); cmd.sourceInfo = SourceInfo.createSourceInfo(token); return cmd; } parseBreak () { const token = this.getToken(); this.pos++; this.checkEOS(); this.pos++; const command = new Commands.Break(); command.sourceInfo = SourceInfo.createSourceInfo(token); return command; } parseReturn () { const token = this.getToken(); this.pos++; let exp = null; if (!this.checkEOS(true)) { exp = this.parseExpressionOR(); this.checkEOS(); } this.pos++; const returnCommand = new Commands.Return(exp); returnCommand.sourceInfo = SourceInfo.createSourceInfo(token); return returnCommand; } parseIDCommand () { const refToken = this.getToken(); const id = this.parseMaybeLibID(); const isID = id.indexOf(".") === -1; if (this.checkOpenBrace(true)) { this.pos++; let lineExpression = null; let columnExpression = null; this.consumeNewLines(); lineExpression = this.parseExpression(); this.consumeNewLines(); this.checkCloseBrace(); this.pos++; if (this.checkOpenBrace(true)) { this.pos++; this.consumeNewLines(); columnExpression = this.parseExpression(); this.consumeNewLines(); this.checkCloseBrace(); this.pos++; } const assignmentToken = this.getToken(); if (assignmentToken.type !== this.ruleNames.ASSIGNMENT) { // TODO BETTER MESSAGE throw SyntaxErrorFactory.token_missing_one("<-", assignmentToken); } this.pos++; const exp = this.parseExpressionOR(); this.checkEOS(); this.pos++; const cmd = new Commands.ArrayIndexAssign( id, lineExpression, columnExpression, exp ); cmd.sourceInfo = SourceInfo.createSourceInfo(assignmentToken); return cmd; } const assignmentOrParenthesis = this.getToken(); if (isID && assignmentOrParenthesis.type === this.ruleNames.ASSIGNMENT) { this.pos++; const exp = this.parseExpressionOR(); this.checkEOS(); this.pos++; const cmd = new Commands.Assign(id, exp); cmd.sourceInfo = SourceInfo.createSourceInfo(assignmentOrParenthesis); return cmd; } else if ( assignmentOrParenthesis.type === this.ruleNames.OPEN_PARENTHESIS ) { const funcCall = this.parseFunctionCallCommand(id); this.checkEOS(); this.pos++; return funcCall; } else if (isID) { throw SyntaxErrorFactory.token_missing_list( ["<-", "("], assignmentOrParenthesis ); } else { throw SyntaxErrorFactory.invalid_id_format(refToken); } } parseForParameters (keyword_code) { if (keyword_code === this.ruleNames.RK_FOR_PASS) { if (this.checkOpenCurly(true)) { return null; } } const from_token = this.getToken(); if (from_token.type !== keyword_code) { // TODO better error message const keyword = this.ivprogLexer.getReservedKeys()[keyword_code]; throw new Error( "Error de sintaxe no comando repita_para: esperava-se " + keyword + " mas encontrou " + from_token.text ); } this.pos += 1; let int_or_id = this.getToken(); let is_unary_op = false; let op = null; if (int_or_id.type === this.ruleNames.SUM_OP) { is_unary_op = true; op = int_or_id.text; this.pos += 1; int_or_id = this.getToken(); } let for_from = null; if (int_or_id.type === this.ruleNames.ID) { for_from = new Expressions.VariableLiteral(this.parseID()); for_from.sourceInfo = SourceInfo.createSourceInfo(int_or_id); } else if (int_or_id.type === this.ruleNames.INTEGER) { this.pos += 1; for_from = this.getIntLiteral(int_or_id); } if (for_from == null) { // TODO better error message const keyword = this.ivprogLexer.getReservedKeys()[keyword_code]; throw new Error( "Error de sintaxe no comando repeita_para: " + int_or_id.text + " não é compativel com o esperado para o paramentro " + keyword + ". O valor deve ser um inteiro ou variável." ); } if (is_unary_op) { for_from = new Expressions.UnaryApp(convertFromString(op), for_from); } return for_from; } parseCases () { const token = this.getToken(); if (token.type !== this.ruleNames.RK_CASE) { throw SyntaxErrorFactory.token_missing_one( this.ivprogLexer.getReservedKeys()[this.ruleNames.RK_CASE], token ); } this.pos++; const nextToken = this.getToken(); if (nextToken.type === this.ruleNames.RK_DEFAULT) { this.pos++; const colonToken = this.getToken(); if (colonToken.type !== this.ruleNames.COLON) { throw SyntaxErrorFactory.token_missing_one(":", colonToken); } this.pos++; this.consumeNewLines(); const block = this.parseCommandBlock(true); const defaultCase = new Commands.Case(null); defaultCase.sourceInfo = SourceInfo.createSourceInfo(token); defaultCase.setCommands(block.commands); return [defaultCase]; } else { const exp = this.parseExpressionOR(); const colonToken = this.getToken(); if (colonToken.type !== this.ruleNames.COLON) { throw SyntaxErrorFactory.token_missing_one(":", colonToken); } this.pos++; this.consumeNewLines(); const block = this.parseCommandBlock(true); const aCase = new Commands.Case(exp); aCase.sourceInfo = SourceInfo.createSourceInfo(token); aCase.setCommands(block.commands); const caseToken = this.getToken(); if (caseToken.type === this.ruleNames.RK_CASE) { return [aCase].concat(this.parseCases()); } else { return [aCase]; } } } /* * Parses an Expression following the structure: * * EOR => EAnd ( 'or' EOR)? #expression and * * EAnd => ENot ('and' EAnd)? #expression or * * ENot => 'not'? ER #expression not * * ER => E ((>=, <=, ==, >, <) ER)? #expression relational * * E => factor ((+, -) E)? #expression * * factor=> term ((*, /, %) factor)? * * term => literal || arrayAccess || FuncCall || ID || '('EOR')' **/ parseExpressionOR () { let exp1 = this.parseExpressionAND(); while (this.getToken().type === this.ruleNames.OR_OPERATOR) { const opToken = this.getToken(); this.pos++; const or = convertFromString("or"); this.consumeNewLines(); const exp2 = this.parseExpressionAND(); const finalExp = new Expressions.InfixApp(or, exp1, exp2); finalExp.sourceInfo = SourceInfo.createSourceInfo(opToken); exp1 = finalExp; } return exp1; } parseExpressionAND () { let exp1 = this.parseExpressionNot(); while (this.getToken().type === this.ruleNames.AND_OPERATOR) { const opToken = this.getToken(); this.pos++; const and = convertFromString("and"); this.consumeNewLines(); const exp2 = this.parseExpressionNot(); const finalExp = new Expressions.InfixApp(and, exp1, exp2); finalExp.sourceInfo = SourceInfo.createSourceInfo(opToken); exp1 = finalExp; } return exp1; } parseExpressionNot () { const maybeNotToken = this.getToken(); if (maybeNotToken.type === this.ruleNames.NOT_OPERATOR) { const opToken = this.getToken(); this.pos++; const not = convertFromString("not"); const exp1 = this.parseExpressionRel(); const finalExp = new Expressions.UnaryApp(not, exp1); finalExp.sourceInfo = SourceInfo.createSourceInfo(opToken); return finalExp; } else { return this.parseExpressionRel(); } } parseExpressionRel () { let exp1 = this.parseExpression(); while (this.getToken().type === this.ruleNames.RELATIONAL_OPERATOR) { const relToken = this.getToken(); this.pos++; const rel = convertFromString(relToken.text); const exp2 = this.parseExpression(); const finalExp = new Expressions.InfixApp(rel, exp1, exp2); finalExp.sourceInfo = SourceInfo.createSourceInfo(relToken); exp1 = finalExp; } return exp1; } parseExpression () { let factor = this.parseFactor(); while (this.getToken().type === this.ruleNames.SUM_OP) { const sumOpToken = this.getToken(); this.pos++; const op = convertFromString(sumOpToken.text); const factor2 = this.parseFactor(); const finalExp = new Expressions.InfixApp(op, factor, factor2); finalExp.sourceInfo = SourceInfo.createSourceInfo(sumOpToken); factor = finalExp; } return factor; } parseFactor () { let term = this.parseTerm(); while (this.getToken().type === this.ruleNames.MULTI_OP) { const multOpToken = this.getToken(); this.pos++; const op = convertFromString(multOpToken.text); const term2 = this.parseTerm(); const finalExp = new Expressions.InfixApp(op, term, term2); finalExp.sourceInfo = SourceInfo.createSourceInfo(multOpToken); term = finalExp; } return term; } parseTerm () { const token = this.getToken(); let sourceInfo = null; let exp = null; switch (token.type) { case this.ruleNames.SUM_OP: this.pos++; sourceInfo = SourceInfo.createSourceInfo(token); exp = new Expressions.UnaryApp( convertFromString(token.text), this.parseTerm() ); exp.sourceInfo = sourceInfo; return exp; case this.ruleNames.INTEGER: this.pos++; return this.getIntLiteral(token); case this.ruleNames.REAL: this.pos++; return this.getRealLiteral(token); case this.ruleNames.STRING: this.pos++; return this.getStringLiteral(token); case this.ruleNames.CHARACTER: this.pos++; return this.getCharLiteral(token); case this.ruleNames.RK_TRUE: case this.ruleNames.RK_FALSE: this.pos++; return this.getBoolLiteral(token); case this.ruleNames.OPEN_CURLY: // No more annonymous array // return this.parseArrayLiteral(); throw SyntaxErrorFactory.annonymous_array_literal(token); case this.ruleNames.ID: return this.parseIDTerm(); case this.ruleNames.OPEN_PARENTHESIS: return this.parseParenthesisExp(); default: throw SyntaxErrorFactory.invalid_terminal(token); } } parseIDTerm () { const tokenA = this.getToken(); const id = this.parseMaybeLibID(); const isID = id.indexOf(".") === -1; if (isID && this.checkOpenBrace(true)) { let tokenB = null; this.pos++; const firstIndex = this.parseExpression(); let secondIndex = null; this.consumeNewLines(); this.checkCloseBrace(); tokenB = this.getToken(); this.pos++; if (this.checkOpenBrace(true)) { this.pos++; secondIndex = this.parseExpression(); this.consumeNewLines(); this.checkCloseBrace(); tokenB = this.getToken(); this.pos++; } const sourceInfo = SourceInfo.createSourceInfoFromList(tokenA, tokenB); const exp = new Expressions.ArrayAccess(id, firstIndex, secondIndex); exp.sourceInfo = sourceInfo; return exp; } else if (this.checkOpenParenthesis(true)) { return this.parseFunctionCallExpression(id); } else if (isID) { const sourceInfo = SourceInfo.createSourceInfo(tokenA); const exp = new Expressions.VariableLiteral(id); exp.sourceInfo = sourceInfo; return exp; } else { throw SyntaxErrorFactory.invalid_id_format(tokenA); } } getFunctionName (id) { const name = LanguageDefinedFunction.getInternalName(id); if (name === null) { if (id === LanguageDefinedFunction.getMainFunctionName()) { return null; } return id; } else { return name; } } parseFunctionCallExpression (id) { const stepBack = id.indexOf(".") === -1 ? 1 : 3; const tokenA = this.getToken(this.pos - stepBack); const actualParameters = this.parseActualParameters(); const tokenB = this.getToken(this.pos - 1); const funcName = this.getFunctionName(id); const sourceInfo = SourceInfo.createSourceInfoFromList(tokenA, tokenB); const cmd = new Expressions.FunctionCall(funcName, actualParameters); cmd.sourceInfo = sourceInfo; return cmd; } parseFunctionCallCommand (id) { return this.parseFunctionCallExpression(id); } parseParenthesisExp () { this.checkOpenParenthesis(); const tokenA = this.getToken(); this.consumeNewLines(); const exp = this.parseExpressionOR(); this.consumeNewLines(); this.checkCloseParenthesis(); const tokenB = this.getToken(); this.pos += 1; exp.sourceInfo = SourceInfo.createSourceInfoFromList(tokenA, tokenB); exp.parenthesis = true; return exp; } parseActualParameters () { this.checkOpenParenthesis(); this.pos++; if (this.checkCloseParenthesis(true)) { this.pos++; return []; } this.consumeNewLines(); const list = this.parseExpressionList(); this.consumeNewLines(); this.checkCloseParenthesis(); this.pos++; return list; } parseExpressionList () { const list = []; for (;;) { const exp = this.parseExpressionOR(); list.push(exp); const maybeToken = this.getToken(); if (maybeToken.type !== this.ruleNames.COMMA) { break; } else { this.pos++; this.consumeNewLines(); } } return list; } getTypeArray () { const types = this.insideScope(IVProgParser.FUNCTION) ? this.functionTypes : this.variableTypes; return types.map((x) => this.lexer.literalNames[x]); } }