– # This file contains the grammar of the RubyBreaker type annotation language. # TODO: This file probably needs more documentation

require “treetop” require “#{File.dirname(__FILE__)}/type” require “#{File.dirname(__FILE__)}/type_unparser”

grammar TypeGrammar

include RubyBreaker

rule meth_type 
  space? meth_name space? '(' space? arg_types_opt:(arg_types)? space? ')' 
  space? blk_type_opt:(blk_type)? space? '->' space? ret_type space?   
  {
    def value
      mname = meth_name.text_value
      args = arg_types_opt.empty? ? [] : arg_types_opt.value
      blk = blk_type_opt.empty? ? nil : blk_type_opt.value
      ret = ret_type.value
      pos = RubyBreaker::Position.get()
      pos.col = meth_name.interval.first
      return RubyBreaker::MethodType.new(mname,args,blk,ret,pos)
    end
  }
end

rule arg_types
  moretypes:( arg_type space? ',' space? )* 
  rest_arg_types
  {
    def value
      types = []
      moretypes.elements.each {|t| types << t.arg_type.value  }
      if rest_arg_types.value.kind_of?(Array) 
        types.concat(rest_arg_types.value)
      else
        types << rest_arg_types.value
      end
      return types
    end
  }
end

rule arg_type
  or_type / or_parent_type / fusion_type / duck_type / simple_type
end

rule unambig_arg_type
  or_parent_type / fusion_type / duck_type / simple_type
end

rule rest_arg_types
  opt_varlen_types / opt_types / arg_type
end

rule opt_types
  moretypes:( opt_type space? ',' space? )* opt_type
  {
    def value
      types = []
      moretypes.elements.each {|t| types << t.opt_type.value }
      types << opt_type.value
      return types
    end
  }
end

rule opt_varlen_types
  moretypes:( opt_type space? ',' space? )* varlen_type
  {
    def value
      types = []
      moretypes.elements.each {|t| types << t.opt_type.value }
      types << varlen_type.value
      return types
    end
  }
end

rule opt_type
  unambig_arg_type '?'
  {
    def value
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::OptionalType.new(unambig_arg_type.value, pos)
    end
  }
end

rule varlen_type
  unambig_arg_type '*'
  {
    def value
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::VarLengthType.new(unambig_arg_type.value, pos)
    end
  }
end

rule or_parent_type
  '(' or_type ')'
  {
    def value(); or_type.value; end
  }
end

rule or_type
  left:(fusion_type / duck_type / simple_type) space? 
  more:(space? '||' space? opt:(fusion_type / duck_type / simple_type) )+
  {
    def value
      types = [left.value]
      # let's flatten the types instead of nesting them
      more.elements.each do |t|
        types << t.opt.value
      end
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::OrType.new(types, pos)
    end
  }
end

rule blk_type 
  blk_type_empty / blk_type_full
end

rule blk_type_empty
  '{' space? '}'
  {
    def value
      return nil
    end
  }
end

rule blk_type_full
  '{' space? '|' space? arg_types_opt:(arg_types)? space? '|' space? 
  blk_type_opt:(blk_type)? space? '->' space? ret_type '}' 
  {
    def value
      args = arg_types_opt.empty? ? [] : arg_types_opt.value
      blk = blk_type_opt.empty? ? nil : blk_type_opt.value
      ret = ret_type.value        
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::BlockType.new(args,blk,ret,pos)
    end
  }
end

rule fusion_type
  simple_type '[' space? meth_name_list space? ']'
  {
    def value      
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::FusionType.new(simple_type.value, meth_name_list.value,pos)
    end
  }
end

rule duck_type
  '[' space? meth_name_list_opt:(meth_name_list)? space? ']'
  {
    def value      
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      if meth_name_list_opt.empty?
        t = RubyBreaker::AnyType.new(pos)
      else
        mnames = meth_name_list_opt.value
        t = RubyBreaker::DuckType.new(mnames,pos)
      end
      return t
    end 
  }
end

rule meth_name_list
  more_meth_names:(meth_name space? ',' space?)* meth_name
  {
    def value
      mnames = []
      more_meth_names.elements.each do |more_mname|
        mnames << more_mname.meth_name.text_value
      end
      mnames << meth_name.text_value
      return mnames
    end 
  }
end

rule ret_type
  more:(simple_type space? ',' space?)* simple_type
  {
    def value
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      if more.empty?
        return simple_type.value
      else
        types = []
        more.elements.each {|e| types << e.simple_type.value  }
        types << simple_type.value
        return types
      end
    end
  }
end

rule simple_type
  nil_type / any_type1 / self_type / nominal_type
end

rule self_type
  'self' {
    def value
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::SelfType.new(pos)
    end
  }
end

rule nominal_type
  [a-z_/]+ { # FIXME: not really accurate
    def value      
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      begin 
        mod_name = RubyBreaker::Util.camelize(text_value)
        mod = eval(mod_name) # do it at the current binding
        t = RubyBreaker::NominalType.new(mod, pos)
      rescue => e
        puts e
        t = RubyBreaker::AnyType.new(pos)
      end
      return t # RubyBreaker::NominalType.new(text_value,pos)
    end
  }
end

rule nil_type
  'nil' {
    def value
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::NilType.new(pos)
    end
  }
end

rule any_type1
  '?' {
    def value
      pos = RubyBreaker::Position.get()
      pos.col = interval.first
      return RubyBreaker::AnyType.new(pos)
    end
  }
end

rule meth_name
  deprecated_meth_name / normal_meth_name
end

rule deprecated_meth_name
  '_deprecated_' normal_meth_name 
end

rule normal_meth_name
  nominal_meth_name / sym_meth_name
end

rule sym_meth_name
  # Be careful about the order. Remember, it finds a match first
  '===' / '<=>' / '[]=' / 
  '!~' / '+@' /
  '==' / '!=' / '<<' / '>>' / '[]' / '**' / '<=' / '>=' / '-@' / '=~' / 
  '<' / '>' / '&' / '|' / '*' / '/' / '%' / '+' / '-' / '^' / '~' 
end

rule nominal_meth_name
  [a-z_] [a-zA-Z0-9_]* [!?=]?
end

rule space
  [\s]+
end

end