[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' ------ |