Like this person on Google Groups, I'm trying to understand how to write a grammar involving Wolfram Language's low-precedence unary & operator.

The operator works like this:

g[#, #^2] & /@ {x, y, z}

The g[#, #^2] & part builds an anonymous function # -> g[#, #^2], and then /@ maps it over the list {x, y, z}.

However, & has much lower precedence than /@. How come the expression above is parsed as:

(g[#, #^2] &) /@ {x, y, z}

My question is, how do I write a grammar with &, this low-precedence postfix operator?

A similar question was asked here: Binary operators with higher precedence than unary operators, but 1) it's about a unary prefix operator and 2) the answer doesn't explain why it "evaluates just fine".

Also, I had no issues parsing a part of Modelica's expression which involves unary prefix + and - operators that have lower precedence than the binary * and /, for example (see Table 3.1 here). So it seems that the issue lies in & being a postfix operator.

I tried the usual (?) approach suggested in Crafting interpreters:

  1. Write one rule for each precedence level, starting with low-precedence rules and going down to high-precedence ones.
  2. Implement the rules so that "each rule only matches expressions at its precedence level or higher".

Given a language like a & /@ b (I don't care about the g[#, #^2] and {x, y, z} parts at the moment), I wrote this grammar:

start: function

// Low precedence first
function: map
 | function "&"

// High precedence next
map: symbol
 | map "/@" symbol

// This is basically a terminal,
// so it has the highest precedence.
symbol: /[a-zA-Z][0-9a-zA-Z]*/

However, as mentioned in the same Google Groups thread, the postfix & operator in the function rule tries to "become" the entire expression:

When this operator occurs in an expression, it means "everything which comes before me in the expression is my argument".

The problem with this is that it leaves no room for the construct to be embedded in another one. Since the operator says "everything is mine!" it means, by the same token (pun intended) that "I am not a subexpression; I am the top-level constituent!". And since that is the case, because it is a postfix operator, it means that the expression ends right there.

Indeed, when I try to parse a & /@ b with LALR(1) using Lark, I get an "unexpected character" error:

lark.exceptions.UnexpectedCharacters: No terminal matches '/' in the current parser context, at line 1 col 3

Expected one of: 
        * AMPERSAND

So it tries to parse function, but it must be followed by & and nothing else, so /@ following & is a syntax error. Quoting the Google Groups post again, "because it is a postfix operator, it means that the expression ends right there", so a& can't be an argument of the binary /@ operator.

Are there any standard ways of transforming the grammar to make a& /@ b be equivalent to (a&) /@ b without hacks in the parser?

In general, how to write a LALR(1) grammar which allows subexpressions that involve low-precedence unary postfix operators?



Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.