# Copyright (c) 2010 The Mirah project authors. All Rights Reserved. # All contributing project authors may be found in the NOTICE file. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Mirah::AST class Body < Node include Java::DubyLangCompiler.Body def initialize(parent, line_number, &block) super(parent, line_number, &block) end # Type of a block is the type of its final element def infer(typer, expression) unless @inferred_type @typer ||= typer @self_type ||= typer.self_type if children.empty? @inferred_type = typer.no_type else children[0..-2].each do |child| typer.infer(child, false) end @inferred_type = typer.infer(children.last, expression) end if @inferred_type resolved! else typer.defer(self) end end @inferred_type end def string_value if children.size == 1 children[0].string_value else super end end def <<(node) super if @typer orig_self = @typer.self_type @typer.known_types['self'] = @self_type @typer.infer(node, true) @typer.known_types['self'] = orig_self end self end def add_node(node) self << node end end # class << self class ClassAppendSelf < Body include Scope include Scoped def initialize(parent, line_number, &block) super(parent, line_number, &block) end def infer(typer, expression) static_scope.self_type = scope.static_scope.self_type.meta super end end class ScopedBody < Body include Scope include Scoped def infer(typer, expression) static_scope.self_type ||= typer.self_type super end def binding_type(mirah=nil) static_scope.binding_type(defining_class, mirah) end def binding_type=(type) static_scope.binding_type = type end def has_binding? static_scope.has_binding? end def type_reference(typer) raise Mirah::SyntaxError.new("Invalid type", self) unless children.size == 1 children[0].type_reference(typer) end def inspect_children(indent=0) indent_str = ' ' * indent str = '' if static_scope.self_node str << "\n#{indent_str}self: " if Node === static_scope.self_node str << "\n" << static_scope.self_node.inspect(indent + 1) else str << static_scope.self_node.inspect end end str << "\n#{indent_str}body:" << super(indent + 1) end end class Block < Node include Scoped include Scope include Java::DubyLangCompiler::Block child :args child :body def initialize(parent, position, &block) super(parent, position) do static_scope.parent = scope.static_scope yield(self) if block_given? end end def prepare(typer, method) mirah = typer.transformer interface_or_abstract_class = method.argument_types[-1] outer_class = scope.defining_class binding = scope.binding_type(mirah) name = "#{outer_class.name}$#{mirah.tmp}" klass = mirah.define_closure(position, name, outer_class) case when interface_or_abstract_class.interface? klass.interfaces = [interface_or_abstract_class] when interface_or_abstract_class.abstract? klass.superclass = interface_or_abstract_class else raise "#{interface_or_abstract_class.name} isn't an interface or abstract" end klass.define_constructor(position, ['binding', binding]) do |c| mirah.eval("@binding = binding", '-', c, 'binding') end @defining_class = klass.static_scope.self_type # TODO We need a special scope here that allows access to the # outer class. static_scope.self_type = typer.infer(klass, true) add_methods(klass, binding, typer) call = parent instance = Call.new(call, position, 'new') instance.target = Constant.new(call, position, name) instance.parameters = [ BindingReference.new(instance, position, binding) ] call.parameters << instance call.block = nil typer.infer(instance, true) end def defining_class @defining_class end # TODO extract this & matching methods into a module def binding_type(mirah=nil) static_scope.binding_type(defining_class, mirah) end def add_methods(klass, binding, typer) method_definitions = body.select{ |node| node.kind_of? MethodDefinition } if method_definitions.empty? build_method(klass, binding, typer) else # TODO warn if there are non method definition nodes # they won't be used at all currently--so it'd be nice to note that. method_definitions.each do |node| node.static_scope = static_scope node.binding_type = binding # node.children.each {|child| child.instance_variable_set '@scope', nil } klass.append_node(node) end end end def build_method(klass, binding, typer) # find all methods which would not otherwise be on java.lang.Object impl_methods = find_abstract_methods(klass).select do |m| begin # Very cumbersome. Not sure how it got this way. mirror = BiteScript::ASM::ClassMirror.for_name('java.lang.Object') mtype = Mirah::JVM::Types::Type.new(mirror) mtype.java_method m.name, *m.argument_types rescue NameError # not found on Object next true end # found on Object next false end # It could also just define all the methods w/ the block as the implementation, assuming the args check out # instead of it being an error. if impl_methods.size > 1 raise Mirah::NodeError.new("Multiple abstract methods found within interface #{klass.interfaces.map(&:name).inspect} [#{impl_methods.map(&:name).join(', ')}]; cannot use block", self) end impl_methods.each do |method| if args.args.length != method.argument_types.length raise Mirah::NodeError.new("Block can't implement #{method.name}: wrong number of arguments. Expected #{method.argument_types.length}, but was #{args.args.length}", self) end mdef = klass.define_method(position, method.name, method.return_type, args.dup) do |mdef| mdef.static_scope = static_scope mdef.binding_type = binding mdef.body = body.dup end typer.infer(mdef.body, method.return_type != typer.no_type) end end def find_abstract_methods(klass) methods = [] interfaces = klass.interfaces.dup until interfaces.empty? interface = interfaces.pop methods += interface.declared_instance_methods.select {|m| m.abstract?} interfaces.concat(interface.interfaces) end if klass.superclass && klass.superclass.abstract? methods += klass.superclass.declared_instance_methods.select{|m| m.abstract? } end methods end end class BindingReference < Node def initialize(parent, position, type) super(parent, position) @inferred_type = type end def infer(typer, expression) resolved! unless resolved? @inferred_type end end class Noop < Node def infer(typer, expression) resolved! @inferred_type ||= typer.no_type end end class Script < Node include Scope include Binding child :body attr_accessor :defining_class attr_reader :filename def initialize(parent, line_number, &block) super(parent, line_number, &block) @package = "" end def infer(typer, expression) resolve_if(typer) do typer.set_filename(self, filename) @defining_class ||= begin static_scope.self_type = typer.self_type end typer.infer(body, false) end end def filename=(filename) @filename = filename if Script.explicit_packages static_scope.package = '' else package = File.dirname(@filename).tr('/', '.') package.sub! /^\.+/, '' static_scope.package = package end end class << self attr_accessor :explicit_packages end end class Annotation < Node attr_reader :values attr_accessor :runtime alias runtime? runtime child :name_node def initialize(parent, position, name=nil, &block) super(parent, position, &block) if name @name = if name.respond_to?(:class_name) name.class_name else name.name end end @values = {} end def name @name end def type BiteScript::ASM::Type.getObjectType(@name.tr('.', '/')) end def []=(name, value) @values[name] = value end def [](name) @values[name] end def infer(typer, expression) @inferred ||= begin @name = name_node.type_reference(typer).name if name_node @values.each do |name, value| if Node === value @values[name] = annotation_value(value, typer) end end true end end def annotation_value(node, typer) case node when String java.lang.String.new(node.literal) when Fixnum java.lang.Integer.new(node.literal) when Array node.children.map {|node| annotation_value(node, typer)} else # TODO Support other types raise "Unsupported Annotation Value Type" end end end end