Versions Compared

Key

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

With Groovy's nice closure support, we can avoid most anonymous class and hence get a much less cluttered parser code. In the Tutorial section, we described how a calculator can be built using Java API. We will now present a groovy version that is much shorter.

Code Block
import jfun.parsec.pattern.Patterns;
import jfun.parsec.*;
import jfun.parsec.tokens.Tokenizers;

def getCalculatorParser(){
    def s_line_comment = Scanners.javaLineComment();
    def s_block_comment = Scanners.isBlockComment("/*","*/");
    def s_whitespace = Scanners.isWhitespaces();
    def l_number = Lexers.decimal();
    def ops = Terms.getOperatorsInstance("+","-","*","/", "(", ")");
    def s_delim = [s_line_comment, s_block_comment, s_whitespace].first().many();
    def l_tok = ops.getLexer() | l_number;
    def lexer = Lexers.lexeme(s_delim, l_tok).followedBy(Parsers.eof());
    def p_binary_ops = ops.getParser(["+","-","*","/"] as String[]);
    def p_whitespace_mul = p_binary_ops.not();
    def getOperator2 = {k,v->ops.getParser(k).seq(Map2.impl(v))};
    def getOperator = {k,v->ops.getParser(k).seq(Map.impl(v))};
    def p_plus = getOperator2("+"){a,b->a+b};
    def p_minus = getOperator2("-"){a,b->a-b};
    def p_mul = (ops.getParser("*") | p_whitespace_mul).seq(Map2.impl{a,b->a*b});
    def p_div = getOperator2("/"){a,b->a/b};
    def p_neg = getOperator("-"){a->-a};
    def p_lparen = ops.getParser("(");
    def p_rparen = ops.getParser(")");
    def p_number = Terms.decimalParser(FromString.impl{
      int from, int len, String s -> Double.valueOf(s)
    });
    def p_expr;
    def p_lazy_expr = {p_expr}.asParser();
    def p_term = Parsers.between(p_lparen, p_rparen, p_lazy_expr)|p_number;
    def optable = new OperatorTable()
      .infixl(p_plus, 10)
      .infixl(p_minus, 10)
      .infixl(p_mul, 20)
      .infixl(p_div, 20)
      .prefix(p_neg, 30);
    p_expr = Expressions.buildExpressionParser(p_term, optable);
    def parser = Parsers.parseTokens(lexer, p_expr.followedBy(Parsers.eof()), "calculator");
}
use(org.codehaus.parsec.groovy.ParserCategory){
  def parser = getCalculatorParser();
  println(parser.parse("1+(1-0) 3.0"));
}
  • org.codehaus.parsec.groovy.ParserCategory class provides many useful convenience methods that make parser construction handy.
  • operator "|" is overloaded for the parser "plus" operation, where the second alternative parser is tried when the first fails.
  • [a parser list].first() is equivalent to Parsers.sum(new Parser[]{a parser list}).
  • SomeInterface.impl{some closure here} is a convenience function that adapts a closure to any target interface.
  • groovy allows the use of local mutable variable in closures, that's why we don't need an array here to get around the "final" requirement in Java.
  • everything else is similar to the Java version, just simpler.

The source code of the ParserCategory class can be downloaded here, binary here