#!/usr/bin/ruby -w # proof of concept: static typed variables and functions # http://www.xs4all.nl/~hipster # TODO # - problem: how to pass params into the self.class.const_get("").call, # the receiving block (specified with the tdef) should have the following # form: tdef [:f, String, [[:p1, String]]] do |p1| body; end # Resurrect the original Binding instance? Use class variables? # - a more readable declaration syntax # - void parameter # - parametrize attribute reader/writer generation # passing multiple procs for pre/postconditions: # def mcall(*fs) # fs.each{ |f| f.call } # end # mcall lambda { p 1 }, lambda { p 2 }, lambda { p 3 } # or: # mcall proc { pre }, proc { body }, proc { post } # default values for pre/post: # mcall proc { body }, proc { pre } = { true }, proc { post } = { true } class TypeMismatch < StandardError def initialize msg super "Type mismatch: " + msg end end class Module # usage: tattr [:name, Type], ... def tattr(*attrs) for attr in attrs name = attr[0].id2name type = attr[1] # generate attribute getter/setter and type checks class_eval %! def #{name} @#{name} end def #{name}= value unless value.is_a? #{type} fail TypeMismatch.new( "attr #{self.to_s}::#{name}: expected #{type}, got " + value.class.to_s) end @#{name} = value end ! end end # usage: tdef [:fname, rtype, [[:param, Type], ...]] do body; end # note: functions must explicitly return a type, e.g. nil def tdef signature, &body fname = signature[0].id2name rtype = signature[1].nil? ? NilClass : signature[1] const_set "#{self}_#{fname}", body code = "def #{fname} " # generate parameter type checks parnames = [] # for generating .call() later on if signature[2] code += signature[2].collect{ |par| parnames << par[0].id2name par[0].id2name }.join(", ") code += "\n" + signature[2].collect{ |p| %! unless #{p[0].id2name}.is_a? #{p[1]}\n! + %! fail TypeMismatch.new("func #{self}::#{fname} ! + %!param #{p[0].id2name}: expected #{p[1].to_s}, got " + ! + %!#{p[0].id2name}.class.to_s)\n! + %! end! }.join("\n") else # void param: f(void) code += "(*args)" #\n fail 'nonvoidbarfout' if args" end # generate function body and return type check code += %!\n! + #%! puts "#{fname} precondition"\n! + %! begin\n! + %! _result = self.class.const_get("_#{self}_#{fname}").! + %!call(#{parnames.join(", ")})\n! + %! ensure\n! + #%! puts "#{fname} postcondition"\n! + %! end\n! + %! unless _result.is_a? #{rtype}\n! + %! fail TypeMismatch.new("func #{self.to_s}::#{fname} retval: ! + %!expected #{rtype.to_s}, got " + _result.class.to_s)\n! + %! end\n! + %! _result\n! + %!end! puts code class_eval code puts "-" * 75 end end class Foo # tattr: typed attribute # C: String str, Fixnum int, Foo nephew tattr [:str, String], [:int, Fixnum], [:nephew, Foo] # tdef: typed def # C: String f1() tdef([:f1, String]) { "f1.body" } # C: String f2(String p1, Fixnum p2) tdef [:f2, String, [[:p1, String], [:p2, Fixnum]]] do "f2.body"; end # C: String f3(String txt, Fixnum iter) tdef [:f3, String, [[:txt, String], [:iter, Fixnum]]] do #iter * txt # FIXME barfs, params aren't passed in "f3.body" end # C: String f4(void) tdef [:f4, String] do end # barfs on purpose # C: void f(void) tdef [:f5, nil] do end # ne pas de barf def foo nephew.bar # relies on correct type of nephew end def bar puts "hi from #{self.class}" end def typed_funcs p f1 p f2("hopsa", 1) p f2("baz", Foo.new) rescue excp # Foo should be Fixnum p f3("abc", 4) p f3(Bar.new, 4) rescue excp # Bar should be String #p f4 "aap" FIXME `f4': wrong # of arguments(1 for 0) (ArgumentError) p f4 rescue excp # return value should be Array p f5 end end class Bar; end class Qux < Foo; end def excp puts $! end z = Foo.new z.str = "hopsa" z.str = 42 rescue excp # should be String z.int = 42 z.int = "42" rescue excp # should be Fixnum z.nephew = Foo.new z.nephew = Qux.new # fine, as Qux < Foo z.nephew = Bar.new rescue excp # should be Foo z.foo z.typed_funcs