Structured code - support for structured programming

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.

Using the standard java-like syntax

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
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);
} catch (JepException e) {
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);

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);

As is while:

    Node n4 = jep.parse(
      	"x=0; tot=0;\n" +
       	"while(x++<100) {\n" +
       	"  tot+=x;\n" +
    Object res4 = jep.evaluate(n4);

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" +
         "}" +
    Object res5 = jep.evaluate(n5);

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.

An example custom grammar

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.addSymbols(new String[]{"(",")","[","]","{","}",",",";",
        this.setImplicitMultiplicationSymbols(new String[]{"(","["});

        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);
        // An if statement: if( expression ) statement else
        // <ifRule>    ::= "if" "(" <expr> ")" <statement> [ "else" <statement> ] 
        JavaIfRule ifRule
        	 = new JavaIfRule(ifTok,ropen,expr,rclose,statement,elseTok);
        // A wile statement: while( expression ) statement
        // <whileRule> ::= "while" "(" <expr> ")" <statement>
        JavaWhileRule whileRule
        	 = new JavaWhileRule(whileTok,ropen,expr,rclose,statement);
        // a for statement: for( expression ; expression ; expression ) statement
        // <forRule>   ::= "for" "(" <expr> ";" <expr> ";" <expr> ")" <statement>  
        JavaForRule forRule
        	 = new JavaForRule(forTok,ropen,expr,semi,rclose,statement);
        // a break statement: break;
        // <breakRule> ::= "break" ";"
        ControlRule breakRule
        	 = new ControlRule(breakTok,semi,ControlValues.BREAK);
        // a continue statement: continue;
        // <contRule>  ::= "continue" ";"
        ControlRule contRule
        	 = new ControlRule(continueTok,semi,ControlValues.CONTINUE);
        // Add the terminated expression 
        // Sets the rules, parameters are the head rule
        // and the rule used for parsing an expression
        // <head> ::= <seq>

This parser could then be used

StructuredEvaluator se = new StructuredEvaluator(new FastEvaluator());
Jep jep = new Jep(new MyStructuredParser(),new JavaOperatorTable(),se);
// 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" +
    "}" +
Object res = jep.evaluate(n);

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.