Skip to content

Commit

Permalink
πŸš€ Update to version v4.0.0, alhamdulillah πŸ’–
Browse files Browse the repository at this point in the history
  • Loading branch information
MuhammadSawalhy committed Jan 11, 2021
1 parent 25bbaa5 commit 12f85a6
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 97 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 🌟 10 Jan 2021, v4.0
# 🌟 11 Jan 2021, v4.0

## Breaking

Expand All @@ -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

Expand Down
54 changes: 25 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -149,11 +141,8 @@ Type = `Array<string>`, 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
_/\_
Expand All @@ -166,15 +155,22 @@ When parsing `a.method(...)`, regardless of options, `method` will be always.

## .builtInIDs

Type = `Array<string>`, default = `["pi", "phi"]`;
Type = `Array<string>`, default = `["infinity", "pi", "phi"]`;

To use multi-char names when setting [`singleCharName`](#.singleCharName) to true, for example:

|Math Expression| Equivalent To | singleCharName |
| ------------- | ------------- | -------------- |
| `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

Expand Down
78 changes: 47 additions & 31 deletions src/math.pegjs
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -14,9 +16,6 @@
"arcsin", "arccos", "arctan", "arcsec", "arccsc", "arccot",
"ln", "log", "exp", "floor", "ceil", "round", "random", "sqrt"
],
additionalFunctions: [

]
}, options, {
extra: {
memberExpressions: true,
Expand All @@ -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)
Expand Down Expand Up @@ -110,7 +124,7 @@
if (o === "[" || c === "]")
error(`unexpected brackets opening and closing`);

return options.keepParentheses
return options.keepParen
? createNode("parentheses", [node])
: node;

Expand Down Expand Up @@ -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];
Expand All @@ -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);
Expand All @@ -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" =
Expand All @@ -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:(
(
Expand Down Expand Up @@ -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 [] }
) {
Expand Down
16 changes: 16 additions & 0 deletions tests/maps/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
16 changes: 12 additions & 4 deletions tests/maps/noSingleChar__BID.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
},

{
Expand All @@ -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"
},

];
Expand Down
2 changes: 1 addition & 1 deletion tests/maps/noSingleChar__functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
19 changes: 19 additions & 0 deletions tests/maps/singleChar__BID.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit 12f85a6

Please sign in to comment.