mirror of
https://github.com/rubyjs/therubyracer
synced 2023-03-27 23:21:42 -04:00
move constructor logic to its own class.
This commit is contained in:
parent
e09bfe5e84
commit
eb70492454
5 changed files with 306 additions and 251 deletions
|
@ -7,6 +7,8 @@ module V8
|
|||
require 'v8/portal'
|
||||
require 'v8/portal/functions'
|
||||
require 'v8/portal/proxies'
|
||||
require 'v8/portal/templates'
|
||||
require 'v8/portal/constructor'
|
||||
require 'v8/context'
|
||||
require 'v8/object'
|
||||
require 'v8/array'
|
||||
|
|
|
@ -10,9 +10,9 @@ module V8
|
|||
with = opts[:with]
|
||||
constructor = nil
|
||||
template = if with
|
||||
constructor = @to.js_constructor_for(with.class)
|
||||
constructor.SetCallHandler(method(:tmp))
|
||||
template = constructor.InstanceTemplate()
|
||||
constructor = @to.templates.to_constructor(with.class)
|
||||
constructor.template.SetCallHandler(method(:tmp))
|
||||
constructor.template.InstanceTemplate()
|
||||
else
|
||||
C::ObjectTemplate::New()
|
||||
end
|
||||
|
@ -20,7 +20,7 @@ module V8
|
|||
@native.enter do
|
||||
@global = @native.Global()
|
||||
@to.proxies.register_javascript_proxy @global, :for => with if with
|
||||
constructor.SetCallHandler(@to.method(:invoke_non_callable_constructor)) if constructor
|
||||
constructor.exposed = false if constructor
|
||||
@scope = @to.rb(@global)
|
||||
@global.SetHiddenValue(C::String::NewSymbol("TheRubyRacer::RubyContext"), C::External::New(self))
|
||||
end
|
||||
|
|
257
lib/v8/portal.rb
257
lib/v8/portal.rb
|
@ -1,100 +1,13 @@
|
|||
|
||||
module V8
|
||||
class Portal
|
||||
attr_reader :context, :proxies
|
||||
|
||||
def js_constructor_for(ruby_class)
|
||||
unless template = @templates[ruby_class]
|
||||
template = @templates[ruby_class] = make_js_constructor(ruby_class)
|
||||
end
|
||||
return template
|
||||
end
|
||||
|
||||
def invoke_non_callable_constructor(arguments)
|
||||
unless arguments.Length() == 1 && arguments[0].kind_of?(C::External)
|
||||
C::ThrowException(C::Exception::Error(C::String::New("cannot call native constructor from javascript")))
|
||||
else
|
||||
object = arguments[0].Value()
|
||||
@proxies.register_javascript_proxy arguments.This(), :for => object
|
||||
end
|
||||
end
|
||||
|
||||
def invoke_callable_constructor(arguments)
|
||||
instance = nil
|
||||
if arguments.Length() > 0 && arguments[0].kind_of?(C::External)
|
||||
instance = arguments[0].Value()
|
||||
else
|
||||
rbargs = []
|
||||
for i in 0..arguments.Length() - 1
|
||||
rbargs << rb(arguments[i])
|
||||
end
|
||||
cls = arguments.Data()
|
||||
instance = rubyprotectraw do
|
||||
cls.new(*rbargs)
|
||||
end
|
||||
end
|
||||
@proxies.register_javascript_proxy arguments.This(), :for => instance
|
||||
rescue StandardError => e
|
||||
warn e
|
||||
end
|
||||
|
||||
def make_js_constructor(cls)
|
||||
template = C::FunctionTemplate::New(method(:invoke_non_callable_constructor))
|
||||
setuptemplate(template.InstanceTemplate())
|
||||
if cls != ::Object && cls.superclass != ::Object && cls.superclass != ::Class
|
||||
template.Inherit(js_constructor_for(cls.superclass))
|
||||
end
|
||||
if cls.name && cls.name =~ /(::)?(\w+?)$/
|
||||
template.SetClassName(C::String::NewSymbol("rb::" + $2))
|
||||
else
|
||||
template.SetClassName("Ruby")
|
||||
end
|
||||
return template
|
||||
end
|
||||
|
||||
def callable_js_constructor_for(ruby_class)
|
||||
@proxies.rb2js(ruby_class) do
|
||||
constructor = js_constructor_for(ruby_class)
|
||||
function = constructor.GetFunction()
|
||||
unless constructor.respond_to?(:embedded)
|
||||
constructor.SetCallHandler(method(:invoke_callable_constructor), ruby_class)
|
||||
#create a prototype so that this constructor also acts like a ruby object
|
||||
prototype = rubytemplate.NewInstance()
|
||||
#set *that* object's prototype to an empty function so that it will look and behave like a function.
|
||||
prototype.SetPrototype(C::FunctionTemplate::New().GetFunction())
|
||||
function.SetPrototype(prototype)
|
||||
def constructor.embedded?;true;end
|
||||
end
|
||||
function
|
||||
end
|
||||
end
|
||||
|
||||
def js_instance_for(ruby_object)
|
||||
constructor = js_constructor_for(ruby_object.class)
|
||||
arguments = C::Array::New(1)
|
||||
arguments.Set(0, C::External::New(ruby_object))
|
||||
constructor.GetFunction().NewInstance(arguments)
|
||||
end
|
||||
attr_reader :context, :access, :proxies, :templates
|
||||
|
||||
def initialize(context, access)
|
||||
@context, @access = context, access
|
||||
@proxies = Proxies.new
|
||||
@named_property_getter = Interceptor(NamedPropertyGetter)
|
||||
@named_property_setter = Interceptor(NamedPropertySetter)
|
||||
@named_property_query = nil
|
||||
@named_property_deleter = nil
|
||||
@named_property_enumerator = Interceptor(NamedPropertyEnumerator)
|
||||
|
||||
@indexed_property_getter = Interceptor(IndexedPropertyGetter)
|
||||
@indexed_property_setter = Interceptor(IndexedPropertySetter)
|
||||
@indexed_property_query = nil
|
||||
@indexed_property_deleter = nil
|
||||
@indexed_property_enumerator = Interceptor(IndexedPropertyEnumerator)
|
||||
|
||||
@functions = Functions.new(self)
|
||||
|
||||
#TODO: This is a memory leak!!
|
||||
@templates = {}
|
||||
@templates = Templates.new(self)
|
||||
end
|
||||
|
||||
def open
|
||||
|
@ -144,11 +57,17 @@ module V8
|
|||
when ::Time
|
||||
C::Date::New(value.to_f * 1000)
|
||||
when ::Class
|
||||
callable_js_constructor_for(value)
|
||||
@proxies.rb2js(value) do
|
||||
constructor = @templates.to_constructor(value)
|
||||
constructor.exposed = true
|
||||
constructor.function
|
||||
end
|
||||
when nil,Numeric,TrueClass,FalseClass, C::Value
|
||||
value
|
||||
else
|
||||
@proxies.rb2js(value, &method(:js_instance_for))
|
||||
@proxies.rb2js(value) do
|
||||
@templates.to_constructor(value.class).allocate(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -182,162 +101,6 @@ module V8
|
|||
obj.send(message, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def rubytemplate
|
||||
C::ObjectTemplate::New().tap do |t|
|
||||
setuptemplate(t)
|
||||
end
|
||||
end
|
||||
|
||||
def setuptemplate(t)
|
||||
t.SetNamedPropertyHandler(
|
||||
@named_property_getter,
|
||||
@named_property_setter,
|
||||
nil,
|
||||
nil,
|
||||
@named_property_enumerator
|
||||
)
|
||||
t.SetIndexedPropertyHandler(
|
||||
@indexed_property_getter,
|
||||
@indexed_property_setter,
|
||||
nil,
|
||||
nil,
|
||||
@indexed_property_enumerator
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class Interceptor
|
||||
def initialize(portal, access)
|
||||
@to, @access = portal, access
|
||||
end
|
||||
|
||||
def intercept(info, retval = nil, &code)
|
||||
obj = @to.rb(info.This())
|
||||
intercepts = true
|
||||
result = @to.rubyprotect do
|
||||
dontintercept = proc do
|
||||
intercepts = false
|
||||
end
|
||||
code.call(obj, dontintercept)
|
||||
end
|
||||
intercepts ? (retval || result) : C::Empty
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def Interceptor(cls)
|
||||
cls.new self, @access
|
||||
end
|
||||
|
||||
class PropertyAttributes
|
||||
attr_reader :flags
|
||||
def initialize
|
||||
@flags = 0
|
||||
end
|
||||
|
||||
def read_only
|
||||
tap do
|
||||
@flags |= V8::C::ReadOnly
|
||||
end
|
||||
end
|
||||
|
||||
def dont_enum
|
||||
tap do
|
||||
@flags |= V8::C::DontEnum
|
||||
end
|
||||
end
|
||||
|
||||
def dont_delete
|
||||
tap do
|
||||
@flags |= V8::C::DontDelete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyGetter < Interceptor
|
||||
def call(property, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.get(obj, @to.rb(property), &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertySetter < Interceptor
|
||||
def call(property, value, info)
|
||||
intercept(info, value) do |obj, dontintercept|
|
||||
@access.set(obj, @to.rb(property), @to.rb(value), &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyQuery
|
||||
def call(property, info)
|
||||
attributes = PropertyAttributes.new
|
||||
result = intercept(info) do |obj, dontintercept|
|
||||
@access.query(obj, @to.rb(property), attributes, &dontintercept)
|
||||
end
|
||||
return result == C::Empty ? result : C::Integer::New(attributes.flags)
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyEnumerator < Interceptor
|
||||
def call(info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.names(obj, &dontintercept).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyDeleter < Interceptor
|
||||
def call(property, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.delete(obj, property, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyGetter < Interceptor
|
||||
def call(index, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.iget(obj, index, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertySetter < Interceptor
|
||||
def call(index, value, info)
|
||||
intercept(info, value) do |obj, dontintercept|
|
||||
@access.iset(obj, index, @to.rb(value), &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyQuery < Interceptor
|
||||
def call(property, info)
|
||||
attributes = PropertyAttributes.new
|
||||
result = intercept(info) do |obj, dontintercept|
|
||||
@access.indices(obj, &dontintercept)
|
||||
end
|
||||
result == C::Empty ? C::Empty : C::Integer::New(attributes.flags)
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyDeleter < Interceptor
|
||||
def call(index, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.idelete(obj, index, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyEnumerator < Interceptor
|
||||
def call(info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.indices(obj, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
257
lib/v8/portal/constructor.rb
Normal file
257
lib/v8/portal/constructor.rb
Normal file
|
@ -0,0 +1,257 @@
|
|||
|
||||
module V8
|
||||
class Portal
|
||||
class Constructor
|
||||
class Interceptor
|
||||
def initialize(portal)
|
||||
@to, @access = portal, portal.access
|
||||
end
|
||||
|
||||
def intercept(info, retval = nil, &code)
|
||||
obj = @to.rb(info.This())
|
||||
intercepts = true
|
||||
result = @to.rubyprotect do
|
||||
dontintercept = proc do
|
||||
intercepts = false
|
||||
end
|
||||
code.call(obj, dontintercept)
|
||||
end
|
||||
intercepts ? (retval || result) : C::Empty
|
||||
end
|
||||
end
|
||||
|
||||
def Interceptor(cls)
|
||||
cls.new portal
|
||||
end
|
||||
|
||||
class PropertyAttributes
|
||||
attr_reader :flags
|
||||
def initialize
|
||||
@flags = 0
|
||||
end
|
||||
|
||||
def read_only
|
||||
tap do
|
||||
@flags |= V8::C::ReadOnly
|
||||
end
|
||||
end
|
||||
|
||||
def dont_enum
|
||||
tap do
|
||||
@flags |= V8::C::DontEnum
|
||||
end
|
||||
end
|
||||
|
||||
def dont_delete
|
||||
tap do
|
||||
@flags |= V8::C::DontDelete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyGetter < Interceptor
|
||||
def call(property, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.get(obj, @to.rb(property), &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertySetter < Interceptor
|
||||
def call(property, value, info)
|
||||
intercept(info, value) do |obj, dontintercept|
|
||||
@access.set(obj, @to.rb(property), @to.rb(value), &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyQuery
|
||||
def call(property, info)
|
||||
attributes = PropertyAttributes.new
|
||||
result = intercept(info) do |obj, dontintercept|
|
||||
@access.query(obj, @to.rb(property), attributes, &dontintercept)
|
||||
end
|
||||
return result == C::Empty ? result : C::Integer::New(attributes.flags)
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyEnumerator < Interceptor
|
||||
def call(info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.names(obj, &dontintercept).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NamedPropertyDeleter < Interceptor
|
||||
def call(property, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.delete(obj, property, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyGetter < Interceptor
|
||||
def call(index, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.iget(obj, index, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertySetter < Interceptor
|
||||
def call(index, value, info)
|
||||
intercept(info, value) do |obj, dontintercept|
|
||||
@access.iset(obj, index, @to.rb(value), &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyQuery < Interceptor
|
||||
def call(property, info)
|
||||
attributes = PropertyAttributes.new
|
||||
result = intercept(info) do |obj, dontintercept|
|
||||
@access.indices(obj, &dontintercept)
|
||||
end
|
||||
result == C::Empty ? C::Empty : C::Integer::New(attributes.flags)
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyDeleter < Interceptor
|
||||
def call(index, info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.idelete(obj, index, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexedPropertyEnumerator < Interceptor
|
||||
def call(info)
|
||||
intercept(info) do |obj, dontintercept|
|
||||
@access.indices(obj, &dontintercept)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :template, :function, :exposed
|
||||
alias_method :exposed?, :exposed
|
||||
|
||||
def initialize(templates, class_id)
|
||||
@exposed = false
|
||||
@class_id = class_id
|
||||
@templates = templates
|
||||
|
||||
@named_property_getter = Interceptor(NamedPropertyGetter)
|
||||
@named_property_setter = Interceptor(NamedPropertySetter)
|
||||
@named_property_query = nil
|
||||
@named_property_deleter = nil
|
||||
@named_property_enumerator = Interceptor(NamedPropertyEnumerator)
|
||||
|
||||
@indexed_property_getter = Interceptor(IndexedPropertyGetter)
|
||||
@indexed_property_setter = Interceptor(IndexedPropertySetter)
|
||||
@indexed_property_query = nil
|
||||
@indexed_property_deleter = nil
|
||||
@indexed_property_enumerator = Interceptor(IndexedPropertyEnumerator)
|
||||
|
||||
@template = C::FunctionTemplate::New(method(:invoke_unexposed))
|
||||
setuptemplate(@template.InstanceTemplate())
|
||||
cls = self.ruby_class
|
||||
superclass = cls.superclass
|
||||
if cls != ::Object && superclass != ::Object && superclass != ::Class
|
||||
@template.Inherit(templates.to_constructor(superclass).template)
|
||||
end
|
||||
if cls.name && cls.name =~ /(::)?(\w+?)$/
|
||||
template.SetClassName(C::String::NewSymbol("rb::" + $2))
|
||||
else
|
||||
template.SetClassName("Ruby")
|
||||
end
|
||||
end
|
||||
|
||||
def function
|
||||
@template.GetFunction()
|
||||
end
|
||||
|
||||
def allocate(object)
|
||||
arguments = C::Array::New(1)
|
||||
arguments.Set(0, C::External::New(object))
|
||||
function.NewInstance(arguments)
|
||||
end
|
||||
|
||||
def invoke_unexposed(arguments)
|
||||
unless arguments.Length() == 1 && arguments[0].kind_of?(C::External)
|
||||
C::ThrowException(C::Exception::Error(C::String::New("cannot call native constructor from javascript")))
|
||||
else
|
||||
object = arguments[0].Value()
|
||||
proxies.register_javascript_proxy arguments.This(), :for => object
|
||||
end
|
||||
end
|
||||
|
||||
def invoke_exposed(arguments)
|
||||
instance = nil
|
||||
if arguments.Length() > 0 && arguments[0].kind_of?(C::External)
|
||||
instance = arguments[0].Value()
|
||||
else
|
||||
rbargs = []
|
||||
for i in 0..arguments.Length() - 1
|
||||
rbargs << @templates.portal.rb(arguments[i])
|
||||
end
|
||||
instance = @templates.portal.rubyprotectraw do
|
||||
self.ruby_class.new(*rbargs)
|
||||
end
|
||||
end
|
||||
proxies.register_javascript_proxy arguments.This(), :for => instance
|
||||
rescue StandardError => e
|
||||
warn e
|
||||
end
|
||||
|
||||
def exposed=(exposed)
|
||||
if exposed && !@exposed
|
||||
@template.SetCallHandler(method(:invoke_exposed))
|
||||
#create a prototype so that this constructor also acts like a ruby object
|
||||
prototype_template = C::ObjectTemplate::New()
|
||||
setuptemplate(prototype_template)
|
||||
prototype = prototype_template.NewInstance()
|
||||
#set *that* object's prototype to an empty function so that it will look and behave like a function.
|
||||
prototype.SetPrototype(C::FunctionTemplate::New().GetFunction())
|
||||
@function = @template.GetFunction()
|
||||
@function.SetPrototype(prototype)
|
||||
@exposed = true
|
||||
elsif !exposed && @exposed
|
||||
@template.SetCallHandler(method(:invoke_unexposed))
|
||||
@exposed = false
|
||||
else
|
||||
@exposed
|
||||
end
|
||||
end
|
||||
|
||||
def ruby_class
|
||||
ObjectSpace._id2ref(@class_id)
|
||||
end
|
||||
|
||||
def proxies
|
||||
@templates.proxies
|
||||
end
|
||||
|
||||
def portal
|
||||
@templates.portal
|
||||
end
|
||||
|
||||
def setuptemplate(t)
|
||||
t.SetNamedPropertyHandler(
|
||||
@named_property_getter,
|
||||
@named_property_setter,
|
||||
nil,
|
||||
nil,
|
||||
@named_property_enumerator
|
||||
)
|
||||
t.SetIndexedPropertyHandler(
|
||||
@indexed_property_getter,
|
||||
@indexed_property_setter,
|
||||
nil,
|
||||
nil,
|
||||
@indexed_property_enumerator
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/v8/portal/templates.rb
Normal file
33
lib/v8/portal/templates.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
module V8
|
||||
class Portal
|
||||
class Templates
|
||||
|
||||
attr_reader :portal
|
||||
|
||||
def initialize(portal)
|
||||
@portal = portal
|
||||
@constructors = {}
|
||||
end
|
||||
|
||||
def to_constructor(ruby_class)
|
||||
class_id = ruby_class.object_id
|
||||
if constructor = @constructors[class_id]
|
||||
return constructor
|
||||
else
|
||||
constructor = @constructors[class_id] = Portal::Constructor.new(self, class_id)
|
||||
ObjectSpace.define_finalizer(ruby_class, bind(@constructors, :delete, class_id))
|
||||
return constructor
|
||||
end
|
||||
end
|
||||
|
||||
def proxies
|
||||
@portal.proxies
|
||||
end
|
||||
|
||||
def bind(object, method, *args)
|
||||
lambda {object.send(:method, *args)}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue