/*description:
{
	This script parses an object modelling file that conform to \samp{CWML}, a modelling
	language proposed by \CodeWorker\ as an illustration.
	
	\samp{\textit{package_declaration} ::= \textbf{"package"} \textit{package_path} \textbf{'\{'} \textit{package_body} \textbf{'\}'}}\\
	\samp{\textit{package_body} ::= \textit{package_declaration} | \textit{class_declaration} | \textit{service_declaration}}\\
	\TODO\
}
*/

translation_unit ::=
		#ignore(C++) #continue
		[
				package_declaration(this)
			|
				service_declaration(this)
			|
				component_declaration
		]*
		#empty;

package_declaration(myParentPackage : node) ::=
		#readIdentifier:"package"
		=> local myPackage;
		#continue
		package_path:listOfPaths
		'{'
		=> {
 			ref myPackage = myParentPackage;
			foreach i in listOfPaths {
				insert myPackage.listOfPackages[i].name = i;
				if myPackage.sequence insert myPackage.listOfPackages[i].sequence = myPackage.sequence + "." + i;
				else insert myPackage.listOfPackages[i].sequence = i;
				ref myPackage = myPackage.listOfPackages[i];
			}
		}
		[
				package_declaration(myPackage)
			|
				class_declaration(myPackage)
			|
				service_declaration(myPackage)
		]*
		'}';

package_path : list ::=
		#readIdentifier:package_path
		[['.' | "::"] #continue #readIdentifier:package_path]*;

class_declaration(myPackage : node) : value ::=
		[class_modifier]?:myModifiers
		#readIdentifier:"class" #continue
		#readIdentifier:class_declaration
		=> merge myPackage.listOfClasses[class_declaration] = myModifiers;
		=> localref myClass = myPackage.listOfClasses[class_declaration];
		=> insert myClass.name = class_declaration;
		[parent_declaration(myClass)]?
		class_declaration_body(myClass)
		=> {
			pushItem this.allClasses[class_declaration].packages;
			ref this.allClasses[class_declaration].packages#back = myPackage;
			insert myPackage.listOfClasses[class_declaration].sequence = myPackage.sequence;
		}
		[';']?;

class_modifier : node	::=
		#readIdentifier:"abstract"
		=> insert class_modifier.isAbstract = true;
		;

parent_declaration(myClass : node)	::=
		[
			#readIdentifier:"extends" #continue
			#readIdentifier:myClass.extendedClass
		]?
		[
			#readIdentifier:"implements" #continue
			=> pushItem myClass.listOfInterfaces;
			#readIdentifier:myClass.listOfInterfaces#back
			[
				',' #continue
				=> pushItem myClass.listOfInterfaces;
				#readIdentifier:myClass.listOfInterfaces#back
			]*
		]?
		;

class_declaration_body(myClass : node)	::=
		'{' #continue
		[
				attribute_declaration(myClass)
			|
				method_declaration(myClass)
		]* '}';

service_declaration(myPackage : node) : value ::=
		#readIdentifier:"service" #continue
		#readIdentifier:service_declaration
		=> insert myPackage.listOfServices[service_declaration].name = service_declaration;
		service_declaration_body(myPackage.listOfServices[service_declaration])
		=> {
			pushItem this.allServices[service_declaration].packages;
			ref this.allServices[service_declaration].packages#back = myPackage;
			insert myPackage.listOfServices[service_declaration].sequence = myPackage.sequence;
		}
		[';']?
		;

service_declaration_body(myService : node)	::=
		'{' #continue [method_declaration(myService)]* '}';

component_declaration	::=
		#readIdentifier:"component"
		#continue
		#readIdentifier:sName
		=> if findElement(sName, this.listOfComponents) error("component '" + sName + "' has already been declared");
		=> insert this.listOfComponents[sName].name = sName;
		=> localref theComponent = this.listOfComponents[sName];
		'{'
		[
			#readIdentifier:{"client", "server"}:sSide
			#continue
			':'
			#readIdentifier:sService
//			['.' #continue #readIdentifier]?
			=> if !this.allServices.findElement(sService) error("unrecognized service '" + sService + "'");
			=>	if sSide == "client" insert theComponent.client[sService] = sService;
				else insert theComponent.server[sService] = sService;
			[
				',' #continue
				#readIdentifier:sService
//				['.' #continue #readIdentifier]?
				=> if !this.allServices.findElement(sService) error("unrecognized service '" + sService + "'");
				=>	if sSide == "client" insert theComponent.client[sService] = sService;
					else insert theComponent.server[sService] = sService;
			]*
			';'
		]*
		'}'
		[';']?
		;

type_specifier(myType : node) ::=
		[
				base_type_specifier(myType)
			|
				container_type_specifier(myType)
			|
				class_type(myType)
		]
		[array_specifier(myType)]*;

base_type_specifier(myType : node)	::=
		numeric_type(myType)
	|
		#readIdentifier:{"char", "string", "date", "bool", "boolean"}:myType.name
		=> if myType.name == "bool" set myType.name = "boolean";
	;

numeric_type(myType : node)	::=
		[numeric_type_modifier(myType)]?
		#readIdentifier:{"byte", "short", "long", "int", "float", "double"}:myType.name;

numeric_type_modifier(myType : node)	::=
		#readIdentifier:{"positive", "negative"}:sModifier
		=> {
			if sModifier == "positive" insert myType.isPositive = true;
			else insert myType.isNegative = true;
		}
		;

class_type(myType : node)	::=
		#readIdentifier:thePath[0]
		[
			['.' | "::"] #continue
			=> pushItem thePath;
			#readIdentifier:thePath#back
		]*
		=> {
			if thePath.size() == 1 {
				insert myType.name = thePath#back;
				insert myType.isObject = true;
			} else {
				error("'class_type' with scope (" + thePath.size() + " elements) not implemented yet");
			}
			if !findElement(myType.name, this.allClasses) error("invalid class type '" + myType.name + "'");
		};

container_type_specifier(myType : node)	::=
		#readIdentifier:{"list", "map", "vector"}:myType.name
		'<' #continue
		[
			#check(myType.name == "map")
			#continue
			base_type_specifier(myType.keyType) ','
		]?
		type_specifier(myType.elementType)
		'>';

array_specifier(myType : node) ::=
		'[' ']'
		=> {
			slideNodeContent(myType, elementType);
			if myType.elementType.isObject insert myType.isObject = true;
			insert myType.isArray = true;
		};

method_declaration(myClass : node)	::=
		[method_pre_modifier(myModifiers)]*
		type_specifier(myType)
		#readIdentifier:sName
		'(' #continue
		[
			#pushItem(listOfParameters)
			parameter_declaration:listOfParameters#back
			[
				',' #continue
				=> pushItem listOfParameters;
				parameter_declaration:listOfParameters#back
			]*
		]?
		')'
		[method_post_modifier(myModifiers)]?
		=>{
			local sKey = sName;
			setall myClass.listOfMethods[sKey] = myModifiers;
			insert myClass.listOfMethods[sKey].name = sName;
			setall myClass.listOfMethods[sKey].listOfParameters = listOfParameters;
			setall myClass.listOfMethods[sKey].type = myType;
		}
		';';

method_pre_modifier(myModifiers : node)	::=
		#readIdentifier:{"synchronized", "abstract", "final"}:sModifier
		=> {
			if sModifier == "synchronized" insert myModifiers.isSynchronized = true;
			else if sModifier == "abstract" insert myModifiers.isAbstract = true;
			else if sModifier == "final" insert myModifiers.isFinal = true;
		};

method_post_modifier(myModifiers : node)	::=
		#readIdentifier:"const"
		=> insert myModifiers.isConst = true;
		;

parameter_declaration : node	::=
		parameter_type_specifier(parameter_declaration.type) #continue
		#readIdentifier:parameter_declaration.name;

parameter_type_specifier(myType : node)	::=
		[parameter_type_modifier(myType)]*
		type_specifier(myType);

parameter_type_modifier(myType : node) : node	::=
		#readIdentifier:{"const", "synchronized"}:sModifier
		=> {
			if sModifier == "const" insert myType.isConst = true;
			else if sModifier == "synchronized" insert myType.isSynchronized = true;
		};

attribute_declaration(myClass : node)	::=
		attribute_type_specifier(myType) #readIdentifier:sName
		#continue
		['=' #continue literal:myClass.listOfAttributes[sName].default]?
		';'
		=> {
			insert myClass.listOfAttributes[sName].name = sName;
			setall myClass.listOfAttributes[sName].type = myType;
		};

attribute_type_specifier(myType : node) ::=
		[attribute_type_modifier(myModifiers)]*
		type_specifier(myType)
		=> merge myType = myModifiers;
		;

attribute_type_modifier(myModifier : node)	::=
		#readIdentifier:{"aggregate", "const", "readonly", "transient", "pertinent", "synchronized", "pkey", "optional"}:sModifier
		=> switch(sModifier) {
			case "aggregate": insert myModifier.isAggregate = true;break;
			case "const": insert myModifier.isConst = true;break;
			case "readonly": insert myModifier.isReadOnly = true;break;
			case "transient": insert myModifier.isTransient = true;break;
			case "pertinent": insert myModifier.isPertinent = true;break;
			case "synchronized": insert myModifier.isSynchronized = true;break;
			case "pkey": insert myModifier.isPrimaryKey = true;break;
			case "optional": insert myModifier.isOptional = true;break;
		};

literal			::=	integer_literal | string_literal | character_literal | numeric_literal | boolean_literal;
boolean_literal	::=	#readIdentifier:{"true", "false"};
integer_literal		::=	INT | OCTAL | HEX;
string_literal		::=	[STRING_LITERAL]+;
character_literal	::=	CHAR_LITERAL;
numeric_literal	::=	FLOAT;


/* COMMON LEXICAL RULES  */
OR			::=	'|';
XOR			::=	'^';
AND			::=	'&';
NOT			::=	'!';
LT			::=	'<';
LSHIFT		::=	"<<";
GT			::=	'>';
RSHIFT		::=	">>";
DIV			::=	'/';
PLUS		::=	'+';
MINUS		::=	'-';
TILDE		::=	'~';
STAR		::=	'*';
MOD			::=	'%';

CHAR_LITERAL	::=	#!ignore '\'' [ESC | ~'\''] '\'';
ESC				::=	#!ignore '\\' ['n' | 't' | 'v' | 'b' | 'r' | 'f' | 'a' | '\\' | '?' | '\'' | '"' | HEX | OCTAL];

DIGIT		::=	'0'..'9';
OCTDIGIT	::=	'0'..'7';
HEXDIGIT	::=	'0'..'9' | 'a'..'f' | 'A'..'F';


OCTAL	::= #!ignore ['0'..'3'] [OCTDIGIT]*;
HEX		::=	#!ignore ["0x" | "0X"] [HEXDIGIT]+;
INT		::=	#!ignore [DIGIT]+;
FLOAT	::=
		#!ignore
		[
				'.' [DIGIT]+
			|
				[DIGIT]+ '.' [DIGIT]*
		]
		[['e' | 'E'] ['+' | '-']? [DIGIT]+]?;
