Skip to content

Instantly share code, notes, and snippets.

@albertpeiro
Last active February 7, 2019 14:37
Show Gist options
  • Save albertpeiro/680ef9fbf5416fe635badde6dc6f1cbf to your computer and use it in GitHub Desktop.
Save albertpeiro/680ef9fbf5416fe635badde6dc6f1cbf to your computer and use it in GitHub Desktop.
Takes a logic expression as a string. Returns an Abstract Syntax Tree (AST) that represents the logic expression entered, or throws an error if not syntactically correct.
/*
GENERATE AST
https://regexr.com/460ng
Valid input:
1
12
12 AND 22
12 AND 22 OR 33
12 AND (22 OR 33)
(1 AND 2) AND 3
1 OR 12 AND (22 OR 33)
(1 OR 2) AND (3 OR 4)
(1)
(1 OR 2)
(1 OR 2 AND 3)
(12 AND (22 OR 33))
1 OR (12 AND (22 OR 33))
*/
const regexNumber = /^(\d+)$/;
const regexPrioBoth = /^\((.+)\) (AND|OR) \((.+)\)$/;
const regexParen = /^\((.+)\)$/;
const regexPrioEnd = /^(\d+) (AND|OR) \((.+)\)$/;
const regexNoPrio = /^(\d+|\(.+\)) (AND|OR) (.+)$/;
const generateAST = (expr, opts) => {
if (!expr || expr.length === 0) return null;
// It's a number eg. "12"
if (regexNumber.test(expr)) {
const i = parseInt(expr);
if (i < opts.rangeStart || i > opts.rangeEnd) {
const err = new ParseError(
`EINVNUMRANGE`,
`Invalid number "${i}". Not in range.`
);
throw err;
}
return i;
}
// It's a priority both operation eg. "(1 OR 12) AND (1 OR 2)"
if (regexPrioBoth.test(expr)) {
const match = regexPrioBoth.exec(expr);
const operationName = match[2];
return {
[operationName]: [
generateAST(match[1], opts),
generateAST(match[3], opts)
]
};
}
// It's something in parenthesis eg. "(1)", "(1 AND 2)"
if (regexParen.test(expr)) {
const match = regexParen.exec(expr);
return generateAST(match[1], opts);
}
// It's a priority end operation eg. "12 AND (1 OR 2)"
if (regexPrioEnd.test(expr)) {
const match = regexPrioEnd.exec(expr);
const operationName = match[2];
return {
[operationName]: [
generateAST(match[1], opts),
generateAST(match[3], opts)
]
};
}
// It's a no priority operation eg. "12 AND 1", "(1 AND 2) OR 2"
if (regexNoPrio.test(expr)) {
const match = regexNoPrio.exec(expr);
const operationName = match[2];
return {
[operationName]: [
generateAST(match[1], opts),
generateAST(match[3], opts)
]
};
}
throw new ParseError(`EINVEXPR`, `Invalid (sub)expression "${expr}".`);
};
class ParseError extends Error {
constructor(code, ...args) {
super(...args);
this.code = code;
Error.captureStackTrace(this, ParseError);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment