Beautiful Code [141]
infix("?", 20, function (left) {
this.first = left;
this.second = expression(0);
advance(":");
this.third = expression(0);
this.arity = "ternary";
return this;
});
The . operator is used to select a member of an object. The token on the right must be a name, but it will be used as a literal:
infix(".", 90, function (left) {
this.first = left;
if (token.arity !== "name") {
token.error("Expected a property name.");
}
token.arity = "literal";
this.second = token;
this.arity = "binary";
advance( );
return this;
});
The [ operator is used to dynamically select a member from an object or array. The expression on the right must be followed by a closing ]:
infix("[", 90, function (left) {
this.first = left;
this.second = expression(0);
this.arity = "binary";
advance("]");
return this;
});
Those infix operators are left associative. We can also make right associative operators, such as short-circuiting logical operators, by reducing the right binding power:
var infixr = function (id, bp, led) {
var s = symbol(id, bp);
s.led = led || function (left) {
this.first = left;
this.second = expression(bp - 1);
this.arity = "binary";
return this;
};
return s;
}
The && operator returns the first operand if the first operand is falsy. Otherwise, it returns the second operand. The || operator returns the first operand if the first operand is truthy; otherwise, it returns the second operand:
infixr("&&", 40);
infixr("||", 40);
Top Down Operator Precedence > Prefix Operators
9.7. Prefix Operators
We can do a similar thing for prefix operators. Prefix operators are right associative. A prefix does not have a left binding power because it does not bind to the left. Prefix operators can sometimes be reserved words (reserved words are discussed in the section "Scope," later in this chapter):
var prefix = function (id, nud) {
var s = symbol(id);
s.nud = nud || function ( ) {
scope.reserve(this);
this.first = expression(80);
this.arity = "unary";
return this;
};
return s;
}
prefix("-");
prefix("!");
prefix("typeof");
The nud of ( will call advance(")") to match a balancing ) token. The ( token does not become part of the parse tree because the nud returns the expression:
prefix("(", function ( ) {
var e = expression(0);
advance(")");
return e;
});
Top Down Operator Precedence > Assignment Operators
9.8. Assignment Operators
We could use infixr to define our assignment operators, but we want to do two extra bits of business, so we will make a specialized assignment function. It will examine the left operand to make sure that it is a proper lvalue. We will also set an assignment flag so that we can later quickly identify assignment statements:
var assignment = function (id) {
return infixr(id, 10, function (left) {
if (left.id !== "." && left.id !== "[" &&
left.arity !== "name") {
left.error("Bad lvalue.");
}
this.first = left;
this.second = expression(9);
this.assignment = true;
this.arity = "binary";
return this;
});
};
assignment("=");
assignment("+=");
assignment("-=");
Notice that we have a sort of inheritance pattern, where assignment returns the result of calling infixr, and infixr returns the result of calling symbol.
Top Down Operator Precedence > Constants
9.9. Constants
The constant function builds constants into the language. The nud mutates a name token into a literal token:
var constant = function (s, v) {
var x = symbol(s);
x.nud = function ( ) {
scope.reserve(this);
this.value = symbol_table[this.id].value;
this.arity = "literal";
return this;
};
x.value = v;
return x;
};
constant("true", true);
constant("false", false);
constant("null", null);
constant("pi", 3.141592653589793);
Top Down Operator Precedence > Scope
9.10. Scope
We use functions such as infix