diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dbe2c2..1c2c4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,24 @@ +# Unreleased, v3.0 + +## Breaking + +- Node of type "function", when its args is `[{ type: "block", name: "()", ... }]`, function's args is assigned to parenthesis args. In other words, in this case it will be `Array` not `{ type: "block", ... }`. +- Removing node type `delimeter`: + - `f(1,3,4)` when parsed as function, it will have args with length 3. + - `(1,3,4)` will be parsed with `type = "tuple"`. + +## Added + +- Built-in function `sqrt` + +- Node of type "tuple": `(1, 2, x, ...)` +- `options.spreadOperatorAllowed` + + # 22 Oct 2020, v2.3.0 + ## Add - - check for validity of block (including brackets) syntax, e.g., make sure that they are put in the right order and nested correctly, the block has opening and closing charachters. + - check for validity of block (including brackets) syntax, e.g., make sure that they are put in the right order and nested correctly, the block has opening and closing characters. # 31 Aug 2020, v2.2.0 Summary of changes: diff --git a/README.md b/README.md index 69bf1df..a2d48fa 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,8 @@ Default: `true` Maths conventionally works with single char named variables and constants, but in programming languages you have freedom. Moreover, the convention is to use multi-char named identifier. For example, if you want to use "pi" or "phi", etc, you have to set this to `false`. +When a member expression is found, properties and methods are allowed to be multichar, despite of `options.singleCharName` + > TODO: make new options `variables`, with default values "pi" and "phi", ..., use this option to deal with some multi-char variable (or constants, or you can say identifiers) in singleCharName mode ## strict: boolean @@ -77,8 +79,23 @@ For example: ## functions: [string] -When autoMult is `true`, some expression like `f(x)` will be considered as multiplication, in order to parse it as a function with name = "f", you can pass `options.functions = ['f']`. Notice that, strict = flase, some expression such as `f()`, an id follwed by empty parentheses, will be parsed with type = "function" whether or not `options.function` includes "f". +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 follwed by empty parentheses, will be parsed with type = "function" +whether or not `options.function` includes "f". +When parsing `obj.method(...)`, regradless of options, it will be always: +``` + member expression + /\_ + / \_ + __/ \________ + id function + | name | name = "method" + | = "obj" | arg = [ ... ] +``` # Unsure about In these confusing cases, you can handle the parsed expression to transform to what you want. diff --git a/src/Node.js b/src/Node.js index a2c0e73..22a6eb3 100644 --- a/src/Node.js +++ b/src/Node.js @@ -44,8 +44,8 @@ Node.prototype.types = { BLOCK: 'block', AUTO_MULT: "automult", OPERATOR: 'operator', - DELIMITER: 'delimiter', MEMBER_EXPRESSION: 'member expression', + TUPLE: 'tuple', }; Node.prototype.types.values = Object.values(Node.prototype.types); diff --git a/src/math.pegjs b/src/math.pegjs index 7427a91..4d52033 100644 --- a/src/math.pegjs +++ b/src/math.pegjs @@ -11,6 +11,7 @@ functions: [], singleCharName: true, memberExpressionAllowed: true, + keepParentheses: false, strict: false, builtInFunctions: [ "sinh", "cosh", "tanh", "sech", "csch", "coth", @@ -18,7 +19,7 @@ "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" + "ln", "log", "exp", "floor", "ceil", "round", "random", "sqrt" ] }, options); /// override the default options @@ -127,20 +128,22 @@ Factor } factorWithoutNumber = - base:(MemberExpression / Functions / BlockParentheses / BlockVBars / NameNME) _ fac:factorial? { + base:(MemberExpression / Functions / TupleOrExprOrBlock / BlockVBars / NameNME) _ fac:factorial? { if (fac) base = createNode('operator', [base], {name: '!', operatorType: 'postfix'}); return base; } -Delimiter - // there is spaces around expressions already no need for _ rule - = head:Expression tail:("," Expression)* { +// there is spaces around expressions already no need for _ rule +delimiterExpression + = head:Expression tail:(delimiters Expression)* { if (tail.length){ - return createNode('delimiter', [head].concat(tail.map(a => a[1])), {name: ','}); + return [head].concat(tail.map(a => a[1])); } return head; } +delimiters = "," + Functions "functions" = BuiltInFunctions / Function @@ -161,7 +164,7 @@ builtInFuncsTitles = "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" + "ln"/ "log"/ "exp"/ "floor"/ "ceil"/ "round"/ "random" / "sum" / "sqrt" ) { return n; } / n:$multiCharName &{ return options.builtInFunctions.indexOf(n) > -1 } { return n; } @@ -178,39 +181,61 @@ builtInFuncArgs = a:( error('invalid syntax, hint: missing * sign'); } ) / - BlockParentheses / + functionParentheses / BlockVBars / Functions ) { - if (a.type === "block" && a.name === "()") return a.args ? a.args : []; - return [a]; // a is not parenthese + return Array.isArray(a) ? a : [a]; // array when it is functionParentheses } -// TODO: 2axsin3y --- singleCharName = true Function = // no need for FnNameNME - name:$NameNME _ parentheses:(BlockParentheses / VoidBlockParentheses) &{ - if(!parentheses.args /*: VoidBlockParentheses */ && !options.strict) { - // it has to be a function, it may or may not be provided in `options.functions` - return true; - } - let functionExists = options.functions.indexOf(name)>-1; - if (!functionExists && !parentheses.args) error("unexpected empty parenthese after a non-function"); - return functionExists; - } { return createNode('function', parentheses.args || [], { name }); } - + name:$NameNME _ args:( + a:(functionParenthesesNotVoid &{ + let exists = options.functions.indexOf(name)>-1; + if (!exists && !options.autoMult) + error("even autoMult is not activated, hint: add \"*\" sign"); + return exists; + }) { return a[0] } / + voidParentheses &{ + let exists = options.functions.indexOf(name)>-1; + if (!exists && options.strict) + error("unexpected empty a after a non-function"); + return true; // in case not strict mode, it is a valid function regardless of `exists` + } { return [] } + ) { + // `a` is eiher array or expr + return createNode('function', args, { name }); + } +// for member expressions MultiCharFunction = - // for member expressions - name:$MultiCharNameNME _ parentheses:(BlockParentheses / VoidBlockParentheses) { - return createNode('function', [parentheses], { name }); + name:$MultiCharNameNME _ a:functionParentheses { + // `a` is eiher array or expr + return createNode('function', a, { name }); } -BlockParentheses = - "(" args:Delimiter /* returns Expression id no delimiter found */ ")" { return createNode('block', [args], {name: '()'}); } +TupleOrExprOrBlock = + "(" delmOrExpr:delimiterExpression ")" { + if(Array.isArray(delmOrExpr)) + return createNode("tuple", delmOrExpr); + return options.keepParentheses + ? createNode('block', [delmOrExpr], { name: '()' }) + : delmOrExpr; + } + +functionParentheses = + a:("(" b:delimiterExpression ")" { return b } / voidParentheses { return [] }) { + return Array.isArray(a) ? a : [a]; + } + +functionParenthesesNotVoid = + "(" a:delimiterExpression ")" { + return Array.isArray(a) ? a : [a]; + } -VoidBlockParentheses = - "(" _ ")" { return createNode('block', null, {name: '()'}); } +// related to functions +voidParentheses = "(" _ ")" { return [] }; BlockVBars = "|" expr:Expression "|" { return createNode('block', [expr], {name: '||'}) } diff --git a/tests/maps/basic.js b/tests/maps/basic.js index 6972199..ff8cb70 100644 --- a/tests/maps/basic.js +++ b/tests/maps/basic.js @@ -27,4 +27,9 @@ module.exports = [ ]), }, + { + math: "(1, 3, \n4)", + struct: node.tuple([1,3,4]), + }, + ] diff --git a/tests/maps/index.js b/tests/maps/index.js index 856e770..8fafa22 100644 --- a/tests/maps/index.js +++ b/tests/maps/index.js @@ -1,22 +1,26 @@ const basic = require("./basic"); -// const singleChar__memExpr = require("./singleChar__memExpr"); -// const singleChar__functions = require("./singleChar__functions"); +const singleChar__memExpr = require("./singleChar__memExpr"); +const singleChar__functions = require("./singleChar__functions"); const singleChar__autoMult = require("./singleChar__autoMult"); -// const noSingleChar__memExpr = require("./noSingleChar__memExpr"); +const noSingleChar__memExpr = require("./noSingleChar__memExpr"); const noSingleChar__functions = require("./noSingleChar__functions"); const noSingleChar__autoMult = require("./noSingleChar__autoMult"); +const strict = require("./strict"); +const keepParentheses = require("./keepParentheses"); module.exports = { basic, options: { + strict, + keepParentheses, "singleCharName=true": { - // "member expression": singleChar__memExpr, - // "function": singleChar__functions, + "member expression": singleChar__memExpr, + "function": singleChar__functions, "auto multiplication": singleChar__autoMult, }, "singleCharName=false": { - // "member expression": noSingleChar__memExpr, + "member expression": noSingleChar__memExpr, "functions": noSingleChar__functions, "auto multiplication": noSingleChar__autoMult, } diff --git a/tests/maps/keepParentheses.js b/tests/maps/keepParentheses.js new file mode 100644 index 0000000..9512152 --- /dev/null +++ b/tests/maps/keepParentheses.js @@ -0,0 +1,6 @@ +// testing options.keepParentheses + +module.exports = [ + +] + diff --git a/tests/maps/noSingleChar__functions.js b/tests/maps/noSingleChar__functions.js index 56cc63b..0d58226 100644 --- a/tests/maps/noSingleChar__functions.js +++ b/tests/maps/noSingleChar__functions.js @@ -9,10 +9,36 @@ module.exports = [ struct: node.am([2, node.pOP("!", [node.F("longFuncName", [])])]), }, + { + title: "function with multiple arguments", + math: "-.2longFuncName(1,2, sqrt(1)^(x))!", + parserOptions: { singleCharName: false, functions: ['longFuncName'] }, + struct: node.am([ + -0.2, + node.pOP("!", [ + node.F("longFuncName", [1,2, + node.op("^", [ + node.F('sqrt', [1]), + "x" + ]) + ]) + ])] + ), + }, + + { + math: "ax sin 3y", + parserOptions: { singleCharName: false }, + struct: node.am([ + "ax", + node.BIF("sin", [node.am([3, "y"])]) + ]) + }, + { title: "should use function id as reference (or variable) when strict=false", math: "(2longFuncName! + x)", - parserOptions: { singleCharName: false, functions: ['longFuncName'] }, + parserOptions: { singleCharName: false, keepParentheses: true, functions: ['longFuncName'] }, struct: node.block("()", [node.op("+", [ node.am([2, node.pOP("!", ["longFuncName"])]), "x" @@ -34,7 +60,7 @@ module.exports = [ node.F("fn", [ node.op("&&", ["variable_name", 2]) ]), - node.block("()", [node.op("/", [3,2])]) + node.op("/", [3,2]) ]), }, diff --git a/tests/maps/noSingleChar__memExpr.js b/tests/maps/noSingleChar__memExpr.js index 5959b6a..c84f4cd 100644 --- a/tests/maps/noSingleChar__memExpr.js +++ b/tests/maps/noSingleChar__memExpr.js @@ -5,32 +5,72 @@ module.exports = [ { math: "point.x", parserOptions: { singleCharName: false, }, + struct: node.mem(["point", "x"]), + }, + + { + math: "should throw: when options.memExpressionAllowed=false, (point.x)", + parserOptions: { singleCharName: false, memExpressionAllowed: false }, + error: true, errorType: "syntax" }, { math: "1+ point.component_1^2!", parserOptions: { singleCharName: false, }, + struct: node.op("+", [ + 1, + node.op("^", [ + node.mem(["point", "component_1"]), + node.pOP("!", [2]) + ]) + ]) }, { math: "1 + point1. func()", parserOptions: { singleCharName: false, }, + struct: node.op("+", [ + 1, + node.mem(["point1", node.F("func", [])]) + ]) }, { - math: "1 + point1 .\\n func(1.2+x)", + math: "1 + point1 .\n func(1.2+x, s)", parserOptions: { singleCharName: false, }, + struct: node.op("+", [ + 1, + node.mem(["point1", node.F("func", [ + node.op("+", [1.2, "x"]), + "s" + ])]) + ]) }, { - math: "1 + p_1.func(1.2+x)!^2", - parserOptions: { singleCharName: false, }, + math: "1 + p_1().func(1.2+x)!^2", + parserOptions: { singleCharName: false, strict: true }, + error: true, // the next one won't throw + errorType: "syntax" }, { - math: "1 + p.func(1.2+x)^2!", - parserOptions: { singleCharName: false }, - struct: , + math: "1 + p_1().func(1.2+x)!^2", + parserOptions: { singleCharName: false, functions: ["p_1"] }, + struct: node.op("+", [ + 1, + node.op("^",[ + node.pOP("!",[ + node.mem([ + node.F("p_1", []), + node.F("func", [ + node.op("+", [1.2, "x"]) + ]) + ]) + ]), + 2 + ]) + ]) }, ]; diff --git a/tests/maps/singleChar__functions.js b/tests/maps/singleChar__functions.js index 60023a8..dd3b90c 100644 --- a/tests/maps/singleChar__functions.js +++ b/tests/maps/singleChar__functions.js @@ -4,48 +4,78 @@ module.exports = [ // incase of using methods from object, member expression // tests are performed there in the memExpr section { - title: "strict=flase, options.function=[]: should parse f() as function", + title: "should parse: f() as function, strict=flase, options.function=[]", math: "f()", - struct: , + struct: node.F("f", []), }, { - title: "strict=true, options.function=[]: should throw error f()", + title: "should throw: error f(), strict=true, options.function=[]", math: "f()", + parserOptions: { strict: true }, error: true, - errorType: parser.SyntaxError, + errorType: "syntax", }, { math: "f(x)", parserOptions: { functions: [ "f" ] }, + struct: node.F("f", ["x"]) + }, + { + math: "f(1,2,3x)", + parserOptions: { functions: [ "f" ] }, + struct: node.F("f", [1,2,node.am([3,"x"])]) }, { - math: "f(v +2 )(3/2)", + math: "axsin3y", parserOptions: { functions: [ "f" ] }, + struct: node.am([ + node.am(["a","x"]), + node.BIF("sin", [node.am([3,"y"])]) + ]) }, { - math: "", + math: "f(v +2 )(3/2)", parserOptions: { functions: [ "f" ] }, + struct: node.am([ + node.F("f", [node.op("+", ["v",2])]), + node.op("/", [3,2]) + ]) }, { - title: "function with no arguments, no parserOptions, 2f()!", + 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: { singleCharName: false, functions: ["f"] }, + parserOptions: { functions: ["f"] }, + struct: node.am([ + 2, + node.pOP("!", [node.F("f", [])]) + ]) }, { - math: "2f(x)!", + math: "2f(cosx,2)!", parserOptions: { functions: [ "f" ] }, + struct: node.am([ + 2, + node.pOP("!", [node.F("f", [ + node.BIF("cos", ["x"]), + 2 + ])]) + ]) }, ]; diff --git a/tests/maps/singleChar__memExpr.js b/tests/maps/singleChar__memExpr.js index dd12e2a..f09b063 100644 --- a/tests/maps/singleChar__memExpr.js +++ b/tests/maps/singleChar__memExpr.js @@ -19,41 +19,46 @@ module.exports = [ }, { - math: "p1.s(x).c - 1^2!", + math: "p1.s(x,2).c - 1^2!", parserOptions: { functions: ['p1.f'] }, struct: node.op("-", [ - + node.mem([ + node.mem(["p1", node.F("s", ["x",2])]), + "c" + ]), + node.op("^", [1, node.pOP("!", [2])]) ]), }, { math: "1 + p1.fn()", struct: node.op("+", [ - - ]), - }, - - { - math: "1 + p1.fn()!^2", - parserOptions: { functions: ['n'] }, - struct: node.op("+", [ - + 1, + node.mem(["p1", node.F("fn", [])]) ]), }, { math: "1 + p1.f(1.2+x)", struct: node.op("+", [ - + 1, + node.mem(["p1", node.F("f", [ + node.op("+", [1.2, "x"]) + ])]) ]), }, { - title: "Function as method in a member expression", - math: "1 + p1.f(1.2+x)!^2", - parserOptions: { functions: ['p1.f'] }, + math: "1 + p1.fn()!^2", + parserOptions: { functions: ['n'] }, struct: node.op("+", [ - + 1, + node.op("^", [ + node.pOP("!", [ + node.mem(["p1", node.F("fn", [])]) + ]), + 2 + ]) ]), }, diff --git a/tests/maps/spreadOperatorAllowed.js b/tests/maps/spreadOperatorAllowed.js new file mode 100644 index 0000000..9ed16aa --- /dev/null +++ b/tests/maps/spreadOperatorAllowed.js @@ -0,0 +1,6 @@ +// testing options.spreadOperatorAllowed + +module.exports = [ + +] + diff --git a/tests/maps/strict.js b/tests/maps/strict.js new file mode 100644 index 0000000..0bcb462 --- /dev/null +++ b/tests/maps/strict.js @@ -0,0 +1,6 @@ +// testing options.strict + +module.exports = [ + +] + diff --git a/tests/maps/utils.js b/tests/maps/utils.js index f1153a2..ed4121f 100644 --- a/tests/maps/utils.js +++ b/tests/maps/utils.js @@ -94,6 +94,12 @@ class NodeCreator { if (!Array.isArray(args)) this.invalidArgs("member expression"); return { type: "member expression", args }; } + + tuple(args) { + if (!Array.isArray(args)) this.invalidArgs("tuple"); + return { type: "tuple", args }; + } + } exports.node = new NodeCreator(); diff --git a/tests/parser.test.js b/tests/parser.test.js index e426965..c3fe5af 100644 --- a/tests/parser.test.js +++ b/tests/parser.test.js @@ -33,6 +33,7 @@ expect.extend({ } return r; } + const simple_node = simplify(node); const simple_struct = simplify(struct); return {