From 12f85a60fe17951b8bb47d8b6a30c5cf9c5c0917 Mon Sep 17 00:00:00 2001 From: Mohammed Samir Date: Sun, 10 Jan 2021 09:06:09 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Update=20to=20version=20v4.0.0,?= =?UTF-8?q?=20alhamdulillah=20=F0=9F=92=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +-- README.md | 54 +++++++++---------- src/math.pegjs | 78 ++++++++++++++++----------- tests/maps/basic.js | 16 ++++++ tests/maps/noSingleChar__BID.js | 16 ++++-- tests/maps/noSingleChar__functions.js | 2 +- tests/maps/singleChar__BID.js | 19 +++++++ tests/maps/singleChar__functions.js | 23 +------- tests/maps/spreadOperatorAllowed.js | 6 --- tools/build.js | 5 +- 10 files changed, 129 insertions(+), 97 deletions(-) delete mode 100644 tests/maps/spreadOperatorAllowed.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 00468bd..d3a3c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 🌟 10 Jan 2021, v4.0 +# 🌟 11 Jan 2021, v4.0 ## Breaking @@ -15,14 +15,15 @@ * `(1, 2,)`: is a tuple, parsed if `extra.trailingComma ` is `true` * `[, a]`: is a matrix, parsed if `extra.blankTerms` is `true` * `(, a)`: is a tuple, parsed if `extra.blankTerms` is `true` +* Remove option `strict`. ## Add +- `options.keepParen`: if you want to parsed parenthesis as nodes in the AST, `{ type: "parenthesis" }`. - `options.extra.matrices` - `options.builtInIDs` - `infinity, pi, phi`: these has specific values or notions in maths. - `phix` is considered as automult of single-char ids, if `options.singleCharName=true`, otherwise it is node of type "id" with name "phix". - - when strict the previous expression will be automult of single-char ids, equivalent to `p*h*i*x`. -- When `options.strict === true`: the following expression is valid `1+3(2-3sqrt(pi)`. + - when strict the previous expression will be automult of single-char ids, equivalent to `p*h*i*x`. # 3 Jan 2021, v3.0.0 diff --git a/README.md b/README.md index 22c1677..ec29dca 100644 --- a/README.md +++ b/README.md @@ -64,18 +64,19 @@ console.log(mathParser.parse('f().someProperty.fn(y).result ^ 2 \n!')); |Operator|Precedence|Associativity| |------|------|-------| -|`!`|5|N/A| -|`^`|4|left-to-right| -|`*`|3|left-to-right| -|`/`|3|left-to-right| -|`+`|2|left-to-right| -|`-`|2|left-to-right| -|`!=`|1|left-to-right| -|`==`|1|left-to-right| -|`>=`|1|left-to-right| -|`<=`|1|left-to-right| -|`>`|1|left-to-right| -|`<`|1|left-to-right| +|`!`|6|N/A| +|`^`|5|left-to-right| +|`*`|4|left-to-right| +|`/`|4|left-to-right| +|`+`|3|left-to-right| +|`-`|3|left-to-right| +|`!=`|2|left-to-right| +|`==`|2|left-to-right| +|`>=`|2|left-to-right| +|`<=`|2|left-to-right| +|`>`|2|left-to-right| +|`<`|2|left-to-right| +|`=`|1|left-to-right| # Options @@ -93,19 +94,10 @@ To perform multiplication in these cases: Type = `boolean`, default: `true`. -Maths conventionally works with single char named variables and constants, but in programming languages you have freedom. The convention in programming is to use multi-char named identifier. -For example, if you want to use "pi" or "phi", etc, you have to set this to `options.builtInIDs = ["pi", "phi"]`, it is set by default. +Maths conventionally works with single char named variables and constants, but in programming languages you have freedom. The convention in programming is to use multi-char named identifier. See: [options.builtInIDs](#.builtInIDs). -When a member expression is found, properties and methods are allowed to be multi-char, despite of `options.singleCharName` +When a member expression is found, properties and methods are allowed to be multi-char, despite of `options.singleCharName`, see: `options.extra.memberExpressions`. -## .strict - -Type = `boolean`, default = `false`; - -When false: - -- `f()`: is parsed as function whether or not it exists in `options.functions` -- `sin + 2`: you can you functions identifiers as references, with no arguments. ## .extra @@ -149,11 +141,8 @@ Type = `Array`, default = `[]`; When `autoMult` is `true`, some expression like `f(x)` will be considered as multiplication `f*(x)`, in order to parse it as a function with name = "f", you can pass `options.functions = ['f']`. -Notice that when `strict = false`, some expression such as `f()`, -an id followed by empty parentheses, will be parsed with type = "function" -whether or not `options.functions` includes "f". +When parsing `a.method(...)`, regardless of `singleCharName`, method names will be always multi-char name. -When parsing `a.method(...)`, regardless of options, `method` will be always. ``` member expression _/\_ @@ -166,7 +155,7 @@ When parsing `a.method(...)`, regardless of options, `method` will be always. ## .builtInIDs -Type = `Array`, default = `["pi", "phi"]`; +Type = `Array`, default = `["infinity", "pi", "phi"]`; To use multi-char names when setting [`singleCharName`](#.singleCharName) to true, for example: @@ -174,7 +163,14 @@ To use multi-char names when setting [`singleCharName`](#.singleCharName) to tru | ------------- | ------------- | -------------- | | `1 + pix` | `1 + p*i*x`|`true`| |`1 + pi`| `1 + pi`|`true`| -|`1 + pix` | `1 + pix`|`singleCharName = false`| +|`1 + pix` | `1 + pix`|`false`| + +## .keepParen + +Type = `boolean`, default = `false`. + +If you want to make grouping parenthesis nodes in the result AST, `{ type: 'parenthesis', ... }`. + # Unsure about diff --git a/src/math.pegjs b/src/math.pegjs index d7b19cd..b3401f6 100644 --- a/src/math.pegjs +++ b/src/math.pegjs @@ -1,11 +1,13 @@ +// TODO: correct how built in function are dealed +// specially when using to without parenthesis after it + { options = Object.assign({ - strict: false, autoMult: true, singleCharName: true, - keepParentheses: false, + keepParen: false, functions: [], - builtInIDs: ["pi", "phi"], + builtInIDs: ["infinity", "pi", "phi"], builtInFunctions: [ "sinh", "cosh", "tanh", "sech", "csch", "coth", "arsinh", "arcosh", "artanh", "arsech", "arcsch", "arcoth", @@ -14,9 +16,6 @@ "arcsin", "arccos", "arctan", "arcsec", "arccsc", "arccot", "ln", "log", "exp", "floor", "ceil", "round", "random", "sqrt" ], - additionalFunctions: [ - - ] }, options, { extra: { memberExpressions: true, @@ -30,10 +29,25 @@ } }); - let hasTrailing; + let hasTrailing, factorNameMatched, builtInFunctionTitle; preParse(input, peg$computeLocation, error); + function pushChar(c, mode) { + if (mode === "builtInFunctions") { + let newTitle = builtInFunctionTitle + c, found = false; + for (let t of options.builtInFunctions) { + if (t.length === newTitle.length && t === newTitle || + t.length > newTitle.length && t.slice(0, newTitle.length) === newTitle) { + found = true; break; + } + } + if (!found) return false; + builtInFunctionTitle = newTitle; + return true; + } + } + function createNode(...args){ let n = new Node(...args); if(n.type === "member expression" && !options.extra.memberExpressions) @@ -110,7 +124,7 @@ if (o === "[" || c === "]") error(`unexpected brackets opening and closing`); - return options.keepParentheses + return options.keepParen ? createNode("parentheses", [node]) : node; @@ -153,7 +167,7 @@ Operation4 "operation or factor" = } Operation5 "operation or factor" = - head:(BuiltInIDs / AutoMult) tail:(_ "^" _ Operation5)* { + head:AutoMult tail:(_ "^" _ Operation5)* { // left to right return tail.reduce(function(base, exponent) { let exp = exponent[3]; @@ -168,6 +182,7 @@ Operation5 "operation or factor" = AutoMult "operation or factor" = /// series of multiplication or one "Factor" head:Factor tail:(_ FactorNotNumber)* { + factorNameMatched = false; return tail.reduce(function(result, element) { return createNode("automult" , [result, element[1]]); }, head); @@ -176,18 +191,21 @@ AutoMult "operation or factor" = /// series of multiplication or one "Factor" Factor = FactorNumber / FactorNotNumber FactorNumber = - base:Number _ fac:factorial? { - if (fac) base = createNode('postfix operator', [base], {name: '!'}); - return base; + n:Number _ fac:factorial? { + if (fac) n = createNode('postfix operator', [n], {name: '!'}); + return n; } FactorNotNumber = - base:( + f:( + // !char to avoid take the first term "pi" of "pix" + &{ return !factorNameMatched } bid:BuiltInIDs !char { return bid } / MemberExpression / Functions / TupleOrExprOrParenOrIntervalOrSetOrMatrix / BlockVBars / NameNME ) _ fac:factorial? { - if (fac) base = createNode('postfix operator', [base], {name: '!'}); - return base; + factorNameMatched = f.type === 'id'; + if (fac) f = createNode('postfix operator', [f], {name: '!'}); + return f; } Functions "functions" = @@ -196,23 +214,21 @@ Functions "functions" = BuiltInFunctions = name:builtInFuncsTitles _ exp:SuperScript? _ args:builtInFuncArgs { let func = createNode('function', args, {name, isBuiltIn:true}); - if(!exp) return func; - else return createNode('operator', [func, exp], {name: '^', operatorType: 'infix'}); + if(exp) func.exp = exp; + return func; } -// builtInFuncsTitles = builtInFuncsTitles = - &{ return options.singleCharName } n:( - // CAUTION: we have to use them literally here to enable - // sinx => node.bunltInFunction("sin", ["x"]); - "sinh"/ "cosh"/ "tanh"/ "sech"/ "csch"/ "coth"/ - "arsinh"/ "arcosh"/ "artanh"/ "arsech"/ "arcsch"/ "arcoth"/ - "sin"/ "cos"/ "tan"/ "sec"/ "csc"/ "cot"/ - "asin"/ "acos"/ "atan"/ "asec"/ "acsc"/ "acot"/ - "arcsin"/ "arccos"/ "arctan"/ "arcsec"/ "arccsc"/ "arccot"/ - "ln"/ "log"/ "exp"/ "floor"/ "ceil"/ "round"/ "random" / "sum" / "sqrt" - ) { return n; } / - n:$multiCharName &{ return options.builtInFunctions.indexOf(n) > -1 } { return n; } + &{ builtInFunctionTitle = ''; return options.singleCharName } + (c:char &{ return pushChar(c, "builtInFunctions") } { return c })+ + // it may not be a complete title + &{ return options.builtInFunctions.indexOf(builtInFunctionTitle) > -1 } { + // increate pos by the title length + /* peg$currPos += builtInFunctionTitle.length; */ + return builtInFunctionTitle; + } / + // singleCharName = false + n:$multiCharName &{ return options.builtInFunctions.indexOf(n) > -1 } { return n } builtInFuncArgs = a:( ( @@ -240,8 +256,8 @@ Function = }) { return a[0] } / voidParentheses &{ let exists = options.functions.indexOf(name)>-1; - if (!exists && options.strict) - error("unexpected empty a after a non-function"); + if (!exists) + error(`"${name}" is not a function, and autoMult is not activated`); return true; // in case not strict mode, it is a valid function regardless of `exists` } { return [] } ) { diff --git a/tests/maps/basic.js b/tests/maps/basic.js index d0e132e..67aa434 100644 --- a/tests/maps/basic.js +++ b/tests/maps/basic.js @@ -95,6 +95,22 @@ module.exports = [ ]) }, + { + math: " 2x^4 ysinx!", + struct: node.am([ + 2, + node.op("^", [ + "x", + node.am([ + node.am([4, "y"]), + node.pOP("!", [ + node.BIF("sin", ["x"]) + ]) + ]) + ]) + ]) + }, + { math: "2x^-45.1y^sinx", struct: node.am([ diff --git a/tests/maps/noSingleChar__BID.js b/tests/maps/noSingleChar__BID.js index 6aa69af..cf647e1 100644 --- a/tests/maps/noSingleChar__BID.js +++ b/tests/maps/noSingleChar__BID.js @@ -9,9 +9,10 @@ module.exports = [ }, { - math: "1+xpi", - parserOptions: { singleCharName: false }, - struct: node.op("+", [1, "xpi"]) + title: "should parse: mine as builtInId when passed through options", + math: "1+mine", + parserOptions: { singleCharName: false, builtInIDs: ["mine"] }, + struct: node.op("+", [1, "mine"]) }, { @@ -20,10 +21,17 @@ module.exports = [ struct: node.op("+", [1, "pix"]) }, + { + math: "1+xpi", + parserOptions: { singleCharName: false }, + struct: node.op("+", [1, "xpi"]) + }, + { math: "1+pi()", parserOptions: { singleCharName: false }, - error: true, errorType: "syntax" + error: true, + errorType: "syntax" }, ]; diff --git a/tests/maps/noSingleChar__functions.js b/tests/maps/noSingleChar__functions.js index 27e1083..1976798 100644 --- a/tests/maps/noSingleChar__functions.js +++ b/tests/maps/noSingleChar__functions.js @@ -38,7 +38,7 @@ module.exports = [ { title: "should use function id as reference (or variable) when strict=false", math: "(2longFuncName! + x)", - parserOptions: { singleCharName: false, keepParentheses: true, functions: ['longFuncName'] }, + parserOptions: { singleCharName: false, keepParen: true, functions: ['longFuncName'] }, struct: node.paren([node.op("+", [ node.am([2, node.pOP("!", ["longFuncName"])]), "x" diff --git a/tests/maps/singleChar__BID.js b/tests/maps/singleChar__BID.js index b70d42b..e4454c4 100644 --- a/tests/maps/singleChar__BID.js +++ b/tests/maps/singleChar__BID.js @@ -21,6 +21,25 @@ module.exports = [ ]), }, + { + math: "2pi", + struct: node.am([2, "pi"]), + }, + + { + math: "2^2pi", + struct: node.op("^", [2, + node.am([2, "pi"]), + ]), + }, + + { + math: "2^2pi!", + struct: node.op("^", [2, + node.am([2, node.pOP("!", ["pi"])]), + ]), + }, + { math: "1+pi()", error: true, errorType: "syntax" diff --git a/tests/maps/singleChar__functions.js b/tests/maps/singleChar__functions.js index dd3b90c..90eafd1 100644 --- a/tests/maps/singleChar__functions.js +++ b/tests/maps/singleChar__functions.js @@ -1,20 +1,11 @@ const { node } = require("./utils"); module.exports = [ - // incase of using methods from object, member expression - // tests are performed there in the memExpr section - { - title: "should parse: f() as function, strict=flase, options.function=[]", - math: "f()", - struct: node.F("f", []), - }, { - title: "should throw: error f(), strict=true, options.function=[]", + title: "should throw: error f(), options.function=[]", math: "f()", - parserOptions: { strict: true }, - error: true, - errorType: "syntax", + error: true, errorType: "syntax" }, { @@ -48,16 +39,6 @@ module.exports = [ }, { - title: "should parse: function with no arguments, no parserOptions, 2f()!", - math: "2f()!", - struct: node.am([ - 2, - node.pOP("!", [node.F("f", [])]) - ]) - }, - - { - title: "function with no arguments, with parserOptions, 2f()!", math: "2f()!", parserOptions: { functions: ["f"] }, struct: node.am([ diff --git a/tests/maps/spreadOperatorAllowed.js b/tests/maps/spreadOperatorAllowed.js deleted file mode 100644 index 9ed16aa..0000000 --- a/tests/maps/spreadOperatorAllowed.js +++ /dev/null @@ -1,6 +0,0 @@ -// testing options.spreadOperatorAllowed - -module.exports = [ - -] - diff --git a/tools/build.js b/tools/build.js index 44c5812..db03984 100644 --- a/tools/build.js +++ b/tools/build.js @@ -3,11 +3,12 @@ const fs = require("fs"); const path = require("path"); const rimraf = require("rimraf"); -let dev = process.env.NODE_ENV !== "production"; - let pegjsOptions = { output: "source", format: "commonjs", + features: { + expected: false + }, }; let replacements = [