class Fancy # This module has methods that can be used as callbacks from # the parser, so that we can have a relatively simple fancy.y # For example, when the parser sees a literal (or any other node) # it just calls methods defined here. that create actual # AST nodes, returning them to the parser to be stored on # $$ class Parser class ParseError < StandardError; end def self.parse_file(filename, lineno = 1) new(filename, lineno).parse_file.script end def self.parse_string(code, lineno = 1, filename = "(eval)") new(filename, lineno).parse_string(code).script end attr_reader :filename, :lineno, :script def initialize(filename, lineno) @filename, @lineno = filename, lineno end def body=(expression_list) @script = AST::Script.new(lineno, filename, expression_list) end def string_literal(line, yytext) str = yytext[1..-2] # omit the starting and ending quotes AST::StringLiteral.new(line, str) end def multiline_string_literal(line, yytext) str = yytext[3..-4] # omit the starting and ending triple-quotes AST::StringLiteral.new(line, str) end def integer_literal(line, yytext, base = 10) AST::FixnumLiteral.new(line, yytext.to_i(base)) end def double_literal(line, yytext) AST::NumberLiteral.new(line, yytext.to_f) end def symbol_literal(line, yytext) str = yytext[1..-1] # omit the quote AST::SymbolLiteral.new(line, str.to_sym) end def parse_error(line, yytext) raise ParseError.new "Parse error at line #{line}, unexpected token: #{yytext.inspect}" end def file_error(*error) raise ParseError.new error.join(" ") end def const_identifier(line, identifier, parent = nil) if parent AST::ScopedConstant.new(line, parent, identifier.name) else identifier end end def ruby_send_open(line, yytext) # A ruby send is identified by the parser by seeing an # identifier immediatly followed by a left-paren. # NO SPACE IS ALLOWED BETWEEN THEM. # So the parser gives yytext as the identifier with its # left-paren as last character. # Remove the paren and simply create an identifier. identifier(line, yytext[0..-2]) end def identifier(line, yytext) if yytext == "self" AST::Self.new(line) else AST::Identifier.new(line, yytext) end end def msg_send_basic(line, receiver, identifier) args = AST::MessageArgs.new(line) AST::MessageSend.new(line, receiver, identifier, args) end def oper_send_basic(line, receiver, operator, argument) args = AST::MessageArgs.new(line, argument) AST::MessageSend.new(line, receiver, operator, args) end def oper_send_multi(line, receiver, operator, *arguments) args = AST::MessageArgs.new(line, *arguments) AST::MessageSend.new(line, receiver, operator, args) end def assignment(line, identifier, value) AST::Assignment.new(line, identifier, value) end def multiple_assignment(line, identifiers, values) AST::MultipleAssignment.new(line, identifiers, values) end def identifier_list(line, identifier_list, identifier) if identifier_list.kind_of?(Array) identifier_list.push(identifier) else [identifier_list, identifier] end end def send_args(line, selector, value, ary = []) selector = AST::Identifier.new(selector.line, selector.identifier+":") ary.push Struct.new(:selector, :value).new(selector, value) end def operator_def(line, operator, parameter, method_body, access = :public) args = AST::MethodArgs.new(line, parameter.identifier) AST::MethodDef.new(line, operator, args, method_body, access) end def operator_def_multi(line, operator, method_body, access = :public, *parameters) args = AST::MethodArgs.new(line, *parameters.map(&:identifier)) AST::MethodDef.new(line, operator, args, method_body, access) end def sin_operator_def(line, identifier, operator, parameter, method_body, access = :public) args = AST::MethodArgs.new(line, parameter.identifier) AST::SingletonMethodDef.new(line, identifier, operator, args, method_body, access) end def sin_operator_def_multi(line, identifier, operator, method_body, access = :public, *parameters) args = AST::MethodArgs.new(line, *parameters.map(&:identifier)) AST::SingletonMethodDef.new(line, identifier, operator, args, method_body, access) end def method_def_no_args(line, method_ident, method_body, access = :public) args = AST::MethodArgs.new(line) AST::MethodDef.new(line, method_ident, args, method_body, access) end def method_arg(line, selector, variable, default = nil) selector = AST::Identifier.new(selector.line, selector.identifier+":") Struct.new(:selector, :variable, :default).new(selector, variable, default) end def helper_method_name(method_args) method_args.map { |a| a.selector.identifier }.join("") end # Creates a single MethodDef node. def method_def(line, method_args, method_body, access) name = helper_method_name(method_args) method_ident = AST::Identifier.new(line, name) args = method_args.map { |a| a.variable.identifier } args = AST::MethodArgs.new(line, *args) AST::MethodDef.new(line, method_ident, args, method_body, access) end def method_delegators(method_args) # find the first default argument idx = method_args.index { |a| a.default } return unless idx line = method_args.first.selector.line target = helper_method_name(method_args) (method_args.size - idx).times do |pos| required = method_args[0...idx+pos] default = method_args[idx+pos..-1] params = required.map(&:variable) + default.map(&:default) forward = AST::MessageSend.new(line, AST::Self.new(line), AST::Identifier.new(line, target), AST::MessageArgs.new(line, *params)) doc = AST::StringLiteral.new(line, "Forward to message #{target}") body = AST::ExpressionList.new(line, doc, forward) yield required, body end end # Generate an expression list of method_defs def method_def_expand(line, method_args, method_body, access = :public) defs = [] method_delegators(method_args) do |sel, fwd| defs << method_def(line, sel, fwd, access) end defs << method_def(line, method_args, method_body, access) AST::ExpressionList.new(line, *defs) end def sin_method_def_expand(line, identifier, method_args, method_body, access = :public) defs = [] method_delegators(method_args) do |sel, fwd| defs << sin_method_def(line, identifier, sel, fwd, access) end defs << sin_method_def(line, identifier, method_args, method_body, access) AST::ExpressionList.new(line, *defs) end def sin_method_def(line, identifier, method_args, method_body, access = :public) name = method_args.map { |a| a.selector.identifier }.join("") method_name = AST::Identifier.new(line, name) args = method_args.map { |a| a.variable.identifier } args = AST::MethodArgs.new(line, *args) AST::SingletonMethodDef.new(line, identifier, method_name, args, method_body, access) end def sin_method_def_no_args(line, identifier, method_name, method_body, access = :public) args = AST::MethodArgs.new(line) AST::SingletonMethodDef.new(line, identifier, method_name, args, method_body, access) end def msg_send_args(line, receiver, method_args) receiver ||= AST::Self.new(line) name = method_args.map { |a| a.selector.identifier }.join("") name = AST::Identifier.new(line, name) args = AST::MessageArgs.new line, *method_args.map { |a| a.value } AST::MessageSend.new(line, receiver, name, args) end def msg_send_ruby(line, receiver, identifier, args = nil) receiver ||= AST::Self.new(line) args ||= ruby_args(line) arguments = AST::MessageArgs.new line, args AST::MessageSend.new(line, receiver, identifier, arguments) end def nil_literal(line) Rubinius::AST::NilLiteral.new(line) end def partial_block(line, block_body) gen_blockarg = AST::Identifier.generate(line) args = AST::BlockArgs.new line, gen_blockarg AST::BlockLiteral.new(line, args, block_body, true) end def block_literal(line, block_args, block_body) block_args ||= Array.new args = AST::BlockArgs.new line, *block_args AST::BlockLiteral.new(line, args, block_body) end def tuple_literal(line, expr_ary = []) # when we have a tuple with 1 element, it's not actually a tuple # but a grouped expression, e.g. (obj foo: bar) # 1-tuples don't make much sense anyway, so we won't support # them via tuple literal syntax. if expr_ary.size == 1 expr_ary.first else AST::TupleLiteral.new(line, *expr_ary) end end def range_literal(line, from, to) AST::RangeLiteral.new(line, from, to) end def block_args(line, identifier, ary = []) ary.push identifier end def expr_list(line, expr = nil, expr_list = nil) expr_list = AST::ExpressionList.new(line) unless expr_list expr_list.add_expression(expr) if expr expr_list end def class_def(line, name, parent, body = nil) AST::ClassDef.new(line, name, parent, body) end def expr_ary(line, exp, ary = []) if exp.is_a? Array ary += exp else ary.push exp end end def array_literal(line, expr_ary = []) AST::ArrayLiteral.new(line, *expr_ary) end def ruby_args(line, array_literal = nil, block = nil) if array_literal.kind_of?(Array) array_literal = AST::ArrayLiteral.new(line, *array_literal) end if array_literal.nil? array_literal = AST::ArrayLiteral.new(line) end AST::RubyArgs.new(line, array_literal, block) end def super_exp(line) AST::Super.new(line) end def retry_exp(line) AST::Retry.new(line) end def catch_handlers(line, handler = nil, handlers = nil) handlers ||= AST::Handlers.new(line) handlers.add_handler handler if handler handlers end def catch_handler(line, body = nil, condition = nil, var = nil) condition ||= AST::Identifier.new(line, "Object") AST::ExceptHandler.new(line, condition, var, body) end def try_catch_finally(line, body, handlers, finally = nil) handlers ||= AST::Handlers.new(line) AST::TryCatchBlock.new(line, body, handlers, finally) end def key_value_list(line, key, value, ary = []) ary.push key ary.push value ary end def hash_literal(line, key_values = []) AST::HashLiteral.new(line, *key_values) end def regex_literal(line, regexp_str) regexp_str = regexp_str[1..-2] AST::RegexLiteral.new(line, regexp_str, 0) end def return_stmt(line, expr = nil) expr ||= nil_literal(expr) AST::Return.new(line, expr) end def return_local(line, expr = nil) expr ||= nil_literal(expr) AST::Return.new(line, expr) end def match_expr(line, expr, clauses = nil) clauses ||= [] AST::Match.new(line, expr, clauses) end def match_body(line, match_clause, match_clauses = []) match_clauses.push(match_clause) end def match_clause(line, match_expr, val_expr, match_args=[]) AST::MatchClause.new(line, match_expr, val_expr, match_args) end end end