Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
.gradle
node_modules
dist
.DS_Store

2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ task generateParser(type:JavaExec) {
outputs.file("$GEN_JS_SRC/${parserName}.tokens")
main = 'org.antlr.v4.Tool'
classpath = sourceSets.main.runtimeClasspath
args = ['-Dlanguage=JavaScript', "${parserName}.g4", '-no-listener', '-no-visitor', '-o', '../../main-generated/javascript']
args = ['-Dlanguage=JavaScript', "${parserName}.g4", '-no-listener','-visitor', '-o', '../../main-generated/javascript']
workingDir = ANTLR_SRC
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"monaco-editor": "^0.17.1"
},
"scripts": {
"test": "mocha"
"test": "mocha",
"start": "./gradlew generateParser; cp ./src/main/javascript/EvalVisitor.js ./src/main-generated/javascript; cp ./src/main/javascript/InputsVisitor.js ./src/main-generated/javascript; tsc ; webpack ; cd server ; ../gradlew runServer"
}
}
1 change: 1 addition & 0 deletions src/main/antlr/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.tokens
.DS_Store
2 changes: 1 addition & 1 deletion src/main/antlr/CalcParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ calc:

expression:
NUMBER_LIT
| ID
| id=ID
| LPAREN expression RPAREN
| expression operator=(MUL|DIV) expression
| expression operator=(MINUS|PLUS) expression
Expand Down
43 changes: 42 additions & 1 deletion src/main/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,45 @@ h2 {

body {
background-color: #eee;
}
}

.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 30%;
left:30%;
top: 30%;
position: absolute;
z-index: 1;
}

input[type="text"] {
width: 95%;
margin-bottom: 10px;
}

.output{
color: #7132a8;
font-size: 14px;
padding: 5px;
display: flex;
align-items: center;
gap: 2px;
}

.output p::after {
content: "";
width: 0.5px;
height: 14px;
background:#7132a8;
display: inline-block;
animation: cursor-blink 1.5s steps(2) infinite;
}

@keyframes cursor-blink {
0% {
opacity: 0;
}
}
82 changes: 80 additions & 2 deletions src/main/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,27 @@
<body>

<h2>Calc Editor</h2>
<div>
<button id="run">
Run
</button>
</div>
<div id="modal">
</div>

<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<div class="output">
<p id="output-value"> >> </p>
</div>

<script src="node_modules/monaco-editor/min/vs/loader.js"></script>
<script src="js/main.js"></script>

<script>
require.config({ paths: { 'vs': 'node_modules/monaco-editor/min/vs' }});

let editor;
let result=[];
let variables=[];
require(['vs/editor/editor.main'], function() {
monaco.languages.register({ id: 'calc' });

Expand Down Expand Up @@ -52,7 +65,7 @@ <h2>Calc Editor</h2>
]
});

let editor = monaco.editor.create(document.getElementById('container'), {
editor = monaco.editor.create(document.getElementById('container'), {
value: [
'input salary',
'input nEmployees',
Expand Down Expand Up @@ -92,6 +105,71 @@ <h2>Calc Editor</h2>
});

});

document.getElementById("run").addEventListener("click", ()=>{
const code = editor.getValue();
// get the inputs only and ask the user for the values
variables = ParserFacade.getInputs(code);
const modals = document.getElementById("modal")
if(variables.length>0){
let html=`<div id="modal">
<div class="modal-content">`
for (let i = 0; i < variables.length; i++) {
html+=`
<input type="text"" id="textInput-${variables[i].variable}" placeholder="Enter the ${variables[i].variable} value">
`
}
html+=`<button id="saveButton">Save</button></div></div>`
modals.innerHTML=html;
document.getElementById("modal").style.display = "block";
}
else {
// run the visitor giving it the input values to the class constructor
result = ParserFacade.calculateExpression(code,variables);
writeOutputToConsole(result);
}

const saveButton = document.getElementById('saveButton');

saveButton.addEventListener('click', () => {
// Get text from modal
for (let i = 0; i < variables.length; i++) {
const textInput = document.getElementById(`textInput-${variables[i].variable}`);
const text = textInput.value;
// Check if the inserted value is a number to prevent misbehavior
if(isNaN(text)){
writeErrorToConsole("Error: Assigning "+text+" to "+ variables[i].variable +" : is not a number");
return;
}
variables[i].value=text
}
document.getElementById("modal").style.display = "none";
// Call visitor to make calculations
result = ParserFacade.calculateExpression(code,variables);
writeOutputToConsole(result);

});
});

function writeOutputToConsole(result){
for (let i = 0; i <result.output.length ; i++) {
let output = document.getElementById("output-value");
output.innerHTML += "<b>"+result.output[i].variable+"</b>" + " = " + result.output[i].value+"<br>";
output.innerHTML += ">> ";
}
for (let i = 0; i <result.errors.length ; i++) {
let output = document.getElementById("output-value");
output.innerHTML += '<span style="color:red">'+result.errors[i].message+"</span>"+"<br>";
output.innerHTML += ">> ";
}
}

function writeErrorToConsole(messsage){
let output = document.getElementById("output-value");
output.innerHTML += '<span style="color:red">'+messsage+"</span>"+"<br>";
output.innerHTML += ">> ";
}

</script>
</body>
</html>
107 changes: 107 additions & 0 deletions src/main/javascript/EvalVisitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Generated from CalcParser.g4 by ANTLR 4.7.2
// jshint ignore: start
var antlr4 = require('antlr4/index');
var CalcParser = require('../../main-generated/javascript/CalcParser').CalcParser;
// This class defines a complete generic visitor for a parse tree produced by CalcParser.

let variablesMapping;
let errors=[];
function EvalVisitor() {
antlr4.tree.ParseTreeVisitor.call(this);
return this;
}

EvalVisitor.prototype = Object.create(antlr4.tree.ParseTreeVisitor.prototype);
EvalVisitor.prototype.constructor = EvalVisitor;

// Visit a parse tree produced by CalcParser#compilationUnit.
EvalVisitor.prototype.visitCompilationUnit = function(ctx,variables) {
// stores the variable values to be evaluated
variablesMapping=variables;
errors=[];
// stores the output values to be printed in the html
let outputVariables={};
// visit all calc nodes
for (let i = 0; i < ctx.calcs.length; i++) {
this.visitCalc(ctx.calcs[i])
}
// visit all output nodes
for (let i = 0; i < ctx.outputs.length; i++) {
outputVariables[this.visitOutput(ctx.outputs[i])]=true;
}
// filter variables for the ones to be showed in the output, for easier handling in the html
variablesMapping = variablesMapping.filter(elem=> outputVariables[elem.variable] && outputVariables[elem.variable]===true);
return {output:variablesMapping,errors:errors};
};

// Visit a parse tree produced by CalcParser#output.
EvalVisitor.prototype.visitOutput = function(ctx) {
// Only return the variable name
return ctx.getChild(1).getText();
};


// Visit a parse tree produced by CalcParser#calc.
EvalVisitor.prototype.visitCalc = function(ctx) {
// Get the current variable name being analyzed
let variable=ctx.target.text;
// evaluate the current expression
let calculatedExpr = this.visitExpression(ctx.value);
let found=false;
// Look for the value in the variablesMapping to update or to add for use in next calcs
for (let i = 0; i <variablesMapping.length ; i++) {
if(variablesMapping[i].variable==variable){
let temp ={variable:variable,value:parseInt(calculatedExpr)}
variablesMapping[i]=temp;
found=true;
break;
}
}
if(!found){
variablesMapping.push({variable:variable,value:parseInt(calculatedExpr)});
}
return variablesMapping;
};


// Visit a parse tree produced by CalcParser#expression.
EvalVisitor.prototype.visitExpression = function(ctx) {
if (ctx.NUMBER_LIT() && ctx.NUMBER_LIT().getText()) {
return parseInt(ctx.getText());
} else if (ctx.ID() && ctx.ID().getText()) {
const variable=ctx.getText();
// In case the variable is not defined we assume 0
let r= undefined;
// get the value for the variable to calculate the expression
for (let i = 0; i <variablesMapping.length ; i++) {
if(variablesMapping[i].variable==variable){
r = parseInt(variablesMapping[i].value);
}
}
if(r===undefined){
errors.push({message:"Error: "+variable + " is not defined and does not have a value to be calculated"});
// Just catching the error without interrupting the expression calculation
r=0;
}
return r;
} else if (ctx.LPAREN() && ctx.LPAREN().getText()) {
return this.visitExpression(ctx.expression(0));
} else if (ctx.MINUS() && ctx.MINUS().getText() && !ctx.expression(1)) {
return -this.visitExpression(ctx.expression(0));
} else if (ctx.operator && ctx.operator.type === CalcParser.MUL) {
return this.visitExpression(ctx.expression(0)) * this.visitExpression(ctx.expression(1));
} else if (ctx.operator && ctx.operator.type === CalcParser.DIV) {
const divisor = this.visitExpression(ctx.expression(1));
// Just catching the error without interrupting the expression calculation
if (divisor === 0) {
errors.push({message:"Error: Division by 0 error."});
}
return this.visitExpression(ctx.expression(0)) / divisor;
} else if (ctx.operator && ctx.operator.type === CalcParser.PLUS) {
return this.visitExpression(ctx.expression(0)) + this.visitExpression(ctx.expression(1));
} else if (ctx.operator && ctx.operator.type === CalcParser.MINUS) {
return this.visitExpression(ctx.expression(0)) - this.visitExpression(ctx.expression(1));
}
};

exports.EvalVisitor = EvalVisitor;
28 changes: 28 additions & 0 deletions src/main/javascript/InputsVisitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Generated from CalcParser.g4 by ANTLR 4.7.2
// jshint ignore: start
var antlr4 = require('antlr4/index');
// This class defines a partial visitor fot the inputs for a parse tree produced by CalcParser.

function InputsVisitor() {
antlr4.tree.ParseTreeVisitor.call(this);
return this;
}

InputsVisitor.prototype = Object.create(antlr4.tree.ParseTreeVisitor.prototype);
InputsVisitor.prototype.constructor = InputsVisitor;

// Visit a parse tree produced by CalcParser#compilationUnit.
InputsVisitor.prototype.visitCompilationUnit = function(ctx) {
let r = []
for (let i = 0; i < ctx.inputs.length; i++) {
r.push({variable:this.visitInput(ctx.inputs[i]),value:""});
}
return r;
};

// Visit a parse tree produced by CalcParser#input.
InputsVisitor.prototype.visitInput = function(ctx) {
return ctx.getChild(1).getText();
};

exports.InputsVisitor = InputsVisitor;
32 changes: 32 additions & 0 deletions src/main/typescript/ParserFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {CommonTokenStream, InputStream, Token, error, Parser} from '../../../nod
import {DefaultErrorStrategy} from '../../../node_modules/antlr4/error/ErrorStrategy.js'
import {CalcLexer} from "../../main-generated/javascript/CalcLexer.js"
import {CalcParser} from "../../main-generated/javascript/CalcParser.js"
import {InputsVisitor} from "../javascript/InputsVisitor.js";
import {EvalVisitor} from "../javascript/EvalVisitor.js";

class ConsoleErrorListener extends error.ErrorListener {
syntaxError(recognizer, offendingSymbol, line, column, msg, e) {
Expand Down Expand Up @@ -139,3 +141,33 @@ export function validate(input) : Error[] {
const tree = parser.compilationUnit();
return errors;
}

export function getInputs(input) : object[] {

const lexer = createLexer(input);
lexer.removeErrorListeners();
lexer.addErrorListener(new ConsoleErrorListener());

const parser = createParserFromLexer(lexer);
parser.buildParseTrees = true;
const tree = parser.compilationUnit();

let inputsVisitor = new InputsVisitor();
let inputs = inputsVisitor.visitCompilationUnit(tree);
return inputs;
}

export function calculateExpression(input,variables) : object[] {

const lexer = createLexer(input);
lexer.removeErrorListeners();
lexer.addErrorListener(new ConsoleErrorListener());

const parser = createParserFromLexer(lexer);
parser.buildParseTrees = true;
const tree = parser.compilationUnit();

let evalVisitor = new EvalVisitor();
let result = evalVisitor.visitCompilationUnit(tree,variables);
return result;
}
Loading