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";
}
;