LogarithmPlotter/common/src/lib/expr-eval/parser.mjs
Ad5001 3a81441d0b
All checks were successful
continuous-integration/drone/push Build is passing
Finished expr-eval testing
2024-10-13 00:33:22 +02:00

160 lines
No EOL
5.2 KiB
JavaScript

/**
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
* http://www.undefined.ch/mparser/index.html
*
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
* https://silentmatt.com/javascript-expression-evaluator/
*
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
*
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
* but don't feel like you have to let me know or ask permission.
*/
import * as Polyfill from "./polyfill.mjs"
import { ParserState } from "./parserstate.mjs"
import { TEOF, TokenStream } from "./tokens.mjs"
import { ExprEvalExpression } from "./expression.mjs"
const optionNameMap = {
"+": "add",
"-": "subtract",
"*": "multiply",
"/": "divide",
"%": "remainder",
"^": "power",
"!": "factorial",
"<": "comparison",
">": "comparison",
"<=": "comparison",
">=": "comparison",
"==": "comparison",
"!=": "comparison",
"||": "concatenate",
"and": "logical",
"or": "logical",
"not": "logical",
"?": "conditional",
":": "conditional",
"[": "array"
}
export class Parser {
constructor(options) {
this.options = options || {}
this.unaryOps = {
sin: Math.sin,
cos: Math.cos,
tan: Math.tan,
asin: Math.asin,
acos: Math.acos,
atan: Math.atan,
sinh: Math.sinh || Polyfill.sinh,
cosh: Math.cosh || Polyfill.cosh,
tanh: Math.tanh || Polyfill.tanh,
asinh: Math.asinh || Polyfill.asinh,
acosh: Math.acosh || Polyfill.acosh,
atanh: Math.atanh || Polyfill.atanh,
sqrt: Math.sqrt,
cbrt: Math.cbrt || Polyfill.cbrt,
log: Math.log,
log2: Math.log2 || Polyfill.log2,
ln: Math.log,
lg: Math.log10 || Polyfill.log10,
log10: Math.log10 || Polyfill.log10,
expm1: Math.expm1 || Polyfill.expm1,
log1p: Math.log1p || Polyfill.log1p,
abs: Math.abs,
ceil: Math.ceil,
floor: Math.floor,
round: Math.round,
trunc: Math.trunc || Polyfill.trunc,
"-": Polyfill.neg,
"+": Number,
exp: Math.exp,
not: Polyfill.not,
length: Polyfill.stringOrArrayLength,
"!": Polyfill.factorial,
sign: Math.sign || Polyfill.sign
}
this.unaryOpsList = Object.keys(this.unaryOps)
this.binaryOps = {
"+": Polyfill.add,
"-": Polyfill.sub,
"*": Polyfill.mul,
"/": Polyfill.div,
"%": Polyfill.mod,
"^": Math.pow,
"||": Polyfill.concat,
"==": Polyfill.equal,
"!=": Polyfill.notEqual,
">": Polyfill.greaterThan,
"<": Polyfill.lessThan,
">=": Polyfill.greaterThanEqual,
"<=": Polyfill.lessThanEqual,
and: Polyfill.andOperator,
or: Polyfill.orOperator,
"in": Polyfill.inOperator,
"[": Polyfill.arrayIndex
}
this.ternaryOps = {
"?": Polyfill.condition
}
this.functions = {
random: Polyfill.random,
fac: Polyfill.factorial,
min: Polyfill.min,
max: Polyfill.max,
hypot: Math.hypot || Polyfill.hypot,
pyt: Math.hypot || Polyfill.hypot,
pow: Math.pow,
atan2: Math.atan2,
"if": Polyfill.condition,
gamma: Polyfill.gamma,
"Γ": Polyfill.gamma,
roundTo: Polyfill.roundTo,
}
// These constants will automatically be replaced the MOMENT they are parsed.
// (Original consts from the parser)
this.builtinConsts = {}
// These consts will only be replaced when the expression is evaluated.
this.consts = {}
}
parse(expr) {
const instr = []
const parserState = new ParserState(
this,
new TokenStream(this, expr),
{ allowMemberAccess: this.options.allowMemberAccess }
)
parserState.parseExpression(instr)
parserState.expect(TEOF, QT_TRANSLATE_NOOP("error", "EOF"))
return new ExprEvalExpression(instr, this)
}
isOperatorEnabled(op) {
const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
const operators = this.options.operators || {}
return !(optionName in operators) || !!operators[optionName]
}
}