Binary Infix Operators

Top Close Open

The Template::TT3::Element::Operator::Binary module defines a base mixin class for all binary operators. Note that this is not a subclass of Template::TT3::Element, or any other class for that matter. The operator class are designed to be used as mixin base classes. So they only define the one or two methods that are related specifically to it

Template::TT3::Element::Operator::Infix defines a base class for all non-chaining binary operators (NOTE: the non-chaining bit doesn't work at present - it behaves just like the regular chaining infix left operator - I need to look at this). It defines the parse_infix() method which subclasses can inherit to implement the operator precedence parsing mechanism required for non-chaining (see earlier note) infix binary operators.

Template::TT3::Element::Operator::InfixLeft defines a base class for all leftward associative binary operators. It defines the parse_infix() method to handle operator precedence parsing for left associative infix binary operators.

Template::TT3::Element::Operator::InfixRight defines a base class for all leftward associative binary operators. It defines the parse_infix() method to handle operator precedence parsing for right associative infix binary operators.

In all cases the parse_infix() method does more-or-less the same thing.

sub parse_infix {
    my ($self, $lhs, $token, $scope, $prec) = @_;
# Operator precedence
return $lhs 
    if $prec && $self->[META]->[LPREC] <= $prec;
# store expression on LHS of operator
$self->[LHS] = $lhs;

# advance token past operator
$$token = $self->[NEXT];

# Parse the RHS expression
$self->[RHS] = $$token->parse_expr(
    $token, $scope, $self->[META]->[LPREC], 1
)
|| return $self->fail_missing( expression => $token );
    # parse any more binary operators following
    return $$token->skip_ws->parse_infix($self, $token, $scope, $prec);
}

The parse_infix() method is called with the following arguments:

$self       # the current element object (a binary operator token)
$lhs        # the expression element on the left of the operator
$token      # a reference to the current token (initially equals \$self)
$scope      # the current lexical scope (TODO)
$prec       # precedence limit

The $prec precedence limit is the key to operator precedence parsing. If the current operator has a precedence higher than the $prec requested then it binds tighter than the preceding operator or expression. In this case, parse_infix() should continue. If on the other hand the current operator has a lower precedence that that requested, the parse_infix() method should return the $lhs expression immediately.

The difference between the infix, infix left and infix right methods all comes down to what happens when the precedence in the same. For left associative operators, the method should return early. This results in equal precedence operators grouping to the left.

a + b + c       # parsed as: (a + b) + c

For right associative operators, the method should continue when the precedence is equal.

a = b = c       # parsed as: a = (b = c)

In terms of code, the only difference is in the comparison operator in the first line of (proper) code in the parse_infix() method.

Template::TT3::Element::Operator::InfixLeft:

return $lhs 
    if $prec && $self->[META]->[LPREC] <= $prec;

Template::TT3::Element::Operator::InfixRight:

return $lhs 
    if $prec && $self->[META]->[LPREC] < $prec;

So we just need to change the <= comparison to <.

NOTE: the non-chaining infix operator should do something different, but I'm not 100% sure what. Needs looking at. For now it works like the infix left.

After the precedence check, we store the $lhs parameter in the LHS slot of the element.

# store expression on LHS of operator
$self->[LHS] = $lhs;

We then update the $token token reference to point to the token immediately following the current operator token ($self->[NEXT]).

# advance token past operator
$$token = $self->[NEXT];

On the right of the operator we expect another expression so we call the parse_expr() method on the next token.

# Parse the RHS expression
$self->[RHS] = $$token->parse_expr(
    $token, $scope, $self->[META]->[LPREC], 1
)
|| return $self->fail_missing( expression => $token );

We pass it the reference to the current token, $token, so that it can advance the token pointer to consume tokens from the input stream. We also pass it the current lexical scope, $scope, although that isn't being used yet, so you can ignore it for now. The next argument is the precedence of the current operator. This ensures that the parse_expr() method will only consume any further binary operators that have a higher precedence (i.e. bind tighter).

The final option is a $force flag which tells the next token that we really, really want an expression. Otherwise it would be a syntax error to have a binary operator without an expression on the right hand side.

This is required as a special dispensation to command keywords that act as operators (e.g. if, for, etc). They have a lower precedence than all the other operators. This is required so that an expression like this:

a = b if c

Is parsed as:

(a = b) if c

And not:

a = (b if c)

(This is a failing of the current TT2 parser).

By giving keyword operators like if and for a lower precedence than the = assignment operator, we can have the regular operator precedence parser take care of it.

However, we also want to be able to commands as the right hand side of variable expressions. Like this, for example:

a = if a { b } else { c }

Or this:

h = do { fill my/header }

In the usual case, the keywords following the = assignment (if and do in these rather contrived examples) would decline and immediately return from the parse_expr() method because their precedence is lower than that of the assignment operator.

The additional $force flag is a hint telling them that it's OK for them to return themselves even if their precedence is lower than the one we specified as the $prec argument. Any operators following on from the command keyword are then parsed as per the specified precedence.

Now that we have an expression for the right hand side of the operator we are all done. Well, almost. There may be further infix binary operators following this one. They haven't been consumed yet because their precedence was lower than ours. So we finally call the parse_infix() method on the next token (following any whitespace) and pass $self as the left hand side expression, along with the current token reference, the scope and the precedence that we were called with.

    # parse any more binary operators following
    return $$token->skip_ws->parse_infix($self, $token, $scope, $prec);
}

http://tt3.template-toolkit.org/docs/Template/TT3/Elements/Operator.html last modified 13:25:07 10-Dec-2009