Skip to content

Commit

Permalink
Check undefined syntax name
Browse files Browse the repository at this point in the history
  • Loading branch information
takamin committed Feb 5, 2021
1 parent 0dc8e19 commit d6470ef
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 11 deletions.
45 changes: 41 additions & 4 deletions lib/language.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,48 @@ class Language {
constructor(rules) {
/** @type {string} */
this.root = rules[0].name;

/** @type {Record<string, SyntaxRule>} */
this.rules = {};
rules.forEach((rule) => {
this.rules[rule.name] = rule;
});
this.rules = Language.convertRules(rules);
}
/**
* Convert array of SyntaxRules to map.
* @private
* @static
* @param {SyntaxRule[]} rules array of syntax definition.
* @return {Record<string, SyntaxRule>} A syntax map.
*/
static convertRules(rules) {
const syntaxMap = {};
rules.forEach((rule) => (syntaxMap[rule.name] = rule));

// Check if a syntax exists
const syntaxNames = Object.keys(syntaxMap);
for(let iSyntax = 0; iSyntax < rules.length; iSyntax++) {
const syntax = rules[iSyntax];
for(let iRule = 0; iRule < syntax.rules.length; iRule++) {
const terms = syntax.rules[iRule];
for(let iTerm = 0; iTerm < terms.length; iTerm++) {
const term = terms[iTerm];
if(typeof term === "string") {
const specName = term.replace(/[*]$/, "");
debug(`.convertRules - [${iSyntax}-${
syntax.name}.${iRule}.${iTerm}]: ${specName}`);
if(!syntaxNames.includes(specName)) {
const message = [
`Undefined syntax name ${specName}`,
`at ${syntax.name}[${iSyntax}][${iTerm}]`,
].join(" ");
debug(`.convertRules - Throws ${message}`);
throw new Error(message);
}
}
}
}
}
debug(`.convertRules - Returns ${
JSON.stringify(syntaxMap, null, 2)}`);
return syntaxMap;
}
/**
* Create a syntax rule as an element for parameter of the constructor.
Expand Down
71 changes: 64 additions & 7 deletions test/language.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,31 @@
const assert = require("chai").assert;
const Language = require("../lib/language.js");
const {syntax, literal: lit, numlit} = Language;
const langCalc = require("../sample/calc.js");
const debug = require("debug")("Language");
describe("Language", () => {
describe("constructor", () => {
describe("validation", ()=>{
it("should throw when the referenced rule is not declared", ()=>{
assert.throw(()=>{
new Language([
syntax("invalid-syntax",
[["additive-expression"]]),
syntax("additive-expression",
[
["integer-constant", lit("-"), "additive-expression"],
["integer-constant"],
],
(term) => {
const terms = term.contents();
const [a, ope, b] = terms;
return !ope ? a : ope == "+" ? a + b: a - b;
}),
// syntax("integer-constant", [[numlit]], (term) => parseInt(term.str())),
]);
})
});
});
});
describe("parse", () => {
it("should throw, if syntax does not exits", ()=>{
assert.throw(()=>{
Expand All @@ -15,12 +37,14 @@ describe("Language", () => {
});
});
it("should be error the expression is incomplete", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1 + `;
const tokens = langCalc.tokenize(expr);
const result = langCalc.parse(tokens);
assert.instanceOf(result.error, Error);
});
it("should accept a token list", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1 + 2`;
const tokens = langCalc.tokenize(expr);
const result = langCalc.parse(tokens);
Expand Down Expand Up @@ -94,26 +118,33 @@ describe("Language", () => {
});
describe("Countermeasure by repeating specifier", ()=>{
describe("Repeating rule", ()=>{
const lang = new Language([
syntax("repeat-term",
[[lit("A"), "repeat*"]]),
syntax("repeat",
[[lit("."), lit("A") ]]),
]);
const repeatTerm = () => {
const lang = new Language([
syntax("repeat-term",
[[lit("A"), "repeat*"]]),
syntax("repeat",
[[lit("."), lit("A") ]]),
]);
return lang;
};
it("should not throw", ()=>{
assert.doesNotThrow(()=>{
const lang = repeatTerm();
lang.parse("A.A.A");
});
});
it("should not be error for no repeating", ()=>{
const lang = repeatTerm();
const result = lang.parse("A");
assert.isNull(result.error);
});
it("should not be error for repeating one time", ()=>{
const lang = repeatTerm();
const result = lang.parse("A.A");
assert.isNull(result.error);
});
it("should not be error for more repeating", ()=>{
const lang = repeatTerm();
const result = lang.parse("A.A.A");
assert.isNull(result.error);
});
Expand Down Expand Up @@ -184,102 +215,117 @@ describe("Language", () => {
describe("calc.js sample implementation", () => {
describe("correct expression", () => {
it("`1` should be 1", () => {
const langCalc = require("../sample/calc.js");
const expr = `1`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 1);
});
it("`(1)` should be 1", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `(1)`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 1);
});
it("`1 + 2` should be 3", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1 + 2`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 3);
});
it("`3 * 4` should be 12", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `3 * 4`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 12);
});
it("`5 - 6` should be -1", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `5 - 6`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, -1);
});
it("`7 / 8` should be 0.875", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `7 / 8`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 0.875);
});
it("`1 - 2 - 3` should be -4", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1 - 2 - 3`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, -4);
});
it("`1 * 2 + 3 * 4` should be 14", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1 * 2 + 3 * 4`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 14);
});
it("`1 / (2 + 3) + 4` should be 4.2", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1 / (2 + 3) + 4`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 4.2);
});
it("`(1 + 2) * (3 + 4)` should be 21", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `(1 + 2) * (3 + 4)`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 21);
});
it("`(1 + 2) * ((3 + 4) / 2)` should be 10.5 (only parsing)", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `(1 + 2) * ((3 + 4) / 2)`;
const result = langCalc.parse(expr);
assert.isNull(result.error);
}).timeout(5000);
it("`(1 + 2) * ((3 + 4) / 2)` should be 10.5", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `(1 + 2) * ((3 + 4) / 2)`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 10.5);
}).timeout(5000);
it("`1.5 * 2` should be 3", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1.5 * 2`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, 3);
});
it("`1.5e+2 * -2` should be -300", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1.5e+2 * -2`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
assert.isNull(result.error);
assert.equal(value, -300);
});
it("`1.5e2 * 2` should be 300", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `1.5e2 * 2`;
const result = langCalc.parse(expr);
const value = langCalc.evaluate(result);
Expand All @@ -288,6 +334,7 @@ describe("Language", () => {
});
describe("Multi lines", ()=>{
it("should not be error even if the expression contains LF", ()=>{
const langCalc = require("../sample/calc.js");
const eol = "\n";
const expr = `1${eol}+ 2${eol}+3${eol} +4`;
const result = langCalc.parse(expr);
Expand All @@ -296,6 +343,7 @@ describe("Language", () => {
assert.equal(value, 10);
});
it("should not be error even if the expression contains CR-LF", ()=>{
const langCalc = require("../sample/calc.js");
const eol = "\r\n";
const expr = `1${eol}+ 2${eol}+3${eol} +4`;
const result = langCalc.parse(expr);
Expand All @@ -307,31 +355,36 @@ describe("Language", () => {
describe("Term#toString", ()=>{
describe("with no syntax error", ()=>{
it("should not throw", ()=>{
const langCalc = require("../sample/calc.js");
assert.doesNotThrow(()=>{
const result = langCalc.parse("1");
result.toString();
});
});
it("should not throw", ()=>{
const langCalc = require("../sample/calc.js");
assert.doesNotThrow(()=>{
const result = langCalc.parse("1 + 2) * (3 + 4)");
result.toString();
});
});
it("should returns string", ()=>{
const langCalc = require("../sample/calc.js");
const result = langCalc.parse("1");
const s = result.toString();
assert.isString(s);
});
});
describe("with syntax error", ()=>{
it("should not throw", ()=>{
const langCalc = require("../sample/calc.js");
assert.doesNotThrow(()=>{
const result = langCalc.parse("(1 + 2) * xyz(3 + 4) / 2)");
result.toString();
});
});
it("should returns string", ()=>{
const langCalc = require("../sample/calc.js");
const result = langCalc.parse("(1 + 2) * xyz(3 + 4) / 2)");
const s = result.toString();
assert.isString(s);
Expand All @@ -341,23 +394,27 @@ describe("Language", () => {
});
describe("parsing error", () => {
it("`+` should be parser error", ()=>{
const langCalc = require("../sample/calc.js");
const expr = `+`;
const result = langCalc.parse(expr);
assert.instanceOf(result.error, Error);
});
it("`1 + 2 * 3 + 4)` should be parser error", () => {
const langCalc = require("../sample/calc.js");
const expr = `1 + 2 * 3 + 4)`;
const result = langCalc.parse(expr);
assert.instanceOf(result.error, Error);
});
it("`1 + 2 * (3 + 4` should be parser error", () => {
const langCalc = require("../sample/calc.js");
const expr = `1 + 2 * (3 + 4`;
const result = langCalc.parse(expr);
assert.instanceOf(result.error, Error);
});
});
describe("evaluation error", () => {
it("`1 .5 * 2` should throw ", () => {
const langCalc = require("../sample/calc.js");
const expr = `1 .5 * 2`;
const result = langCalc.parse(expr);
assert.throw(()=>{
Expand Down

0 comments on commit d6470ef

Please sign in to comment.