+/*********************************************************************
+Copyright 2012, Ralph Ronnquist.
+
+This file is part of GORITE.
+
+GORITE is free software: you can redistribute it and/or modify it
+under the terms of the Lesser GNU General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+GORITE is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of the Lesser GNU General Public
+License along with GORITE. If not, see <http://www.gnu.org/licenses/>.
+**********************************************************************/
+
+package com.intendico.sdp;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.text.ParseException;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * The Grammar class is used for defining a grammar.
+ */
+public class Grammar {
+
+ //
+ // Support methods for text processing.
+ //
+
+ /**
+ * Utility method to make a string from a named file.
+ */
+ static public String loadFile(String filename)
+ throws IOException {
+ FileReader f = new FileReader( filename );
+ char [] buffer = new char [ 1024 ];
+ int length;
+ StringBuffer s = new StringBuffer();
+ while ( ( length = f.read( buffer ) ) >= 0 ) {
+ if ( length > 0 )
+ s.append( buffer, 0, length );
+ }
+ f.close();
+ return s.toString();
+ }
+
+ private String strip(String a) {
+ Class [] c = { BNFGrammar.class, getClass() };
+ for ( int i=0; i < c.length; i++ ) {
+ if ( a.startsWith( c[i].getName() + "$" ) )
+ return a.substring( c[i].getName().length() + 1 );
+ }
+ return a;
+ }
+
+ /**
+ * Utility method to map a string index into a line number, by
+ * counting new-lines.
+ */
+ static public int lineNumber(CharSequence s,int index) {
+ int lineno = 1;
+ for ( int i = 0; i < index; i++ ) {
+ if ( s.charAt( i ) == '\n' ) {
+ lineno++;
+ }
+ }
+ return lineno;
+ }
+
+ /**
+ * Utility method that forms a string to mark out the indexed
+ * position in the input string.
+ */
+ static public String inputPosition(CharSequence input,int index) {
+ return inputPosition( input, index, 0 );
+ }
+
+ /**
+ * Utility method that forms a string to mark out the indexed
+ * position and span in the input string.
+ */
+ static public String inputPosition(CharSequence input,int index,int span) {
+ if ( index < 0 )
+ return "" ;
+
+ int from = lastIndexOf( input, "\n", index - 1 ) + 1;
+ int to = indexOf( input, "\n", index );
+ boolean spandots = false;
+
+ if ( to < from )
+ to = input.length();
+
+ int lineno = lineNumber( input, from );
+ int at = index - from;
+
+ if ( span < 1 )
+ span = 1;
+
+ String line = lineno + ": ";
+
+ if ( span > 43 ) {
+ span = 40;
+ spandots = true;
+ }
+
+ if ( at + span > 60 ) {
+ // Adjust to fit on a line
+ int clip = at + span - 55;
+ at -= clip;
+ if ( at < 0 ) {
+ clip -= at;
+ at = 0;
+ }
+ from += clip;
+ line += "...";
+ }
+
+ if ( to - from > 70 )
+ to = from + 70;
+
+ at += line.length();
+ line += input.subSequence( from, to ).toString();
+ if ( at + span > to )
+ span = to - at;
+
+ if ( span < 1 )
+ span = 1;
+
+ StringBuffer out = new StringBuffer( line );
+ out.append( "\n" );
+
+ for (int i=0; i<at; i++ ) {
+ if ( Character.isWhitespace( line.charAt( i ) ) ) {
+ out.append( line.charAt( i ) );
+ } else {
+ out.append( " " );
+ }
+ }
+
+ for (int i=0; i<span; i++ ) {
+ out.append( "^" );
+ }
+
+ if ( spandots )
+ out.append( "..." );
+
+ return out.toString();
+ }
+
+ /**
+ * Utility method to determine if an CharSequence starts with a given
+ * string at a given position.
+ */
+ static public boolean startsWith(CharSequence input,int from,String what) {
+ int end = from + what.length();
+ if ( end > input.length() )
+ return false;
+ return what.contentEquals( input.subSequence( from, end ) );
+ }
+
+ /**
+ * Returns the last occurence of a given string before a given index.
+ */
+ static public int lastIndexOf(CharSequence s,String what,int from) {
+ int endw = what.length();
+ if ( endw == 0 )
+ return from;
+ int end = s.length();
+ int end1 = end - endw;
+ char first = what.charAt( endw - 1 );
+ for ( from = from - 1; from >= endw; from-- ) {
+ if ( s.charAt( from ) != first )
+ continue;
+ for ( int p = from - 1, k = endw - 2; p >= 0; p--, k-- ) {
+ if ( k < 0 )
+ return from;
+ if ( s.charAt( p ) != what.charAt( k ) )
+ break;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the first occurence of a given string.
+ */
+ static public int indexOf(CharSequence s,String what,int from) {
+ int endw = what.length();
+ if ( endw == 0 )
+ return from;
+ int end = s.length();
+ int end1 = end - endw;
+ char first = what.charAt( 0 );
+ for ( ; from < end1; from++ ) {
+ if ( s.charAt( from ) != first )
+ continue;
+ for ( int p = from + 1, k = 1;; p++, k++ ) {
+ if ( k >= endw )
+ return from;
+ if ( s.charAt( p ) != what.charAt( k ) )
+ break;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Utility method to scan past blank or comment.
+ */
+ static public int skipBlanksAndComments(CharSequence s,int from) {
+ int end = s.length();
+ for ( ; from < end; from++ ) {
+ if ( Character.isWhitespace( s.charAt( from ) ) )
+ continue;
+ if ( s.charAt( from ) != '/' )
+ return from;
+ if ( s.length() < 2 )
+ return from;
+ if ( s.charAt( from + 1 ) == '*' ) {
+ int x = indexOf( s, "*/", from );
+ if ( x < 0 ) {
+ // Unterminated comment
+ return end;
+ }
+ from = x + 1;
+ continue;
+ }
+ if ( s.charAt( from + 1 ) == '/' ) {
+ int x = indexOf( s, "\n", from );
+ if ( x < 0 ) {
+ // Unterminated comment
+ return end;
+ }
+ from = x;
+ continue;
+ }
+ return from;
+ }
+ return end;
+ }
+
+ /**
+ * The Parse interface represents the result from parsing, prior
+ * to semantic processing.
+ */
+ public interface ParseTree {
+
+ /**
+ * Returns the first character position spanned by this parse
+ * tree.
+ */
+ public int getFrom();
+
+ /**
+ * Returns the first significant character position within the
+ * region spanned by this parse tree.
+ */
+ public int getFirst();
+
+ /**
+ * Returns the first character position after the region
+ * spanned by this parse tree.
+ */
+ public int getTo();
+
+ /**
+ * Returns the grammar rule index for this result.
+ */
+ public int getIndex();
+
+ /**
+ * Returns the grammar rule for this result, if any.
+ */
+ public String getRule();
+
+ /**
+ * Returns the 'substance' portion of the span.
+ */
+ public String getText();
+
+ /**
+ * Returns the parsetree's generic syntax name, which is the
+ * rule name for production syntax, and ":" otherwise.
+ */
+ public String getSyntaxName();
+
+ /**
+ * Utility method to form a string representation of this
+ * parse tree element, with an indentation argument.
+ */
+ public String toString(String head);
+
+ /**
+ * Forms a string representation of two lines to indicate the
+ * span of this result on the input line.
+ */
+ public String inputSpan();
+
+ /**
+ * Forms a string representation of the span region.
+ */
+ public String spanString();
+
+ /**
+ * Processing method for this parse tree.
+ */
+ public Object process() throws Throwable;
+
+ /**
+ * Processing method for the children of this parse tree.
+ */
+ public Vector processChildren() throws Throwable;
+ }
+
+ /**
+ * The Result class is for carrying parsing result.
+ */
+ public class Result implements ParseTree {
+
+ /**
+ * Refers to the input being parsed.
+ */
+ public final CharSequence input;
+
+ /**
+ * The character position in the input from where this Result
+ * object spans.
+ */
+ public final int from;
+
+ /**
+ * The first character position recognised as not whitespace
+ * or comment; the beginning of 'substance'.
+ */
+ public final int first;
+
+ /**
+ * The character position in the input to where this Result
+ * object spans. Thus, the span is input.substring(from,to).
+ */
+ public final int to;
+
+ /**
+ * Flag as to whether or not the text should be preserved upon
+ * semantic processing.
+ */
+ public final boolean preserve;
+
+ /**
+ * Returns the first character position spanned by this parse
+ * tree.
+ */
+ public int getFrom() {
+ return from;
+ }
+
+ /**
+ * Returns the first significant character position within the
+ * region spanned by this parse tree.
+ */
+ public int getFirst() {
+ return first;
+ }
+
+ /**
+ * Returns the first character position after the region
+ * spanned by this parse tree.
+ */
+ public int getTo() {
+ return to;
+ }
+
+ /**
+ * Constructor.
+ */
+ public Result(CharSequence s,int a,int b,int c) {
+ this( s, a, b, c, false );
+ }
+
+ /**
+ * Constructor.
+ */
+ public Result(CharSequence s,int a,int b,int c,boolean p) {
+ from = a;
+ first = b;
+ to = c;
+ input = s;
+ preserve = p;
+ }
+
+ /**
+ * Returns the 'substance' portion of the span.
+ */
+ public String getText() {
+ return input.subSequence( first, to ).toString();
+ }
+
+ /**
+ * Returns the grammar rule index for this result.
+ */
+ public int getIndex() {
+ return -1;
+ }
+
+ /**
+ * Returns the grammar rule for this result.
+ */
+ public String getRule() {
+ return null;
+ }
+
+ /**
+ * Returns the parsetree's generic syntax name, which is the
+ * rule name for production syntax, and ":" otherwise.
+ */
+ public String getSyntaxName() {
+ return ":";
+ }
+
+ /**
+ * Returns the canonical token string for this result.
+ */
+ public String getToken() {
+ return "'" + getText();
+ }
+
+ /**
+ * Forms a String representaion of this Result object.
+ * The argument is ignored.
+ */
+ public String toString(String head) {
+ return toString();
+ }
+
+ /**
+ * Forms a string representation of the region spanned.
+ */
+ public String spanString() {
+ return "[" + from + "," + to + "]";
+ }
+
+ /**
+ * Presents this result bmo of an input context on one line,
+ * followed by a 'marker' for the span beneath it.
+ */
+ public String inputSpan() {
+ return inputPosition( input, first, to - first );
+ }
+
+ /**
+ * Forms a String representaion of this Result object.
+ */
+ public String toString() {
+ return "{" + from + "," + first + "," + to +
+ ( preserve? "=!'" : "='" ) +
+ getText() + "'}" ;
+ }
+
+ /**
+ * Default result processing returns null.
+ */
+ public Object process() throws Throwable {
+ if ( preserve )
+ return getText();
+ return null;
+ }
+
+ /**
+ * Processing method for the children of this parse tree.
+ */
+ public Vector processChildren() throws Throwable {
+ return null;
+ }
+ }
+
+ /**
+ * The ProductionResult class is for carrrying production rule
+ * result.
+ */
+ public class ProductionResult extends Result {
+
+ /**
+ * Tells the name of the non-terminal produced.
+ */
+ public String rule;
+
+ /**
+ * Tells the index for the production used.
+ */
+ public int index;
+
+ /**
+ * Holds the result objects of associated with production rule
+ * tokens.
+ */
+ public ParseTree [] results;
+
+ /**
+ * Returns the grammar rule index for this result.
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Returns the grammar rule for this result.
+ */
+ public String getRule() {
+ return rule;
+ }
+
+ /**
+ * Constructor intended for Lexical rules.
+ */
+ public ProductionResult(String r,CharSequence s,int a,int b,int c) {
+ super( s, a, b, c );
+ rule = r;
+ index = -1;
+ }
+
+ /**
+ * General constructor.
+ */
+ public ProductionResult(String r,CharSequence s,int i,ParseTree [] rs) {
+ this( r, s,
+ rs == null? 0 : rs[0].getFrom(),
+ rs == null? 0 : rs[0].getFirst(),
+ rs == null? -1 : rs[ rs.length - 1 ].getTo() );
+ index = i;
+ results = rs;
+ }
+
+ /**
+ * Returns the parsetree's generic syntax name, which is the
+ * rule name for production syntax, and ":" otherwise.
+ */
+ public String getSyntaxName() {
+ return rule;
+ }
+
+ /**
+ * Returns the canonical token string for this result.
+ */
+ public String getToken() {
+ return "<" + getRule() + ">";
+ }
+
+ /**
+ * Forms a string representation of the parse tree.
+ */
+ public String toString() {
+ return toString( "" );
+ }
+
+ /**
+ * Forms a string representation of the parse tree, using the
+ * given string as indentation prefix.
+ */
+ public String toString(String head) {
+ StringBuffer s = new StringBuffer();
+ s.append( rule );
+ s.append( ":" );
+ s.append( index );
+ s.append( "[" );
+ head += " ";
+ if ( results == null ) {
+ s.append( super.toString() );
+ } else {
+ for ( int i=0; i<results.length; i++ ) {
+ if ( i > 0 ) {
+ s.append( "," );
+ }
+ s.append( "\n" );
+ s.append( head );
+ s.append( results[i].toString( head ) );
+ }
+ }
+ s.append( "]" );
+ return s.toString();
+ }
+
+ /**
+ * Default result processing invokes the process method of the
+ * syntax rule.
+ */
+ public Object process() throws Throwable {
+ try {
+ return getDefinition( rule ).process( this );
+ } catch (Throwable e) {
+ throw hideParse( e );
+ }
+ }
+
+ /**
+ * Utility method to process the children
+ */
+ public Vector processChildren() throws Throwable {
+ Vector v = new Vector();
+ if ( results != null ) {
+ for ( int i=0; i<results.length; i++ ) {
+ Object x = results[i].process();
+ if ( x != null )
+ v.add( x );
+ }
+ }
+ return v;
+ }
+ }
+
+
+ /**
+ * The non-terminals.
+ */
+ public TreeMap rules = new TreeMap();
+
+ //
+ // Inner interfaces to support concept generalisation.
+ // These are: Rule, Token and ErrorToken.
+ //
+
+ /**
+ * The Rule interface is for all kinds of syntax definitions.
+ * Presently there are only the two kinds of ProductionList and
+ * Lexical.
+ */
+ public interface Rule {
+
+ /**
+ * Returns a ParseTree object for successful parse, or
+ * null.
+ */
+ public ParseTree parse(CharSequence s,int from) throws ParseException;
+
+ /**
+ * Returns the rule name; the non-terminal defined.
+ */
+ public String getName();
+
+ /**
+ * Performs synthesising operations on a ParseTree
+ * parse tree, and returns a value representing the parse tree
+ * value.
+ */
+ public Object process(ParseTree pr) throws Throwable;
+ }
+
+ //
+ // The parsing elements.
+ //
+
+ /**
+ * Bit field for stderr tracing of parser operation.
+ */
+ public int trace = 0;
+
+ /**
+ * A counter of entries to syntax rules; "attempts"
+ */
+ public int trace_count = 0;
+
+ public static int TRACE_TERMINALS = 1;
+ public static int TRACE_PARSE = 2;
+ public static int TRACE_RECURSION = 4;
+ public static int TRACE_ACTION = 8;
+ public static int TRACE_OPTIONS = 16;
+ public static int TRACE_LINK = 32;
+ public static int TRACE_STACK = 64;
+
+ public static int REUSE_ALWAYS = 0;
+ public static int REUSE_RECURSIVE = 1;
+ public static int REUSE_RARELY = 2;
+ public int reuse_mode = 0;
+ /**
+ * Trace variable.
+ */
+ public int current_from = 0;
+
+ /**
+ * Utility method for tracing.
+ */
+ public void log(CharSequence input,int from,String rule,String m) {
+ ParseTree last = last( from, rule );
+
+ String text = "At " + from + ", " + rule + " " +
+ ( last != null? last.spanString() : "[null]" ) + " - " + m ;
+
+ if ( from != current_from && input != null ) {
+ System.err.println();
+ int span = current_from > from? current_from - from : 0;
+ text = inputPosition( input, from, span ) + "\n" + text;
+ }
+ current_from = from;
+
+ System.err.println( text );
+ }
+
+ /**
+ * Utility method to test trace mode.
+ */
+ public boolean tracing(int mode) {
+ return ( trace & mode ) != 0;
+ }
+
+ /**
+ * Table of successful parse results.
+ */
+ public Hashtable results = null;
+
+ /**
+ * Table of known failed parse postions.
+ */
+ public HashSet failed = null;
+
+ /**
+ * Current parsing level.
+ */
+ public int level = 0;
+
+ /**
+ * Maps positions to tried lexical rules and terminals
+ */
+ public TreeMap errors;
+
+ /**
+ * Support method to create a hash key for a rule at a position.
+ */
+ public String key(int from,String rule) {
+ return rule + " at " + from ;
+ }
+
+ /**
+ * Support method to create a hash key for a rule at a position.
+ */
+ public String key(int from,String rule,int index) {
+ return rule + ":" + index + " at " + from;
+ }
+
+ /**
+ * Returns the last parse result for a rule at a position.
+ */
+ public ParseTree last(int from,String rule) {
+ if ( results == null )
+ return null;
+ return (ParseTree) results.get( key( from, rule ) );
+ }
+
+ /**
+ * Support method to set the parse result for a rule at a position.
+ */
+ public void saveLast(int from,String rule,ParseTree x) {
+ if ( x == null )
+ return;
+ if ( results == null )
+ results = new Hashtable();
+ results.put( key( from, rule ), x );
+ }
+
+ /**
+ * Support method to remove the parse result for a rule at a
+ * position.
+ */
+ public void clearLast(int from,String rule) {
+ if ( results != null )
+ results.remove( key( from, rule ) );
+ }
+
+ /**
+ * Tells whether a parse position is known to be failed.
+ */
+ public boolean isFailed(int from,String rule) {
+ return failed != null && failed.contains( key( from, rule ) );
+ }
+
+ /**
+ * Marks a parse position as failed.
+ */
+ public void setFailed(int from,String rule) {
+ if ( failed == null )
+ failed = new HashSet();
+ failed.add( key( from, rule ) );
+ }
+
+ /**
+ * Unmarks a parse position as failed.
+ */
+ public void clearFailed(int from,String rule) {
+ if ( failed != null )
+ failed.remove( key( from, rule ) );
+ }
+
+ /**
+ * Tells whether a parse position is known to be failed.
+ */
+ public boolean isFailed(int from,String rule,int index) {
+ return failed != null && failed.contains( key( from, rule, index ) );
+ }
+
+ /**
+ * Marks a parse option position as failed.
+ */
+ public void setFailed(int from,String rule,int index) {
+ if ( failed == null )
+ failed = new HashSet();
+ failed.add( key( from, rule, index ) );
+ }
+
+ /**
+ * Unmarks a parse position as failed.
+ */
+ public void clearFailed(int from,String rule,int index) {
+ if ( failed != null )
+ failed.remove( key( from, rule, index ) );
+ }
+
+ //
+ // Error handling...
+ //
+
+ /**
+ * Support method to log an attempted lexical match.
+ */
+ public void tryToken(ErrorToken token,int from) {
+ if ( errors == null )
+ errors = new TreeMap(
+ new Comparator() {
+ public int compare(Object a,Object b)
+ {
+ return ((Integer) a).compareTo( (Integer) b );
+ }
+ } );
+ Integer i = new Integer( from );
+ TreeSet t = (TreeSet) errors.get( i );
+ if ( t == null ) {
+ t = new TreeSet( new Comparator() {
+ public int compare(Object a,Object b) {
+ return compare( (ErrorToken) a, (ErrorToken) b );
+ }
+
+ public int compare(ErrorToken a,ErrorToken b) {
+ return a.errorName().compareTo( b.errorName() );
+ }
+ } );
+ errors.put( i, t );
+ }
+ t.add( token );
+ }
+
+ /**
+ * Report all error tokens.
+ */
+ public String allErrors() {
+ StringBuffer s = new StringBuffer();
+ for ( Iterator i = errors.keySet().iterator(); i.hasNext(); ) {
+ Integer index = (Integer) i.next();
+ TreeSet list = getErrors( index.intValue() );
+ s.append( "Pos " );
+ s.append( index.toString() );
+ s.append( ":" );
+ for ( Iterator e = list.iterator(); e.hasNext(); ) {
+ s.append( " " );
+ s.append( (String) e.next() );
+ }
+ s.append( "\n" );
+ }
+ return s.toString();
+ }
+
+ /**
+ * Utility method that presents the deepest error in format.
+ * <lineno>: <line>
+ * ^
+ * Expected: ...
+ */
+ public String lastError(CharSequence input) {
+ return errors == null? null : lastError( input, lastErrorPosition() );
+ }
+
+ public int lastErrorPosition() {
+ if ( errors == null )
+ return -1;
+ return ((Integer) errors.lastKey()).intValue();
+ }
+
+ /**
+ * Returns a string of the error names produced by the indicated
+ * error tokens.
+ */
+ public TreeSet getErrors(int from) {
+ return (TreeSet) errors.get( new Integer( from ) );
+ }
+
+ /**
+ * Utility method that presents the deepest error in format.
+ * <lineno>: <line>
+ * ^
+ * Expected: ...
+ */
+ public String lastError(CharSequence input,int from) {
+ StringBuffer out = new StringBuffer();
+ out.append( inputPosition( input, from ) );
+ out.append( " [" + from + "]" );
+ out.append( "\nExpected: " );
+
+ TreeSet set = getErrors( from );
+
+ int at = set.size();
+ int i = at;
+
+ for ( Iterator e = set.iterator(); e.hasNext(); i--) {
+ if ( i == 1 ) {
+ if ( at > 1 )
+ out.append( " or " );
+ } else {
+ if ( i != at )
+ out.append( ", " );
+ }
+ out.append( ((ErrorToken) e.next()).errorName() );
+ }
+ out.append( "." );
+ return out.toString();
+ }
+
+ /**
+ * The ErrorToken interface is for producing error token name.
+ */
+ public interface ErrorToken {
+
+ /**
+ * Returns the token name.
+ */
+ public String errorName();
+ }
+
+
+ //
+ // Parsing elements
+ //
+
+ /**
+ * A ProductionList defines a rule by a list of alternative
+ * productions.
+ */
+ public class ProductionList implements Rule {
+
+ /**
+ * The rule defined by the productions.
+ */
+ public final String rule;
+
+ /**
+ * The list of optional productions for this rule.
+ */
+ public final Production [] options;
+
+ /**
+ * Constructor.
+ */
+ public ProductionList(String r,Production [] list) {
+ rule = r;
+ options = list;
+ if ( list != null ) {
+ for ( int i=0; i<list.length; i++ ) {
+ list[i].setRule( r, i );
+ }
+ }
+ }
+
+ /**
+ * Returns the name of the non-terminal defined.
+ */
+ public String getName() {
+ return rule;
+ }
+
+ int usage = -1;
+
+ /**
+ * The default parsing of a ProductionList rule is to apply a
+ * modified recursive decent algorithm, which allows
+ * left-recursive as grammars.
+ */
+ public ParseTree parse(CharSequence input,int from)
+ throws ParseException {
+ if ( options == null ) {
+ // Missing syntax definition
+ throw new ParseException(
+ "<" + rule + "> is not defined.", from );
+ }
+
+ boolean recursive = usage == from; // Re-entering this rule
+
+ ParseTree last = last( from, rule );
+ ParseTree result = null;
+
+ boolean reuse = reuse_mode < 2 && last != null &&
+ ( reuse_mode == 0 || recursive );
+
+ if ( reuse ) {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule,
+ "re-using last (mode=" + reuse_mode + ")" );
+ return last;
+ }
+
+ int prior_usage = usage;
+ usage = from;
+
+ boolean again = false;
+
+ try {
+ do {
+ trace_count ++;
+ again = false;
+ result = null;
+ for (int i=0; result == null && i<options.length; i++ ) {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule, "check option " + i );
+ result = options[i].parse( input, from );
+ }
+ if ( result == null ) {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule, "all options failed" );
+ result = last;
+ again = false;
+ } else {
+ if ( last == null || last.getTo() < result.getTo() ) {
+ saveLast( from, rule, result );
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule, "new result" );
+ again = true;
+ last = result;
+ } else {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule, "old result" );
+ result = last;
+ again = false;
+ }
+ }
+ } while ( again );
+ } finally {
+ usage = prior_usage;
+ }
+
+ return result;
+ }
+
+ /**
+ * Forms a string representation of this rule.
+ */
+ public String toString() {
+ if ( options == null ) {
+ // Missing syntax definition
+ return rule + " IS NOT DEFINED";
+ }
+
+ StringBuffer s = new StringBuffer();
+
+ s.append( rule );
+ s.append( " ::= " );
+ for ( int i=0; i<options.length; i++ ) {
+ if ( i > 0 )
+ s.append( "\n | " );
+ s.append( options[i].toString() );
+ }
+ s.append( "\n" );
+ return s.toString();
+ }
+
+ /**
+ * Default rule processing applies the option action to the
+ * processed children.
+ */
+ public Object process(ParseTree pr) throws Throwable {
+ try {
+ return options[ pr.getIndex() ].
+ action( pr, pr.processChildren() );
+ } catch (Throwable t) {
+ throw hideParse( t );
+ }
+ }
+
+ }
+
+ /**
+ * Represents a production alternative.
+ */
+ public class Production {
+
+ /**
+ * The production rule tokens.
+ */
+ public Token [] tokens;
+
+ /**
+ * Set to mark where this rule is attempted
+ */
+ public int usage = -1;
+
+ /**
+ * Set to the non-terminal that this is a production for, if
+ * any.
+ */
+ public String rule = null;
+
+ /**
+ * Help variable used for tracing purposes.
+ */
+ private String rulex;
+
+ /**
+ * Set to be the option index for the rule the Production
+ * belongs to.
+ */
+ public int index = -1;
+
+ /**
+ * Constructor.
+ */
+ public Production() {
+ }
+
+ /**
+ * Alternative constructor.
+ */
+ public Production(Token [] t) {
+ tokens = t;
+ }
+
+ /**
+ * Utility method to set the tokens of the production.
+ */
+ public void setTokens(Token [] t) {
+ tokens = t;
+ }
+
+ /**
+ * Utility method to set the rule name.
+ */
+ public void setRule(String r,int i) {
+ rule = r;
+ index = i;
+ rulex = rule + ":" + i;
+ }
+
+ /**
+ * Utility variable to hold the key string.
+ */
+ public String key = null;
+
+ /**
+ * Returns the key string; computes it once first.
+ */
+ public String getKey() {
+ if ( key == null )
+ key = toString();
+ return key;
+ }
+
+ /**
+ * Equality method is based on production string.
+ */
+ public boolean equals(Object x) {
+ return x instanceof Production &&
+ getKey().equals( ((Production) x).getKey() );
+ }
+
+ /**
+ * Hash code method; uses the key string.
+ */
+ public int hashCode() {
+ return getKey().hashCode();
+ }
+
+ /**
+ * Returns the number of tokens.
+ */
+ public int length() {
+ return tokens == null? -1 : tokens.length;
+ }
+
+ boolean recursive;
+
+ /**
+ * The default parsing of a Production is to try the tokens
+ * from left to right.
+ */
+ public ParseTree parse(CharSequence input,int from)
+ throws ParseException {
+ if ( tokens == null ) {
+ throw new ParseException(
+ rule + ":" + index + " has no tokens", from );
+ }
+
+ recursive = usage == from;
+
+ if ( usage == from ) {
+ // Reentering this Production recursively.
+ if ( tokens.length > 1 ) {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rulex, "recursive fail");
+ return null;
+ }
+ // Recursive singleton
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rulex, "recursive singleton");
+ return null;
+ }
+
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rulex, "previous usage = " + usage );
+
+ int prior = usage;
+ usage = from;
+
+ try {
+ ParseTree [] results = new ParseTree [ tokens.length ];
+ ParseTree result = null;
+ for ( int i=0; i<tokens.length; i++ ) {
+ results[i] = tokens[i].parse( input, from );
+ if ( results[i] == null ) {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rulex,
+ "failed at token " + tokens[i] );
+ return null;
+ }
+ usage = -1;
+ from = results[i].getTo() ;
+ }
+ result = new ProductionResult( rule, input, index, results );
+
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, prior, rulex,
+ "result = " + result.spanString() );
+ return result;
+ } finally {
+ usage = prior;
+ }
+
+ }
+
+ /**
+ * Forms a string representation of this Production.
+ */
+ public String toString() {
+ StringBuffer s = new StringBuffer();
+
+ for ( int i=0; i<tokens.length; i++ ) {
+ s.append( tokens[i].toString() );
+ s.append( " " );
+ }
+ String actual = getClass().getName();
+ if ( ! actual.equals( Production.class.getName() ) ) {
+ s.append( "# " );
+ s.append( strip( actual ) );
+ s.append( " " );
+ }
+ return s.toString();
+ }
+
+ /**
+ * Default option action is to call action(Vector), and
+ * thereby ignore the source position.
+ */
+ public Object action(ParseTree pr,Vector v) throws Throwable {
+ try {
+ if ( tracing( TRACE_ACTION ) )
+ System.err.println(
+ "action " + pr.getRule() + ":" + pr.getIndex() +
+ " with " + v.size() + " arguments" );
+ return action( v );
+ } catch (Throwable t) {
+ t = new Exception(
+ t + "\n" + pr.inputSpan() + " " + pr.spanString(), t );
+ t.setStackTrace( new StackTraceElement [0] );
+ throw t;
+ }
+ }
+
+ /**
+ * Default Production action is to return the first argument
+ * if there is a single one, otherwise the whole argument
+ * vector.
+ */
+ public Object action(Vector v) throws Exception {
+ if ( tracing( TRACE_ACTION ) )
+ System.err.println( "default Production action with " +
+ v.size() + " arguments" );
+ if ( v.size() == 0 )
+ return null;
+ if ( v.size() == 1 )
+ return v.elementAt( 0 );
+ return v;
+ }
+ }
+
+ /**
+ * The Token interface is for production rule tokens.
+ */
+ public interface Token {
+
+ /**
+ * Returns the token name.
+ */
+ public String getName();
+
+ /**
+ * Returns the token's generic syntax name, which is the rule
+ * name for production syntax, and ":" otherwise.
+ */
+ public String getSyntaxName();
+
+ /**
+ * Returns a parse Result object, or null
+ */
+ public ParseTree parse(CharSequence s,int from) throws ParseException;
+
+ }
+
+ /**
+ * A Terminal is a symbol to find verbatim.
+ */
+ public class Terminal implements Token, ErrorToken {
+
+ /**
+ * The symbol to find.
+ */
+ public final String text;
+
+ /**
+ * The key symbol to use for this token.
+ */
+ public final String ttext;
+
+ /**
+ * Flag to mark whether or not this terminal token is visible
+ * in semantics.
+ */
+ public final boolean preserve;
+
+ /**
+ * Flag to mark whether or not this terminal token is visible
+ * in semantics.
+ */
+ public final boolean optional;
+
+ /**
+ * Constructor.
+ */
+ public Terminal(String t) {
+ this( t, false, false );
+ }
+
+ /**
+ * Constructor.
+ */
+ public Terminal(String t,boolean p) {
+ this( t, p, false );
+ }
+
+ /**
+ * Constructor.
+ */
+ public Terminal(String t,boolean p,boolean opt) {
+ text = t;
+ preserve = p;
+ optional = opt;
+ if ( optional )
+ ttext = "?" + text;
+ else if ( preserve )
+ ttext = "!" + text;
+ else
+ ttext = "'" + text;
+ }
+
+ /**
+ * Equality for terminals are measured on their name strings.
+ */
+ public boolean equals(Object x) {
+ return x instanceof Terminal &&
+ getName().equals( ((Terminal) x).getName() ) ;
+ }
+
+ /**
+ * Hash code method; uses the key string.
+ */
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ /**
+ * Returns the rule name; the non-terminal defined.
+ */
+ public String getName() {
+ return text;
+ }
+
+ /**
+ * Returns the token's generic syntax name, which is the rule
+ * name for production syntax, and ":" otherwise.
+ */
+ public String getSyntaxName() {
+ return ":";
+ }
+
+ /**
+ * The default parsing of a terminal token is to match it
+ * against the current position, after ignoring whitespace and
+ * comments.
+ */
+ public ParseTree parse(CharSequence s,int from) throws ParseException {
+ ParseTree result = last( from, ttext );
+ if ( result != null )
+ return result;
+ if ( isFailed( from, ttext ) )
+ return null;
+ tryToken( this, from );
+ if ( tracing( TRACE_TERMINALS ) )
+ System.err.println( "Find " + text + " at pos " + from );
+ int first = skipBlanksAndComments( s, from );
+ if ( ! startsWith( s, first, text ) ) {
+ if ( optional ) {
+ if ( tracing( TRACE_TERMINALS ) )
+ System.err.println(
+ "Found omitted optional " + text +
+ " at pos " + first );
+ result = new Result(
+ s, from, first, first, false );
+ saveLast( from, "", result );
+ return result;
+ } else {
+ setFailed( from, ttext );
+ return null;
+ }
+ }
+ if ( tracing( TRACE_TERMINALS ) )
+ System.err.println( "Found " + text + " at pos " + first );
+ result = new Result(
+ s, from, first, first + text.length(), preserve );
+ saveLast( from, ttext, result );
+ return result;
+ }
+
+ /**
+ * Forms a string representation of this terminal token.
+ */
+ public String toString() {
+ return ttext ;
+ }
+
+ /**
+ * The standard error name for a terminal token.
+ */
+ public String errorName() {
+ return "'" + text + "'" ;
+ }
+ }
+
+ /**
+ * A NonTerminal is a reference to a syntax rule.
+ */
+ public class NonTerminal implements Token {
+
+ /**
+ * Holds the name of the non-terminal.
+ */
+ public final String rule;
+
+ /**
+ * Constructor.
+ */
+ public NonTerminal(String r) {
+ rule = r;
+ }
+
+ /**
+ * Returns the rule name; the non-terminal defined.
+ */
+ public String getName() {
+ return rule;
+ }
+
+ /**
+ * Returns the token's generic syntax name, which is the rule
+ * name for production syntax, and ":" otherwise.
+ */
+ public String getSyntaxName() {
+ return getDefinition( rule ) instanceof ProductionList?
+ rule : ":" ;
+ }
+
+ /**
+ * The default parsing of a non-terminal token is to defer to
+ * apply its definition to the current input position.
+ */
+ public ParseTree parse(CharSequence input,int from)
+ throws ParseException {
+ try {
+ if ( tracing( TRACE_PARSE ) ) {
+ log( input, from, rule, ">>-- " + (++level) + " -->>" );
+ }
+
+ Rule r = getDefinition( rule );
+ if ( r == null ) {
+ throw new ParseException(
+ "<" + rule + "> is not defined.", from );
+ }
+
+ ParseTree result = r.parse( input, from );
+
+ if ( result == null ) {
+ if ( tracing( TRACE_PARSE ) )
+ log( input, from, rule,
+ "<<-- " + (level--) + " --<< failed" );
+ return null;
+ }
+
+ if ( tracing( TRACE_PARSE ) )
+ log( input, from, rule, "<<-- " + (level--) + " --<< " +
+ result.spanString() );
+
+ return result;
+ } catch (ParseException e) {
+ throw (ParseException) hideParse( e );
+ } catch (Throwable e) {
+ ParseException pe = new ParseException( e.toString(), from );
+ pe.initCause( e );
+ throw pe;
+ }
+ }
+
+ /**
+ * Forms a string representation of a non-terminal token.
+ */
+ public String toString() {
+ return "<" + rule + ">" ;
+ }
+ }
+
+ /**
+ * The Lexical class is a base class for lexical tokens.
+ */
+ abstract public class Lexical implements Rule, ErrorToken {
+
+ /**
+ * The rule name.
+ */
+ final public String rule;
+
+ /**
+ * Constructor.
+ */
+ public Lexical(String r) {
+ rule = r;
+ }
+
+ /**
+ * Returns the rule name.
+ */
+ public String getName() {
+ return rule;
+ }
+
+ /**
+ * Equality for terminals are measured on their error name
+ * strings.
+ */
+ public boolean equals(Object x) {
+ return x instanceof Lexical &&
+ errorName().equals( ((Lexical) x).errorName() ) ;
+ }
+
+ /**
+ * Hash code method; uses the key string.
+ */
+ public int hashCode() {
+ return errorName().hashCode();
+ }
+
+ /**
+ * Handle token ingress, which typically means to skip
+ * whitespace.
+ */
+ public int ingress(CharSequence input,int from) {
+ return skipBlanksAndComments( input, from );
+ }
+
+ /**
+ * The default parsing of a lexical rule: skips blanks and
+ * comments, and then invokes scan.
+ */
+ public ParseTree parse(CharSequence input,int from)
+ throws ParseException {
+ ParseTree last = last( from, rule );
+ if ( isFailed( from, rule ) ) {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule, "lexical known failed" );
+ return null;
+ }
+ if ( last != null ) {
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule,
+ "lexical reuse " + last.spanString() );
+ return last;
+ }
+ tryToken( this, from );
+ int first = ingress( input, from );
+ int to = scan( input, first );
+ if ( to < 0 ) {
+ setFailed( from, rule );
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule, "lexical failed" );
+ return null;
+ }
+ last = new ProductionResult( rule, input, from, first, to );
+ saveLast( from, rule, last );
+ if ( tracing( TRACE_OPTIONS ) )
+ log( input, from, rule, "lexical success" );
+ return last;
+ }
+
+ /**
+ * The lexical function, which returns the end of the span
+ * covered by the lexical rule, or -1. The method is given the
+ * input string and the position to scan, which is neither
+ * whitespace nor in a comment.
+ */
+ abstract public int scan(CharSequence s,int from);
+
+ /**
+ * Forms a string representation of this lexical rule.
+ */
+ public String toString() {
+ return
+ rule + " is lexical " + strip( getClass().getName() ) + "\n" ;
+ }
+
+ /**
+ * The standard error name for a lexical rule.
+ */
+ public String errorName() {
+ return "<" + rule + ">";
+ }
+
+ /**
+ * Semantic processing for a lexical will return the matched
+ * string unless it's an empty string.
+ */
+ public Object process(ParseTree pr) {
+ return pr.getFirst() == pr.getTo()? null : pr.getText();
+ }
+ }
+
+ /**
+ * The Link class is a base class for rules that link to
+ * other grammars.
+ */
+ public class Link implements Rule {
+
+ /**
+ * The grammar linked to.
+ */
+ public final Grammar grammar;
+
+ /**
+ * The name of the non-terminal in this grammar.
+ */
+ public final String rule;
+
+ /**
+ * The corresponding name in the linked grammar.
+ */
+ public final String goal;
+
+ /**
+ * Constructor.
+ */
+ public Link(String r,String g,Grammar gr) {
+ grammar = gr;
+ rule = r;
+ goal = g;
+ }
+
+ /**
+ * Constructor. Alternative constructor using the same rule
+ * name in both grammars.
+ */
+ public Link(String r,Grammar gr) {
+ this( r, r, gr );
+ }
+
+ /**
+ * Returns the 'local' rule name
+ */
+ public String getName() {
+ return rule;
+ }
+
+ /**
+ * Parse input, save result, and return true if successful.
+ */
+ public ParseTree parse(CharSequence input,int from)
+ throws ParseException {
+ if ( grammar.tracing( TRACE_LINK ) )
+ grammar.log( input, from, rule, "link: " + goal );
+ int trace_start = grammar.trace_count;
+
+ ParseTree p = grammar.parse( goal, input, from );
+
+ trace_count += grammar.trace_count - trace_start;
+ int index = grammar.lastErrorPosition();
+ TreeSet t = grammar.getErrors( index );
+ for ( Iterator i = t.iterator(); i.hasNext(); )
+ tryToken( (ErrorToken) i.next(), index );
+
+ if ( p != null ) {
+ if ( tracing( TRACE_LINK ) )
+ log(
+ input, from, rule, "link return: " + goal + " = " +
+ p.spanString() + " with last error at " + index );
+ } else {
+ if ( tracing( TRACE_LINK ) )
+ log(
+ input, from, rule,
+ "link return: " + goal + " failed" );
+ }
+ return p;
+ }
+
+ /**
+ * Semantic processing for a Link
+ */
+ public Object process(ParseTree pr) throws Throwable {
+ // The parse tree is contained in the other grammar.
+ return grammar.process( pr );
+ }
+
+ public String toString() {
+ String pkg = Grammar.this.getClass().getName();
+ pkg = pkg.substring( 0, pkg.lastIndexOf( "." ) + 1 );
+ String name = grammar.getClass().getName();
+ if ( name.startsWith( pkg ) )
+ name = name.substring( pkg.length() );
+ return
+ rule + " is linked to <" + goal + "> in " + name + "\n";
+ }
+ }
+
+ //
+ // Additional Grammar methods
+ //
+
+ /**
+ * Constructor. Invokes the initialisation() method.
+ */
+ public Grammar() {
+ initialisation();
+ }
+
+ /**
+ * Overridable method to perform grammar initialisation.
+ */
+ public void initialisation() {
+ }
+
+ /**
+ * Utility method to add a syntax rule.
+ */
+ public void addRule(Rule rule) {
+ rules.put( rule.getName(), rule );
+ }
+
+ /**
+ * Applies a syntax rule to a string.
+ */
+ public ParseTree parse(String rule,CharSequence input)
+ throws ParseException {
+ for ( Iterator i = getGrammars().iterator(); i.hasNext(); ) {
+ Grammar g = (Grammar) i.next();
+ g.reset();
+ }
+ return parse( rule, input, 0 );
+ }
+
+ /**
+ * Resets the parsing machinery.
+ */
+ public void reset() {
+ errors = null;
+ results = null;
+ failed = null;
+ trace_count = 0;
+ }
+
+ /**
+ * Applies a syntax rule to a string.
+ */
+ public ParseTree parse(String rule,CharSequence input,int from)
+ throws ParseException {
+ Rule r = getDefinition( rule );
+ if ( r == null )
+ throw new ParseException(
+ "<" + rule + "> is not defined.", from );
+ return r.parse( input, from );
+ }
+
+ /**
+ * Utility method to change the stack trace of an exception by
+ * removing all Grammar elements.
+ */
+ public Throwable hideParse(Throwable e) {
+ return hideParse( e, Grammar.class.getName() );
+ }
+
+ /**
+ * Utility method to change the stack trace of an exception by
+ * removing all elements with agiven class name prefix.
+ */
+ public Throwable hideParse(Throwable e,String prefix) {
+ if ( tracing( TRACE_STACK ) )
+ return e;
+
+ for ( Throwable t = e; t != null; t = t.getCause() ) {
+ StackTraceElement[] stack = t.getStackTrace();
+ Vector v = new Vector();
+ for ( int i=0; i<stack.length; i++ ) {
+ String s = stack[i].getClassName();
+ if ( ! ( s.startsWith( prefix ) ||
+ s.startsWith( "java." ) ||
+ s.startsWith( "sun." ) ) )
+ v.add( stack[i] );
+ }
+ t.setStackTrace( (StackTraceElement[]) v.toArray(
+ new StackTraceElement [ v.size() ] ) );
+ }
+ return e;
+ }
+
+ public Rule getDefinition(String rule) {
+ return (Rule) rules.get( rule );
+ }
+
+ /**
+ * Utility method to process a parse, and catch exception
+ */
+ public Object process(ParseTree pr) throws Throwable {
+ return pr.process();
+ }
+
+ /**
+ * Applies a syntax rule to a string and performs its semantics.
+ */
+ public Object parseAndProcess(String rule,String input)
+ throws Throwable {
+ ParseTree p = parse( rule, input );
+ if ( p != null )
+ return process( p );
+ System.err.println( lastError( input ) );
+ return null;
+ }
+
+ /**
+ * Returns a TreeSet of all included grammars, sorted by class
+ * name.
+ */
+ public Vector getGrammars() {
+ return getGrammars( null );
+ }
+
+ /**
+ * Collect all grammars involved, by linking.
+ */
+ private Vector getGrammars(Vector v) {
+ Vector links = v != null? v : new Vector();
+
+ links.add( this );
+
+ for ( Iterator i = rules.keySet().iterator(); i.hasNext(); ) {
+ Rule r = getDefinition( (String) i.next() );
+ if ( r instanceof Link ) {
+ Link link = (Link) r;
+ if ( ! links.contains( link.grammar ) )
+ link.grammar.getGrammars( links );
+ }
+ }
+ return links;
+ }
+
+ /**
+ * Utility method for text representation.
+ */
+ public String toStringAlone() {
+ StringBuffer s = new StringBuffer();
+ for ( Iterator i = rules.keySet().iterator(); i.hasNext(); ) {
+ Rule r = getDefinition( (String) i.next() );
+ s.append( r.toString() );
+ }
+ return s.toString();
+ }
+
+ /**
+ * Textual representation of a grammar.
+ */
+ public String toString() {
+ Vector t = getGrammars( null );
+ StringBuffer s = new StringBuffer();
+ boolean rest = false;
+ for ( Iterator i = t.iterator(); i.hasNext(); ) {
+ Grammar g = (Grammar) i.next();
+ if ( rest )
+ s.append( "\n" );
+ rest = true;
+ s.append( "/*** Definitions of grammar " );
+ s.append( g.getClass().getName() );
+ s.append( " follow ***/\n" );
+ s.append( g.toStringAlone() );
+ }
+ return s.toString();
+ }
+
+ //
+ // Auxillary support for 'remoteHCyl2 semantics'. These are used by the
+ // action tagging of LLBNF.
+ //
+
+ /**
+ * An interface for production rule actions.
+ */
+ public interface RuleAction {
+ public Object action(Vector v)
+ throws Exception;
+ }
+
+ /**
+ * A table of tagged rule actions.
+ */
+ Hashtable rule_actions = new Hashtable();
+
+ /**
+ * Utility method for defining rule actions.
+ */
+ public void setAction(String tag,RuleAction r) {
+ rule_actions.put( tag, r );
+ }
+
+ /**
+ * Utility method for defining rule actions.
+ */
+ public void setAction(Class c,String tag,RuleAction r) {
+ for ( Iterator i = getGrammars().iterator(); i.hasNext(); ) {
+ Grammar g = (Grammar) i.next();
+ if ( c.isInstance( g ) )
+ g.rule_actions.put( tag, r );
+ }
+ }
+}
+