[Rubydotnet-developer] Getting necessary metadata on Ruby classes for externalization...
Status: Alpha
Brought to you by:
thomas
|
From: Richard K. <ri...@in...> - 2003-07-19 03:54:33
|
All,
I did not quite know where to post this, and I hope this list is ok...
I have been thinking a bit on how to allow a developer to annotate
their class's methods with parameter types when used externally by
statically typed languages (Java, C#, SOAP). I want to present a
syntax, and at the bottom have the code to back it up.
Given the following class:
class Account
def Account.open(first, last)
end
def close
end
def add(money)
end
def remove(money)
end
def transfer(money, account)
end
end
And assuming you wanted to make that accessible for statically typed
systems you would just do this:
require 'extern'
class Account
extern :Account.open, :return[Account], :first[String], :last[String]
def Account.open(first, last)
end
extern :close
def close
end
extern :add, :money[Float]
def add(money)
end
extern :remove, :money[Float]
def remove(money)
end
extern :transfer, :money[Float], :account[Account]
def transfer(money, account)
end
end
So, for each method you want to externalize you just include the
'extern' call.
Usage: extern <method symbol>, [ [<:return[Classname]>],
<:param[Classname]>, ... ]
To reflect on this metadata you do:
Account.each_externalized_method do | method |
puts method.to_s
end
Which outputs:
Account Account.open(String first, String last)
NilClass close()
NilClass add(Float money)
NilClass remove(Float money)
NilClass transfer(Float money, Account account)
You can, of course, inspect the method (ExternalMethodDefinition
instance) instead of printing it out. So this creates a runtime
structure to store type information about methods, which again is
useful is you want to bridge .NET to Ruby at the level of having a CLR
type subclass a Ruby class, or generate a CLR type that represents a
Ruby class.
Of course, since Ruby's classes are open, you can define this extern
metadata on existing classes:
class ThreadGroup
extern :ThreadGroup.new, :return[ThreadGroup]
extern :add, :return[ThreadGroup], :thread[Thread]
extern :list, :return[Array]
end
So what do folks think about the syntax?
-rich
PS...here is the magic code that makes this syntax work:
________ BEGIN 'extern.rb' _________
class ExternalMethodDefinition
attr_accessor :klass, :name, :return_type
Parameter = Struct.new(:name, :type)
@@definitions = Hash.new([])
def self.[](klass)
@@definitions[klass]
end
def initialize(klass, name, *parameters)
list = @@definitions[klass]
unless list
list = []
@@defintions[klass] = list
end
list << self
@klass = klass
@name = name.to_s
@name.gsub!(/__/, '.')
@parameters = []
parameters.each do |param|
if param.name=="return"
@return_type = param.type
else
@parameters << param
end
end
@return_type = NilClass unless @return_type
end
def add_parameter(name, type)
unless type.kind_of?(Class)
raise "Parameter #{name}'s type on method '#{@name}' of class
'#{@klass}' must be a Ruby class"
end
if name == :return
@return_type = type
else
@parameters << Parameter.new(name.to_s, type)
end
end
def each_parameter
@parameters.each {|param| yield param}
end
def to_s
params = (@parameters.collect {|param| param.type.to_s+'
'+param.name}).join(', ')
return "#{@return_type.to_s} #{@name}(#{params})"
end
end
class Symbol
def [](klass=nil)
if klass
ExternalMethodDefinition::Parameter.new(self.to_s, klass)
else
(self.to_s+"__[]").intern
end
end
def method_missing(method, *args, &block)
if (?A..?Z).member?(self.to_s[0])
return (self.to_s+"__"+method.to_s).intern
end
super
end
end
class Module
def extern(method, *parameters)
ExternalMethodDefinition.new(self, method, *parameters)
end
def each_externalized_method
ExternalMethodDefinition[self].each { |exdef| yield exdef }
end
end
------ END 'extern.rb' ------
|