// iVProg - www.usp.br/line/ivprog // LInE - Free Education, Private Data // This is used when is loaded "ivph" file (under the button "upload" or GET) import { IVProgParser } from "../ast/ivprogParser"; import * as Expressions from "../ast/expressions"; import { Types } from "../typeSystem/types"; import { convertBoolToString } from "../typeSystem/parsers"; import * as Commands from "../ast/commands"; import { ArrayType } from "../typeSystem/array_type"; import { Literal } from "../ast/expressions/literal"; const TYPES = { VARIABLE: "var", CONST: "const", FUNCTION: "function", RELATIONAL: "relational", LOGIC: "logic", ARITHMETIC: "arithmetic", }; function translateOp (type, op) { switch (type) { case TYPES.ARITHMETIC: return op.value; case TYPES.RELATIONAL: return op.value; case TYPES.LOGIC: { if (op.ord === 11) { return "and"; } else if (op.ord === 12) { return "or"; } else { return "not"; } } } } function getOpType (op) { switch (op.ord) { case 0: case 1: case 2: case 3: case 4: return TYPES.ARITHMETIC; case 5: case 6: case 7: case 8: case 9: case 10: return TYPES.RELATIONAL; default: return TYPES.LOGIC; } } /** * @param {Commands.Case} switchCase * */ function switchCaseWalker (switchCase) { const commands = switchCase.commands.map(commandWalker); const expression = switchCase.isDefault ? null : expressionWalker(switchCase.expression); return { type: "switchcase", line: switchCase.sourceInfo.line, expression, commands, }; } /** * @param {Commands.Switch} switchCommand * */ function switchWalker (switchCommand) { const expression = expressionWalker(switchCommand.expression); const cases = switchCommand.cases.map(switchCaseWalker); return { type: "switch", expression, cases, }; } /** * @param {Commands.Return} returnCommand * */ function returnWalker (returnCommand) { let expression = null; if (returnCommand.expression !== null) { expression = expressionWalker(returnCommand.expression); } return { type: "return", expression, }; } function breakWalker (_) { return { type: "break" }; } /** * @param {Commands.For} forLoop * */ function forWalker (forLoop) { const var_attribution = expressionWalker(forLoop.for_id); const var_initial = expressionWalker(forLoop.for_from); const condition = expressionWalker(forLoop.for_to); const step_expression = forLoop.for_pass ? expressionWalker(forLoop.for_pass) : []; const commands = forLoop.commands.map(commandWalker); return { type: "repeatNtimes", var_attribution, var_initial, condition, step_expression, commands, }; } /** * @param {Commands.While} whileLoop * */ function whileWalker (whileLoop) { const expression = expressionWalker(whileLoop.expression); const commands = whileLoop.commands.map(commandWalker); let type = whileLoop.testFirst ? "whiletrue" : "dowhiletrue"; return { type, expression, commands, }; } /** * @param {Commands.IfThenElse} ifthenelse * */ function ifThenElseWalker (ifthenelse) { //ifthenelse. const expression = expressionWalker(ifthenelse.condition); const ifTrue = ifthenelse.ifTrue.commands.map(commandWalker); let ifFalse = []; if (ifthenelse.ifFalse) { if (ifthenelse.ifFalse instanceof Commands.CommandBlock) { ifFalse = ifthenelse.ifFalse.commands.map(commandWalker); } else { ifFalse = [ifThenElseWalker(ifthenelse.ifFalse)]; } } return { type: "iftrue", expression, ifTrue, ifFalse, }; } /** * @param {Commands.Assign} assingment * */ function assignmentWalker (assingment) { let variable = null; if (assingment instanceof Commands.ArrayIndexAssign) { const line = expressionWalker(assingment.line); let arrayClass = "vector"; let column = null; if (assingment.column) { arrayClass = "matrix"; column = expressionWalker(assingment.column); } variable = [ { instance: "expression", type: TYPES.VARIABLE, class: arrayClass, column: column, line: line, value: assingment.id, }, ]; } else { variable = [ { instance: "expression", type: TYPES.VARIABLE, value: assingment.id }, ]; } const expression = expressionWalker(assingment.expression); return { type: "attribution", variable, expression, }; } /** * @param {Command} command * */ function commandWalker (command) { let parsedCommand = null; if (command instanceof Commands.FunctionCall) { parsedCommand = functionCallWalker(command); } else if (command instanceof Commands.Assign) { parsedCommand = assignmentWalker(command); } else if (command instanceof Commands.IfThenElse) { parsedCommand = ifThenElseWalker(command); } else if (command instanceof Commands.While) { parsedCommand = whileWalker(command); } else if (command instanceof Commands.Break) { parsedCommand = breakWalker(command); } else if (command instanceof Commands.Return) { parsedCommand = returnWalker(command); } else if (command instanceof Commands.Switch) { parsedCommand = switchWalker(command); } else if (command instanceof Commands.For) { parsedCommand = forWalker(command); } else { throw new Error("not implemented"); } parsedCommand.line = command.sourceInfo.line; return parsedCommand; } /** * @param {Commands.FunctionCall} functionCall * */ function functionCallWalker (functionCall) { let name = functionCall.id; if (name.indexOf(".") !== -1) { name = name.split(".")[1]; } const parameters = functionCall.actualParameters.map(expressionWalker); if (name === "$write") { const lastInput = parameters[parameters.length - 1][0]; // if lastInput is an object with value === '\n', newLine is true const newLine = lastInput.value && lastInput.value.match(/^\n$/) !== null; const content = newLine ? parameters.slice(0, parameters.length - 1) : parameters; return { type: "writer", newLine, content, }; } if (name === "$read") { return { type: "reader", variable: parameters[0], }; } return { type: "functioncall", parameters_list: parameters, name: functionCall.id, }; } /** * @param {Commands.Function} func * */ function functionWalker (func) { const funcDeclaration = { name: func.name, line: func.sourceInfo.line, return_type: "", return_dimensions: 0, parameters_list: [], variables_list: [], commands: [], }; if (func.returnType instanceof ArrayType) { funcDeclaration.return_type = func.returnType.innerType.value; funcDeclaration.return_dimensions = func.returnType.dimensions; } else { funcDeclaration.return_type = func.returnType.value; } funcDeclaration.parameters_list = func.formalParameters.map(functionParameterWalker); funcDeclaration.variables_list = func.variablesDeclarations.map(variableDeclarationWalker); funcDeclaration.commands = func.commands.map(commandWalker); return funcDeclaration; } /** * @param {Commands.FormalParameter} formalParameter * */ function functionParameterWalker (formalParameter) { const variable = { name: formalParameter.id, line: formalParameter.sourceInfo.line, type: "", rows: 0, columns: 0, dimension: 0, value: 0, is_const: false, reference: formalParameter.byRef, }; if (formalParameter.type instanceof ArrayType) { variable.type = formalParameter.type.innerType.value; variable.dimension = formalParameter.type.dimensions; } else { variable.type = formalParameter.type.value; } return variable; } /** * @param {Commands.Declaration} command * @param {boolean} global * */ function variableDeclarationWalker (command, global = false) { const variable = { name: command.id, line: command.sourceInfo.line, type: "", rows: 0, columns: 0, dimension: 0, value: 0, is_const: false, }; variable.is_const = global && command.isConst; if (command instanceof Commands.ArrayDeclaration) { // array const lines = expressionWalker(command.lines).pop(); variable.type = command.type.innerType.value; if (command.isVector) { variable.columns = lines.value; variable.dimension = 1; const values = command.initial.value.map((exp) => variableInitialWalker(exp)); variable.value = values; } else { const columns = expressionWalker(command.columns).pop(); variable.dimension = 2; variable.rows = lines.value; variable.columns = columns.value; const values = command.initial.value.map((rows) => rows.value.map((exp) => variableInitialWalker(exp))); variable.value = values; } } else { // atomic variable.type = command.type.value; variable.value = variableInitialWalker(command.initial); } return variable; } /// @param {any} expression function variableInitialWalker (expression) { if (expression instanceof Expressions.UnaryApp) { const left = variableInitialWalker(expression.left); const opType = getOpType(expression.op); if (opType !== TYPES.ARITHMETIC) { throw new Error("invalid variable initial value: " + expression.toString()); } return `${expression.op.value}${left}`; } else if (expression instanceof Expressions.BoolLiteral) { const value = expression.value; return convertBoolToString(value); } else if (expression instanceof Literal) { let value = expression.value; if (expression.value.toNumber) { if (Types.REAL.isCompatible(expression.type) && expression.value.decimalPlaces() == 0) { value = Number(expression.value.toFixed(2)); } else { value = expression.value.toNumber(); } } return value; } throw new Error("invalid variable initial value: " + expression.toString()); } /// @return {[]} function expressionWalker (expression) { let result; if (expression instanceof Expressions.VariableLiteral) { result = [ { instance: "expression", type: TYPES.VARIABLE, value: expression.id }, ]; } else if (expression instanceof Expressions.FunctionCall) { const funcObj = { instance: "expression", type: TYPES.FUNCTION, value: expression.id, }; const paramsList = expression.actualParameters.map((e) => expressionWalker(e)); funcObj.params = paramsList; result = [funcObj]; } else if (expression instanceof Expressions.UnaryApp) { const left = expressionWalker(expression.left); const opType = getOpType(expression.op); const opValue = translateOp(opType, expression.op); result = [{ instance: "operator", type: opType, value: opValue }, ...left]; } else if (expression instanceof Expressions.InfixApp) { const left = expressionWalker(expression.left); const right = expressionWalker(expression.right); const opType = getOpType(expression.op); const opValue = translateOp(opType, expression.op); result = [ ...left, { instance: "operator", type: opType, value: opValue }, ...right, ]; } else if (expression instanceof Expressions.ArrayAccess) { const line = expressionWalker(expression.line); let arrayClass = "vector"; let column = null; if (expression.column) { arrayClass = "matrix"; column = expressionWalker(expression.column); } result = [ { instance: "expression", type: TYPES.VARIABLE, class: arrayClass, column: column, line: line, value: expression.id, }, ]; } else if (expression instanceof Expressions.BoolLiteral) { const value = expression.value; result = [ { instance: "expression", class: "simple", type: TYPES.CONST, value: convertBoolToString(value), }, ]; } else { let value = expression.value; //x if (expression.value.toNumber) if (value !== null && value !== undefined && typeof value.toNumber === "function") { //x if (Types.REAL.isCompatible(expression.type) && expression.value.decimalPlaces() == 0) if (Types.REAL.isCompatible(expression.type) && value.decimalPlaces() == 0) { //x value = Number(expression.value.toFixed(2)); value = Number(value.toFixed(2)); } else { //x value = expression.value.toNumber(); value = value.toNumber(); } } result = [ { instance: "expression", class: "simple", type: TYPES.CONST, value: value, }, ]; } if (expression.parenthesis) return ["(", ...result, ")"]; else return result; } export function parseExpression (text) { const parser = IVProgParser.createParser(text); const expressionAST = parser.parseExpressionOR(); return expressionWalker(expressionAST); } /// @param {string} text /// @calledby js/util/iassignHelpers.js!setPreviousAlgorithm(code) export function parseCode (text) { console.log("js/util/parseFromVisual.js!parseCode(text): can this function be removed? If is it appearing in log, then no..."); //D console.trace(); return parserCodeVisual(text); } /// @param {string} text /// @calledby js/util/iassignHelpers.js!setPreviousAlgorithm(code) export function parserCodeVisual (text) { // Gleyce; Edilson console.log("js/util/parseFromVisual.js!parserCodeVisual(text): starting"); //D console.trace(); const parser = IVProgParser.createParser(text, false); const codeLinesMap = new Map(); const tokens = Array.from(parser.lexer.reset(text)); const tokenStream = []; for (const token of tokens) { if (token.type === parser.ruleNames.ERROR) { return null; } if (token.type === parser.ruleNames.COMMENTS) { for (let i = 0; i <= token.lineBreaks; i++) { if (codeLinesMap.has(i + token.line)) codeLinesMap.get(i + token.line).push(token); else codeLinesMap.set(i + token.line, [token]); } continue; } if (token.type !== parser.ruleNames.WHITESPACE) { tokenStream.push(token); } } parser.fill(tokenStream); try { const program = parser.parseTree(); const globals = program.global.map((decl) => variableDeclarationWalker(decl, true)); const functions = program.functions.map(functionWalker); //Gleyce start: new code to read IVPH file with comment (//) const allComments = []; for (const [line, commentTokens] of codeLinesMap.entries()) { for (const token of commentTokens) { const commentText = token.text.replace(/^\/\//, "").trim(); allComments.push({ type: "comment", comment_text: commentText, line: token.line, }); } } const astFunctions = program.functions; functions.forEach((visualFunc, index) => { const astFunc = astFunctions[index]; const funcStartLine = astFunc.sourceInfo.line; let funcEndLine; if (astFunctions[index + 1]) { funcEndLine = astFunctions[index + 1].sourceInfo.line; } else { funcEndLine = Infinity; } const funcComments = allComments.filter((comment) => comment.line > funcStartLine && comment.line < funcEndLine); if (funcComments.length > 0) { let firstCommandLine = Infinity; if (visualFunc.commands.length > 0) { firstCommandLine = visualFunc.commands[0].line; } const commentsForVariables = funcComments.filter((c) => c.line < firstCommandLine); if (commentsForVariables.length > 0) { const allVariables = [ ...visualFunc.variables_list, ...commentsForVariables, ]; allVariables.sort((a, b) => a.line - b.line); visualFunc.variables_list = allVariables; } if (visualFunc.commands.length > 0) { const lastCommandLine = visualFunc.commands[visualFunc.commands.length - 1]?.line || funcEndLine; const commentsForCommands = funcComments.filter((c) => c.line >= firstCommandLine); visualFunc.commands = associateCommentsToCommands(visualFunc.commands, commentsForCommands, firstCommandLine - 1, lastCommandLine + 100); } } }); //Gleyce end: new code to read IVPH file with comment (//) return { globals, functions }; } catch (e) { console.error(e); return null; } } // export function parserCodeVisual(text) //comments starts (Edilson) function associateCommentsToCommands (commands, allComments, startLine, endLine) { const scopeComments = allComments.filter((c) => c.line > startLine && c.line < endLine); if (scopeComments.length === 0) return commands; const processedCommands = commands.map((cmd) => { if (cmd.type === "comment") return cmd; const processedCmd = { ...cmd }; if (cmd.type === "iftrue") { if (cmd.ifTrue && cmd.ifTrue.length > 0) { const ifBlock = cmd.ifTrue; const blockStart = cmd.line; const blockEnd = ifBlock[ifBlock.length - 1]?.line || cmd.line; processedCmd.ifTrue = associateCommentsToCommands(ifBlock, allComments, blockStart, blockEnd); } if (cmd.ifFalse && cmd.ifFalse.length > 0) { const elseBlock = cmd.ifFalse; const blockStart = cmd.ifTrue && cmd.ifTrue.length > 0 ? cmd.ifTrue[cmd.ifTrue.length - 1]?.line || cmd.line : cmd.line; const blockEnd = elseBlock[elseBlock.length - 1]?.line || blockStart; processedCmd.ifFalse = associateCommentsToCommands(elseBlock, allComments, blockStart, blockEnd); } } if (cmd.type === "whiletrue" || cmd.type === "dowhiletrue") { if (cmd.commands && cmd.commands.length > 0) { const blockStart = cmd.line; const blockEnd = cmd.commands[cmd.commands.length - 1]?.line || cmd.line; processedCmd.commands = associateCommentsToCommands(cmd.commands, allComments, blockStart, blockEnd); } } if (cmd.type === "repeatNtimes") { if (cmd.commands && cmd.commands.length > 0) { const blockStart = cmd.line; const blockEnd = cmd.commands[cmd.commands.length - 1]?.line || cmd.line; processedCmd.commands = associateCommentsToCommands(cmd.commands, allComments, blockStart, blockEnd); } } if (cmd.type === "switch") { if (cmd.cases) { processedCmd.cases = cmd.cases.map((caseObj) => { if (caseObj.commands && caseObj.commands.length > 0) { const blockStart = caseObj.line || cmd.line; const blockEnd = caseObj.commands[caseObj.commands.length - 1]?.line || blockStart; return { ...caseObj, commands: associateCommentsToCommands(caseObj.commands, allComments, blockStart, blockEnd), }; } return caseObj; }); } } return processedCmd; }); const usedCommentLines = new Set(); function collectUsedComments (cmdList) { cmdList.forEach((cmd) => { if (cmd.type === "comment") { usedCommentLines.add(cmd.line); return; } if (cmd.type === "iftrue") { if (cmd.ifTrue) collectUsedComments(cmd.ifTrue); if (cmd.ifFalse) collectUsedComments(cmd.ifFalse); } if ((cmd.type === "whiletrue" || cmd.type === "dowhiletrue" || cmd.type === "repeatNtimes") && cmd.commands) { collectUsedComments(cmd.commands); } if (cmd.type === "switch" && cmd.cases) { cmd.cases.forEach((c) => { if (c.commands) collectUsedComments(c.commands); }); } }); } collectUsedComments(processedCommands); const availableComments = scopeComments.filter((c) => !usedCommentLines.has(c.line)); const merged = [...processedCommands, ...availableComments].sort((a, b) => a.line - b.line); return associateInlineComments(merged, allComments); } function associateInlineComments (commands, allComments) { if (!allComments || allComments.length === 0) return commands; const commentsByLine = new Map(); allComments.forEach((comment) => { if (!commentsByLine.has(comment.line)) { commentsByLine.set(comment.line, []); } commentsByLine.get(comment.line).push(comment); }); const inlineCommentLines = new Set(); function processCommands (cmdList) { return cmdList.map((cmd) => { if (cmd.type === "comment") return cmd; const processedCmd = { ...cmd }; if (cmd.line && commentsByLine.has(cmd.line)) { const commentsOnLine = commentsByLine.get(cmd.line); const availableComment = commentsOnLine.find((c) => !inlineCommentLines.has(c.line)); if (availableComment) { processedCmd.inlineComment = availableComment.comment_text; inlineCommentLines.add(cmd.line); } } if (cmd.type === "iftrue") { if (cmd.ifTrue) processedCmd.ifTrue = processCommands(cmd.ifTrue); if (cmd.ifFalse) processedCmd.ifFalse = processCommands(cmd.ifFalse); } if ((cmd.type === "whiletrue" || cmd.type === "dowhiletrue" || cmd.type === "repeatNtimes") && cmd.commands) { processedCmd.commands = processCommands(cmd.commands); } if (cmd.type === "switch" && cmd.cases) { processedCmd.cases = cmd.cases.map((caseObj) => ({ ...caseObj, commands: caseObj.commands ? processCommands(caseObj.commands) : [], })); } return processedCmd; }); } // function processCommands(cmdList) const processedCommands = processCommands(commands); function filterInlineComments (cmdList) { return cmdList.filter((cmd) => { if (cmd.type === "comment" && inlineCommentLines.has(cmd.line)) { return false; } if (cmd.type === "iftrue") { if (cmd.ifTrue) cmd.ifTrue = filterInlineComments(cmd.ifTrue); if (cmd.ifFalse) cmd.ifFalse = filterInlineComments(cmd.ifFalse); } if ((cmd.type === "whiletrue" || cmd.type === "dowhiletrue" || cmd.type === "repeatNtimes") && cmd.commands) { cmd.commands = filterInlineComments(cmd.commands); } if (cmd.type === "switch" && cmd.cases) { cmd.cases = cmd.cases.map((caseObj) => ({ ...caseObj, commands: caseObj.commands ? filterInlineComments(caseObj.commands) : [], })); } return true; }); } return filterInlineComments(processedCommands); } // function associateInlineComments(commands, allComments) //comments end (Edilson)