mkvsynth/mkvsynth — pull request #10 by lukechampine (2014-01-23 19:24:46+00:00)
Title: add => operator

This change adds the `=>` operator, outlined in pull request #8. It is used like so:
```ruby
x = 0;
x => sin -> cos; # equivalent to x = x -> sin -> cos, so x == 1

# assignment operators return the assigned value, so we can also do:
y = 0;
(y => cos) -> print; # sets y = 1 and prints "1"
```
Unit tests have been updated to use this new operator. It has also been added to the language documentation.

Additionally, the internals of variable assignment have been refactored. Originally, `assign()` handled both simple assignments and assignment operations, i.e. a combination of assignment and a binary operation. Now, `assign()` only handles simple assignments, and a separate function, `assignOp()`, handles assignment operations by creating a new AST and reevaluating it. Specifically, `assignOp()` takes in an expression like `x += 1`, transforms it into `x = x + 1`, and evaluates it. This approach greatly simplifies how assignment operations are performed.

Originally I assumed that this model could easily be extended to `=>`, but unfortunately that did not prove to be the case. This arose because function chains were being parsed using "left" recursion:
```
function_expr
: primary_expr
| primary_expr arg_list
| function_expr CHAIN primary_expr
| function_expr CHAIN primary_expr arg_list
;
```
This meant that in order to implement `=>`, the parser has to traverse through the entire chained function expression, insert the new argument, assign the modified expression to the variable, and reevaluate. Complicating matters is the fact that a function expression is usually parsed as an "operation" node (the function name, plus arguments), but a bare function name without arguments is initially parsed as an "identifier." Long story short, `=>` was implemented with a massive nesting of if/else statements, plus a recursive traversal. It wasn't pretty, and it didn't even work properly!

As a result, I have chosen to refactor function expressions to use right recursion, which makes implementing `=>` vastly simpler at the cost of making standard `->` operations slightly less elegant. It also cleans up the grammar a bit. In doing so, I also uncovered a precedence bug: unary expressions had lower precedence than function expressions. This made sense with the old syntax (e.g. `x = -foo(a, b, c)`), but not so much with the new syntax (`x = -foo a b c`???). Their precedences have been swapped, so the correct form is now `x = -(foo a b c)`. The end result looks like this:
```
chain_expr
: function_expr
| function_expr CHAIN chain_expr
;
function_expr
: unary_expr
| unary_expr arg_list
;
```
You can see in the definition of `assignOp()` how this change allows `=>` to implemented as easily as the other assignment operators. In the future, `->` will likely be redefined as a standard binary operator (instead of as a separate `chain_expr` rule), which will allow `assignOp()` to be further simplified. But that's a pull request for another day.