Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

I Hate Anonyous Classes!

As you might have guessed, the whole idea of jparsec is pretty much based on a functional mind set. After starting using jparsec, it won't take long for you to find yourself implementing Map, Map2, Map3 etc a lot. And implementing them using anonymous classes can be verbose. For example, we have a Parser<A>, Parser<B> and a Parser<C> and they will be sequentially executed and the 3 result objects will be used to construct a D, as in:

Code Block
Parser<D> d = Parsers.sequence(a, b, c, new Map3<A, B, C, D>() {
  public D map(A a, B b, C c) {
    return new D(a, b, c);
  }
});

Not too bad, huh? Now imagine in real life A becomes UnbelievableGadget<Ipod>, B is IncredibleCartoon<Panda<KungFu>> and C is ViciouslyBeautiful<KingKong>, how does the following look like?

Code Block
Parser<D> d = Parsers.sequence(a, b, c, new Map3<UnbelievableGadget<Ipod>, IncredibleCartoon<Panda<KungFu>>, ViciouslyBeautiful<KingKong>, D>() {
  public D map(UnbelievableGadget<Ipod> ipod, IncredibleCartoon<Panda<KungFu>> panda, ViciouslyBeautiful<KingKong> kingkong) {
    return new D(ipod, panda, kingkong);
  }
});

I hope you enjoyed the ride. (smile)

Mapper

So what would we do if in case we are in a dynamic language like Ruby that's not so crazy about the pointy brackets? Hang on:

Code Block
d = sequence(a, b, c) do |ipod, panda, kingkong|
  new D(ipod, panda, kingkong);
end

And what do the functional nerds do in Haskell? You mean like this?

Code Block
d = sequence a b c D

Awwwwww! And you said you're a Java programmer?

Wait, before you numb yourself with drugs, there is still hope! Every code monkey is created equal (though Haskell programmers are more equal).

The Mapper class is designed to help. So to do it the Ruby way:

Code Block
Parser<D> d = new Mapper<D>() {
  D map(UnbelievableGadget<Ipod> ipod, IncredibleCartoon<Panda<KungFu>> panda, ViciouslyBeautiful<KingKong> kingkong) {
    return new D(ipod, panda, kingkong);
  }
}.sequence(a, b, c);

"Hey, that's not truly comparable, what are those '<' and '>' joggling there for?"

Hang on, don't push your luck, JAVA programmers!

And here's an interesting one, it's called "curry":

Code Block
Parser<D> d = Mapper.curry(D.class).sequence(a, b, c);

The curry() method takes extra parameters for "currying" purpose. i.e. you can pass in extra parameters to the constructor of D if they are known before the parsers run. For example, if the D constructor takes an extra boolean parameter and you know that you want to pass a "true", here is how it's done:

Code Block
Parser<D> d = Mapper.curry(D.class, true).sequence(a, b, c);

Anchor
ternaryop
ternaryop

Currying Operators

A real example is to parse the Java ternary "?:" operator. let's first assume that the conditional expression is modeled as:

Code Block
public class ConditionaExpression implements Expression {
  // ...
  public ConditionalExpression(Expression cond, Expression consequence, Expression alternative) {
    // ...
  }
}

After careful observation of the precedence and associativity, the "? consequenceExpression :" part is indeed a right-associative binary operator. Any expression can be the consequenceExpression, but the "?:" binds more tightly to the alternativeExpression than the condExpression.

In order to declare the "?:" operator as a binary right associative operator, we'll need to create a parser that parses the consequence expression between a "?" and a ":". This parser should return a Map2 that transforms the left operand (condition) and the right operand (alternative) to the conditional expression. Like this:

Code Block
static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) {
  return Parsers.between(terminals.token("?"), consequence, terminals.token(":")).map(new Map<Expression, Binary<Expression>>() {
    public Binary<Expression> map(final Expression consequenceExpr) {
      return new Binary<Expression>() {
        public Expression map(Expression condExpr, Expression alternativeExpr) {
          return new ConditionalExpression(condExpr, consequenceExpr, alternativeExpr);
        }
      };
    }
  };
}

I'll pause for 5 minutes for you to read through the above code snippet and understand the wits buried in the annonymous class nested in the outer anonymous class, and of course, to understand this sentence. (smile)


Okay, time's up.

The returned Parser<Binary<Expression>> can then be used in an OperatorTable, as:

Code Block
Parser.Reference<Expression> ref = Parser.newReference();
Parser<Expression> expression = new OperatorTable<Expression>()
  .prefix(...)
  .postfix(...)
  .infixr(conditionalOperator(ref.lazy()), 50)
  ....;
ref.set(expression);

And if you now see what I'm really up to, and unsurprisingly not impressed by the extra noises in the anonymous classes, here's how we can do it differently with Mapper:

Code Block
static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) {
  return Mapper.<Expression>curry(ConditionalExpression.class).infix(consequence.between(terminals.token("?"), terminals.token(":")));
}

This code does exactly the same thing as our super duper anonymous classes above.

And using the _ method to explicitly ignore the return values of the "?" and ":" operators, we can make it look even more intuitive:

Code Block
import static org.codehaus.jparsec.misc.Mapper._;

static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) {
  return Mapper.<Expression>curry(ConditionalExpression.class).infix(_(terminals.token("?")), consequence, _(terminals.token(":")));
}

The End

For something as cunning as Mapper, I hope you aren't surprised by its extra requirement of cglib to stay sane performance wise.