/*
 * Copyright 2005 Martin Traverso
 *
 * This grammar is made available under the Ruby license.
 */
grammar Ruby;

/*
options {
    output = AST;
}
*/

program:
		compound_statement
	;

compound_statement: (statement)? (eol statement)* eol?
	;

statement:
	(
	    rescue_statement |
		expression
	) conditional_modifier
	;

class_def:
	'class' (
		CONSTANT ('<' statement)?
	 	| '<<' statement
	 ) eol
		compound_statement
	'end'
	;	
	
module_def:
	'module' CONSTANT
		compound_statement
	'end'
	;
	
begin_or_end_block:
	('BEGIN' | 'END') eol? '{' compound_statement '}'
	;



method_def:
    'def' method_name method_args?
		    compound_statement
	'end'
	;

method_name:
    IDENTIFIER |
    (variable | CONSTANT ) ('.' | '::') IDENTIFIER
	;

method_args:
      (args eol) => args
    | ('(') => '(' args? ')'
    ;

args: arg (',' arg)* (',' '*' arg)? (',' '&' IDENTIFIER)?
    ;

arg : IDENTIFIER ('=' statement)?
    ;

rescue_statement:
	'begin'
		compound_statement
	('rescue' rescue_clause?)*
	('else'
		compound_statement
	)?
	('ensure'
		compound_statement
	)?
	'end'
	;

rescue_clause: (then) => then compound_statement
            |  (eol) => compound_statement
            |  (statement (',' statement)*)? ('=>' variable)? then?
		            compound_statement
    ;

block:
		do_end_block
	|   brace_block
	;

do_end_block:
		'do' block_body 'end'
	;

brace_block:
		'{' block_body '}'
	;
		
block_body:
	('|' block_args '|')?
	compound_statement
	;

block_args:
	IDENTIFIER (',' IDENTIFIER)*
	;


conditional_modifier:
	(('if' | 'unless' | 'while') eol? statement)*
	;
		
return_statement:
	'return' statement
	;
	
catch_statement:
	'catch' catch_clause
	;

catch_clause:
       (~'(') => statement do_end_block
    |  statement block  // if expression is surrounded in (), a {} block is allowed, otherwise only a do..end block is allowed
    ;

if_statement: 
	'if' statement then?
		compound_statement
	('elsif' statement then?
		compound_statement
	)*
	('else'
		compound_statement
	)?
	'end'
	;
		
		
then: 
    (eol 'then') => eol 'then'
    | 'then'
    | ':'
	;
	
case_statement:
	'case' statement
	// TODO: when xxx then
	
	;
	
	 
while_statement: 
	'while' statement ('do' | ':')?
		compound_statement
	'end'
	;
	
until_statement:
	'until' statement ('do' | ':')?
		compound_statement
	'end'
	;		

unless_statement:
	'unless' statement then?
		compound_statement
	('else'
		compound_statement
	)?
	'end'
	;

expression: negation (('and' | 'or') negation)*
    ;

negation: 'not' negation | defined;

defined: 'defined?' defined | assignment;


// TODO: mutiple return values
assignment: (lhs '=') => lhs '=' statement
    | ternary
    ;

lhs:
    variable |
    CONSTANT |
    atom (trailer)+
    ;

ternary: range ('?' statement ':' statement )? // TODO: should allow eol after '?' and after ':'
    ;

//  "x" .. defined? true is parsed as "x" .. (defined? true)
//  1 .. b = 2 is parsed as 1 .. (b = 2)
range: or_expression (('..' | '...') defined)?
    ;

or_expression: and_expression ('||' defined)*
    ;

and_expression: equality ('&&' defined)*
    ;

equality: comparison (('<=>' | '==' | '===' | '!=' | '=~' | '!~') defined)?
    ;

comparison: bitwise_or (('<=' | '<' | '>' | '>=') defined)?
    ;

bitwise_or: bitwise_and (('|' | '^') defined)*
    ;

bitwise_and: shift ('&' defined)*
    ;

shift: additive (('<<' | '>>') defined)*
    ;

additive: multiplicative (('+' | '-') defined)* // TODO: fix ambiguity with unary
        ;

multiplicative: unary (('*' | '/' | '%') defined)*
        ;

unary: ('!' | '~' | '+' | '-') defined
    | exponentiation
    ;

exponentiation: method_call ('**' defined)?
    ;

// TODO
method_call: element_reference  // (actual_args? eol? block?)?
   ;

/*
actual_args: '(' actual_arg_list? ')'
    | actual_arg_list
  ;

actual_arg_list
    :   statement (',' statement)* (',' '*' statement)?
    |   '*' statement
    ;
*/

element_reference: atom (trailer)*
    ;

trailer:
     '[' statement ']'
    | (
         '.' (IDENTIFIER | CONSTANT)
       | '::' (IDENTIFIER | CONSTANT)
      )+
	;


// TODO: fix precedence of the following alternatives
atom:
    begin_or_end_block |
    class_def |
    module_def |
    method_def |
    catch_statement |
    if_statement |
    until_statement |
    unless_statement |
    while_statement |
    'break' |
    'next' |
    'redo' |
    return_statement |
	array |
	hash |
	'true' |
	'false' |
	'nil' |
	'self' |
	'__FILE__' |
	'__LINE__' |
    INTEGER |
    FLOAT |
    SINGLE_STRING |
    variable |
    CONSTANT |
    '(' statement ')'
    ;

variable:
	('@' | '@@' | '$')? IDENTIFIER;
	
array: '[' (statement (',' statement)*)? ']'
	;

hash: '{' (statement ('=>' statement)? (',' statement ('=>' statement)?)*)? '}'
 	;
	
eol: (';' | '\n' | '\r')+
	;

INTEGER: DIGITS
    | '0x' (DIGITS | 'a'..'f' | 'A'..'F')+
    ;

FLOAT: (DIGITS '.' DIGITS) => DIGITS '.' DIGITS EXPONENT?           // is this the best we can do to disambiguate with range?
    |   DIGITS EXPONENT
    ;

fragment
DIGITS: ('0'..'9')+;

fragment
EXPONENT: ('e' | 'E') ( '+' | '-' )? DIGITS
    	;


CONSTANT:  'A'..'Z' ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
	;

IDENTIFIER: ('a'..'z' | '_') ('a'..'z' | 'A'..'Z' | '0'..'9' | '_')*
	;
	
SYMBOL: (':' (IDENTIFIER | CONSTANT)) => ':' (IDENTIFIER | CONSTANT)
	;


WS	:	(	' '
		|	'\t'
		|	'\f'
		)
		{ channel = 99; }
	;
	


// TODO
SINGLE_STRING
    :   '\'' (options {greedy=false;}: .)* '\''
    ;



