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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ It shows how to integrate ANTLR with monaco.

![Calc Example](./doc/images/calc_example.png)

## Features

* Text editing provided by [monaco](https://github.com/microsoft/monaco-editor)
* Syntax highlighting for custom language
* Syntax error reporting with expected alternatives
* Semantic error reporting for undeclared and redeclared variables

## Generating the lexer and the parser

```
Expand Down
60 changes: 60 additions & 0 deletions src/main/typescript/ParserFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,65 @@ export function validate(input) : Error[] {
parser._errHandler = new CalcErrorStrategy();

const tree = parser.compilationUnit();

const scope = []; // ? Should be a Set but can't use it unless typescript targets ES6+

for (const input of tree.inputs) {

if (input.ID() === null) continue;

const inputIdentifier = input.ID().symbol;

if (scope.some(x => x === inputIdentifier.text)) {
errors.push(createErrorAt(inputIdentifier, "input already declared"));
}
else {
scope.push(inputIdentifier.text);
}
}

for (const calculation of tree.calcs) {
validateExpression(calculation.value);

if (!scope.some(x => x === calculation.target.text)) {
scope.push(calculation.target.text);
}
}

for (const output of tree.outputs) {

if (output.ID() === null) continue;

const outputIdentifier = output.ID().symbol;

if (outputIdentifier.text === "<missing null>") continue;

if (!scope.some(x => x === outputIdentifier.text)) {
errors.push(createErrorAt(outputIdentifier, "undeclared symbol"));
}
}

function validateExpression(expression) {
if (expression === null || expression.children === null) return;

for (const child of expression.children) {
if (child.children) {
validateExpression(child);
}
else if (child.symbol && child.symbol.type == CalcLexer.ID) {

const symbol = child.symbol;

if (!scope.some(x => x == symbol.text)) {
errors.push(createErrorAt(symbol, "undeclared symbol"));
}
}
}
}

return errors;
}

function createErrorAt(node, message) {
return new Error(node.line, node.line, node.column + 1, node.column + node.stop - node.start + 2, message);
}
66 changes: 64 additions & 2 deletions src/test/javascript/parsingTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ describe('Basic parsing of simple script', function () {

describe('Validation of simple errors on single lines', function () {
describe('should have recognize missing operand', function () {
parseAndCheckErrors("o = i + \n", [
parseAndCheckErrors("o = 1 + \n", [
new parserFacade.Error(1, 1, 8, 9, "mismatched input '\\n' expecting {NUMBER_LIT, ID, '(', '-'}")
]);
});
describe('should have recognize extra operator', function () {
parseAndCheckErrors("o = i +* 2 \n", [
parseAndCheckErrors("o = 1 +* 2 \n", [
new parserFacade.Error(1, 1, 7, 8, "extraneous input '*' expecting {NUMBER_LIT, ID, '(', '-'}")
]);
});
Expand Down Expand Up @@ -131,3 +131,65 @@ describe('Unrecognized tokens cause errors', function () {
]);
});
});

describe('Semantic validation', function () {
describe('should report input already declared', function () {
let input = "input i\ninput i\n";
parseAndCheckErrors(input, [
new parserFacade.Error(2, 2, 7, 8, "input already declared")
]);
});
describe('should report input already declared twice', function () {
let input = "input i\ninput i\ninput i\n";
parseAndCheckErrors(input, [
new parserFacade.Error(2, 2, 7, 8, "input already declared"),
new parserFacade.Error(3, 3, 7, 8, "input already declared")
]);
});
describe('should report undeclared symbol', function () {
let input = "o = i\n";
parseAndCheckErrors(input, [
new parserFacade.Error(1, 1, 5, 6, "undeclared symbol")
]);
});
describe('should report undeclared symbol in line 4', function () {
let input = "input i\no = i\no = o\no = u\n";
parseAndCheckErrors(input, [
new parserFacade.Error(4, 4, 5, 6, "undeclared symbol")
]);
});
describe('should report undeclared symbol for output', function () {
let input = "output o\n";
parseAndCheckErrors(input, [
new parserFacade.Error(1, 1, 8, 9, "undeclared symbol")
]);
});
describe('should report undeclared symbol for second output', function () {
let input = "input i\no = i\noutput o\noutput x\n";
parseAndCheckErrors(input, [
new parserFacade.Error(4, 4, 8, 9, "undeclared symbol")
]);
});
});

describe('Semantic validation of examples being edited', function () {
describe('deleting input identifier', function () {
let input = "input a\ninput\ninput a\n";
parseAndCheckErrors(input, [
new parserFacade.Error(2, 2, 5, 6, "missing ID at '\\n'"),
new parserFacade.Error(3, 3, 7, 8, "input already declared")
]);
});
describe('deleting calculation value still declares the target', function () {
let input = "a = 1 + 1\nb = \nc = a + b\n";
parseAndCheckErrors(input, [
new parserFacade.Error(2, 2, 4, 5, "mismatched input '\\n' expecting {NUMBER_LIT, ID, '(', '-'}"),
]);
});
describe('deleting output identifier', function () {
let input = "input a\noutput\noutput a\n";
parseAndCheckErrors(input, [
new parserFacade.Error(2, 2, 6, 7, "missing ID at '\\n'"),
]);
});
});