/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/apps/jrpcgen/JrpcgenParser.cup,v 1.2 2005/11/11 21:29:44 haraldalbrecht Exp $
 *
 * Copyright (c) 1999, 2000
 * Lehrstuhl fuer Prozessleittechnik (PLT), RWTH Aachen
 * D-52064 Aachen, Germany.
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This library 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 Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program (see the file LICENSE.txt for more
 * details); if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * To compile into java code use:
 *   java java_cup.Main -interface \
 *     -runtime org.acplt.oncrpc.apps.jrpcgen.cup_runtime \
 *     -symbols JrpcgenSymbols -parser JrpcgenParser < JrpcgenParser.cup
 */

package org.acplt.oncrpc.apps.jrpcgen;

import org.acplt.oncrpc.apps.jrpcgen.cup_runtime.*;
import java.util.Vector;

//
// We need to set up some code within the parser class...
//
parser code  {:

	JrpcgenModule module;
	
	public void setModule(JrpcgenModule module) {
		this.module = module;
	}
	
    public void report_error(String message, Object info) {
        StringBuilder msg = new StringBuilder("jrpcgen: error");
        if (info instanceof Symbol) {
            Symbol s = (Symbol) info;
            msg.append(" in line ").append(s.left);
        }
        msg.append(": ").append(message);
        System.out.println(msg);
        throw(new JrpcgenParserException());
    }

    public void report_error(String message, int line) {
    	String msg = "jrpcgen: error in line " + line + ": " + message;
        System.out.println(msg);
        throw(new JrpcgenParserException());
    }

    public void syntax_error(Symbol cur_token) {
        String msg = "jrpcgen: syntax error in line " + cur_token.left;
        System.out.println(msg);
        throw(new JrpcgenParserException());
    }

    public void report_fatal_error(String message, Object info) {
        report_error(message, info);
        throw(new RuntimeException("Fatal Syntax Error"));
    }

:};

//
// As per convention, we use all uppercase letters for TERMINALs.
// Notes:
//   - we do not define the terminals U_SHORT etc. but instead map these
//     terminals in the scanner to the corresponding signed base data type.
//
terminal SEMICOLON;                     // ";"
terminal COMMA;                         // ","
terminal COLON;                         // ":"
terminal EQUAL;                         // "="
terminal STAR;                          // "*"
terminal LPAREN, RPAREN;                // "(" ")"
terminal LBRACE, RBRACE;                // "{" "}"
terminal LBRACKET, RBRACKET;            // "[" "]"
terminal LANGLE, RANGLE;                // "<" ">"

terminal PROGRAM;                       // "PROGRAM" "program"
terminal VERSION;                       // "VERSION" "version"
terminal CONST;                         // "const"
terminal TYPEDEF;                       // "typedef"
terminal SWITCH;                        // "switch"
terminal CASE;                          // "case"
terminal DEFAULT;                       // "default"

terminal VOID;                          // "void"
terminal CHAR;                          // "char"
terminal SHORT;                         // "short"
terminal INT;                           // "int"
terminal LONG;                          // "long"
terminal HYPER;                         // "hyper"
terminal FLOAT;                         // "float"
terminal DOUBLE;                        // "double"
terminal QUADRUPLE;                     // "quadruple"
terminal BOOL;                          // "bool"
terminal ENUM;                          // "enum"
terminal OPAQUE;                        // "opaque"
terminal STRING;                        // "string"
terminal STRUCT;                        // "struct"
terminal UNION;                         // "union"
terminal UNSIGNED;                      // "unsigned"

terminal String INTEGER_LITERAL; // yes, that's right.
terminal String IDENTIFIER;
terminal String JAVA_DOC_COMMENT;


non terminal compilation_unit;
non terminal definitions; 
non terminal JrpcgenDocumentable  definition;

non terminal JrpcgenDocumentable  constant_def;

non terminal JrpcgenDocumentable  enum_def;
non terminal JrpcgenConst.Table   enum_elements;
non terminal JrpcgenConst         enum_element;

non terminal JrpcgenStruct        struct_def;

non terminal JrpcgenDocumentable  union_def;
non terminal Vector               union_cases;
non terminal JrpcgenUnionArm      union_case;

non terminal JrpcgenTypedefinition typedef_def;

non terminal JrpcgenProgramInfo   program_def;
non terminal JrpcgenVersionInfo.Table  version_defs;
non terminal JrpcgenVersionInfo   version_def;
non terminal JrpcgenProcedureInfo.Table procedure_defs;
non terminal JrpcgenProcedureInfo procedure_def;

non terminal JrpcgenParamInfo.Table  argument_list;
non terminal JrpcgenParamInfo.Table  argument_list_no_void;

non terminal String               type_specifier_incl_specials;
non terminal String               type_specifier_incl_string;
non terminal String               type_specifier;

non terminal JrpcgenDeclaration.Table declarations;
non terminal JrpcgenDeclaration   declaration;

non terminal String               int_types;
non terminal String               value;

non terminal String 			 javadoc;


start with compilation_unit;

//
// A ".x" file consists of zero or more definitions (typedef, struct, union,
// enum, const) followed by exactly one program definition.
//
// After the compilation unit has been parsed, the jrpcgen class will contain
// all necessary information in its static variable members to generate the
// various source code files.
//
// ** SF patch #1159721: add possibility to generate from .x file without program def **
compilation_unit ::= definitions ;


javadoc ::=
	JAVA_DOC_COMMENT:jdoc
		{: RESULT = jdoc; :}
	|
	/* empty */ {: RESULT = "/** TODO */"; :}
	;

definitions ::=
        javadoc:jdoc definition:def
        	{:  /* add documentation */
        		def.setDocumentation(jdoc); 
        	:}
        |
        definitions javadoc:jdoc definition:def
        	{:  /* add documentation */
        		def.setDocumentation(jdoc); 
        	:}
        ;

//
// A single definition can be either a typedef, struct, union, enum or const
// definition.
//
definition ::=
        constant_def:jdoc 
        	{: RESULT=jdoc; :}
        | enum_def:jdoc 
        	{: RESULT=jdoc; :}
        | struct_def:jdoc 
        	{: RESULT=jdoc; :}
        | union_def:jdoc 
        	{: RESULT=jdoc; :}
        | typedef_def:jdoc
        	{: RESULT=jdoc; :}
        | program_def:pdef
        	{: RESULT=pdef; :} 
        ;

//
// A constant definition assigns either an integer literal or an identifier
// to a (constant) identifier:
//   - add constant identifier to global identifier list and remember the
//     "value" of the constant (either an integer literal or another
//     identifier).
//
constant_def ::=
        CONST IDENTIFIER:id EQUAL value:value SEMICOLON
            {:           
               RESULT = parser.module.addConstant(id, value);
               
               if (RESULT == null) {
               		parser.report_error("const identifier \"" + id + "\" already defined", idleft);
               }
            :}
        ;

//
// Parse an enumeration:
//   - add enumeration identifier to global identifier list and remember
//     which enumerated elements belong to the enumeration.
//   - add the individual enumerated elements to the global identifier list.
//
enum_def ::=
        ENUM IDENTIFIER:id LBRACE
            enum_elements:elements
        RBRACE SEMICOLON
            {:
               //
               // Create teh new enumeration and bind the
               // elements to the new enumeration.
               //
               RESULT = parser.module.addEnumeration(id, elements);

               if (RESULT == null) {
                   parser.report_error("enum identifier \"" + id + "\" already defined", idleft);
               }
            :}
        ;

enum_elements ::=
        enum_element:el
            {:
               //
               // If no specific value has been assigned to this element of
               // an enumeration, assign zero by default.
               //
               if ( el.getValue() == null ) {
                   el.setValue("0");
               }
               RESULT = new JrpcgenConst.Table();
               RESULT.putItem(el);
            :}
        |
        enum_elements:elements COMMA enum_element:el
            {:
               //
               // If no specific value has been assigned to this element of
               // an enumeration, increment value of previous element and
               // assign the new value to this element. BTW -- we let the
               // Java compiler do the calculation, so references to
               // other constants by name are valid.
               //
               if ( el.getValue() == null ) {
                   el.setValue(elements.getLastItem().getValue().concat("+1"));
               }
               elements.putItem(el);
               RESULT = elements;
            :}
        ;

enum_element ::=
        javadoc:jdoc IDENTIFIER:id
            {:
               //
               // We don't know yet the value, so we don't specify one. The
               // value will be fixed up when the production for the complete
               // enumeration triggers.
               //
               RESULT = parser.module.addEnumerationElement(id, null);
               
               if (RESULT == null) {
                   parser.report_error("identifier \"" + id + "\" already defined", idleft);
               }
               
               RESULT.setDocumentation(jdoc);
            :}
        |
        javadoc:jdoc IDENTIFIER:id EQUAL value:val
            {:            
               RESULT = parser.module.addEnumerationElement(id, val);
               
               if (RESULT == null) {
                   parser.report_error("identifier \"" + id + "\" already defined", idleft);
               }
               
               RESULT.setDocumentation(jdoc);
            :}
        ;

//
// Parse a structure:
//   - add structure identifier to global list of identifiers.
//
struct_def ::=
        STRUCT IDENTIFIER:id LBRACE
            declarations:elements
        RBRACE SEMICOLON
            {:
               RESULT = parser.module.addStruct(id, elements);
               
               if (RESULT == null) {
                   parser.report_error("struct identifier \"" + id + "\" already defined", idleft);
               }
            :}
        ;

//
// Parse a union:
//   - add union identifier to global list of identifiers.
// Note that this grammar is more liberal with respect to the "default"
// arm: it can appear anywhere within the union and need not to be the
// last arm. This makes the grammar more straight then Sun's rpcgen.
//
union_def ::=
        UNION IDENTIFIER:id SWITCH LPAREN declaration:discrim RPAREN LBRACE
            union_cases:elements
        RBRACE SEMICOLON
            {:
               RESULT = parser.module.addUnion(id, discrim, elements);
               
               if (RESULT == null) {
                   parser.report_error("union identifier \"" + id + "\" already defined", idleft);
               }
            :}
        ;

union_cases ::=
        union_case:arm
            {:
                RESULT = new Vector<JrpcgenUnionArm>();
                RESULT.addElement(arm);
            :}
        |
        union_cases:arms union_case:arm
            {:
                arms.addElement(arm);
                RESULT = arms;
            :}
        ;

union_case ::=
        CASE value:val COLON
            {:
               RESULT = new JrpcgenUnionArm(val, null);
            :}
        |
        CASE value:val COLON declaration:element SEMICOLON
            {:
               RESULT = new JrpcgenUnionArm(val, element);
            :}
        |
        DEFAULT COLON declaration:element SEMICOLON
            {:
               RESULT = new JrpcgenUnionArm(null, element);
            :}
        ;

//
// New type definition.
//
typedef_def ::=
        TYPEDEF declaration:decl SEMICOLON
            {:
               RESULT = parser.module.addTypedefinition(decl);
               
               if (RESULT == null) {
                   parser.report_error("typedef identifier \"" + decl.getIdentifier() + "\" already defined", declleft);
               }
            :}
        ;


//
// Parse a program definition:
//   - add program identifier to global list of identifiers.
//   - remember the individual versions of this program.
//
program_def ::=
        PROGRAM IDENTIFIER:progId LBRACE
            version_defs:versions
        RBRACE EQUAL value:progNumber SEMICOLON
            {:
               RESULT = parser.module.addProgramInfo(progId, progNumber, versions);
               
               if (RESULT == null) {
                   parser.report_error("program identifier \"" + progId + "\" already defined", progIdleft);
               }
            :}
        ;

//
// Parse a version definition:
//   - add version identifier to global list of identifiers.
//   - remember the procedures defined for this program version.
//
version_defs ::=
        version_def:version
            {:            
                JrpcgenVersionInfo.Table versions = new JrpcgenVersionInfo.Table();
                
                //
                // This is the first version. The returned boolean
                // is expected to be 'true' in any case.
                //
                versions.putItem(version);
                RESULT = versions;
            :}
        |
        version_defs:versions version_def:version
            {:
                if (! versions.addItem(version)) {
                	parser.report_error("version identifier \"" + version.versionId + "\" already defined", versionleft);
                }                
                
                RESULT = versions;
            :}
        ;

version_def ::=
        VERSION IDENTIFIER:versId LBRACE
            procedure_defs:procs
        RBRACE EQUAL value:versNumber SEMICOLON
            {:            
               //
               // Some work has to be done here.
               // First create a new version info instance. If the version number
               // is globally unique, an instance is returned and an attempt has been
               // made to resolve the version number.
               //
               JrpcgenProcedureInfo conflictingProcedureInfo;
               
               RESULT = parser.module.newVersionInfo(versId, versNumber, procs);
               
               if (RESULT == null) {
                   parser.report_error("version identifier \"" + versId + "\" already defined", versIdleft);
               }
               
               if (RESULT.resolvedVersionNumber == null) {
                   parser.report_error("Can not resolve version identifier \"" +
                                      versId + "\" to integer literal", versNumberleft);
               }
               
               conflictingProcedureInfo = RESULT.completeProcedureInfos();
               
               if (conflictingProcedureInfo != null) {
                   parser.report_error("procedure identifier \"" + conflictingProcedureInfo.procedureId + "\" already defined", 0);
               }
            :}
        ;

procedure_defs ::=
        procedure_def:proc
            {:
            	JrpcgenProcedureInfo.Table procedures = new JrpcgenProcedureInfo.Table();
            	
            	procedures.putItem(proc);
                RESULT = procedures;
            :}
        |
        procedure_defs:procs procedure_def:proc
            {:
                //
                // Within a program version procedure names have to be unique.
                // Global uniqueness is required to the constants defined later
                // by concatenating the procedure name by the version number.
                //
                // As the version number is unknown at this stage, the procedure
                // name is checked against the so far given procedure names, ensuring
                // unique procedure names within a program version.
                //
                if (! procs.addItem(proc)) {
                 	parser.report_error("procedure identifier \"" + proc.procedureId + "\" already defined", procleft);
                }
                                
                RESULT = procs;
            :}
        ;

//
// A procedure definition consists of the return type, the procedure identifier,
// one parameter type and the procedure number to assign. Nothing more.
//
// Please note that RFC 1832 is fishy: it talks about multiple parameters
// separated by commas, but at least rpcgen for ONC/RPC protocol version 2
// does only support a single parameter. Also, it forgets about "void"
// being valid and opaque being invalid -- see Sun's rpcgen source. Sigh.
//
// Due to "popular request" (SPQR, anyone?!) the grammar has been updated
// to cope with multiple parameters.
//		
procedure_def ::=
        javadoc:jdoc type_specifier_incl_specials:resultType IDENTIFIER:procId LPAREN
            argument_list:args
        RPAREN EQUAL value:procNumber SEMICOLON
            {:
               RESULT = parser.module.newProcedureInfo(jdoc, procId, procNumber, resultType, args);
               //
               // NOTE: we can not add the identifier for the procedure number
               // to the list of global identifiers yet, as we do not know the
               // version number. This has to be fixed after the embracing
               // version definition was parsed in toto.
               //
            :}
        ;

//
// We either accept only one "void" argument, indicating that there are
// no arguments to a particular remote procedure at all, or a list of
// arguments, either named or unnamed.
//
argument_list ::=
        VOID
            {:
               RESULT = null; // special case, allows fast tests
            :}
        |
        argument_list_no_void:args
            {:
               RESULT = args; // just pass through
            :}
        ;
//
// Accept a list of arguments, either named or unnamed.
//
argument_list_no_void ::=
        type_specifier_incl_string:paramType
            {:
               RESULT = new JrpcgenParamInfo.Table();
               RESULT.putItem(parser.module.newParamInfo(paramType, "arg1"));
            :}
        |
        type_specifier_incl_string:paramType IDENTIFIER:paramName
            {:
               RESULT = new JrpcgenParamInfo.Table();
               RESULT.putItem(parser.module.newParamInfo(paramType, paramName));
            :}
        |
        argument_list:args COMMA type_specifier_incl_string:paramType
            {:            
               String paramName = "arg".concat(String.valueOf(args.size() + 1));
               
               if (! args.addItem(parser.module.newParamInfo(paramType, paramName))) {
                   parser.report_error("parameter identifier \"" + paramName + "\" already defined", paramTypeleft);
               }
               
               RESULT = args;
            :}
        |
        argument_list:args COMMA type_specifier_incl_string:paramType
          IDENTIFIER:paramName
            {:
               if (! args.addItem(parser.module.newParamInfo(paramType, paramName))) {
                   parser.report_error("parameter identifier \"" + paramName + "\" already defined", paramNameleft);
               }
               
               RESULT = args;
            :}
        ;

type_specifier_incl_specials ::=
        VOID
            {: RESULT = JrpcgenBaseType.VOID.getDefinitionName(); :}
        |
        type_specifier_incl_string:type
            {: RESULT = type; :}
        ;

type_specifier_incl_string ::=
        STRING
            {: RESULT = JrpcgenBaseType.STRING.getDefinitionName(); :}
        |
        type_specifier:type
            {: RESULT = type; :}
        ;

//
// A type specifier can be either a base type ("builtin" type), an
// enumeration type, structure, union or typedef'd type. The "string"
// type specifier is insane, as it does not accept size specification.
// Somehow the whole x-file grammar is brain-damaged: it was never
// carefully designed but instead grew like the pest.
//
type_specifier ::=
        IDENTIFIER:id
            {: RESULT = id; :}
        |
        BOOL
            {: RESULT = JrpcgenBaseType.BOOL.getDefinitionName(); :}
        |
        // As the Java language and the [Jk...]VM can only handle signed
        // integers of various sizes, we ignore the UNSIGNED token completely.
        UNSIGNED int_types:type
            {: RESULT = type; :}
        |
        int_types:type
            {: RESULT = type; :}
        |
        // Per default unsigned without any following type maps to "int"
        UNSIGNED
            {: RESULT = JrpcgenBaseType.INT.getDefinitionName(); :}
        |
        FLOAT
            {: RESULT = JrpcgenBaseType.FLOAT.getDefinitionName(); :}
        |
        DOUBLE
            {: RESULT = JrpcgenBaseType.DOUBLE.getDefinitionName(); :}
        |
        QUADRUPLE // FIXME!
            {: RESULT = JrpcgenBaseType.DOUBLE.getDefinitionName(); :}
        |
        ENUM IDENTIFIER:id
            {: RESULT = id; :}
        |
        STRUCT IDENTIFIER:id
            {: RESULT = id; :}
        |
        UNION IDENTIFIER:id
            {: RESULT = id; :}
        ;

//
//
//
declarations ::=
        javadoc:jdoc declaration:decl SEMICOLON
            {:
               decl.setDocumentation(jdoc);
               RESULT = new JrpcgenDeclaration.Table();
               RESULT.putItem(decl);
            :}
        |
        declarations:decls javadoc:jdoc declaration:decl SEMICOLON
            {:
               decl.setDocumentation(jdoc);
               
               if (! decls.addItem(decl)) {
                   parser.report_error("struct identifier \"" + decl.getIdentifier() + "\" already defined", declleft);
               }
               RESULT = decls;
            :}
        ;

declaration ::=
        OPAQUE IDENTIFIER:id LBRACKET value:size RBRACKET
            {:
                // Note: we use the pseudo-type "opaque" here to distinguish
                // real byte arrays from fake byte (char) arrays.
                RESULT = parser.module.newFixedOpaqueDeclaration(id, size);
            :}
        |
        OPAQUE IDENTIFIER:id LANGLE value:size RANGLE
            {:
                // Note: we use the pseudo-type "opaque" here to distinguish
                // real byte arrays from fake byte (char) arrays.
                RESULT = parser.module.newOpaqueDeclaration(id, size);
            :}
        |
        OPAQUE IDENTIFIER:id LANGLE RANGLE
            {:
                // Note: we use the pseudo-type "opaque" here to distinguish
                // real byte arrays from fake byte (char) arrays.
                RESULT = parser.module.newOpaqueDeclaration(id, null);
            :}
        |
        STRING IDENTIFIER:id LANGLE value:size RANGLE
            {:
                RESULT = parser.module.newStringDeclaration(id, size);
            :}
        |
        STRING IDENTIFIER:id LANGLE RANGLE
            {:
                RESULT = parser.module.newStringDeclaration(id, null);
            :}
        |
        type_specifier:type IDENTIFIER:id
            {:
               RESULT = parser.module.newScalarDeclaration(id, type);
            :}
        |
        type_specifier:type IDENTIFIER:id LBRACKET value:size RBRACKET
            {:
               RESULT = parser.module.newFixedVectorDeclaration(id, type, size);
            :}
        |
        type_specifier:type IDENTIFIER:id LANGLE value:size RANGLE
            {:
                RESULT = parser.module.newDynamicVectorDeclaration(id, type, size);
            :}
        |
        type_specifier:type IDENTIFIER:id LANGLE RANGLE
            {:
                RESULT = parser.module.newDynamicVectorDeclaration(id, type, null);
            :}
        |
        type_specifier:type STAR IDENTIFIER:id
            {:
                RESULT = parser.module.newIndirectionDeclaration(id, type);
            :}
        |
        VOID
            {:
                RESULT = parser.module.newVoidDeclaration();
            :}
        ;

//
// There are quite some integer types defined in RFC 1832, with additional
// types used by Sun's rpcgen.
//
int_types ::=
        SHORT INT
            {: RESULT = JrpcgenBaseType.SHORT.getDefinitionName(); :}
        |
        SHORT
            {: RESULT = JrpcgenBaseType.SHORT.getDefinitionName(); :}
        |
        //
        // Note that we try to retain compatibility to most C compilers by
        // mapping a "C char" to a "Java byte".
        //
        CHAR
            {: RESULT = JrpcgenBaseType.CHAR.getDefinitionName(); :}
        |
        INT
            {: RESULT = JrpcgenBaseType.INT.getDefinitionName(); :}
        |
        LONG INT
            {: RESULT = JrpcgenBaseType.INT.getDefinitionName(); :}
        |
        LONG
            {: RESULT = JrpcgenBaseType.INT.getDefinitionName(); :}
        |
        HYPER
            {: RESULT = JrpcgenBaseType.HYPER.getDefinitionName(); :}
        |
        HYPER INT
            {: RESULT = JrpcgenBaseType.HYPER.getDefinitionName(); :}
        ;

//
// "Values" can be either integer literals or identifiers, which then can
// be (hopefully) resolved to an integer literal.
//
value ::=
        INTEGER_LITERAL:lit
            {: RESULT = lit; :}
        |
        IDENTIFIER:id
            {: RESULT = id; :}
        ;


/* End of file JrpcgenParser.cup */
