Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made pow-operator right associative. #2

Closed
wants to merge 1 commit into from
Closed

Made pow-operator right associative. #2

wants to merge 1 commit into from

Conversation

bkiers
Copy link
Contributor

@bkiers bkiers commented Mar 20, 2013

Hi Jos, as usual with your work: great stuff! I did find a little bug though: your parser handles the power operator like any other binary operator (left associative), while the power operator is (in most cases) right associative. I.e., the expression a^b^c is now being parsed as:

    ^
   / \
  ^   c
 / \
a   b

while it should be parsed as:

  ^ 
 / \
a   ^
   / \
  b   c

Since the parser is a LL (top-down) parser, this desired structure is a bit tricky to get. A possible solution:

For the input "a^b^c" the parser would first create (^ a b) and would then encounter "^c". Now instead of creating the AST (^ (^ a b) c), you do the following inside Parser.prototype.parse_pow:

  • make the 'old' left child of (^ a b) (node a) the left child of the new node: (^ a (^ . b))
  • move the old right child, node b, to the left: (^ a (^ b . ))
  • put the new node, c, to the right of b: (^ a (^ b c))

My pull request has the scheme above implemented, including some unit tests.

Cheers,

Bart.

@bkiers
Copy link
Contributor Author

bkiers commented Mar 20, 2013

The scheme outlined above does not work with expressions consisting of more than 3 terms: a^b^c^d.... I'll have another look later. Bedtime now! :)

@bkiers bkiers closed this Mar 20, 2013
@josdejong
Copy link
Owner

Thanks for pointing this out! I grew up with Matlab and Octave, which evaluate the power operator left associative, I just took that behavior for granted...

I have commited a solution in the develop branch, see e9bddfd

Parser.prototype.parse_pow = function (scope) {
    var nodes = [
        this.parse_factorial(scope)
    ];

    // stack all operands of a chained power operator (like '2^3^3')
    while (this.token == '^') {
        this.getToken();
        nodes.push(this.parse_factorial(scope));
    }

    // evaluate the operands from right to left (right associative)
    var node = nodes.pop();
    while (nodes.length) {
        var leftNode = nodes.pop();
        var name = '^';
        var fn = pow;
        var params = [leftNode, node];
        node = new Symbol(name, fn, params);
    }

    return node;
};

@bkiers
Copy link
Contributor Author

bkiers commented Mar 21, 2013

Cool, thanks Jos!

@josdejong
Copy link
Owner

There is another, similar issue: unary minus. How to interpret -2^3: as (-2)^3 or -(2^3)? There seems to be no single convention for this.

@bkiers
Copy link
Contributor Author

bkiers commented Mar 21, 2013

Personally, I like to follow Python's rules in this regard, where the power operator has a higher precedence than the unary minus:

bart@kerberos:~$ python
Python 2.7.3 (default, Apr 20 2012, 22:44:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> -2**4
-16
>>> -(2**4)
-16
>>> (-2)**4
16

But, as you mention, there's no strict convention about this. Your call!

@josdejong
Copy link
Owner

I think I'm going to list the precedence choices of the most used math/programming languages and then make a choice to consistently follow one of the two camps. So far that has been the matlab/octave camp. Let's see which camp matches best with the user community that math.js aims to reach.

@josdejong
Copy link
Owner

Ok, I did some checking.

Left or right associative exponentiation, for example a^b^c

  • right associative (a^(b^c)): python, ruby, perl, mathematica, bash, basic
  • left associative ((a^b)^c): octave, matlab, excel

Precedence of unary minus over exponentiation, for example -a^b

  • power first (-(a^b)): python, ruby, perl, octave, matlab, mathematica, basic
  • unary minus first ((-a)^b): excel, bash

As math.js is library for JavaScript (a scripting language), I think it's good to follow the scripting languages in this respect: python, ruby, perl, etc. So I will go for the right associative power with higher precedence than unary minus. Code changes are on the way...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants