These examples come from the test/src/grammar
and
test/src/java
directories. The source code has been
simplified in these examples in order to be easier to read. Please
refer to the original source code in the above directories for the
complete compilable versions. Also note that the corresponding C#
examples are found in the test/src/csharp
directory.
The figure below contains a complete and working example of a grammar file for a simple arithmetic language.
%header% GRAMMARTYPE = "LL" DESCRIPTION = "A grammar for a simple arithmetic language." AUTHOR = "Per Cederberg, <per at percederberg dot net>" VERSION = "1.0" DATE = "10 June 2003" LICENSE = "Permission is granted to copy this document verbatim in any medium, provided that this copyright notice is left intact." COPYRIGHT = "Copyright (c) 2003 Per Cederberg. All rights reserved." %tokens% ADD = "+" SUB = "-" MUL = "*" DIV = "/" LEFT_PAREN = "(" RIGHT_PAREN = ")" NUMBER = <<[0-9]+>> IDENTIFIER = <<[a-z]>> WHITESPACE = <<[ \t\n\r]+>> %ignore% %productions% Expression = Term [ExpressionTail] ; ExpressionTail = "+" Expression | "-" Expression ; Term = Factor [TermTail] ; TermTail = "*" Term | "/" Term ; Factor = Atom | "(" Expression ")" ; Atom = NUMBER | IDENTIFIER ;
Figure 1. A grammar for a simple arithmetic language.
To create a parser for the grammar above, the parser source
code must first be created. If the grammar above has been stored
in the file arithmetic.grammar
, this can be
accomplished with the following command:
# java -jar lib/grammatica-1.4.jar arithmetic.grammar --javaoutput test
This will create the files ArithmeticAnalyzer.java
,
ArithmeticConstants.java
,
ArithmeticParser.java
, and
ArithmeticTokenizer.java
in the test
subdirectory. These files contain the source code for the parser.
In order to call the parser, the method in the figure below can
be inserted into another Java class.
private Node parseArithmetic(String input) { Parser parser = null; parser = new ArithmeticParser(new StringReader(input)); return parser.parse(); }
Figure 2. The source code for a method parsing an arithmetic string.
In the case of the arithmetic language, is it also interesting
to analyze and evaluate the contents of the string. This can be
done by subclassing the ArithmeticAnalyzer
class and
overloading the callback methods for the relevant productions. A
small example of this can be seen in the figure below.
class ArithmeticCalculator extends ArithmeticAnalyzer { protected Node exitNumber(Token node) { node.addValue(new Integer(node.getImage())); return node; } protected Node exitExpression(Production node) { ArrayList values = getChildValues(node); Integer value1; Integer value2; String op; int result; if (values.size() == 1) { result = ((Integer) values.get(0)).intValue(); } else { value1 = (Integer) values.get(0); value2 = (Integer) values.get(2); op = (String) values.get(1); result = operate(op, value1, value2); } node.addValue(new Integer(result)); return node; } protected Node exitExpressionTail(Production node) { node.addValues(getChildValues(node)); return node; } protected Node exitTerm(Production node) { ArrayList values = getChildValues(node); Integer value1; Integer value2; String op; int result; if (values.size() == 1) { result = ((Integer) values.get(0)).intValue(); } else { value1 = (Integer) values.get(0); value2 = (Integer) values.get(2); op = (String) values.get(1); result = operate(op, value1, value2); } node.addValue(new Integer(result)); return node; } protected Node exitTermTail(Production node) { node.addValues(getChildValues(node)); return node; } protected Node exitFactor(Production node) throws ParseException { int result; if (node.getChildCount() == 1) { result = getIntValue(getChildAt(node, 0), 0); } else { result = getIntValue(getChildAt(node, 1), 0); } node.addValue(new Integer(result)); return node; } protected Node exitAtom(Production node) { node.addValues(getChildValues(node)); return node; }
Figure 3. The partial (and incomplete) source code for an analyzer calculating the result of an arithmetic expression.