header {
	package antlr.actions.csharp;
}

{
	import java.io.StringReader;
	import antlr.collections.impl.Vector;
	import antlr.*;
}

/* ANTLR Translator Generator
 * Project led by Terence Parr at http://www.cs.usfca.edu
 * Software rights: http://www.antlr.org/license.html
 *
 * Port to C# by Kunle Odutola : kunle UNDERSCORE odutola AT hotmail DOT com
 *
 * $Id:$
 */

// HISTORY:
//
// 01-Aug-2002 kunle    Now handles C# preprocessor directives
//						[ Based on Ric's C/C++ preprocessor code ]
// 07-Dec-2002 kunle    Now supports the new #[ID,text,nodeType] syntax.
//


/** Perform the following translations:

    AST related translations

	##				-> currentRule_AST
	#(x,y,z)		-> codeGenerator.getASTCreateString(vector-of(x,y,z))
	#[x]			-> codeGenerator.getASTCreateString(x)
	#x				-> codeGenerator.mapTreeId(x)

	Inside context of #(...), you can ref (x,y,z), [x], and x as shortcuts.

    Text related translations

	$append(x)	  -> text.append(x)
	$setText(x)	  -> text.setLength(_begin); text.append(x)
	$getText	  -> new String(text.getBuffer(),_begin,text.length()-_begin)
	$setToken(x)  -> _token = x
	$setType(x)	  -> _ttype = x
    $FOLLOW(r)    -> FOLLOW set name for rule r (optional arg)
    $FIRST(r)     -> FIRST set name for rule r (optional arg)
 */
class ActionLexer extends Lexer;
options {
	k=3;
	charVocabulary='\3'..'\377';
	testLiterals=false;
	interactive=true;
}

{
	protected RuleBlock currentRule;
	protected CodeGenerator generator;
	protected int lineOffset = 0;
	private Tool antlrTool;	// The ANTLR tool
	ActionTransInfo transInfo;

 	public ActionLexer( String s, RuleBlock currentRule,
						CodeGenerator generator,
						ActionTransInfo transInfo ) 
	{
		this(new StringReader(s));
		this.currentRule = currentRule;
		this.generator = generator;
		this.transInfo = transInfo;
	}

	public void setLineOffset(int lineOffset) 
	{
		setLine(lineOffset);
	}

	public void setTool(Tool tool) 
	{
		this.antlrTool = tool;
	}

	public void reportError(RecognitionException e)
	{
		antlrTool.error("Syntax error in action: "+e,getFilename(),getLine(),getColumn());
	}

	public void reportError(String s)
	{
		antlrTool.error(s,getFilename(),getLine(),getColumn());
	}

	public void reportWarning(String s)
	{
		if ( getFilename()==null )
			antlrTool.warning(s);
		else
			antlrTool.warning(s,getFilename(),getLine(),getColumn());
	}
}

// rules are protected because we don't care about nextToken().

public
ACTION
	:	(	STUFF
		|	AST_ITEM
		|	TEXT_ITEM
		)+
	;

/** stuff in between #(...) and #id items
 * Allow the escaping of the # for C# preprocessor stuff.
 */
protected
STUFF
	:	COMMENT
	|	STRING
	|	CHAR
	|	"\r\n" 		{ newline(); }
	|	'\\' '#'	{ $setText("#"); }
	|	'\r' 		{ newline(); }
	|	'\n'		{ newline(); }
	|	'/' ~('/'|'*')	// non-comment start '/'
//	|	( ~('/'|'\n'|'\r'|'$'|'#'|'"'|'\'') )+
	|	~('/'|'\n'|'\r'|'$'|'#'|'"'|'\'')
	;

protected
AST_ITEM
	:	'#'! t:TREE													// #( )
	|	'#'! (WS)? id:ID											// #a_name (=)?
		{
			String idt = id.getText();
			String mapped = generator.mapTreeId(id.getText(), transInfo);

			// verify that it's not a preprocessor macro...
			if ( (mapped != null) && !idt.equals(mapped) )
			{
				$setText(mapped);
			}
			else
			{
				if (idt.equals("define")	||
					idt.equals("undef")		||
					idt.equals("if")		||
					idt.equals("elif")		||
					idt.equals("else")		||
					idt.equals("endif")		||
					idt.equals("line")		||
					idt.equals("error")		||
					idt.equals("warning")	||
					idt.equals("region")	||
					idt.equals("endregion"))
				{
					$setText("#"+idt);
				}
			}
		}
		(WS)?
		( options {greedy=true;} : VAR_ASSIGN )?
	|	'#'! ctor:AST_CONSTRUCTOR								// #[ ]
	|	"##"
		{
			if( currentRule != null )
			{
				String r = currentRule.getRuleName()+"_AST";
				$setText(r);

				if ( transInfo!=null )  {
					transInfo.refRuleRoot=r;	// we ref root of tree
				}
			}
			else
			{
				reportWarning("\"##\" not valid in this context");
				$setText("##");
			}
		}
		(WS)?
		( options {greedy=true;} : VAR_ASSIGN )?
	;

protected
TEXT_ITEM
	:	"$append" (WS)? '(' a1:TEXT_ARG ')'
		{
			String t = "text.Append("+a1.getText()+")";
			$setText(t);
		}
	|	"$set"
		(	"Text" (WS)? '(' a2:TEXT_ARG ')'
			{
			String t;
			t = "text.Length = _begin; text.Append("+a2.getText()+")";
			$setText(t);
			}
		|	"Token" (WS)? '(' a3:TEXT_ARG ')'
			{
			String t="_token = "+a3.getText();
			$setText(t);
			}
		|	"Type" (WS)? '(' a4:TEXT_ARG ')'
			{
			String t="_ttype = "+a4.getText();
			$setText(t);
			}
		)
	|	"$getText"
		{
			$setText("text.ToString(_begin, text.Length-_begin)");
		}
	|	"$FOLLOW" ( (WS)? '(' a5:TEXT_ARG ')' )?
		{
			String rule = currentRule.getRuleName();
			if ( a5!=null ) {
				rule = a5.getText();
			}
			String setName = generator.getFOLLOWBitSet(rule, 1);
			// System.out.println("FOLLOW("+rule+")="+setName);
			if ( setName==null ) {
				reportError("$FOLLOW("+rule+")"+
							": unknown rule or bad lookahead computation");
			}
			else {
				$setText(setName);
			}
		}
	|	"$FIRST" ( (WS)? '(' a6:TEXT_ARG ')' )?
		{
			String rule = currentRule.getRuleName();
			if ( a6!=null ) {
				rule = a6.getText();
			}
			String setName = generator.getFIRSTBitSet(rule, 1);
			// System.out.println("FIRST("+rule+")="+setName);
			if ( setName==null ) {
				reportError("$FIRST("+rule+")"+
							": unknown rule or bad lookahead computation");
			}
			else {
				$setText(setName);
			}
		}
	;

protected
TREE!
{
	StringBuffer buf = new StringBuffer();
	int n=0;
	Vector terms = new Vector(10);
}
	:	'('
		(WS)?
		t:TREE_ELEMENT
		{
			terms.appendElement(
				generator.processStringForASTConstructor(t.getText())
									 );
		}
		(WS)?
		(	','	(WS)?
			t2:TREE_ELEMENT
	 		{
				terms.appendElement(
					generator.processStringForASTConstructor(t2.getText())
										  );
			}
			(WS)?
		)*
		{$setText(generator.getASTCreateString(terms));}
		')'
	;

protected
TREE_ELEMENT { boolean was_mapped; }
	:	'#'! TREE
	|	'#'! AST_CONSTRUCTOR
	|	'#'! was_mapped=id:ID_ELEMENT
		{	// RK: I have a queer feeling that this maptreeid is redundant..
			if ( ! was_mapped )
			{
				String t = generator.mapTreeId(id.getText(), null);
				if ( t!=null ) {
					$setText(t);
				}
			}
		}
	|	"##"
		{
			if( currentRule != null )
			{
				String t = currentRule.getRuleName()+"_AST";
				$setText(t);
			}
			else
			{
				reportError("\"##\" not valid in this context");
				$setText("##");
			}
		}
	|	TREE
	|	AST_CONSTRUCTOR
	|	ID_ELEMENT
	|	STRING
	;

// FIXME: RK - the getASTCreateString here is broken.
// getASTCreateString can not cleanly see if a constructor like
// tokens { FOR<AST=ForNode>; }
// forLoop:! "for" bla bla
//  { #forLoop = #([FOR,"for"], bla bla ) }
// should use ForNode as AST.
//
protected
AST_CONSTRUCTOR!
	:	'[' (WS)? x:AST_CTOR_ELEMENT (WS)?
		(',' (WS)? y:AST_CTOR_ELEMENT (WS)? )?
		(',' (WS)? z:AST_CTOR_ELEMENT (WS)? )? ']'
		{
			String args = generator.processStringForASTConstructor(x.getText());

			// the second does not need processing coz it's a string
			// (eg second param of astFactory.create(x,y)
			if ( y!=null )
				args += ","+y.getText();
			if ( z!=null )
				args += ","+z.getText();

			$setText(generator.getASTCreateString(null,args));
		}
	;

/** The arguments of a #[...] constructor are text, token type,
 *  or a tree.
 */
protected
AST_CTOR_ELEMENT
	:	STRING
	|	INT
	|	TREE_ELEMENT
	;

/** An ID_ELEMENT can be a func call, array ref, simple var,
 *  or AST label ref.
 */
protected
ID_ELEMENT returns [boolean mapped=false]
	:	id:ID (options {greedy=true;}:WS!)?
		(	'(' (options {greedy=true;}:WS!)? ( ARG (',' (WS!)? ARG)* )? (WS!)? ')'	// method call
		|	( '[' (WS!)? ARG (WS!)? ']' )+				// array reference
		|	'.' ID_ELEMENT
		|	"->" ID_ELEMENT
		|	/* could be a token reference or just a user var */
			{
				mapped = true;
				String t = generator.mapTreeId(id.getText(), transInfo);
//				System.out.println("mapped: "+id.getText()+" -> "+t);
				if ( t!=null ) {
					$setText(t);
				}
			}
			// if #rule referenced, check for assignment
			(	options {greedy=true;}
			:	{transInfo!=null && transInfo.refRuleRoot!=null}?
				(WS)? VAR_ASSIGN
			)?
		)
	;

protected
TEXT_ARG
	:	(WS)? ( TEXT_ARG_ELEMENT (options {greedy=true;}:WS)? )+
	;

protected
TEXT_ARG_ELEMENT
	:	TEXT_ARG_ID_ELEMENT
	|	STRING
	|	CHAR
	|	INT_OR_FLOAT
	|	TEXT_ITEM
	|	'+'
	;

protected
TEXT_ARG_ID_ELEMENT
	:	id:ID (options {greedy=true;}:WS!)?
		(	'(' (options {greedy=true;}:WS!)? ( TEXT_ARG (',' TEXT_ARG)* )* (WS!)? ')'	// method call
		|	( '[' (WS!)? TEXT_ARG (WS!)? ']' )+				// array reference
		|	'.' TEXT_ARG_ID_ELEMENT
		|	"->" TEXT_ARG_ID_ELEMENT
		|
		)
	;

protected
ARG	:	(	TREE_ELEMENT
		|	STRING
		|	CHAR
		|	INT_OR_FLOAT
		)
		(options {greedy=true;} : (WS)? ( '+'| '-' | '*' | '/' ) (WS)? ARG )*
	;

protected
ID	:	('a'..'z'|'A'..'Z'|'_')
		(options {greedy=true;} : ('a'..'z'|'A'..'Z'|'0'..'9'|'_'))*
	;

protected
VAR_ASSIGN
	:	'='
		{
			// inform the code generator that an assignment was done to
			// AST root for the rule if invoker set refRuleRoot.
			if ( LA(1)!='=' && transInfo!=null && transInfo.refRuleRoot!=null ) {
				transInfo.assignToRoot=true;
			}
		}
	;

protected
COMMENT
	:	SL_COMMENT
	|	ML_COMMENT
	;

protected
SL_COMMENT
	:	"//" (options {greedy=false;}:.)* ('\n'|"\r\n"|'\r')
		{newline();}
	;

protected
ML_COMMENT :
	"/*"
	(	options {greedy=false;}
	:	'\r' '\n'	{newline();}
	|	'\r' 		{newline();}
	|	'\n'		{newline();}
	|	.
	)*
	"*/"
	;

protected
CHAR :
	'\''
	( ESC | ~'\'' )
	'\''
	;

protected
STRING :
	'"'
	(ESC|~'"')*
	'"'
	;

protected
ESC	:	'\\'
		(	'n'
		|	'r'
		|	't'
		|	'b'
		|	'f'
		|	'"'
		|	'\''
		|	'\\'
		|	('0'..'3')
			(	options {greedy=true;}
			:	DIGIT
				(	options {greedy=true;}
				:	DIGIT
				)?
			)?
		|	('4'..'7') (options {greedy=true;}:DIGIT)?
		)
	;

protected
DIGIT
	:	'0'..'9'
	;

protected
INT	:	(DIGIT)+
	;

protected
INT_OR_FLOAT
	:	(options {greedy=true;}:DIGIT)+
		(	options {greedy=true;}
		:	'.' (options {greedy=true;}:DIGIT)*
		|	'L'
		|	'l'
		)?
	;

protected
WS	:	(	options {greedy=true;}
		: 	' '
		|	'\t'
		|	'\r' '\n'	{newline();}
		|	'\r'		{newline();}
		|	'\n'		{newline();}
		)+
	;
