Last active
February 7, 2019 14:37
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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