/* * 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;}: .)* '\'' ;