header { /* * $Id$ * * Copyright (c) 2001-2004, RIACA, Technische Universiteit Eindhoven (TU/e). * All Rights Reserved. * * --------------------------------------------------------------------------- * * The contents of this file are subject to the RIACA Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.riaca.win.tue.nl/ * * --------------------------------------------------------------------------- */ package nl.tue.win.riaca.gap.parser; import antlr.collections.*; import java.util.*; } class GapOMParser extends Parser; options { k = 2; //two token lookahead codeGenMakeSwitchThreshold = 5; codeGenBitsetTestThreshold = 6; buildAST=true; ASTLabelType = "antlr.CommonAST"; // change default of "AST" } tokens { AST_TOP; /* imaginary token */ AST_LIST; /* imaginary token */ AST_FUNCTION; /* imaginary token */ AST_CYCLE; /* imaginary token */ AST_PERM; AST_STRING; UNARY_MINUS; /* helper token to match unary minus. */ UNARY_PLUS; /* helper token to match unary plus. */ } expr : assignExpr SEMI! { #expr = #([AST_TOP, "AST_TOP"], expr); } ; assignExpr : equalityExpression ( ASSIGN^ assignExpr )? ; /** * Matches equality expressions.

* * @since 1.0 */ equalityExpression : relationalExpression ( ( EQ^ | NEQ^ ) relationalExpression )* ; /** * Matches relational expressions.

* * @since 1.0 */ relationalExpression : additiveExpression ( ( LT^ | GT^ | LE^ | GE^ ) additiveExpression )* ; /** * Matches additive expressions.

* * @since 1.0 */ additiveExpression : multiplicativeExpression ( ( PLUS^ | MINUS^ ) multiplicativeExpression )* ; /** * Matches multiplicative expressions.

* * @since 1.0 */ multiplicativeExpression : powerExpression ( ( STAR^ | DIV^ ) powerExpression )* ; /** * Matches power expressions.

* * @since 1.0 */ powerExpression : unaryExpression ( POWER^ unaryExpression )* ; /** * Matches the unary expressions.

* * @since 1.0 */ unaryExpression : MINUS^ { #MINUS.setType( UNARY_MINUS ); } unaryExpression | PLUS^ { #PLUS.setType( UNARY_PLUS ); } unaryExpression | unaryExpressionRest ; /** * Matches the rest of the unary expressions.

* * @since 1.0 */ unaryExpressionRest : ( (LPAREN RPAREN) | ( LPAREN INT COMMA ) ) => permutation | ( ID LPAREN ) => gapFunction | parenthesizedExpression | gapList | string | atom ; /** * Matches one cycle in a permutation (cycles in gap have length > 1). */ cycle : LPAREN! INT COMMA! INT (COMMA! INT)* RPAREN! { #cycle = #([AST_CYCLE, "AST_CYCLE"], cycle); } ; /** * Matches a permutation in cycle notation: either () or a nonempty sequence * of cycles of length at least 2. */ permutation : ( LPAREN! RPAREN! | ( cycle )+ ) { #permutation = #([AST_PERM, "AST_PERM"], permutation); } ; gapFunction : ID^ LPAREN! ( assignExpr ( COMMA! assignExpr )* )? RPAREN! { #gapFunction = #([AST_FUNCTION, "AST_FUNCTION"], gapFunction); } ; parenthesizedExpression : LPAREN! assignExpr RPAREN! ; gapList : LBRACK! ( additiveExpression ( COMMA! additiveExpression )* )? RBRACK! { #gapList = #([AST_LIST, "AST_LIST"], gapList); } ; string : STRING_LITERAL { #string = #([AST_STRING, "AST_STRING"], string); } ; atom : ID | INT | CHAR_LITERAL ; /** * The Gap Lexer.

* * It reads in the input that is said to be GAP input. It tries * to read the tokens that valid GAP input should have. If it * succeeds it generates a sequence of tokens, which in turn can * be read by the Gap parser.

* * @author Manfred Riem (mriem@win.tue.nl). * @version 1.0 */ class GapOMLexer extends Lexer; options { k = 2; charVocabulary = '\u0000'..'\u00FF'; } /** * Reads whitespace and throws it away.

* * @since 1.0 */ WS : (' ' | '\t' | '\n' | '\r') { _ttype = Token.SKIP; } ; /** * Reads the last semi-colon.

* * @since 1.0 */ SEMI : ';' ; /** * Reads the left parenthesis.

* * @since 1.0 */ LPAREN : '(' ; /** * Reads the right parenthesis.

* * @since 1.0 */ RPAREN : ')' ; /** * Reads the left bracket.

* * @since 1.0 */ LBRACK : '[' ; /** * Reads the right bracket.

* * @since 1.0 */ RBRACK : ']' ; /** * Reads the plus operator.

* * @since 1.0 */ PLUS : '+'; /** * Reads the minus operator.

* * @since 1.0 */ MINUS : '-'; /** * Reads the star operator.

* * @since 1.0 */ STAR : '*'; /** * Reads the divide operator.

* * @since 1.0 */ DIV : '/'; /* * Reads the power operator.

* * @since 1.0 */ POWER : '^'; /** * Reads the assignment operator.

* * @since 1.0 */ ASSIGN : ":=" ; /** * Reads the comma operator.

* * @since 1.0 */ COMMA : ',' ; /** * Reads the '<' operator.

* * @since 1.0 */ LT : '<'; /** * Reads the '>' operator.

* * @since 1.0 */ GT : '>'; /** * Reads the "<=" operator.

* * @since 1.0 */ LE : "<="; /** * Reads the ">=" operator.

* * @since 1.0 */ GE : ">="; /** * Reads the '=' operator.

* * @since 1.0 */ EQ : '='; /** * Reads the "<>" operator.

* * @since 1.0 */ NEQ : "<>"; /** * Reads a simple character-literal.

* * @since 1.0 */ CHAR_LITERAL : '\'' (ESC|~'\'') '\'' ; /** * Reads a string literal.

* * @since 1.0 */ //STRING_LITERAL : '"'! (ESC|~'"')* '"'! ; STRING_LITERAL : '"'! (~'"')* '"'! ; /** * Reads an escape-sequence.

* * @since 1.0 */ protected ESC : '\\' ( 'n' | 'r' | 't' | 'b' | 'f' | '"' | '\'' | '\\' | ('0'..'3') ( options { warnWhenFollowAmbig = false; } : ('0'..'9') ( options { warnWhenFollowAmbig = false; } : '0'..'9' )? )? | ('4'..'7') ( options { warnWhenFollowAmbig = false; } : ('0'..'9') )? ) ; protected DIGIT : '0'..'9' ; INT : (DIGIT)+ ; ID options { testLiterals = true; } : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')* ; /** * Tree walker.

* * Transform the AST-tree to an XML-encoded OpenMath object. It does * NOT use any of the libraries for OpenMath to make it possible to * run without any of those libraries.

* * @author Manfred Riem (mriem@win.tue.nl) * @version 1.0 */ class GapOMTreeWalker extends TreeParser; { /** * Parse a GRAPE graph into an OpenMath graph. The method only works * if the graph uses the trivial group as its automorphism group; it * may have a bigger automorphism group, but gamma.group must be the * trivial group. Otherwise, it is relatively difficult to obtain the * adjacency information from the GRAPE syntax. Any graph can be put * into this form by applying the GAP function * (gamma -> NewGroupGraph (Group (()), gamma)) * to it. * @author Erik Postma * @since 1.7 * @param theRecord the Map as returned by {@see GapOMTreeWalker#mapify}. * @return a String with the XML-encoded OpenMath representation for this graph. * @throws RecognitionException if the graph cannot be parsed. */ private String parse_graph (Map theRecord) throws RecognitionException { int i; // Multi-purpose counter. int order; String [] names; boolean isDirected; String tResult = "\n"; String setStartTag = "\n\n"; String listStartTag = "\n\n"; if (theRecord.containsKey ("isSimple") && ((AST) theRecord.get ("isSimple")).getText ().equals ("true")) { tResult += "\n"; isDirected = false; } else { tResult += "\n"; isDirected = true; } // Generate the vertex names. try { order = Integer.parseInt (((AST) theRecord.get ("order")).getText ()); names = new String [order]; if (theRecord.containsKey ("names")) { AST currentName = ((AST) theRecord.get ("names")).getFirstChild (); for (i = 0; i < order; i++) { names [i] = this.expr (currentName); currentName = currentName.getNextSibling (); } } else for (i = 0; i < order; i++) names [i] = " " + Integer.toString (i + 1) + " \n"; } catch (NumberFormatException e) {throw new RecognitionException ("Malformed graph -- order is not a number.");} catch (NullPointerException e) {throw new RecognitionException ("Malformed graph -- maybe order doesn't fit " + "the number of names, or order not present.");} tResult += setStartTag; for (i = 0; i < order; i++) tResult += names [i]; tResult += "\n"; tResult += setStartTag; try { AST currentNeighbourSet = ((AST) theRecord.get ("adjacencies")).getFirstChild (); for (i = 0; i < order; i++) // Now currentNeighbourSet is a list of vertex numbers adjacent to vertex i. { AST currentNeighbour = currentNeighbourSet.getFirstChild (); while (currentNeighbour != null) { int j = Integer.parseInt (currentNeighbour.getText ()); // This represents an edge from i to j. If it's undirected, we will // find it twice. Count it only once. if (isDirected) tResult += listStartTag + names [i] + names [j - 1] + "\n"; else if (j > i) tResult += setStartTag + names [i] + names [j - 1] + "\n"; currentNeighbour = currentNeighbour.getNextSibling (); } currentNeighbourSet = currentNeighbourSet.getNextSibling (); } } catch (NumberFormatException e) {throw new RecognitionException ("Malformed graph -- non-numerical adjacency");} catch (NullPointerException e) {throw new RecognitionException ("Malformed graph -- probably the \"group\" field is not set to " + "the trivial group.");} tResult += "\n" /* End edge set */ + "\n"; /* End graph */ return tResult; } /** * Build a java.util.Map with the key-value pairs in the record. * @author Erik Postma * @since 1.7 * @param record same as in {@see GapOMTreeWalker#parse_record} * @return a java.util.TreeMap with the key-value pairs in the record. * @throws RecognitionException if it is not a well-formed record. */ private Map mapify (AST record) throws RecognitionException { Map tResult = new TreeMap (); AST child = record.getFirstChild (); while (child != null) { if (! child.getText ().equals (":=")) throw new RecognitionException ("Record containing non-assignment."); AST key = child.getFirstChild (); if (key == null) throw new RecognitionException ("Record contains assignment without key."); AST value = key.getNextSibling (); if (value == null) throw new RecognitionException ("Record contains assignment without value for key " + key.getText () + "."); tResult.put (key.getText (), value); child = child.getNextSibling (); } return tResult; } /** * Parse a record. Not all records can be parsed; there is no structure * in OpenMath corresponding to a record. But some classes of records have * a special meaning; like GRAPE graphs, recognizable by having a field * "isGraph" with value true. If you'd like to extend this to some other * class, please put only a simple check here and do the actual parsing in * another method. * @author Erik Postma * @since 1.7 * @param record the AST representing the record (excluding the AST_FUNCTION). * @return a String with an XML-encoded OpenMath representation of the record. * @throws RecognitionException if the record is not recognized or malformed. */ private String parse_record( AST record ) throws RecognitionException { Map theRecord = mapify (record); if (theRecord.containsKey ("isGraph") && ((AST) theRecord.get ("isGraph")).getText ().equals ("true")) return parse_graph (theRecord); throw new RecognitionException ("Record of a class that is not understood."); } /** * Transform a string into a valid XML string (i.e., no "&", "<", ">"). */ private String xmlify(String s) { StringBuffer sb = new StringBuffer(s); replaceChar(sb, '&', "&"); replaceChar(sb, '<', "<"); replaceChar(sb, '>', ">"); return sb.toString(); } private void replaceChar(StringBuffer buf, char c, String s) { // Replace all c in buf by s. int i = buf.toString().lastIndexOf(c); while (i != -1) { buf.replace(i, i + 1, s); i = buf.toString().lastIndexOf(c, i-1); } } } expr returns [String tResult] { String tLeft,tRight; tResult = ""; } : tTop : AST_TOP { AST tChild = tTop.getFirstChild(); tResult = "\n"; if ( tChild != null ) { tResult += this.expr( tChild ); } tResult += "\n"; } | #( tMinus:MINUS tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n" ; } | tUnaryMinus:UNARY_MINUS { AST tAST = tUnaryMinus.getFirstChild(); if ( tAST.getType() == INT ) tResult = "-" + tAST + "\n"; else tResult = "\n" + "\n" + this.expr( tAST ) + ""; } | #( tPlus:PLUS tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n" ; } | #( tLess:LT tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | #( tGreater:GT tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | #( tLessEqual:LE tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | #( tGreaterEqual:GE tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | tUnaryPlus:UNARY_PLUS { AST tAST = tUnaryPlus.getFirstChild(); if ( tAST.getType() == INT ) tResult = "" + tAST + "\n"; else tResult = this.expr( tAST ); } | #( tStar:STAR tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | tDiv:DIV { tResult = "\n"; AST tAST1 = tDiv.getFirstChild(); AST tAST2 = tAST1.getNextSibling(); if ( tAST1.getType() == INT && tAST2.getType() == INT ) { tResult += "\n"; } else { tResult += "\n"; } tResult += this.expr( tAST1 ) + this.expr( tAST2 ) + "\n"; } | #( tPower:POWER tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | #( tEqual:EQ tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | #( tNotEqual:NEQ tLeft=expr tRight=expr ) { tResult = "\n" + "\n" + tLeft + tRight + "\n"; } | tInteger : INT { tResult = "" + tInteger.getText() + "\n"; } | tCycle : AST_CYCLE { tResult = "\n\n"; AST tChild = tCycle.getFirstChild(); while ( tChild != null ) { tResult += this.expr( tChild ); tChild = tChild.getNextSibling(); } tResult += "\n"; } | tPerm : AST_PERM { tResult = "\n\n"; AST tChild = tPerm.getFirstChild(); while ( tChild != null ) { tResult += this.expr( tChild ); tChild = tChild.getNextSibling(); } tResult += "\n"; } | tList : AST_LIST { /* * Add each child to the list.

*/ AST tChild = tList.getFirstChild(); tResult = "\n" + "\n"; if ( tChild != null ) { tResult += this.expr( tChild ); tChild = tChild.getNextSibling(); while( tChild != null ) { tResult += this.expr( tChild ); tChild = tChild.getNextSibling(); } } tResult += "\n"; } | tFunction : AST_FUNCTION { /* * A function call or a record. */ AST tChild = tFunction.getFirstChild(); if ( tChild.getText().equals( "rec" )) tResult = parse_record (tChild); else if (tChild.getText().equals("E")) { // parse "E(5)" tResult = "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "2\n" + "\n" + "\n" + "\n" + "" + tChild.getFirstChild().getText() + "\n" + "\n" + "\n"; } else if (tChild.getText().equals("Group")) { // E.g.: Group([(1,2)(3,4),(1,2,3)]) or Group(()) tResult = "\n" + "\n" + "\n"; tChild = tChild.getFirstChild(); if ( tChild.getType() == AST_LIST ) { // "delistify" the list of generators tChild = tChild.getFirstChild(); } while( tChild != null ) { tResult += this.expr( tChild ); tChild = tChild.getNextSibling(); } tResult += "\n"; } else if (tChild.getText().equals("Z")) { tResult = "\n" + "\n" + this.expr(tChild.getFirstChild()) + "\n"; } else { tResult = "\n" + "\n"; tChild = tChild.getFirstChild(); while( tChild != null ) { tResult += this.expr( tChild ); tChild = tChild.getNextSibling(); } tResult += "\n"; } } | tIdentifier : ID { if ( tIdentifier.getText().equals( "true" ) || tIdentifier.getText().equals( "false" ) ) { tResult = "\n"; } else if ( tIdentifier.getText().equalsIgnoreCase( "infinity" ) ) { tResult = "\n"; } else if ( tIdentifier.getText().equals( "fail" ) ) { tResult = "\n"; } else tResult = "\n"; } | tString : AST_STRING { tResult = "" + xmlify( tString.getFirstChild().getText() ) + "\n"; } ;