デコレータRubyの作り方
長いけど例によって基本コピペ。相変わらず一度動けばおkって感じで結構ひどいですね。
大枠は
- node.hで
- RNodeにnd_annotationsていうの追加して
struct RNode *nd_annotations;
- RNodeにnd_annotationsていうの追加して
- parse.yで
- @{...} をデコレータ?とみなして
annotation : '@' tLBRACE method_call '}'
- 全ステートメントでデコレータ?を受け入れるようにして
stmt : annotation stmt
- @{...} をデコレータ?とみなして
- compile.cで
- デコレータのあるメソッド定義は、メソッド定義後にデコレータを「self」と「定義されたメソッド名」を引数に加えて呼び出す(例えば"def do_something"の前に@{ReturnType(String)}ってあったらdo_something定義後にReturnType(self, 'do_something', String)というメソッド呼び出しを追加)
- prelude.rbで
- デフォルトのデコレータ(ReturnType, ParamTypes)を実装
見ての通り最初はデコレータじゃなくてアノテーションにしたくて結局諦めました。メソッドをデコレートするだけならcompile.cに書いてある処理をparse.yに持ってきてもう少し単純にできたはず。
Common subdirectories: ruby-1.9.3-p0/bcc32 and annotated_ruby/bcc32 Common subdirectories: ruby-1.9.3-p0/benchmark and annotated_ruby/benchmark Common subdirectories: ruby-1.9.3-p0/bin and annotated_ruby/bin Common subdirectories: ruby-1.9.3-p0/bootstraptest and annotated_ruby/bootstraptest diff -c ruby-1.9.3-p0/compile.c annotated_ruby/compile.c *** ruby-1.9.3-p0/compile.c 2011-07-10 12:11:52.000000000 +0900 --- annotated_ruby/compile.c 2011-12-24 10:02:33.000000000 +0900 *************** *** 265,270 **** --- 265,274 ---- (debug_compile("== " desc "\n", \ iseq_compile_each(iseq, (anchor), (node), (poped)))) + #define COMPILE_ANNOTATIONS(anchor, desc, node, recv, target) \ + (debug_compile("== " desc "\n", \ + iseq_compile_annotations(iseq, (anchor), (node), (recv), (target)))) + #define OPERAND_AT(insn, idx) \ (((INSN*)(insn))->operands[(idx)]) *************** *** 332,337 **** --- 336,342 ---- static ADJUST *new_adjust_body(rb_iseq_t *iseq, LABEL *label, int line); static int iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *anchor, NODE * n, int); + static int iseq_compile_annotations(rb_iseq_t *iseq, LINK_ANCHOR *anchor, NODE * n, NODE * recv, NODE * target); static int iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *anchor); static int iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor); static int iseq_insns_unification(rb_iseq_t *iseq, LINK_ANCHOR *anchor); *************** *** 4618,4623 **** --- 4623,4630 ---- ADD_INSN1(ret, nd_line(node), putobject, ID2SYM(node->nd_mid)); ADD_INSN1(ret, nd_line(node), putiseq, iseqval); ADD_SEND (ret, nd_line(node), ID2SYM(id_core_define_method), INT2FIX(3)); + NODE *method_name = NEW_STR(rb_sprintf("%s", rb_id2name(node->nd_mid))); + COMPILE_ANNOTATIONS(ret, "annotations", node->nd_annotations, NULL, method_name); if (poped) { ADD_INSN(ret, nd_line(node), pop); *************** *** 4638,4643 **** --- 4645,4652 ---- ADD_INSN1(ret, nd_line(node), putobject, ID2SYM(node->nd_mid)); ADD_INSN1(ret, nd_line(node), putiseq, iseqval); ADD_SEND (ret, nd_line(node), ID2SYM(id_core_define_singleton_method), INT2FIX(3)); + NODE *method_name = NEW_STR(rb_sprintf("%s", rb_id2name(node->nd_mid))); + COMPILE_ANNOTATIONS(ret, "annotations", node->nd_annotations, node->nd_recv, method_name); if (poped) { ADD_INSN(ret, nd_line(node), pop); *************** *** 4689,4694 **** --- 4698,4706 ---- ADD_INSN3(ret, nd_line(node), defineclass, ID2SYM(node->nd_cpath->nd_mid), iseqval, INT2FIX(noscope ? 3 : 0)); + NODE *class_name = NEW_STR(rb_sprintf("%s", rb_id2name(node->nd_cpath->nd_mid))); + COMPILE_ANNOTATIONS(ret, "annotations", node->nd_annotations, NULL, class_name); + if (poped) { ADD_INSN(ret, nd_line(node), pop); } *************** *** 5039,5044 **** --- 5051,5117 ---- return COMPILE_OK; } + /* concat two lists */ // copy from parse.y + static NODE* + list_concat_gen(NODE *head, NODE *tail) + { + NODE *last; + + if (head->nd_next) { + last = head->nd_next->nd_end; + } + else { + last = head; + } + + head->nd_alen += tail->nd_alen; + last->nd_next = tail; + if (tail->nd_next) { + head->nd_next->nd_end = tail->nd_next->nd_end; + } + else { + head->nd_next->nd_end = tail; + } + + return head; + } + static int + iseq_compile_annotations(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * vals, NODE * r, NODE *target) + { + if (vals != NULL) { + do { + NODE *val = vals->nd_head; + // copy from NODE_FCALL/NODE_VCALL + DECL_ANCHOR(recv); + DECL_ANCHOR(args); + VALUE argc; + VALUE flag = 0; + VALUE parent_block = iseq->compile_data->current_block; + iseq->compile_data->current_block = Qfalse; + + INIT_ANCHOR(recv); + INIT_ANCHOR(args); + ADD_CALL_RECEIVER(recv, nd_line(val)); + + if (val->nd_args == NULL) { + val->nd_args = NEW_LIST(target); + } + else { + val->nd_args = list_concat_gen(NEW_LIST(target), val->nd_args); + } + val->nd_args = list_concat_gen(NEW_LIST(r), val->nd_args); + + argc = setup_args(iseq, args, val->nd_args, &flag); + ADD_SEQ(ret, recv); + ADD_SEQ(ret, args); + flag |= VM_CALL_FCALL_BIT; + ADD_SEND_R(ret, nd_line(val), ID2SYM(val->nd_mid), + argc, parent_block, LONG2FIX(flag)); + ADD_INSN(ret, nd_line(val), pop); + } while ((vals = vals->nd_next) != NULL); + } + } + /***************************/ /* instruction information */ /***************************/ Common subdirectories: ruby-1.9.3-p0/cygwin and annotated_ruby/cygwin Common subdirectories: ruby-1.9.3-p0/defs and annotated_ruby/defs Common subdirectories: ruby-1.9.3-p0/doc and annotated_ruby/doc Common subdirectories: ruby-1.9.3-p0/enc and annotated_ruby/enc Common subdirectories: ruby-1.9.3-p0/ext and annotated_ruby/ext Common subdirectories: ruby-1.9.3-p0/include and annotated_ruby/include Common subdirectories: ruby-1.9.3-p0/lib and annotated_ruby/lib Common subdirectories: ruby-1.9.3-p0/man and annotated_ruby/man Common subdirectories: ruby-1.9.3-p0/misc and annotated_ruby/misc Common subdirectories: ruby-1.9.3-p0/missing and annotated_ruby/missing diff -c ruby-1.9.3-p0/node.h annotated_ruby/node.h *** ruby-1.9.3-p0/node.h 2011-06-18 07:43:38.000000000 +0900 --- annotated_ruby/node.h 2011-12-24 10:02:49.000000000 +0900 *************** *** 235,240 **** --- 235,241 ---- typedef struct RNode { VALUE flags; VALUE nd_reserved; /* ex nd_file */ + struct RNode *nd_annotations; union { struct RNode *node; ID id; diff -c ruby-1.9.3-p0/parse.y annotated_ruby/parse.y *** ruby-1.9.3-p0/parse.y 2011-09-24 14:21:19.000000000 +0900 --- annotated_ruby/parse.y 2011-12-24 10:02:56.000000000 +0900 *************** *** 703,708 **** --- 703,709 ---- %type <node> mlhs mlhs_head mlhs_basic mlhs_item mlhs_node mlhs_post mlhs_inner %type <id> fsym keyword_variable user_variable sym symbol operation operation2 operation3 %type <id> cname fname op f_rest_arg f_block_arg opt_f_block_arg f_norm_arg f_bad_arg + %type <node> annotation /*%%%*/ /*% %type <val> program reswords then do dot_or_colon *************** *** 952,958 **** } ; ! stmt : keyword_alias fitem {lex_state = EXPR_FNAME;} fitem { /*%%%*/ $$ = NEW_ALIAS($2, $4); --- 953,973 ---- } ; ! stmt : annotation stmt ! { ! /*%%%*/ ! $$ = $2; ! if ($$->nd_annotations != NULL) { ! $$->nd_annotations = list_append($$->nd_annotations, $1); ! } ! else { ! $$->nd_annotations = NEW_LIST($1); ! } ! /*% ! $$ = $2; ! %*/ ! } ! | keyword_alias fitem {lex_state = EXPR_FNAME;} fitem { /*%%%*/ $$ = NEW_ALIAS($2, $4); *************** *** 4813,4818 **** --- 4828,4851 ---- } ; + annotation : '@' tLBRACE method_call '}' + { + /*%%%*/ + $$ = $3; + /*% + //$$ = dispatch2(assoc_new, $1, $2); + %*/ + } + | annotation term + { + /*%%%*/ + $$ = $1; + /*% + //$$ = dispatch2(assoc_new, $1, $2); + %*/ + } + ; + operation : tIDENTIFIER | tCONSTANT | tFID diff -c ruby-1.9.3-p0/prelude.rb annotated_ruby/prelude.rb *** ruby-1.9.3-p0/prelude.rb 2011-05-15 20:55:52.000000000 +0900 --- annotated_ruby/prelude.rb 2011-12-24 10:03:18.000000000 +0900 *************** *** 29,31 **** --- 29,117 ---- } end end + + module Annotations + class AnnotationTypeError < RuntimeError; end + + def annotation_params(receiver, method_name) + method = (receiver.nil? ? self : class <<self; self end).instance_method method_name + params = (0...method.arity.abs).to_a.map{|i| "v#{i}"}.to_a + params[-1] = "*#{params[-1]}" if method.arity < 0 + params + end + + def annotation_chain(receiver, method_name, function_name) + return if method_name =~ /.*_with(out)?_.*/ + + (receiver.nil? ? self : class <<self; self end).instance_eval do + alias_method "#{method_name}_without_#{function_name}", method_name + alias_method method_name, "#{method_name}_with_#{function_name}" + end + end + + def ReturnType(receiver, method_name, type) + params = annotation_params(receiver, method_name).join ',' + + type_check = + case type + when Array + type.map do |t| + "raise AnnotationTypeError.new('No Method: #{t}') " \ + "unless ret.respond_to? #{t.inspect}" + end.join("\n") + when Symbol + "raise AnnotationTypeError.new('No Method: #{type}') " \ + "unless ret.respond_to? #{type.inspect}" + else + "raise AnnotationTypeError.new('Invalid Class: #{type}') " \ + "unless ret.is_a? #{type}" + end + + eval <<-EOS + def #{method_name}_with_returntype(#{params}) + ret = #{method_name}_without_returntype(#{params}) + #{type_check} + ret + end + EOS + + annotation_chain receiver, method_name, 'returntype' + end + + def ParamTypes(receiver, method_name, *types) + params = annotation_params receiver, method_name + type_check = [] + params.zip(types) do |p, t| + p = $1 if p =~ /^\*(.+)/ + type_check.push( + case t + when Symbol + "raise AnnotationTypeError.new('No Method: #{t}') " \ + "unless #{p}.respond_to? #{t.inspect}" + when Array + t.map do |tt| + "raise AnnotationTypeError.new('No Method: #{tt}') " \ + "unless #{p}.respond_to? #{tt.inspect}" + end + when Class + "raise AnnotationTypeError.new('Invalid Class: #{t}') " \ + "unless #{p}.is_a? #{t}" + end + ) + end + + eval <<-EOS + def #{method_name}_with_paramtypes(#{params.join(',')}) + #{type_check.flatten.join("\n")} + #{method_name}_without_paramtypes(#{params.join(',')}) + end + EOS + + annotation_chain receiver, method_name, 'paramtypes' + end + end + + class Object + include Annotations + end + Common subdirectories: ruby-1.9.3-p0/sample and annotated_ruby/sample Common subdirectories: ruby-1.9.3-p0/spec and annotated_ruby/spec Common subdirectories: ruby-1.9.3-p0/symbian and annotated_ruby/symbian Common subdirectories: ruby-1.9.3-p0/template and annotated_ruby/template Common subdirectories: ruby-1.9.3-p0/test and annotated_ruby/test Common subdirectories: ruby-1.9.3-p0/tool and annotated_ruby/tool Common subdirectories: ruby-1.9.3-p0/win32 and annotated_ruby/win32