/* *************************************************************************
                          gdlc.g  -  gdl lexer/parser
                             -------------------
    begin                : July 22 2002
    copyright            : (C) 2002 by Marc Schellens
    email                : m_schellens@users.sf.net
 ***************************************************************************/

/* *************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

header "pre_include_cpp" {
#include "includefirst.hpp"
}
header "post_include_cpp" {
#include <errno.h>

#include <cstdlib>
}

header {
#include <fstream>

#include "GDLParser.hpp"

#include "str.hpp"
#include "dnodefactory.hpp"
#include "objects.hpp"
#include "initsysvar.hpp"

#include "antlr/TokenStreamSelector.hpp"

#include "antlr/SemanticException.hpp"
#include "antlr/NoViableAltForCharException.hpp"
#include "antlr/TokenStreamIOException.hpp"
#include "antlr/CharInputBuffer.hpp"

//#include "dinterpreter.hpp"

// defintion in dinterpreter.cpp
//void SetActualCompileOpt( unsigned int cOpt);
}

options {
	language="Cpp";
	genHashLines = false;
	namespaceStd="std";         // cosmetic option to get rid of long defines
	namespaceAntlr="antlr";     // cosmetic option to get rid of long defines
}	

// the GDL Parser *********************************************
class GDLParser extends Parser;

options {
	exportVocab = GDL;	// use vocab generated by lexer
	buildAST = true;
  	ASTLabelType = "RefDNode";
	k=2;
    defaultErrorHandler = false;
//    defaultErrorHandler = true;
}

// if something is changed here
// identifier below needs to change as well
tokens {
	ALL;		// arrayindex (*, e.g. [1:*])
	ASSIGN;
	ASSIGN_INPLACE;
	ASSIGN_REPLACE;
    ASSIGN_ARRAYEXPR_MFCALL;
	ARRAYDEF;
	ARRAYDEF_CONST;
	ARRAYIX;
	ARRAYIX_ALL;
	ARRAYIX_ORANGE;
	ARRAYIX_RANGE;
	ARRAYIX_ORANGE_S; // with stride
	ARRAYIX_RANGE_S;
	ARRAYEXPR;
	ARRAYEXPR_FCALL;
	ARRAYEXPR_MFCALL;
	BLOCK;
    BREAK;
	CSBLOCK;
    CONTINUE;
	COMMONDECL;
	COMMONDEF;
    CONSTANT;
	DEREF;
	ELSEBLK;
	EXPR;
    FOR;
    FOR_STEP; // for with step
    FOREACH;
    FOREACH_INDEX; // foreach with index (hash) variable
    FOR_LOOP;
    FOR_STEP_LOOP; // for with step
    FOREACH_LOOP;
    FOREACH_INDEX_LOOP;
	FCALL;
	FCALL_LIB; // library function call
 	FCALL_LIB_DIRECT; // direct call
 	FCALL_LIB_N_ELEMENTS; // N_ELEMENTS
	FCALL_LIB_RETNEW; // library function call always return newly allocated data
    GDLNULL;
    IF_ELSE;
	KEYDECL;
	KEYDEF;
	KEYDEF_REF; // keyword passed by reference
	KEYDEF_REF_CHECK; // keyword maybe passed by reference
	KEYDEF_REF_EXPR;  // keyword with assign/inc/dec passed by reference
  	LABEL;
	MPCALL;
	MPCALL_PARENT; // explicit call to parent 
	MFCALL;
	MFCALL_LIB;
	MFCALL_LIB_RETNEW;
	MFCALL_PARENT; // explicit call to parent
	MFCALL_PARENT_LIB;
	MFCALL_PARENT_LIB_RETNEW;
  	NOP;     // no operation
	NSTRUC;     // named struct
	NSTRUC_REF; // named struct reference
    ON_IOERROR_NULL;
	PCALL;
	PCALL_LIB; // libraray procedure call
	PARADECL;
	PARAEXPR;  // parameter
	PARAEXPR_VN; // _VN Variable Number of parameters version
    DEC_REF_CHECK; // called from EvalRefCheck() (no temporary needed then)
    INC_REF_CHECK; // called from EvalRefCheck() (no temporary needed then)
    POSTDEC;
    POSTINC; 
    DECSTATEMENT; // as a statement
    INCSTATEMENT; // as a statement
    REF;        // expr pass by reference
    REF_VN;        // expr pass by reference
    REF_CHECK;  // expr maybe be passed by reference
    REF_CHECK_VN;  // expr maybe be passed by reference
    REF_EXPR;   // assign/dec/inc expr passed by reference
    REF_EXPR_VN;   // assign/dec/inc expr passed by reference
    REPEAT;
    REPEAT_LOOP;
    RETURN;  // unspecified return (replaced by tree parser with RETF/RETP)
  	RETF;    // return from function (return argument)
  	RETP;    // return from procedure (no return argument)
	STRUC;  // struct
	SYSVAR;
//	UPLUS;
	UMINUS;
	VAR;     // varaible, referenced through index
	VARPTR;  // variable, referenced through pointer
    WHILE;
}

{
    public:
    enum CompileOpt {
        NONE=0,
        DEFINT32=1,
        HIDDEN=2,
        OBSOLETE=4,
        STRICTARR=8,
        LOGICAL_PREDICATE=16, // *** functionality not implemeted yet
        IDL2=DEFINT32 | STRICTARR,
        STRICTARRSUBS=32
    };

    void SetCompileOpt( unsigned int cOpt)
    {
        this->compileOpt = cOpt;
    }
    
    private:
    void AddCompileOpt( const std::string opt)
    {
        if(      opt == "DEFINT32")          compileOpt |= DEFINT32;
        else if( opt == "HIDDEN")            compileOpt |= HIDDEN;
        else if( opt == "OBSOLETE")          compileOpt |= OBSOLETE;
        else if( opt == "STRICTARR")         compileOpt |= STRICTARR;
        else if( opt == "LOGICAL_PREDICATE") compileOpt |= LOGICAL_PREDICATE;
        else if( opt == "IDL2")              compileOpt |= IDL2;
        else if( opt == "STRICTARRSUBS")     compileOpt |= STRICTARRSUBS;
        else throw GDLException("Unrecognised COMPILE_OPT option: "+opt);
//        SetActualCompileOpt( compileOpt);
    }

    std::string subName; // name of procedure function to be compiled ("" -> all file)
    bool   searchForPro; // true -> procedure subName, false -> function subName 
    bool   subReached; 
    unsigned int compileOpt;

    bool ConstantExprNode( int t)
    {
        return (t == CONSTANT) || 
               (t == ARRAYDEF_CONST);
    }

    public:
    GDLParser(antlr::TokenStream& selector, 
              const std::string& sName, 
              bool searchPro, // true -> search for procedure sName, false -> for function
              unsigned int compileOptIn):
    antlr::LLkParser(selector,2), subName(sName), searchForPro( searchPro), 
    subReached(false), compileOpt(compileOptIn)
    { 
        //        setTokenNames(_tokenNames);
    }
}

// 'reverse' identifier
// allows reserved words as identifiers
// needed for keyword abbreviations
// if you change some keywords here you probably need to change
// the reserved word list above
identifier
    : IDENTIFIER
    | a:AND_OP { #a->setType( IDENTIFIER);}
	| b:BEGIN  { #b->setType( IDENTIFIER);}
	| c:CASE { #c->setType( IDENTIFIER);}
    | co:COMMON { #co->setType( IDENTIFIER);}
    | com:COMPILE_OPT { #com->setType( IDENTIFIER);}
    | d:DO { #d->setType( IDENTIFIER);}
    | e:ELSE { #e->setType( IDENTIFIER);}
    | en:END { #en->setType( IDENTIFIER);}
    | end:ENDCASE { #end->setType( IDENTIFIER);}
    | ende:ENDELSE { #ende->setType( IDENTIFIER);}
    | endf:ENDFOR { #endf->setType( IDENTIFIER);}
    | endfe:ENDFOREACH { #endf->setType( IDENTIFIER);}
    | endi:ENDIF { #endi->setType( IDENTIFIER);}
    | endr:ENDREP { #endr->setType( IDENTIFIER);}
    | ends:ENDSWITCH { #ends->setType( IDENTIFIER);}
    | endw:ENDWHILE { #endw->setType( IDENTIFIER);}
    | eq:EQ_OP { #eq->setType( IDENTIFIER);}
    | f:FOR { #f->setType( IDENTIFIER);}
    | fe:FOREACH { #f->setType( IDENTIFIER);}
    | fo:FORWARD { #fo->setType( IDENTIFIER);}
    | fu:FUNCTION { #fu->setType( IDENTIFIER);}
    | g:GE_OP { #g->setType( IDENTIFIER);}
    | go:GOTO { #go->setType( IDENTIFIER);}
    | gt:GT_OP { #gt->setType( IDENTIFIER);}
    | i:IF { #i->setType( IDENTIFIER);}
    | in:INHERITS { #in->setType( IDENTIFIER);}
    | l:LE_OP { #l->setType( IDENTIFIER);}
    | lt:LT_OP { #lt->setType( IDENTIFIER);}
    | m:MOD_OP { #m->setType( IDENTIFIER);}
    | n:NE_OP { #n->setType( IDENTIFIER);}
    | no:NOT_OP { #no->setType( IDENTIFIER);}
    | o:OF { #o->setType( IDENTIFIER);}
    | on:ON_IOERROR { #on->setType( IDENTIFIER);}
    | o_:OR_OP { #o_->setType( IDENTIFIER);}
    | p:PRO { #p->setType( IDENTIFIER);}
    | r:REPEAT { #r->setType( IDENTIFIER);}
    | s:SWITCH { #s->setType( IDENTIFIER);}
    | t:THEN { #t->setType( IDENTIFIER);}
    | u:UNTIL { #u->setType( IDENTIFIER);}
    | w:WHILE { #w->setType( IDENTIFIER);}
    | x:XOR_OP { #x->setType( IDENTIFIER);}
    ;


// file parsing
translation_unit
{ 
    subReached=false;
    compileOpt=NONE; // reset compileOpt    
}
    :   ( options {greedy=true;}: end_unit
        | forward_function end_unit
        | procedure_def 
            { 
                compileOpt=NONE; // reset compileOpt    
                if( subReached) goto bailOut;
            }
        | function_def  
            { 
                compileOpt=NONE; // reset compileOpt    
                if( subReached) goto bailOut;
            }
        | common_block
        )* // optional - only main program is also ok

        ( statement_list END! (end_unit)? )? // $MAIN$ program

        (EOF!)   // braces necessary because goto crosses initialization otherwise
        { bailOut:;} // bailout jump label
        // catch lexer exceptions also
        exception 
        catch [ GDLException& e] 
        { 
            throw;
        }
        catch [ antlr::NoViableAltException& e] 
        {
            // PARSER SYNTAX ERROR
            throw GDLException( e.getLine(), e.getColumn(), "Parser syntax error: "+e.getMessage());
        }
        catch [ antlr::NoViableAltForCharException& e] 
        {
            // LEXER SYNTAX ERROR
            throw GDLException( e.getLine(), e.getColumn(), "Lexer syntax error: "+e.getMessage());
        }
        catch [ antlr::RecognitionException& e] 
        {
            // SYNTAX ERROR
            throw GDLException( e.getLine(), e.getColumn(), "Lexer/Parser syntax error: "+e.getMessage());
        }
        catch [ antlr::TokenStreamIOException& e] 
        {
            // IO ERROR
            throw GDLException( returnAST, "Input/Output error: "+e.getMessage());
        }
        catch [ antlr::TokenStreamException& e] 
        {
            throw GDLException( returnAST, "Token stream error: "+e.getMessage());
        }
    ;

// to give a more precise error message
// interactive compilation is not allowed
interactive_compile!
    : (FUNCTION | PRO)
        IDENTIFIER 
        {
            throw GDLException( "Programs can't be compiled from "
                "single statement mode.");
        }
        (METHOD IDENTIFIER)?
        (COMMA parameter_declaration)? 
        end_unit
    ;

// intercative usage
interactive
    :   ( end_unit (end_mark)? 
        | interactive_statement
        | interactive_compile
        )+
        // catch lexer exceptions also
        exception 
        catch [ GDLException& e] 
        { 
            throw;
        }
        catch [ antlr::NoViableAltException& e] 
        {
            // PARSER SYNTAX ERROR
            throw GDLException( e.getLine(), e.getColumn(), "Parser syntax error: "+
                e.getMessage());
        }
        catch [ antlr::NoViableAltForCharException& e] 
        {
            // LEXER SYNTAX ERROR
            throw GDLException( e.getLine(), e.getColumn(), "Lexer syntax error: "+
                e.getMessage());
        }
        catch [ antlr::RecognitionException& e] 
        {
            // SYNTAX ERROR
            throw GDLException( e.getLine(), e.getColumn(), 
                "Lexer/Parser syntax error: "+e.getMessage());
        }
        catch [ antlr::TokenStreamIOException& e] 
        {
            // IO ERROR
            throw GDLException( returnAST, "Input/Output error: "+e.getMessage());
        }
        catch [ antlr::TokenStreamException& e] 
        {
            throw GDLException( returnAST, "Token stream error: "+e.getMessage());
        }
    ;

// compound statements in the original don't care about the specific end_mark
// in interactive mode end need not to be there and labels are ignored
interactive_statement
  :  (BEGIN! | IDENTIFIER! COLON!)* 
	statement end_unit
  ;


// idl allows more than one ELSE: first is executed, *all*
// (including expr) later branches are ignored (case) or 
// executed (switch)
switch_statement
{
    int numBranch=0;
}
	: SWITCH^ expr OF! (end_unit)? 
		(switch_body
            {
                numBranch++;
            }
        )*
		endswitch_mark
        {
        #SWITCH->SetNumBranch(numBranch);
        }
	;

switch_body
	: expr COLON! 
		( statement
		| BEGIN! statement_list endswitch_mark)? end_unit
		{ #switch_body = #([BLOCK, "block"], #switch_body);}
	| ELSE! COLON! 
		( statement
		| BEGIN! statement_list endswitchelse_mark)? end_unit
		{ #switch_body = #([ELSEBLK, "elseblk"], #switch_body);}
	;	

case_statement
{
    int numBranch=0;
}
	: CASE^ expr OF! (end_unit)? 
		(case_body
            {
                numBranch++;
            }
        )*
		endcase_mark
        {
        #CASE->SetNumBranch(numBranch);
        }
	;

case_body
	: expr COLON! 
		(statement
		| BEGIN! statement_list endcase_mark)? end_unit
		{ #case_body = #([BLOCK, "block"], #case_body);}
	| ELSE! COLON! 
		(statement
		| BEGIN! statement_list endcaseelse_mark)? end_unit
		{ #case_body = #([ELSEBLK, "elseblk"], #case_body);}
	;

// whereever one END_U is there might be more
// end_unit is syntactical necessary, but not for the AST
end_unit!
	: (options {greedy=true;}: END_U)+
	;


forward_function
  : FORWARD^ identifier_list
  ;


parameter_declaration
	: (IDENTIFIER | keyword_declaration) 
		(COMMA! (IDENTIFIER | keyword_declaration))*
		{ #parameter_declaration = 
			#([PARADECL,"paradecl"], #parameter_declaration);}
	;
	
	
keyword_declaration
	: IDENTIFIER EQUAL! IDENTIFIER
		{ #keyword_declaration =
			#([KEYDECL,"keydecl"], #keyword_declaration);}
	;


object_name! returns [std::string name] // !//
  	: i1:IDENTIFIER m:METHOD i2:IDENTIFIER
		{ 
        // here we translate IDL_OBECT to GDL_OBJECT for source code compatibility
        {
            if( #i1->getText() == "IDL_OBJECT")
                #i1->setText(GDL_OBJECT_NAME);
        }

            #object_name = #(NULL, i2, m, i1); // NULL -> no root
            name= std::string( i1->getText()+"__"+i2->getText());
        }
  	;	

procedure_def
{
    std::string name;
}
    : p:PRO^
        ( n:IDENTIFIER { name=n->getText(); }
        | name=object_name  
        )
        (COMMA! parameter_declaration)? end_unit
        (statement_list)? END!
        { 
            if( subName == name && searchForPro == true) subReached=true;
            #p->SetCompileOpt( compileOpt);
        }
  ;

function_def
{
    std::string name;
}
    : f:FUNCTION^
        ( n:IDENTIFIER { name=n->getText(); }
        | name=object_name
        )
        (COMMA! parameter_declaration)? end_unit
        (statement_list)? END!
        { 
            if( subName == name && searchForPro == false) subReached=true;
            #f->SetCompileOpt( compileOpt);
        }
    ;

// change defaultbehaviour of the compiling
compile_opt!
	: COMPILE_OPT i:IDENTIFIER 
        {
            AddCompileOpt( i->getText());
        }
        (COMMA ii:IDENTIFIER
            {
                AddCompileOpt( ii->getText());
            }
        )*
	;

common_block
	: COMMON! IDENTIFIER 
		(
			{ #common_block = #([COMMONDECL,"commondecl"], #common_block);}
		| COMMA! identifier_list
			{ #common_block = #([COMMONDEF,"commondef"], #common_block);}
		)
	;

identifier_list
	: IDENTIFIER (COMMA! IDENTIFIER)*
	;

// no ASTs for end marks
end_mark!
	: END
	| ENDIF
	| ENDELSE
	| ENDCASE
	| ENDSWITCH
	| ENDFOR
	| ENDFOREACH
	| ENDWHILE
	| ENDREP
	;

endforeach_mark!
	: ENDFOREACH | END
	;

endfor_mark!
	: ENDFOR | END
	;

endrep_mark!
	: ENDREP | END
	;

endwhile_mark!
	: ENDWHILE | END
	;

endif_mark!
	: ENDIF	| END
	;

endelse_mark!
	: ENDELSE | END
	;

endcase_mark!
	: ENDCASE | END
	;

endcaseelse_mark!
	: endcase_mark | ENDELSE
	;

endswitch_mark!
	: ENDSWITCH | END
	;

endswitchelse_mark!
	: endswitch_mark | ENDELSE
	;

statement_list
	: (end_unit 
		| compound_statement end_unit 
		| label_statement end_unit)+
	;

label
  	: IDENTIFIER^ COLON
  	;

label_statement
	: (label)+ (compound_statement)?
	;

// compound statements don't care about the specific end_mark
compound_statement
	: statement
	| BEGIN! statement_list end_mark
		{ #compound_statement = #([BLOCK, "block"], #compound_statement);}
	;

baseclass_method
	: s:IDENTIFIER METHOD!
        // here we translate IDL_OBECT to GDL_OBJECT for source code compatibility
        {
            if( #s->getText() == "IDL_OBJECT")
                #s->setText(GDL_OBJECT_NAME);
        }
	;

statement
// assignment and member_procedure_call starting with deref_expr
{
    bool parent=false;
}
    : (assign_expr)=> assign_expr (DEC^ | INC^)?
//     | (deref_expr_dot)=>
//       (deref_expr 
// 			// assignment
// 			(EQUAL! expr 			
//                 { #statement = #([ASSIGN,":="], #statement);}
//             |   ( AND_OP_EQ^ 
//                 | ASTERIX_EQ^ 
//                 | EQ_OP_EQ^ 
//                 | GE_OP_EQ^
//                 | GTMARK_EQ^
//                 | GT_OP_EQ^
//                 | LE_OP_EQ^
//                 | LTMARK_EQ^
//                 | LT_OP_EQ^
//                 | MATRIX_OP1_EQ^
//                 | MATRIX_OP2_EQ^
//                 | MINUS_EQ^
//                 | MOD_OP_EQ^
//                 | NE_OP_EQ^
//                 | OR_OP_EQ^
//                 | PLUS_EQ^
//                 | POW_EQ^
//                 | SLASH_EQ^
//                 | XOR_OP_EQ^) expr
// 			| (DEC^ | INC^) // no POSTDEC/POSTINC for statements			
// 			| MEMBER! // member procedure call 
//                 (baseclass_method { parent=true; })? 
//                 formal_procedure_call
// 				{ 
//                     if( parent)
//                         #statement = #([MPCALL_PARENT, "mpcall::"], 
//                                         #statement);
//                     else
//                         #statement = #([MPCALL, "mpcall"], #statement);
//                 }
//             | // empty -> member procedure call with DOT parsed by tag_access
//             )
//       )


    | (deref_dot_expr_keeplast IDENTIFIER COMMA)=>
        d1:deref_dot_expr_keeplast formal_procedure_call
				{ 
                        #statement = #([MPCALL, "mpcall"], #statement);
                        #statement->SetLine( #d1->getLine());

                }
    | (deref_dot_expr_keeplast baseclass_method)=>
        d2:deref_dot_expr_keeplast baseclass_method formal_procedure_call
				{ 
                        #statement = #([MPCALL_PARENT, "mpcall::"], 
                                        #statement);
                        #statement->SetLine( #d2->getLine());
                }
    | ( deref_expr
                ( EQUAL
                | AND_OP_EQ^ 
                | ASTERIX_EQ^ 
                | EQ_OP_EQ^ 
                | GE_OP_EQ^
                | GTMARK_EQ^
                | GT_OP_EQ^
                | LE_OP_EQ^
                | LTMARK_EQ^
                | LT_OP_EQ^
                | MATRIX_OP1_EQ^
                | MATRIX_OP2_EQ^
                | MINUS_EQ^
                | MOD_OP_EQ^
                | NE_OP_EQ^
                | OR_OP_EQ^
                | PLUS_EQ^
                | POW_EQ^
                | SLASH_EQ^
                | XOR_OP_EQ^
                | DEC^  
                | INC^ // no POSTDEC/POSTINC for statements			
                | MEMBER! // member procedure call 
                )
      )=>
        deref_expr
			(EQUAL! expr 			
                { #statement = #([ASSIGN,":="], #statement);}
            |   ( AND_OP_EQ^ 
                | ASTERIX_EQ^ 
                | EQ_OP_EQ^ 
                | GE_OP_EQ^
                | GTMARK_EQ^
                | GT_OP_EQ^
                | LE_OP_EQ^
                | LTMARK_EQ^
                | LT_OP_EQ^
                | MATRIX_OP1_EQ^
                | MATRIX_OP2_EQ^
                | MINUS_EQ^
                | MOD_OP_EQ^
                | NE_OP_EQ^
                | OR_OP_EQ^
                | PLUS_EQ^
                | POW_EQ^
                | SLASH_EQ^
                | XOR_OP_EQ^) expr
			| (DEC^ | INC^) // no POSTDEC/POSTINC for statements			
            | MEMBER! // member procedure call 
                (baseclass_method { parent=true; })? 
                formal_procedure_call
				{ 
                    if( parent)
                        #statement = #([MPCALL_PARENT, "mpcall::"], 
                                        #statement);
                    else
                        #statement = #([MPCALL, "mpcall"], #statement);
                }
			)
    | // (deref_dot_expr_keeplast IDENTIFIER)=>
      d3:deref_dot_expr_keeplast formal_procedure_call
				{ 
                    #statement = #([MPCALL, "mpcall"], #statement);
                    #statement->SetLine( #d3->getLine());
                }
    | (DEC^ | INC^) expr
	| procedure_call // next two handled by procedure_call also
//	| BREAK     // only valid in loops and switch_statement
//	| CONTINUE  // only valid in loops
	| for_statement 
	| foreach_statement 
	| repeat_statement
	| while_statement
	| jump_statement
	| if_statement
	| case_statement
	| switch_statement
	| forward_function
	| common_block
    | compile_opt
	;


repeat_statement
	: REPEAT^ 
		repeat_block
		UNTIL! expr
	;


repeat_block
	: st:statement
		{ #repeat_block = #([BLOCK, "block"], #st);}
	| BEGIN! stl:statement_list endrep_mark
		{ #repeat_block = #([BLOCK, "block"], #stl);}
	;


while_statement
	: WHILE^
		expr DO! 
		while_block
	;


while_block
	: statement 
	| BEGIN! statement_list endwhile_mark
		{ #while_block = #([BLOCK, "block"], #while_block);}
	;


for_statement
	: FOR^ IDENTIFIER EQUAL! expr COMMA! expr 
		(COMMA! expr)? DO!
		for_block
	;

for_block
	: st:statement
		{ #for_block = #([BLOCK, "block"], #st);}
	| BEGIN! stl:statement_list endfor_mark
		{ #for_block = #([BLOCK, "block"], #stl);}
	;	

foreach_statement
	: FOREACH^ IDENTIFIER COMMA! expr (COMMA! IDENTIFIER)? DO!
		foreach_block
	;

foreach_block
	: st:statement
		{ #foreach_block = #([BLOCK, "block"], #st);}
	| BEGIN! stl:statement_list endforeach_mark
		{ #foreach_block = #([BLOCK, "block"], #stl);}
	;	

jump_statement
	: GOTO^ COMMA! IDENTIFIER
// now handled as a procedure_call because RETURN is no reserved word
//	| RETURN^ (COMMA! expr)?
	| ON_IOERROR^ COMMA! IDENTIFIER
	;

// the classical greedy case (match ELSE as soon as possible)
if_statement
	: IF^ expr THEN!
		if_block
		( options {greedy=true;}: ELSE! 
			else_block
		)?
	;


if_block
	: statement 
	| BEGIN! statement_list endif_mark
		{ #if_block = #([BLOCK, "block"], #if_block);}
	;


else_block
	: statement
	| BEGIN! statement_list endelse_mark
		{ #else_block = #([BLOCK, "block"], #else_block);}
	;

formal_procedure_call
	: IDENTIFIER (COMMA! parameter_def_list)?
	;	

// must handle RETURN, BREAK, CONTINUE also
procedure_call!//
// was:
// formal_procedure_call
    : id:IDENTIFIER 
        ( {id->getText() == "RETURN"}?
            (COMMA! e:expr)?
            { 
                #id->setType(RETURN); // text is already "return"
                #procedure_call = #( #id, #e); // make root
            }
        | {id->getText() == "BREAK"}?
            {
                #id->setType(BREAK); // text is already "break"
                #procedure_call = #id;
            }
        | {id->getText() == "CONTINUE"}?
            {
                #id->setType(CONTINUE); // text is already "continue"
                #procedure_call = #id;
            }
        | (COMMA! pa:parameter_def_list)? 
        { 
            #procedure_call = #([PCALL, "pcall"], #id, #pa);
            #procedure_call->SetLine(id->getLine());
        }
        )
	;	

// ambiguity with arrays 
// but as arrays got priority, only real function calls need
// to be handled here
formal_function_call
	: IDENTIFIER LBRACE! (parameter_def_list)? RBRACE!
	;

parameter_def
//	: IDENTIFIER EQUAL! expr
	: identifier EQUAL! expr
		{ #parameter_def =
			#([KEYDEF,"!=!"], #parameter_def);}
	| expr
//	| SLASH! id:IDENTIFIER
	| SLASH! id:identifier
		{
            RefDNode c=static_cast<RefDNode>( #[CONSTANT,"1"]);
            c->Text2Int(10);
            c->SetLine( #id->getLine());
            #parameter_def = #([KEYDEF,"!=!"], id, c);
        }
	;

parameter_def_list
	: parameter_def ( COMMA! parameter_def)*
	;

// [expr,...]
array_def
{
bool constant = true;
}
	: LSQUARE! e:expr 
        {if( !ConstantExprNode( #e->getType())) 
            constant = false;}
        (COMMA! ee:expr
        {if( !ConstantExprNode( #ee->getType())) 
            constant = false;}
        )* RSQUARE!
		{ 
            if( constant)
            #array_def = #([ARRAYDEF_CONST, "array_def_const"], #array_def);
            else
            #array_def = #([ARRAYDEF, "array_def"], #array_def);
        }
	;

struct_identifier
	:   ( IDENTIFIER 
        | s:SYSVARNAME  { #s->setType( IDENTIFIER);}  
        | e:EXCLAMATION { #e->setType( IDENTIFIER);}  
        | i:INHERITS    { #i->setType( IDENTIFIER);}  
        ) 
        // fake IDENTIFIER (struct tag can also be "!" or "!XXXX")
        // no additinal subtype is needed here, as struct_def already creates
        // the appropriate node (ie. there is no ambiguity in the parser output)
    ;

struct_name
	:   s:struct_identifier
        // here we translate IDL_OBECT to GDL_OBJECT for source code compatibility
        {
            if( #s->getText() == "IDL_OBJECT")
                #s->setText(GDL_OBJECT_NAME);
        }
    ;

struct_def
	: LCURLY! 
        (struct_name (COMMA! named_tag_def_list)? RCURLY!
			{ #struct_def = 
				#([NSTRUC_REF, "nstruct_ref"], #struct_def);}
		| tag_def_list RCURLY!
			{ #struct_def = 
				#([STRUC, "struct"], #struct_def);}
		)
	;

tag_def
    : struct_identifier COLON! expr	
	;	

tag_def_list
	: tag_def (options {greedy=true;} : COMMA! tag_def)*
	;	

ntag_def
	: tag_def
    | expr // for named structs, just the definition is ok
	;	

ntag_defs
	: ntag_def (options {greedy=true;} : COMMA! ntag_def)*
	;	

named_tag_def_entry
    :   ( (INHERITS) => INHERITS struct_name
        | ntag_def
        )
    ;

named_tag_def_list
	: named_tag_def_entry ( COMMA! named_tag_def_entry)*
	;	

numeric_constant!//
	: c1:CONSTANT_HEX_BYTE    
		{ #numeric_constant=#[CONSTANT,c1->getText()];
		  #numeric_constant->Text2Byte(16);	
		  #numeric_constant->SetLine( c1->getLine());	
		}  
	| c2:CONSTANT_HEX_LONG 
		{ #numeric_constant=#[CONSTANT,c2->getText()];
		  #numeric_constant->Text2Long(16);	
		  #numeric_constant->SetLine( c2->getLine());	
		}  
	| c3:CONSTANT_HEX_LONG64 
		{ #numeric_constant=#[CONSTANT,c3->getText()];
		  #numeric_constant->Text2Long64(16);	
		  #numeric_constant->SetLine( c3->getLine());	
		}  
	| c4:CONSTANT_HEX_INT 
		{ #numeric_constant=#[CONSTANT,c4->getText()];
		  #numeric_constant->Text2Int(16);	
		  #numeric_constant->SetLine( c4->getLine());	
		}  
	| c44:CONSTANT_HEX_I 
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c44->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2Long(16,true);	
            else
                #numeric_constant->Text2Int(16,true);	
		  #numeric_constant->SetLine( c44->getLine());	
		}  
    | c5:CONSTANT_HEX_ULONG 
		{ #numeric_constant=#[CONSTANT,c5->getText()];
		  #numeric_constant->Text2ULong(16);	
		  #numeric_constant->SetLine( c5->getLine());	
		}  
    | c6:CONSTANT_HEX_ULONG64 
		{ #numeric_constant=#[CONSTANT,c6->getText()];
		  #numeric_constant->Text2ULong64(16);	
		  #numeric_constant->SetLine( c6->getLine());	
		}  
	| c77:CONSTANT_HEX_UI
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c77->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2ULong(16,true);	
            else
                #numeric_constant->Text2UInt(16,true);	
		  #numeric_constant->SetLine( c77->getLine());	
		}  
	| c7:CONSTANT_HEX_UINT
		{ #numeric_constant=#[CONSTANT,c7->getText()];
		  #numeric_constant->Text2UInt(16);	
		  #numeric_constant->SetLine( c7->getLine());	
		}  
	| c8:CONSTANT_BYTE  
		{ #numeric_constant=#[CONSTANT,c8->getText()];
		  #numeric_constant->Text2Byte(10);	
		  #numeric_constant->SetLine( c8->getLine());	
		}  
	| c9:CONSTANT_LONG 
		{ #numeric_constant=#[CONSTANT,c9->getText()];
		  #numeric_constant->Text2Long(10);	
		  #numeric_constant->SetLine( c9->getLine());	
		}  
	| c10:CONSTANT_LONG64 
		{ #numeric_constant=#[CONSTANT,c10->getText()];
		  #numeric_constant->Text2Long64(10);	
		  #numeric_constant->SetLine( c10->getLine());	
		}  
	| c11:CONSTANT_INT
		{ #numeric_constant=#[CONSTANT,c11->getText()];
		  #numeric_constant->Text2Int(10);	
		  #numeric_constant->SetLine( c11->getLine());	
		}  
	| c111:CONSTANT_I
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c111->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2Long(10,true);	
            else
                #numeric_constant->Text2Int(10,true);	
		  #numeric_constant->SetLine( c111->getLine());	
		}  
    | c12:CONSTANT_ULONG 
		{ #numeric_constant=#[CONSTANT,c12->getText()];
		  #numeric_constant->Text2ULong(10);	
		  #numeric_constant->SetLine( c12->getLine());	
		}  
    | c13:CONSTANT_ULONG64 
		{ #numeric_constant=#[CONSTANT,c13->getText()];
		  #numeric_constant->Text2ULong64(10);	
		  #numeric_constant->SetLine( c13->getLine());	
		}  
	| c144:CONSTANT_UI
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c144->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2ULong(10,true);	
            else
                #numeric_constant->Text2UInt(10,true);	
		  #numeric_constant->SetLine( c144->getLine());	
		}  
	| c14:CONSTANT_UINT
		{ #numeric_constant=#[CONSTANT,c14->getText()];
		  #numeric_constant->Text2UInt(10);	
		  #numeric_constant->SetLine( c14->getLine());	
		}  
	| c15:CONSTANT_OCT_BYTE  
		{ #numeric_constant=#[CONSTANT,c15->getText()];
		  #numeric_constant->Text2Byte(8);	
		  #numeric_constant->SetLine( c15->getLine());	
		}  
	| c16:CONSTANT_OCT_LONG 
		{ #numeric_constant=#[CONSTANT,c16->getText()];
		  #numeric_constant->Text2Long(8);	
		  #numeric_constant->SetLine( c16->getLine());	
		}  
	| c17:CONSTANT_OCT_LONG64 
		{ #numeric_constant=#[CONSTANT,c17->getText()];
		  #numeric_constant->Text2Long64(8);	
		  #numeric_constant->SetLine( c17->getLine());	
		}  
	| c18:CONSTANT_OCT_INT
		{ #numeric_constant=#[CONSTANT,c18->getText()];
		  #numeric_constant->Text2Int(8);	
		  #numeric_constant->SetLine( c18->getLine());	
		}  
	| c188:CONSTANT_OCT_I
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c188->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2Long(8,true);	
            else
                #numeric_constant->Text2Int(8,true);	
		  #numeric_constant->SetLine( c188->getLine());	
		}  
    | c19:CONSTANT_OCT_ULONG 
		{ #numeric_constant=#[CONSTANT,c19->getText()];
		  #numeric_constant->Text2ULong(8);	
		  #numeric_constant->SetLine( c19->getLine());	
		}  
    | c20:CONSTANT_OCT_ULONG64 
		{ #numeric_constant=#[CONSTANT,c20->getText()];
		  #numeric_constant->Text2ULong64(8);	
		  #numeric_constant->SetLine( c20->getLine());	
		}  
	| c211:CONSTANT_OCT_UI
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c211->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2ULong(8,true);	
            else
                #numeric_constant->Text2UInt(8,true);	
		  #numeric_constant->SetLine( c211->getLine());	
		}  
	| c21:CONSTANT_OCT_UINT
		{ #numeric_constant=#[CONSTANT,c21->getText()];
		  #numeric_constant->Text2UInt(8);	
		  #numeric_constant->SetLine( c21->getLine());	
		}  
	| c22:CONSTANT_FLOAT     
		{ #numeric_constant=#[CONSTANT,c22->getText()];
		  #numeric_constant->Text2Float();	
		  #numeric_constant->SetLine( c22->getLine());	
		}  
	| c23:CONSTANT_DOUBLE
		{ #numeric_constant=#[CONSTANT,c23->getText()];
		  #numeric_constant->Text2Double();	
		  #numeric_constant->SetLine( c23->getLine());	
		}  
	| c24:CONSTANT_BIN_BYTE  
		{ #numeric_constant=#[CONSTANT,c24->getText()];
		  #numeric_constant->Text2Byte(2);	
		  #numeric_constant->SetLine( c24->getLine());	
		}  
	| c25:CONSTANT_BIN_LONG 
		{ #numeric_constant=#[CONSTANT,c25->getText()];
		  #numeric_constant->Text2Long(2);	
		  #numeric_constant->SetLine( c25->getLine());	
		}  
	| c26:CONSTANT_BIN_LONG64 
		{ #numeric_constant=#[CONSTANT,c26->getText()];
		  #numeric_constant->Text2Long64(2);	
		  #numeric_constant->SetLine( c26->getLine());	
		}  
	| c27:CONSTANT_BIN_INT
		{ #numeric_constant=#[CONSTANT,c27->getText()];
		  #numeric_constant->Text2Int(2);	
		  #numeric_constant->SetLine( c27->getLine());	
		}  
	| c277:CONSTANT_BIN_I
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c277->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2Long(2,true);	
            else
                #numeric_constant->Text2Int(2,true);	
		  #numeric_constant->SetLine( c277->getLine());	
		}  
    | c28:CONSTANT_BIN_ULONG 
		{ #numeric_constant=#[CONSTANT,c28->getText()];
		  #numeric_constant->Text2ULong(2);	
		  #numeric_constant->SetLine( c28->getLine());	
		}  
    | c29:CONSTANT_BIN_ULONG64 
		{ #numeric_constant=#[CONSTANT,c29->getText()];
		  #numeric_constant->Text2ULong64(2);	
		  #numeric_constant->SetLine( c29->getLine());	
		}  
	| c300:CONSTANT_BIN_UI
        // DEFINT32
		{ #numeric_constant=#[CONSTANT,c300->getText()];
            if( compileOpt & DEFINT32)
                #numeric_constant->Text2ULong(2,true);	
            else
                #numeric_constant->Text2UInt(2,true);	
		  #numeric_constant->SetLine( c300->getLine());	
		}  
	| c30:CONSTANT_BIN_UINT
		{ #numeric_constant=#[CONSTANT,c30->getText()];
		  #numeric_constant->Text2UInt(2);	
		  #numeric_constant->SetLine( c30->getLine());	
		}  
	;

arrayindex_list
{		
    int rank = 1;
}
	: LSQUARE! arrayindex (COMMA! arrayindex)* RSQUARE!
	| LBRACE! arrayindex 
        ({++rank <= MAXRANK}? COMMA! arrayindex
            // {   // this is needed here because an ARRAYEXPR_FN which 
            //     // is a function call might have more paremeters
            //     if( 
            //         throw antlr::NoViableAltException(LT(1), getFilename());
            // }
        )* RBRACE!
	;	

all!
	: ASTERIX { #all = #([ALL,"*"]);}
	;

// used only from arrayindex_list
arrayindex
  : ((ASTERIX (COMMA|RBRACE|RSQUARE))=> all
	| expr
	  (COLON! 
		(
		  (ASTERIX (COMMA|RBRACE|RSQUARE|COLON))=> all
		| expr
		)
                (COLON! 
                    (
                      (ASTERIX (COMMA|RBRACE|RSQUARE))=> ASTERIX!
                        {
                        throw 
                            GDLException( "n:n:* subscript form not allowed.");
                        }
                    | expr
                    )
                )?
	  )?
	)
	{ #arrayindex = #([ARRAYIX,"arrayix"], #arrayindex);}
	;

// the expressions *************************************

// system variable name
sysvar
  : SYSVARNAME
	{ #sysvar = #([SYSVAR,"SYSVAR"],sysvar);}
  	;

// variable name
var!
    :   ( id:IDENTIFIER
            { 
                #var = #([VAR,"VAR"],id);
            }
        | ih:INHERITS 
            { 
                #ih->setType( IDENTIFIER);
                #var = #([VAR,"VAR"],ih);
            }  
        // fake IDENTIFIER (variable name can be "INHERITS")
        )
    ;

// this is SYNTACTIALLY ok as an lvalue, but if one try to assign
// something to an non-var an error is raised
brace_expr
    // tags need this
	:  LBRACE! expr RBRACE!
		{ #brace_expr = 
			#([EXPR,"expr"], #brace_expr);}
	;

// only used in deref_expr
// sysvar or expr (first in struct access - therefore the name)
array_expr_1st_sub
    // a variable MUST be already defined here
	: var
    | sysvar 
    | brace_expr
	;

array_expr_1st!
// a variable MUST be already defined here
    : e:array_expr_1st_sub
		( al:arrayindex_list
			{ #array_expr_1st = 
				#([ARRAYEXPR,"arrayexpr"], #e, #al);}
        | // empty
			{ #array_expr_1st = #e;}
		)
	;	

array_expr_nth_sub
	: IDENTIFIER
    | brace_expr
    ;

// expr
array_expr_nth!
	: e:array_expr_nth_sub
		( al:arrayindex_list
			{ #array_expr_nth = 
                #([ARRAYEXPR,"arrayexpr"], #e, #al);}
        | // empty
			{ #array_expr_nth = #e;}
		)
	;	

tag_array_expr_nth_sub
	: IDENTIFIER
    | s:SYSVARNAME  
        { #s->setType( IDENTIFIER); /* #s->setText( "!" + #s->getText()); */}  
    | e:EXCLAMATION { #e->setType( IDENTIFIER);}  
    | brace_expr
    ;

tag_array_expr_nth!
	: e:tag_array_expr_nth_sub
		( al:arrayindex_list
			{ #tag_array_expr_nth = 
                #([ARRAYEXPR,"arrayexpr"], #e, #al);}
        | // empty
			{ #tag_array_expr_nth = #e;}
		)
	;	

tag_access_keeplast returns [int nDot]
// {
//     nDot=0;
//     SizeT dotCount = 0 ;
//     int last;
// }
// 	: 
//         (options {greedy=true;}: DOT! { ++nDot;} tag_array_expr_nth)+
//     ;
{
    int t;
    bool parent = false;
    nDot=1;
}
	: DOT!
        (
            (tag_array_expr_nth DOT)=>
// not working: (tag_array_expr_nth tag_access_keeplast)=>
                    (tag_array_expr_nth t=tag_access_keeplast { nDot += t;})
        |   //(tag_array_expr_nth DOT tag_array_expr_nth)=> 
            //        (tag_array_expr_nth)
        )
    ;

// // can be an array expr
// deref_expr_dot // only used in predicate
// 	: array_expr_1st t=tag_access
//     | ASTERIX! deref_expr_dot
//         { #deref_expr = 
// 			#([DEREF,"deref"], #deref_expr);}
// 	;

deref_dot_expr_keeplast
{
    RefDNode dot;
    int nDot;
}
	: a1:array_expr_1st 
        (// (tag_access_keeplast)=>
        nDot=tag_access_keeplast
            { 
                if( --nDot > 0)
                    {
                        dot=#[DOT,"."];
                        dot->SetNDot( nDot);    
                        dot->SetLine( #a1->getLine());
                        #deref_dot_expr_keeplast = #(dot, #deref_dot_expr_keeplast);
                    }
            }		
//      |   { #deref_dot_expr_keeplast = #a1;}
        )
    | ASTERIX! deref_dot_expr_keeplast
        { #deref_dot_expr_keeplast = 
			#([DEREF,"deref"], #deref_dot_expr_keeplast);}
	;
// {
//     RefDNode dot;
//     SizeT nDot = 1;
// }
//     :
//  (array_expr_1st tag_access_keeplast)=> 
//       a1:array_expr_1st
//         ( /*(tag_access_keeplast)=>*/ nDot=tag_access_keeplast
//             { 
//                 dot=#[DOT,"."];
//                 dot->SetNDot( nDot);    
//                 dot->SetLine( #a1->getLine());

//                 #deref_dot_expr_keeplast = #(dot, #deref_dot_expr_keeplast);
//             }
//         )
//     |
//  (array_expr_1st DOT tag_array_expr_nth)=> 
//       array_expr_1st 
// //        {/*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/}
// //             {
// //                 dot=#[DOT,"."];
// //                 dot->SetNDot( nDot);    
// //                 dot->SetLine( #a1->getLine());

// //                 #deref_dot_expr_keeplast = #(dot, #deref_dot_expr_keeplast);
// //             }
//     | ASTERIX! deref_dot_expr_keeplast
//         { #deref_dot_expr_keeplast = 
// 			#([DEREF,"deref"], #deref_dot_expr_keeplast);}
// ;

tag_access returns [SizeT nDot]
{
    nDot=0;
}
	: (options {greedy=true;}: DOT! { ++nDot;} tag_array_expr_nth)+
    ;

deref_dot_expr
{
    RefDNode dot;
    SizeT nDot;
}
//	: array_expr_1st (DOT array_expr_nth)*
	: a1:array_expr_1st 
        (nDot=tag_access
            { 

                dot=#[DOT,"."];
                dot->SetNDot( nDot);    
                dot->SetLine( #a1->getLine());

                #deref_dot_expr = #(dot, #deref_dot_expr);
            }		
//        |   { #deref_expr = #a1;}
        )
    | ASTERIX! deref_dot_expr
        { #deref_dot_expr = 
			#([DEREF,"deref"], #deref_dot_expr);}
	;

deref_expr
{
    RefDNode dot;
    SizeT nDot;
}
//	: array_expr_1st (DOT array_expr_nth)*
	: a1:array_expr_1st 
        // ((tag_access)=> nDot=tag_access
        ((DOT)=> nDot=tag_access
            { 

                dot=#[DOT,"."];
                dot->SetNDot( nDot);    
                dot->SetLine( #a1->getLine());

                #deref_expr = #(dot, #deref_expr);
            }		
        |   { #deref_expr = #a1;}
        )
    | ASTERIX! deref_expr
        { #deref_expr = 
			#([DEREF,"deref"], #deref_expr);}
	;



member_function_call returns [bool parent]
	: { parent = false;} MEMBER! 
        (s:IDENTIFIER METHOD! 
            { 
        // here we translate IDL_OBECT to GDL_OBJECT for source code compatibility
        {
            if( #s->getText() == "IDL_OBJECT")
                #s->setText(GDL_OBJECT_NAME);
        }
                parent = true;
            } )? formal_function_call
  	;
member_function_call_dot
	:  DOT! (s:IDENTIFIER METHOD!
        // here we translate IDL_OBECT to GDL_OBJECT for source code compatibility
        {
            if( #s->getText() == "IDL_OBJECT")
                #s->setText(GDL_OBJECT_NAME);
        }
        ) formal_function_call
  	;

assign_expr
    : LBRACE! deref_expr EQUAL! expr RBRACE! // assignment
        { #assign_expr = #([ASSIGN,":="], #assign_expr);}
    ;

// arrayexpr_mfcall_last
//     : (IDENTIFIER^ arrayindex_list) 
// 	;

// only used for production in primary_expr
arrayexpr_mfcall!
{
    RefDNode dot;
    RefDNode tag;
    int nDot;
}
	: a1:array_expr_1st 
        (   // this rule is only for prodction // (tag_access_keeplast)=>
            nDot=t1:tag_access_keeplast
            { 
                if( --nDot > 0)
                    {
                        dot=#[DOT,"DOT_A_MF"];
                        dot->SetNDot( nDot);    
                        dot->SetLine( #a1->getLine());
                        tag = #(dot, #a1, #t1);
                    }
           }		
        )
        id:IDENTIFIER al:arrayindex_list
        {
            if( nDot > 0)
                #arrayexpr_mfcall = #([ARRAYEXPR_MFCALL,"arrayexpr_mfcall"], #tag, #id, #al);
            else
                #arrayexpr_mfcall = #([ARRAYEXPR_MFCALL,"arrayexpr_mfcall"], #a1, #id, #al);
        }
    | ASTERIX deref_arrayexpr_mfcall:arrayexpr_mfcall
        { #arrayexpr_mfcall = 
			#([DEREF,"deref"], #deref_arrayexpr_mfcall);}
	;


// only here a function call is ok also (all other places must be an array)
primary_expr 
{
    bool parent;
}
	   
    : 
        // with METHOD
        (deref_dot_expr_keeplast baseclass_method)=>
        d1:deref_dot_expr_keeplast baseclass_method formal_function_call
        {
            #primary_expr = #([MFCALL_PARENT, "mfcall::"], #primary_expr);
        }   
    | 
        // ambiguity (arrayexpr or mfcall)
        (deref_dot_expr_keeplast 
            (IDENTIFIER LBRACE expr (COMMA expr)* RBRACE))=>

        arrayexpr_mfcall
        // d2:deref_dot_expr_keeplast 
        //     // here it is impossible to decide about function call
        //     // as we do not know the object type/struct tag
        //     IDENTIFIER arrayindex_list 
        //     { #primary_expr = #([ARRAYEXPR_MFCALL,"arrayexpr_mfcall"], #primary_expr);}
    | 
        // not the above -> unambigous mfcall (or unambigous array expr handled below)
        (deref_dot_expr_keeplast formal_function_call)=> 
        d3:deref_dot_expr_keeplast 
            // here it is impossible to decide about function call
            // as we do not know the object type/struct tag
            formal_function_call
			{ #primary_expr = #([MFCALL, "mfcall"], #primary_expr);}
	|   // a member function call starts with a deref_expr 
 		(deref_dot_expr)=>
        // same parsing as (deref_expr)=> see below
		deref_expr 
        ( parent=member_function_call
            { 
                if( parent)
                {
                    #primary_expr = #([MFCALL_PARENT, "mfcall::"], #primary_expr);
                }
                else
                {
                    #primary_expr = #([MFCALL, "mfcall"], #primary_expr);
                }
            }
        | // empty -> array expression
        )
    |   
        // ambiguity (arrayexpr or fcall)
        (IDENTIFIER LBRACE expr (COMMA expr)* RBRACE)=>
		(

			// already known function 
			// (could be reordered, but this is conform to original)
			{ IsFun(LT(1))}? formal_function_call
			{ 
//             std::cout << "+++(IDENTIFIER LBRACE expr (COMMA expr)* RBRACE) 1" << std::endl;

//                 if( StrUpCase(#i->getText) == "N_ELEMENTS")
//                     #primary_expr = #([FCALL_LIB_N_ELEMENTS, "fcall_n_elements"], #primary_expr);
//                 else
                   #primary_expr = #([FCALL, "fcall"], #primary_expr);
            }
		| 
            // still ambiguity (arrayexpr or fcall)
        (var arrayindex_list)=> var arrayindex_list     // array_expr_fn
            { 
//             std::cout << "***(IDENTIFIER LBRACE expr (COMMA expr)* RBRACE) 2" << std::endl;

                #primary_expr = #([ARRAYEXPR_FCALL,"arrayexpr_fcall"], #primary_expr);}
        |   // if arrayindex_list failed (due to to many indices)
            // this must be a function call
            formal_function_call
			{ 
                   #primary_expr = #([FCALL, "fcall"], #primary_expr);
            }
// 	  		( parent=member_function_call
// 				{ 
//                     if( parent)
//                     {
//                         #primary_expr = 
//                         #([MFCALL_PARENT, "mfcall::"], #primary_expr);
//                     }
//                     else
//                     {
//                         #primary_expr = 
//                         #([MFCALL, "mfcall"], #primary_expr);
//                     }
//                 }
// 	  		| (DOT IDENTIFIER METHOD)=> member_function_call_dot
//                     {
//                         #primary_expr = 
//                         #([MFCALL_PARENT, "mfcall::"], #primary_expr);
//                     }
//             | DOT! formal_function_call
//                 {
//                     #primary_expr = #([MFCALL, "mfcall"], #primary_expr);
//                 }
//             |
//          )		
// commented out to leave checking to tree parser (if MEMBER and DOT are
// present it could be decided already here if it can be a FCALL)
//		|
//		// can formally be both 
//	  	// (during compiling we know as a var must be already defined)
//			array_expr_fn
		)
		
	|   // not the above => keyword parameter (or no args) => function call
	 	(formal_function_call)=> formal_function_call
	 	{ #primary_expr = #([FCALL, "fcall"], #primary_expr);}


	|   // a member function call starts with a deref_expr 
        // deref_dot_expr already failed
 		(deref_expr)=>
		deref_expr 
        ( parent=member_function_call
            { 
                if( parent)
                {
                    #primary_expr = #([MFCALL_PARENT, "mfcall::"], #primary_expr);
                }
                else
                {
                    #primary_expr = #([MFCALL, "mfcall"], #primary_expr);
                }
            }
// 	  	| (DOT IDENTIFIER METHOD)=> member_function_call_dot
//                     {
//                         #primary_expr = 
//                         #([MFCALL_PARENT, "mfcall::"], #primary_expr);
//                     }
//         | DOT! formal_function_call
//                 {
//                     #primary_expr = #([MFCALL, "mfcall"], #primary_expr);
//                 }
        | // empty -> array expression
        )

	|! sl:STRING_LITERAL // also a CONSTANT
		{ #primary_expr=#[CONSTANT,sl->getText()];
            #primary_expr->Text2String();	
            #primary_expr->SetLine( #sl->getLine());
		}  
    | assign_expr
	| numeric_constant
	| array_def
	| struct_def
    | ! ls:LSQUARE !RSQUARE
		{ #primary_expr=#[GDLNULL,"GDLNULL[]"];
            #primary_expr->SetLine( #ls->getLine());
		}  
    | ! lc:LCURLY !RCURLY
		{ #primary_expr=#[GDLNULL,"GDLNULL{}"];
            #primary_expr->SetLine( #lc->getLine());
		}  
	;

// only one INC/DEC allowed per target
decinc_expr
	: primary_expr 
        ( i:INC^ { #i->setType( POSTINC); #i->setText( "_++");} 
        | d:DEC^ { #d->setType( POSTDEC); #d->setText( "_--");} 
        | // empty
        )
    | INC^ primary_expr
    | DEC^ primary_expr
	;

exponential_expr
	: decinc_expr 
        (POW^ decinc_expr 
        )*
	;


multiplicative_expr
	: exponential_expr
		(
			( ASTERIX^
			| MATRIX_OP1^
			| MATRIX_OP2^
			| SLASH^ 
			| MOD_OP^
			) exponential_expr
		)*
	;

// only one allowed per target
signed_multiplicative_expr
    : PLUS! multiplicative_expr
    | m:MINUS^ multiplicative_expr
        { 
        #m->setType( UMINUS); 
        #m->setText( "u-"); 
        } 
    | multiplicative_expr
    ;

additive_expr
	: (signed_multiplicative_expr | neg_expr)
		( 
			( PLUS^
			| MINUS^
			| LTMARK^
			| GTMARK^
			) (multiplicative_expr | neg_expr)
		)*
// //	| NOT_OP^ additive_expr // multiple allowed
// 	| NOT_OP^ multiplicative_expr // multiple not allowed
// // true precedence of ~ operator
//     | LOG_NEG^ multiplicative_expr // multiple not allowed
	;

neg_expr
    : NOT_OP^ multiplicative_expr
// true precedence of ~ operator
    | LOG_NEG^ multiplicative_expr
  	;


relational_expr
	: additive_expr
		(
			( EQ_OP^
			| NE_OP^
			| LE_OP^
			| LT_OP^
			| GE_OP^
			| GT_OP^
			) additive_expr
		)*
	;

boolean_expr
	: relational_expr
		( 
			( AND_OP^ 
			| OR_OP^ 
			| XOR_OP^ 
			) relational_expr
		)*
	;

logical_expr
	: boolean_expr
		( 
			( LOG_AND^ 
			| LOG_OR^ 
			) boolean_expr
		)*
	;


// boolean_expr
// 	: relational_expr
// 		( 
// 			( AND_OP^ 
// 			| OR_OP^ 
// 			| XOR_OP^ 
// 			) (boolean_expr | log_neg_expr)
// 		)?
// 	;

// log_neg_expr
//     : LOG_NEG^ boolean_expr // multiple not allowed
//  	;

// logical_expr
// 	: (boolean_expr | log_neg_expr)
// 		( 
// 			( LOG_AND^ 
// 			| LOG_OR^ 
// 			) logical_expr // multiple allowed boolean_expr
// 		)?
// 	;

// expr is referenced in several places
expr
  : logical_expr
	(
	  QUESTION^ expr
	  COLON! expr
	)?
  ;

// the GDL Lexer *********************************************
class GDLLexer extends Lexer;

options {
	charVocabulary = '\3'..'\377';
	caseSensitive=false;
	testLiterals =false;
	caseSensitiveLiterals=false;
	exportVocab=GDL;
	k=3;
    defaultErrorHandler = false;
//    defaultErrorHandler = true;
//  	analyzerDebug=true;
}

// the reserved words
tokens {
	AND_OP="and"; 
	BEGIN="begin";
//	BREAK="break";
	CASE="case"; 
	COMMON="common";
	COMPILE_OPT="compile_opt";
//	CONTINUE="continue";
	DO="do";
	ELSE="else";
	END="end";
	ENDCASE="endcase";
	ENDELSE="endelse";
	ENDFOR="endfor";
	ENDFOREACH="endforeach";
	ENDIF="endif";
	ENDREP="endrep";
	ENDSWITCH="endswitch";
	ENDWHILE="endwhile";
	EQ_OP="eq";
	FOR="for";
	FOREACH="foreach";
	FORWARD="forward_function";
	FUNCTION="function";
	GE_OP="ge";
	GOTO="goto";
	GT_OP="gt";
	IF="if";
	INHERITS="inherits";
	LE_OP="le";
	LT_OP="lt";
	MOD_OP="mod";
	NE_OP="ne";
	NOT_OP="not";
	OF="of";
	ON_IOERROR="on_ioerror";
	OR_OP="or";
	PRO="pro";
	REPEAT="repeat";
//	RETURN="return";
	SWITCH="switch";
	THEN="then";
	UNTIL="until";
	WHILE="while";
	XOR_OP="xor";
}
{
  // Stuff for include files (@filename)
  private:
    std::auto_ptr<std::ifstream>    inputFile; // stores ifsteam* and deletes 
                                     // it when it is deleted itself
  
    antlr::TokenStreamSelector*     selector; 
    GDLLexer*                       mainLexerPtr;
    GDLParser*                      parserPtr;

    int                             lineContinuation;

  public:
    ~GDLLexer() 
    {
        if( mainLexerPtr != this)
            selector->pop(); // return to old lexer/stream
        else
        {
            delete parserPtr;
            delete selector;
        }
    }

    // main lexer constructor
    GDLLexer( std::istream& in, const std::string f, unsigned int compileOptIn,
        const std::string pro="", bool searchForPro=true) 
    : antlr::CharScanner(new antlr::CharBuffer(in),false),
      lineContinuation( 0)
//    : antlr::CharScanner(in)
    {
        setCaseSensitive(false);
        initLiterals();
  
        selector=     new antlr::TokenStreamSelector();
        mainLexerPtr= this;
        parserPtr=    new GDLParser( *selector, pro, searchForPro, compileOptIn);

        parserPtr->setFilename(f);
        parserPtr->initializeASTFactory( DNodeFactory);
        parserPtr->setASTFactory( &DNodeFactory );
//        parserPtr->setASTNodeFactory( DNode::factory );
        
        selector->addInputStream(this, f);
        selector->select(f); // start with main lexer
        
        // set line number to 0 in interactive mode
        if( f == "")
            { 
                setLine(0);
            }
//        p=parserPtr;
    }

    // sublexer constructor
    GDLLexer( std::ifstream& in, const std::string& name,
        GDLLexer* parent)
    : antlr::CharScanner(new antlr::CharBuffer(in),false),
      inputFile( &in)
    //    : antlr::CharScanner(new antlr::CharInputBuffer(in))
    //    : antlr::CharScanner(new antlr::CharBuffer(in))
    {
        setCaseSensitive(false);
        initLiterals();
        
        selector=     parent->selector;
        mainLexerPtr= parent->mainLexerPtr;
        parserPtr=    parent->parserPtr;
        
//        inputFile.Reset( &in); // make sure file 
//                               // gets deleted (closed) 
// 	  				             // when lexer finish

		// make sure errors are reported in right file
		setFilename(name);
        parserPtr->setFilename(name);
        selector->push(this);
    }
 
    GDLParser& Parser()
    {
        return *parserPtr;
    }
    
    int LineContinuation()
    {
        int lC = lineContinuation;
        lineContinuation = 0;
        return lC;
    }

  void uponEOF() /*throws TokenStreamException, CharStreamException*/ 
  {
  if ( selector->getCurrentStream() != mainLexerPtr ) {
  //if( this != mainLexerPtr ) {
	  
      // make copy as we delete 'this'
      antlr::TokenStreamSelector* sel=selector; 

      // here 'this' is deleted (pops selector)
      delete sel->getCurrentStream();

      // make sure errors are reported in right file
      parserPtr->setFilename(
		static_cast<GDLLexer*>(selector->getCurrentStream())->getFilename());
            
	  // don't allow EOF until main lexer.  Force the
	  // selector to retry for another token.
	  sel->retry();
	}	
  }
}

protected
STRING
	: ( ~('\n' | '\r' ))*
	;

INCLUDE!
  	:	'@' f:STRING
		{
		ANTLR_USING_NAMESPACE(std)
		// create lexer to handle include
		std::string name = f->getText();

        // find comments on the same line
        long pos = name.find_first_of(';', 0);   
        if( pos != std::string::npos) // remove them  
            name = name.substr(0, pos);

	  	StrTrim(name);

	  	std::string appName=name;
	  	AppendIfNeeded(appName,".pro");

        errno = 0; // zero it to detect errors

        bool found = CompleteFileName( appName);
        if( found) 
            name = appName;
        else
            found = CompleteFileName( name);
            
        if( !found)
            {
                if( errno == EMFILE)
                    throw GDLException( "Too many open files "
                    "(recursive use of '@'?): " + name);
                else 
                    throw GDLException( "File not found: " + name);
           }

        std::ifstream* input = new std::ifstream(name.c_str());
		if (!*input) 
			{
		  	delete input;
            throw GDLException( "Error opening file. File: " + name);
		  	cerr << SysVar::MsgPrefix() << "Error opening file. File: " << name << endl;
			}

	  	if( *input) 
	  		{
			new GDLLexer(*input,name,this);
			selector->retry(); // throws TokenStreamRetryException
			}
		}
	;

AND_OP_EQ: { LA(4) == '='}? "and="; 
ASTERIX_EQ:"*=";
EQ_OP_EQ:"eq=";
GE_OP_EQ:"ge=";
GTMARK_EQ:">=";
GT_OP_EQ:"gt=";
LE_OP_EQ:"le=";
LTMARK_EQ:"<=";
LT_OP_EQ:"lt=";
MATRIX_OP1_EQ:"#=";
MATRIX_OP2_EQ:"##=";
MINUS_EQ:"-=";
MOD_OP_EQ: { LA(4) == '='}? "mod=";
NE_OP_EQ:"ne=";
OR_OP_EQ:"or=";
PLUS_EQ:"+=";
POW_EQ:"^=";
SLASH_EQ:"/=";
XOR_OP_EQ: { LA(4) == '='}? "xor=";

MATRIX_OP1:'#';
MATRIX_OP2:"##";
METHOD:"::";
MEMBER:"->";
COMMA:',';
COLON:':';
EQUAL:'=';
LCURLY:'{';
RCURLY:'}';
LSQUARE:'[';
RSQUARE:']';
LBRACE:'(';
RBRACE:')';
QUESTION:'?';
EXCLAMATION:'!';
POW:'^';
ASTERIX:'*';
SLASH:'/';
MINUS:'-';
PLUS:'+';
INC:"++";
DEC:"--";
GTMARK:'>';
LTMARK:'<';
LOG_AND:"&&";
LOG_OR:"||";
LOG_NEG:'~';

protected
END_U:;

protected
EOL
	: 	( ("\r\n")=> "\r\n" // WINDOOF
//		| ("\n\r")=> "\n\r" // ???	
		| '\n'   			// Unix
		| '\r'   			// macintosh
		) { newline(); }
	;

protected     
W
//	: ( '\003'..'\010' | '\t' | '\r' | '\013' | '\f' | '\016'.. '\037' | ' ' )
	: (' ' | '\t' | '\014') // 014=FF 
	;

protected
D
	: ('0'..'9')
	;

protected
L
	: ('a'..'z'|'_')
	;

protected
H 
	: ('a'..'f'|'0'..'9')
	;

protected
O
	: ('0'..'7')
	;
protected
B
	: ('0'..'1')
	;

protected
EXP
	: ('e' (('+'|'-')? (D)+)? )
	;

protected 
DBL_E
    : 'd' { $setText( "E");}
    ;

protected
DBL
	: (DBL_E (('+'|'-')? (D)+)? )
	;

protected
CONSTANT_HEX_BYTE:;
protected
CONSTANT_HEX_LONG:;
protected
CONSTANT_HEX_LONG64:;
protected
CONSTANT_HEX_I:; // integer or larger
protected
CONSTANT_HEX_INT:;
protected
CONSTANT_HEX_ULONG:;
protected
CONSTANT_HEX_ULONG64:;
protected
CONSTANT_HEX_UI:;
protected
CONSTANT_HEX_UINT:;
protected
CONSTANT_BYTE:;
protected
CONSTANT_LONG:;
protected
CONSTANT_LONG64:;
protected
CONSTANT_I:; // integer or larger if necessary
protected
CONSTANT_INT:;
protected
CONSTANT_ULONG:;
protected
CONSTANT_ULONG64:;
protected
CONSTANT_UI:;
protected
CONSTANT_UINT:;
protected
CONSTANT_OCT_BYTE:;
protected
CONSTANT_OCT_LONG:;
protected
CONSTANT_OCT_LONG64:;
protected
CONSTANT_OCT_I:; // integer or larger if necessary
protected
CONSTANT_OCT_INT:;
protected
CONSTANT_OCT_ULONG:;
protected
CONSTANT_OCT_ULONG64:;
protected
CONSTANT_OCT_UI:;
protected
CONSTANT_OCT_UINT:;
protected
CONSTANT_FLOAT:;
protected
CONSTANT_DOUBLE:;
protected
STRING_LITERAL:;
protected
DOT:;

CONSTANT_OR_STRING_LITERAL
	// returns everything 'cleaned', ready to use
	// could be a string, but octals have priority
	: ('\"'(O)+ ( 'b' | 's' | "us" | "ub" | 'l' | 'u' | "ul" )?) => 
		('\"'! (O)+		{ _ttype=CONSTANT_OCT_I; }  // DEFINT32
			( 's'!		{ _ttype=CONSTANT_OCT_INT; }
			| 'b'!		{ _ttype=CONSTANT_OCT_BYTE; }
			| 'u'!     	{ _ttype=CONSTANT_OCT_UI; }   // DEFINT32
			| "us"!    	{ _ttype=CONSTANT_OCT_UINT; } 
			| "ub"!		{ _ttype=CONSTANT_OCT_BYTE; }
			| 'l'!     	{ _ttype=CONSTANT_OCT_LONG; }
			| "ll"!    	{ _ttype=CONSTANT_OCT_LONG64; }
			| "ul"!		{ _ttype=CONSTANT_OCT_ULONG; }
			| "ull"!	{ _ttype=CONSTANT_OCT_ULONG64; }
			)?)
	| ('\''(H)+'\'' ( 'x' | "xs" | "xb" | "xl" | "xu" | "xus" | "xub" | "xul")) =>
		('\''! (H)+ '\''! 'x'!
	  	(      			{ _ttype=CONSTANT_HEX_I; } // DEFINT32
			| 's'!		{ _ttype=CONSTANT_HEX_INT; }
			| 'b'!		{ _ttype=CONSTANT_HEX_BYTE; }
			| 'u'!    	{ _ttype=CONSTANT_HEX_UI; }   // DEFINT32
			| "us"!    	{ _ttype=CONSTANT_HEX_UINT; } 
	        | "ub"!		{ _ttype=CONSTANT_HEX_BYTE; }
			| 'l'!	    { _ttype=CONSTANT_HEX_LONG; }
			| "ll"!     { _ttype=CONSTANT_HEX_LONG64; }
			| "ul"!	    { _ttype=CONSTANT_HEX_ULONG; }
	  		| "ull"!	{ _ttype=CONSTANT_HEX_ULONG64; }
			))
	| ('\''(O)+'\''	( 'o' | "os" | "ol" | "ou" | "oul")) =>
		('\''! (O)+ '\''! 'o'!
			(       	{ _ttype=CONSTANT_OCT_I; } // DEFINT32
			| 's'!     	{ _ttype=CONSTANT_OCT_INT; }
			| 'b'!     	{ _ttype=CONSTANT_OCT_BYTE; }
			| 'u'!     	{ _ttype=CONSTANT_OCT_UI; }   // DEFINT32
			| "us"!    	{ _ttype=CONSTANT_OCT_UINT; } 
			| "ub"!    	{ _ttype=CONSTANT_OCT_BYTE; }
			| 'l'!     	{ _ttype=CONSTANT_OCT_LONG; }
			| "ll"!     { _ttype=CONSTANT_OCT_LONG64; }
			| "ul"!    	{ _ttype=CONSTANT_OCT_ULONG; }
			| "ull"!	{ _ttype=CONSTANT_OCT_ULONG64; }
			))
	| ('\''(B)+'\''	( 'b' | "bs" | "bl" | "bu" | "bul" )) =>
		('\''! (B)+ '\''! 'b'!
			(       	{ _ttype=CONSTANT_BIN_I; } // DEFINT32
			| 's'!     	{ _ttype=CONSTANT_BIN_INT; }
			| 'b'!     	{ _ttype=CONSTANT_BIN_BYTE; }
			| 'u'!     	{ _ttype=CONSTANT_BIN_UI; }   // DEFINT32
			| "us"!    	{ _ttype=CONSTANT_BIN_UINT; } 
			| "ub"!    	{ _ttype=CONSTANT_BIN_BYTE; }
			| 'l'!     	{ _ttype=CONSTANT_BIN_LONG; }
			| "ll"!         { _ttype=CONSTANT_BIN_LONG64; }
			| "ul"!    	{ _ttype=CONSTANT_BIN_ULONG; }
			| "ull"!	{ _ttype=CONSTANT_BIN_ULONG64; }
			))
	// strings in the original do not need trailing " or '	
	| '\"'! (~('\"'|'\r'|'\n')| '\"' '\"'! )* 
		( '\"'!
		| 		
		)      		    { _ttype=STRING_LITERAL; }
	| '\''! (~('\''|'\r'|'\n')| '\'' '\''!  )* 
		( '\''!
		| 		
		)      			{ _ttype=STRING_LITERAL; }
	| (((D)+ (DBL | '.'(D)*(DBL))) | '.'(D)+(DBL)) =>
		(
			(
				(D)+ 
				( DBL 
				| '.'(D)*(DBL)
				)
			) 
		| '.'(D)+(DBL)) 
						{ _ttype=CONSTANT_DOUBLE; }
	| (((D)+ (EXP | '.'(D)*(EXP)?)) | '.'(D)+(EXP)?) =>
		(
			(
				(D)+ 
				( EXP 
				| '.'(D)*(EXP)?
				)
			) 
		| '.'(D)+(EXP)?) 
		{ _ttype=CONSTANT_FLOAT; }
	| '.'             	{ _ttype=DOT; }
	| (D)+ 		  		{ _ttype=CONSTANT_I; }
		( 's'!          { _ttype=CONSTANT_INT; }
		| 'b'!			{ _ttype=CONSTANT_BYTE; }
		| 'u'!('s'!)?	{ _ttype=CONSTANT_UINT; }
		| "ub"!			{ _ttype=CONSTANT_BYTE; }
		| 'l'!    		{ _ttype=CONSTANT_LONG; }
		| "ll"!    		{ _ttype=CONSTANT_LONG64; }
		| "ul"!    		{ _ttype=CONSTANT_ULONG; }
		| "ull"!   		{ _ttype=CONSTANT_ULONG64; }
		)?	
	;	

COMMENT
  : ';' (options {greedy=true;}: ~('\r'|'\n'))* { _ttype=antlr::Token::SKIP;}
  ;

// look here for reserved words
IDENTIFIER
options
{
	testLiterals = true;
}
	: (L)(L|D|'$')*
	{ 
	  std::string s=StrUpCase( $getText);
	  $setText( s); 
	}
	;

SYSVARNAME
	: ('!') (L|D|'$')+
	{ 
	  std::string s=StrUpCase( $getText);
	  $setText( s); 
	}
	;

END_MARKER
  : '&' { _ttype=END_U; }
  ;

WHITESPACE
  : (W)+
	{ _ttype=antlr::Token::SKIP; }
  ;

// this subrule eats lines to skip
// 1. comment only lines
// 2. blank lines
protected
SKIP_LINES
  : ( COMMENT
	| W
	| EOL
	)*
  ;

// IDL ignores everything on the line after the $
CONT_STATEMENT
    : '$' (~('\r'|'\n'))* EOL
        SKIP_LINES
        { 
            ++lineContinuation;
            _ttype=antlr::Token::SKIP; 
        }
    ;

END_OF_LINE 
  : EOL
	SKIP_LINES
	{ _ttype=END_U; }
  ;

// just to know how many tokens are there
protected
MAX_TOKEN_NUMBER :;
