/*
 * USE - UML based specification environment
 * Copyright (C) 1999-2004 Mark Richters, University of Bremen
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  
 */

/* $ProjectHeader: use 2-3-1-release.3 Wed, 02 Aug 2006 17:53:29 +0200 green $ */

// set package for all generated classes
header { 
/*
 * USE - UML based specification environment
 * Copyright (C) 1999-2004 Mark Richters, University of Bremen
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  
 */

package org.tzi.use.parser.use; 
}

// ------------------------------------
//  USE parser
// ------------------------------------

{
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.PrintWriter;
import org.tzi.use.parser.MyToken;
import org.tzi.use.parser.ParseErrorHandler;

import org.tzi.use.parser.ocl.*;
}
class GUSEParser extends GOCLParser;
options {
    exportVocab = GUSE;
    defaultErrorHandler = true;
    buildAST = false;
    k = 5;
    //codeGenMakeSwitchThreshold = 2;
    //codeGenBitsetTestThreshold = 3;
}
 

// grammar start

/* ------------------------------------
  model ::= 
    "model" id
    { enumTypeDefinition } 
    {   classDefinition 
      | associationDefinition 
      | "constraints" { invariant | prePost }
    } 
*/
model returns [ASTModel n]
{ 
    ASTEnumTypeDefinition e = null;
    ASTAssociation a = null;
    ASTConstraintDefinition cons = null;
    ASTPrePost ppc = null;
    n = null;
}
:
    "model" name:IDENT { n = new ASTModel((MyToken) name); }
    ( e=enumTypeDefinition { n.addEnumTypeDef(e); } )*
    (   ( generalClassDefinition[n] )
      | ( a=associationDefinition { n.addAssociation(a); } )
      | ( "constraints"
          (   cons=invariant { n.addConstraint(cons); } 
            | ppc=prePost { n.addPrePost(ppc); } 
          )*  
        )
    )*
    EOF
    ;


/* ------------------------------------
  enumTypeDefinition ::= 
    "enum" id "{" idList "}" [ ";" ]
*/
enumTypeDefinition returns [ASTEnumTypeDefinition n]
{ List idList; n = null; }
:
    "enum" name:IDENT LBRACE idList=idList RBRACE ( SEMI )?
        { n = new ASTEnumTypeDefinition((MyToken) name, idList); }
    ;



/* ------------------------------------
  generalClassDefinition ::=
    [ "abstract" ]
   	 { classDefinition | associationClassDefinition }
*/
generalClassDefinition[ASTModel n]
{ boolean isAbstract = false;
  ASTClass c = null;
  ASTAssociationClass ac = null;}
:
    ( "abstract" { isAbstract = true; } )? 
    ( ( c=classDefinition[isAbstract] { n.addClass(c); } ) | 
      ( ac=associationClassDefinition[isAbstract] { n.addAssociationClass(ac); } ) )
       
;


/* ------------------------------------
  classDefinition ::= 
    [ "abstract" ] "class" id [ specialization ] 
    [ attributes ] 
    [ operations ] 
    [ constraints ]
    "end"

  specialization ::= "<" idList
  attributes ::= "attributes" { attributeDefinition }
  operations ::= "operations" { operationDefinition }
  constraints ::= "constraints" { invariantClause }
*/
classDefinition[boolean isAbstract] returns [ASTClass n]
{ List idList; n = null; }
:
    "class" name:IDENT { n = new ASTClass((MyToken) name, isAbstract); }
    ( LESS idList=idList { n.addSuperClasses(idList); } )?
    ( "attributes" 
      { ASTAttribute a; }
      ( a=attributeDefinition { n.addAttribute(a); } )* 
    )?
    ( "operations" 
      { ASTOperation op; }
      ( op=operationDefinition { n.addOperation(op); } )* 
    )?
    ( "constraints"
      ( { ASTInvariantClause inv; } 
        inv=invariantClause { n.addInvariantClause(inv); }
      )*
    )?
    "end"
    ;


/* ------------------------------------
  associationClassDefinition ::= 
    [ "abstract" ] "associationClass" id [ specialization ] 
    [ attributes ]
    [ operations ] 
    [ constraints ]
    [( "aggregation" | "composition" )] "between"
    associationEnd { associationEnd }+
    "end"

  specialization ::= "<" idList
  attributes ::= "attributes" { attributeDefinition }
  operations ::= "operations" { operationDefinition }
  constraints ::= "constraints" { invariantClause }
*/
associationClassDefinition[boolean isAbstract] returns [ASTAssociationClass n]
{List idList; n = null; ASTAssociationEnd ae; }
:
    { MyToken t1 = (MyToken) LT(1); } ("associationClass"|"associationclass") 
        { 
            if (t1.getText().equals("associationClass")) {
               reportWarning("the 'associationClass' keyword is deprecated and will " +
                             "not be supported in the future, use 'associationclass' instead");
            }  
        }
    name:IDENT { n = new ASTAssociationClass((MyToken) name, isAbstract); }
    ( LESS idList=idList { n.addSuperClasses(idList); } )?
    "between"
    ae=associationEnd { n.addEnd(ae); }
    ( ae=associationEnd { n.addEnd(ae); } )+
    ( "attributes" 
      { ASTAttribute a; }
      ( a=attributeDefinition { n.addAttribute(a); } )* 
    )?
    ( "operations" 
      { ASTOperation op; }
      ( op=operationDefinition { n.addOperation(op); } )* 
    )?
    ( "constraints"
      ( { ASTInvariantClause inv; } 
        inv=invariantClause { n.addInvariantClause(inv); }
      )*
    )?
    ( { MyToken t = (MyToken) LT(1); }
      ( "aggregation" | "composition" )
      { n.setKind(t); }
    )?
    "end"
    ;








/* ------------------------------------
  attributeDefinition ::= 
    id ":" type [ ";" ]
*/
attributeDefinition returns [ASTAttribute n]
{ ASTType t; n = null; }
:
    name:IDENT COLON t=type ( SEMI )?
        { n = new ASTAttribute((MyToken) name, t); }
    ;


/* ------------------------------------
  operationDefinition ::= 
    id paramList [ ":" type [ "=" expression ] ] 
    { prePostClause } [ ";" ]
*/
operationDefinition returns [ASTOperation n]
{ List pl; ASTType t = null; ASTExpression e = null; 
  ASTPrePostClause ppc = null; n = null; 
  ASTALActionList al = null;
}
:
    name:IDENT
    pl=paramList
    ( COLON t=type )?
    (EQUAL e=expression )? 
	("begin" al=alActionList "end" )?
    { n = new ASTOperation((MyToken) name, pl, t, e,al); }
    ( ppc=prePostClause { n.addPrePostClause(ppc); } )*
    ( SEMI )?
    ;


/* ------------------------------------
  associationDefinition ::= 
    ( "association" | "aggregation" | "composition" ) 
    id "between"
    associationEnd associationEnd { associationEnd }
    "end"
*/
associationDefinition returns [ASTAssociation n]
{ ASTAssociationEnd ae; n = null; }
:
    { MyToken t = (MyToken) LT(1); }
    ( "association" | "aggregation" | "composition" )
    //    ( classDefinition | (name:IDENT { n = new ASTAssociation(t, (MyToken) name); }) )
    name:IDENT { n = new ASTAssociation(t, (MyToken) name); }
    "between"
    ae=associationEnd { n.addEnd(ae); }
    ( ae=associationEnd { n.addEnd(ae); } )+
    "end"
    ;


/* ------------------------------------
  associationEnd ::= 
    id "[" multiplicity "]" [ "role" id ] [ "ordered" ] [ ";" ]
*/
associationEnd returns [ASTAssociationEnd n]
{ ASTMultiplicity m; n = null; }
:
    name:IDENT LBRACK m=multiplicity RBRACK 
    { n = new ASTAssociationEnd((MyToken) name, m); } 
    ( "role" rn:IDENT { n.setRolename((MyToken) rn); } )?
    ( "ordered" { n.setOrdered(); } )?
    ( SEMI )?
    ;


/* ------------------------------------
  multiplicity ::= 
    multiplicityRange { "," multiplicityRange }

  multiplicityRange ::=
    multiplicitySpec [ ".." multiplicitySpec ]

  multiplicitySpec ::=
    "*" | INT
*/
multiplicity returns [ASTMultiplicity n]
{ ASTMultiplicityRange mr; n = null; }
:
    { 
	MyToken t = (MyToken) LT(1); // remember start position of expression
	n = new ASTMultiplicity(t); 
    }
    mr=multiplicityRange { n.addRange(mr); }
    ( COMMA mr=multiplicityRange  { n.addRange(mr); } )*
    ;

multiplicityRange returns [ASTMultiplicityRange n]
{ int ms1, ms2; n = null; }
:
    ms1=multiplicitySpec { n = new ASTMultiplicityRange(ms1); }
    ( DOTDOT ms2=multiplicitySpec { n.setHigh(ms2); } )?
    ;

multiplicitySpec returns [int m]
{ m = -1; }
:
      i:INT { m = Integer.parseInt(i.getText()); }
    | STAR  { m = -1; }
    ;


/* ------------------------------------
  constraintDefinition ::= 
    invariant | prePost
*/
//  constraintDefinition returns [ASTConstraintDefinition n]
//  { n = null; }
//  :
//      n=invariant // | prePost
//      ;

/* ------------------------------------
  invariant ::= 
    invariantContext invariantClause { invariantClause }

  invariantContext := 
    "context" [ id ":" ] simpleType
*/
invariant returns [ASTConstraintDefinition n]
{ n = null; ASTType t = null; ASTInvariantClause inv = null; }
:
    { n = new ASTConstraintDefinition(); }
    "context"
    ( v:IDENT COLON { n.setVarName((MyToken) v); } )? // requires k = 2

    t=simpleType { n.setType(t); }
    ( inv=invariantClause { n.addInvariantClause(inv); } )* //+
    ;


/* ------------------------------------
  invariantClause ::= 
    "inv" [ id ] ":" expression
*/
invariantClause returns [ASTInvariantClause n]
{ ASTExpression e; n = null; }
:
    "inv" ( name:IDENT )? COLON e=expression
    { n = new ASTInvariantClause((MyToken) name, e); }
    ;


/* ------------------------------------
  prePost ::=
    prePostContext prePostClause { prePostClause }

  prePostContext := 
    "context" id "::" id paramList [ ":" type ]
*/
prePost returns [ASTPrePost n]
{ n = null; List pl = null; ASTType rt = null; ASTPrePostClause ppc = null; }
:
    "context" classname:IDENT COLON_COLON opname:IDENT pl=paramList ( COLON rt=type )?
    { n = new ASTPrePost((MyToken) classname, (MyToken) opname, pl, rt); }
    ( ppc=prePostClause { n.addPrePostClause(ppc); } )+
    ;

/* ------------------------------------
  prePostClause :=
    ("pre" | "post") [ id ] ":" expression
*/
prePostClause returns [ASTPrePostClause n]
{ ASTExpression e; n = null; }
:
    { MyToken t = (MyToken) LT(1); }
    ( "pre" | "post" )  ( name:IDENT )? COLON e=expression
    { n = new ASTPrePostClause(t, (MyToken) name, e); }
    ;



alActionList returns [ASTALActionList al] 
{
	al = null;
	ASTALAction action = null;
	al = new ASTALActionList();
}
:
	( action=alAction {al.add(action);})*
;

alAction returns [ASTALAction action] 
{
	action = null;
}
:
    action = alCreateVar
|	action = alDelete
|	action = alSet
|	action = alSetCreate
|   action = alInsert
|	action = alDestroy
|	action = alIf
|	action = alWhile
|   action = alFor
|   action = alExec
;



alCreateVar returns [ASTALCreateVar var] 
{
	var = null;
	ASTType type = null;
}
:
	("var"|"declare") name:IDENT COLON type=type 
	{ var = new ASTALCreateVar(name,type); }
;

alSet returns [ASTALSet set]
{ 
    set = null;
    ASTExpression lval = null;
    ASTExpression rval = null;
}
:

    "set" lval=expression COLON_EQUAL rval=expression
    { set = new ASTALSet(lval, rval); }
; 

alSetCreate returns [ASTALSetCreate setcreate]
{
    setcreate = null;
    ASTExpression lval = null;
    ASTExpression nameExpr = null;
}
:
    "create" lval=expression COLON_EQUAL { LT(1).getText().equals("new") }? new_:IDENT cls:IDENT 
    ( "namehint" nameExpr=expression )?
    { setcreate = new ASTALSetCreate(lval, (MyToken)cls, nameExpr);}
;


alInsert returns [ASTALInsert insert]
{ ASTExpression e; List exprList = new ArrayList(); insert = null; }
:
    "insert" LPAREN 
    e=expression { exprList.add(e); } COMMA
    e=expression { exprList.add(e); } ( COMMA e=expression { exprList.add(e); } )* 
    RPAREN "into" id:IDENT
    { insert = new ASTALInsert(exprList, (MyToken) id); }
    ;


alDelete returns [ASTALDelete n]
{ ASTExpression e; List exprList = new ArrayList(); n = null; }
:
    "delete" LPAREN
    e=expression { exprList.add(e); } COMMA
    e=expression { exprList.add(e); } ( COMMA e=expression { exprList.add(e); } )*
    RPAREN "from" id:IDENT
    { n = new ASTALDelete(exprList, (MyToken) id); }
    ;


alDestroy returns [ASTALDestroy n]
{ ASTExpression e = null;  n = null; }
:
     "destroy" e=expression
    { n = new ASTALDestroy(e); }
    ;

alIf returns [ASTALIf i]
{
	i = null;
	ASTExpression ifexpr;
	ASTALActionList thenlist;
	ASTALActionList elselist=null;
}
:
	"if" ifexpr=expression 
	"then" thenlist=alActionList
	("else" elselist=alActionList)?
	"endif" 
	{ i = new ASTALIf(ifexpr,thenlist,elselist); }
;

alWhile returns [ASTALWhile w]
{
	w = null;
	ASTExpression expr;
	ASTALActionList body;
}
: 
	"while" expr=expression 
	"do" 
	body=alActionList
	"wend"
	{ w = new ASTALWhile(expr,body); }
;


alFor returns [ASTALFor f]
{
	f = null;
	ASTExpression expr;
	ASTALActionList body;
	ASTType type;
}
: 
	"for" var:IDENT COLON type=type "in" expr=expression 
	"do" 
	body=alActionList
	{ LT(1).getText().equals("next") }? next:IDENT
	{ f = new ASTALFor(var,type,expr,body); }
;

alExec returns [ASTALExecute c]
{ 
    ASTExpression op;
    c=null;
}
:
    "execute" op=expression
    { c = new ASTALExecute(op); }
;


// ------------------------------------
// USE lexer
// ------------------------------------
{
	import java.io.PrintWriter;
	import org.tzi.use.util.Log;
	import org.tzi.use.util.StringUtil;
	import org.tzi.use.parser.ParseErrorHandler;
	import org.tzi.use.parser.MyToken;
}
class GUSELexer extends GOCLLexer;
options {
    importVocab=GUSE;

	// MyLexer.reportError depends on defaultErrorHandler == true for
	// providing correct column number information
    defaultErrorHandler = true;	

    // don't automatically test all tokens for literals
    testLiterals = false;    

    k = 2;
}

{
	protected int fTokColumn = 1;
    private PrintWriter fErr;
    private ParseErrorHandler fParseErrorHandler;
	
	    public void consume() throws CharStreamException {
        if (inputState.guessing == 0 ) {
            if (text.length() == 0 ) {
                // remember token start column
                fTokColumn = getColumn();
            }
        }
        super.consume();
    }

    public String getFilename() {
        return fParseErrorHandler.getFileName();
    }
    
    protected Token makeToken(int t) {
        MyToken token = 
            new MyToken(getFilename(), getLine(), fTokColumn);
        token.setType(t);
        if (t == EOF )
            token.setText("end of file or input");
        return token;
    }

    public void reportError(RecognitionException ex) {
        fParseErrorHandler.reportError(
                ex.getLine() + ":" + ex.getColumn() + ": " + ex.getMessage());
    }
 

    /**
     * Returns true if word is a reserved keyword. 
     */
    public boolean isKeyword(String word) {
        ANTLRHashString s = new ANTLRHashString(word, this);
        boolean res = literals.get(s) != null;
        Log.trace(this, "keyword " + word + ": " + res);
        return res;
    }

    public void traceIn(String rname) throws CharStreamException {
        traceIndent();
        traceDepth += 1;
        System.out.println("> lexer " + rname + ": c == '" + 
                           StringUtil.escapeChar(LA(1), '\'') + "'");
    }

    public void traceOut(String rname) throws CharStreamException {
        traceDepth -= 1;
        traceIndent();
        System.out.println("< lexer " + rname + ": c == '" +
                           StringUtil.escapeChar(LA(1), '\'') + "'");
    }
    
    public void init(ParseErrorHandler handler) {
        fParseErrorHandler = handler;
    }
}

// Whitespace -- ignored
WS:
    ( ' '
    | '\t'
    | '\f'
    | (   "\r\n"
    | '\r'
	| '\n'
      )
      { newline(); }
    )
    { $setType(Token.SKIP); }
    ;

// Single-line comments
SL_COMMENT:
    ("//" | "--")
    (~('\n'|'\r'))* ('\n'|'\r'('\n')?)
    { $setType(Token.SKIP); newline(); }
    ;

// multiple-line comments
ML_COMMENT:
    "/*"
    ( options { generateAmbigWarnings = false; } : { LA(2)!='/' }? '*'
    | '\r' '\n' { newline(); }
    | '\r'	{ newline(); }
    | '\n'	{ newline(); }
    | ~('*'|'\n'|'\r')
    )*
    "*/"
    { $setType(Token.SKIP); }
    ;

// Use paraphrases for nice error messages
//EOF 		options { paraphrase = "\"end of file\""; } : EOF;
ARROW 		options { paraphrase = "'->'"; }        : "->";
AT     		options { paraphrase = "'@'"; }         : '@';
BAR 		options { paraphrase = "'|'"; }         : '|';
COLON 		options { paraphrase = "':'"; }         : ':';
COLON_COLON	options { paraphrase = "'::'"; }        : "::";
COLON_EQUAL	options { paraphrase = "':='"; }        : ":=";
COMMA 		options { paraphrase = "','"; }         : ',';
DOT 		options { paraphrase = "'.'"; }         : '.';
DOTDOT 		options { paraphrase = "'..'"; }        : "..";
EQUAL 		options { paraphrase = "'='"; }         : '=';
GREATER 	options { paraphrase = "'>'"; }         : '>';
GREATER_EQUAL 	options { paraphrase = "'>='"; }        : ">=";
HASH 		options { paraphrase = "'#'"; }         : '#';
LBRACE 		options { paraphrase = "'{'"; }         : '{';
LBRACK 		options { paraphrase = "'['"; }         : '[';
LESS 		options { paraphrase = "'<'"; }         : '<';
LESS_EQUAL 	options { paraphrase = "'<='"; }        : "<=";
LPAREN 		options { paraphrase = "'('"; }         : '(';
MINUS 		options { paraphrase = "'-'"; }         : '-';
NOT_EQUAL 	options { paraphrase = "'<>'"; }        : "<>";
PLUS 		options { paraphrase = "'+'"; }         : '+';
RBRACE 		options { paraphrase = "'}'"; }         : '}';
RBRACK 		options { paraphrase = "']'"; }         : ']';
RPAREN		options { paraphrase = "')'"; }         : ')';
SEMI		options { paraphrase = "';'"; }         : ';';
SLASH 		options { paraphrase = "'/'"; }         : '/';
STAR 		options { paraphrase = "'*'"; }         : '*';

protected
INT:
    ('0'..'9')+
    ;

protected
REAL:
    INT ( '.' INT )? 
    ( ('e'|'E') ('+'|'-')? INT )?
    ;

RANGE_OR_INT:
      ( INT ".." )    => INT    { $setType(INT); }
    | ( INT '.' INT)  => REAL   { $setType(REAL); }
    | ( INT ('e'|'E'))  => REAL { $setType(REAL); }
    |   INT                     { $setType(INT); }
    ;

// String literals

STRING
{ char c1; StringBuffer s = new StringBuffer(); }
:	
    '\'' { s.append('\''); } 
    (   c1=ESC { s.append(c1); } 
      | 
        c2:~('\''|'\\') { s.append(c2); } 
    )* 
    '\'' { s.append('\''); } 
    { $setText(s.toString()); }
    ;

// escape sequence -- note that this is protected; it can only be called
//   from another lexer rule -- it will not ever directly return a token to
//   the parser
// There are various ambiguities hushed in this rule.  The optional
// '0'...'7' digit matches should be matched here rather than letting
// them go back to STRING_LITERAL to be matched.  ANTLR does the
// right thing by matching immediately; hence, it's ok to shut off
// the FOLLOW ambig warnings.
protected
ESC returns [char c]
{ c = 0; int h0,h1,h2,h3; }
:
    '\\'
     (  'n' { c = '\n'; }
     | 'r' { c = '\r'; }
     | 't' { c = '\t'; }
     | 'b' { c = '\b'; }
     | 'f' { c = '\f'; }
     | '"' { c = '"'; }
     | '\'' { c = '\''; }
     | '\\' { c = '\\'; }
     | ('u')+ 
       h3=HEX_DIGIT h2=HEX_DIGIT h1=HEX_DIGIT h0=HEX_DIGIT 
	 { c = (char) (h0 + h1 * 16 + h2 * 16 * 16 + h3 * 16 * 16 * 16); }
     | o1:'0'..'3' { c = (char) Character.digit(o1, 8); } 
        ( options { warnWhenFollowAmbig = false; } : o2:'0'..'7' 
          { c = (char) (c * 8 + Character.digit(o2, 8)); } 
          ( options { warnWhenFollowAmbig = false; } : o3:'0'..'7' 
            { c = (char) (c * 8 + Character.digit(o3, 8)); } 
          )? 
        )?
     | d1:'4'..'7' { c = (char) Character.digit(d1, 8); } 
       ( options { warnWhenFollowAmbig = false; } : d2:'0'..'7' 
         { c = (char) (c * 8 + Character.digit(d2, 8)); } 
       )?
     )
     ;

// hexadecimal digit (again, note it's protected!)
protected
HEX_DIGIT returns [int n]
{ n = 0; }
:
    (   c1:'0'..'9' { n = Character.digit(c1, 16); }
      | c2:'A'..'F' { n = Character.digit(c2, 16); }
      | c3:'a'..'f' { n = Character.digit(c3, 16); }
    )
    ;


// An identifier.  Note that testLiterals is set to true!  This means
// that after we match the rule, we look in the literals table to see
// if it's a literal or really an identifer.

IDENT
    options { 
        testLiterals = true; 
	paraphrase = "an identifier";
    } :	
    ('$' |'a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*
    ;

// A dummy rule to force vocabulary to be all characters (except
// special ones that ANTLR uses internally (0 to 2)

protected
VOCAB:	
    '\3'..'\377'
    ;
