The com.singularsys.extensions.structure
package provides limited support for block style programming with if
,
for
and while
statements.
The exact syntax can be configurable, and a standard configuration provides a java like syntax.
It is not intended to be a fully featured parser, it is more a way to add some
structured programming elements like looping to Jep code.
The key classes in this package are
During parsing using a sub class of StructuredParser
any fragment which matches on of the GrammaticalRuleI
will be recognised.
This will add a node to the parse tree which can either be the a standard function, operator, variable or constant node
or special nodes which implement StructureNode
. These have an eval(Evaluator ev)
method which is used by the StructuredEvaluator
to evaluate the node.
The StandardStructuredParser
gives a Java like syntax. The following example uses a for
loop to add the numbers from 1 to 10.
// Create the standard structured parser. StandardStructuredParser sp = new StandardStructuredParser(); // Create an evaluator which can work with structured elements // and uses the FastEvaluator for normal Jep expressions StructuredEvaluator se = new StructuredEvaluator(new FastEvaluator()); // Create a Jep instance using these components Jep jep = new Jep(sp, se); // needed so i is not the imaginary constant jep.getVariableTable().remove("i"); try { // parse an expression with a for loop Node n = jep.parse("for(i=1;i<=10;i=i+1) x=x+i;" ); // set the value of x to zero jep.addVariable("x", 0.0); Object res = jep.evaluate(n); System.out.println(res); } catch (JepException e) { System.out.println(e.getMessage()); }The standard grammar is quite loose
Node n1 = jep.parse("for(i=1;i<=10;i=i+1) x=x+i" ); Node n2 = jep.parse("for(i=1;i<=10;i=i+1) { x=x+i; }" ); Node n3 = jep.parse("for(i=1;i<=10;i=i+1) { x=x+i }" );are all acceptable.
A java like grammar is further enhanced by using the
JavaOperatorTable
which adds prefix and postfix operators ++i
, --i
and assignment with operators
tot+=i
. This allows simpler for loops
// Set the JavaOperatorTable component jep.setComponent(new JavaOperatorTable()); // parse a for loop using prefix operators Node n2 = jep.parse( "tot=0;" + // initialise the tot variable "for(i=1;i<=10;++i)" + " tot+=i;"); // evaluate Object res2 = jep.evaluate(n2); System.out.println(res2);
If statements are also available.
//Adds up even numbers from 1 to 10 Node n3 = jep.parse( "for(i=1;i<=10;++i) {" + " if( i%2 ==0) {tot+=i;}" + "}"); jep.addVariable("tot", 0.0); Object res3 = jep.evaluate(n3); System.out.println(res3);
As is while
:
Node n4 = jep.parse( "x=0; tot=0;\n" + "while(x++<100) {\n" + " tot+=x;\n" + "}"); Object res4 = jep.evaluate(n4); System.out.println(res4);
The break
and continue
statements can be used inside loops:
//Adds up odd numbers from 1 to 10 Node n5 = jep.parse( "tot=0;\n" + "for(i=1;i<=20;i=i+1) {\n" + " if( i%2 ==0) continue;\n" + " if( i>=10) break;\n" + " tot+=i;\n" + "}" + "tot;\n"); Object res5 = jep.evaluate(n5); System.out.println(res5);
Note that with the java-like grammar statements are separated by semi-colons, curly-brackets can be used to specify blocks of code and new-lines are not significant.
A customised structure-aware parser can be created by using the StructuredParser, either by sub-classing or creating an instance of the class. The grammar is specified by using a set of rules which implement GrammaticalRuleI together with the other configuration options of the Configurable Parser.
Various classes implementing
GrammaticalRuleI
interface for rules defining the grammar
are combined to make the grammar.
ExpressionRule
matches standard Jep expressions,
JavaIfRule
match C/Java style if statements,
JavaWhileRule
matches while loops.
Sequences of statements are matched by
SequenceRule
and blocks code enclosed in brackets are matched by
BlockRule.
The
StatementRule
matches a single statement which can be anyone of the above, the different possibilities
are specified using the addRule()
methods.
The grammar is constructed by creating instances of the above rules and combining them.
The base element, here a SequenceRule
is added to the parser using
setStructuredRules(rule)
.
An example which sets up a java-style grammar is
public class MyStructuredParser extends StructuredParser { private static final long serialVersionUID = 350L; public MyStructuredParser() { // Add typical rules for syntax for normal expressions // with some new symbols for "if" etc. this.addSlashComments(); this.addDoubleQuoteStrings(); this.addWhiteSpace(); this.addExponentNumbers(); this.addOperatorTokenMatcher(); this.addSymbols(new String[]{"(",")","[","]","{","}",",",";", "for","while","break","continue","if","else"}); this.setImplicitMultiplicationSymbols(new String[]{"(","["}); this.addIdentifiers(); this.addWhiteSpaceCommentFilter(); this.addBracketMatcher("(",")"); this.addFunctionMatcher("(",")",","); this.addListMatcher("[","]",","); this.addArrayAccessMatcher("[","]"); SymbolToken ropen = getSymbolToken("("); SymbolToken rclose = getSymbolToken(")"); SymbolToken copen = getSymbolToken("{"); SymbolToken cclose = getSymbolToken("}"); SymbolToken forTok = getSymbolToken("for"); SymbolToken whileTok = getSymbolToken("while"); SymbolToken breakTok = getSymbolToken("break"); SymbolToken continueTok = getSymbolToken("continue"); SymbolToken ifTok = getSymbolToken("if"); SymbolToken elseTok = getSymbolToken("else"); SymbolToken semi = getSymbolToken(";"); // Rule for expressions handled by the Configurable parser // BNF nonterminal <expr> ::= ... ExpressionRule expr = new ExpressionRule(); // Expression possibly terminated by a semi colon // <pexpr> ::= <expr> | <expr>";" PossiblyTerminatedExpressionRule pexpr = new PossiblyTerminatedExpressionRule(expr,semi); // Alternatively use this if you want to be strict about terminating expressions // <texpr> ::= <expr>";" // TerminatedExpressionRule texpr // = new TerminatedExpressionRule(expr,semi); // A single statement, represents a list of possibilities // <statement> ::= <blockRule> | <ifRule> | .... StatementRule statement = new StatementRule(); // A sequence of one of more statements // <seq> ::= <statement>+ SequenceRule seq = new SequenceRule(statement); // A block of code surrounded by { and } // <blockRule> ::= "{" <seq> "}" BlockRule blockRule = new BlockRule(copen,seq,cclose); statement.addRule(blockRule); // An if statement: if( expression ) statement else // <ifRule> ::= "if" "(" <expr> ")" <statement> [ "else" <statement> ] JavaIfRule ifRule = new JavaIfRule(ifTok,ropen,expr,rclose,statement,elseTok); statement.addRule(ifRule); // A wile statement: while( expression ) statement // <whileRule> ::= "while" "(" <expr> ")" <statement> JavaWhileRule whileRule = new JavaWhileRule(whileTok,ropen,expr,rclose,statement); statement.addRule(whileRule); // a for statement: for( expression ; expression ; expression ) statement // <forRule> ::= "for" "(" <expr> ";" <expr> ";" <expr> ")" <statement> JavaForRule forRule = new JavaForRule(forTok,ropen,expr,semi,rclose,statement); statement.addRule(forRule); // a break statement: break; // <breakRule> ::= "break" ";" ControlRule breakRule = new ControlRule(breakTok,semi,ControlValues.BREAK); statement.addRule(breakRule); // a continue statement: continue; // <contRule> ::= "continue" ";" ControlRule contRule = new ControlRule(continueTok,semi,ControlValues.CONTINUE); statement.addRule(contRule); // Add the terminated expression statement.addRule(pexpr); // Sets the rules, parameters are the head rule // and the rule used for parsing an expression // <head> ::= <seq> setStructuredRules(seq); } }
This parser could then be used
StructuredEvaluator se = new StructuredEvaluator(new FastEvaluator()); Jep jep = new Jep(new MyStructuredParser(),new JavaOperatorTable(),se); jep.getVariableTable().remove("i"); // Adds up even numbers from 1 to 10 Node n = jep.parse( "tot=0;\n" + "for(i=1;i<=10;i=i+1) {\n" + " if( i%2 ==1) continue;\n" + " tot+=i;\n" + "}" + "tot;\n"); Object res = jep.evaluate(n); System.out.println(res);
In Backus-Naur Form this grammar would be
<head> ::= <seq> <seq> ::= <statement>+ <statement> ::= <blockRule> | <ifRule> | <whileRule> | <forRule> | <breakRule> | <contRule> | <texpr> <blockRule> ::= "{" <seq> "}" <ifRule> ::= "if" "(" <expr> ")" <statement> [ "else" <statement> ] <whileRule> ::= "while" "(" <expr> ")" <statement> <forRule> ::= "for" "(" <expr> ";" <expr> ";" <expr> ")" <statement> <breakRule> ::= "break" ";" <contRule> ::= "continue" ";" <texpr> ::= <expr> [";"] <expr> ::= // expression parsed by normal Jep, not easily expressible in BNF
Each part of the grammar is expressed by an instance of GramaticalRuleI
.
Important classes are
ExpressionRule
which matches the normal Jep syntax.
SequenceRule
matches a sequences of statements.
StatementRule
matches one of a set of different rules.
These rules are added using the addRule
methods each rule is tested in turn.
BlockRule
matches a block like { statement }
. Other rules match specific constructs like
if
, for
and while
.
Different syntaxes can be constructed by using these rules specifying different tokens in their constructors or
writing a new class which implement GramaticalRuleI
.
During parsing the match
method of each GramaticalRuleI
returns an object of type Node
if there is successful match or null
it if did not match.
Typically the returned object will be a subclass of
StructureNode.
During evaluation the StructuredEvaluator
will call its eval
method of each StructureNode
For example an IfNode
has three children, and during eval
it evaluates the it's first child, depending on the result it will evaluate either it's second or third child.