Revision: 3420 http://trac.macosforge.org/projects/ruby/changeset/3420 Author: martinlagardette@apple.com Date: 2010-02-03 12:51:32 -0800 (Wed, 03 Feb 2010) Log Message: ----------- - Adds the flip-flop operator (fixes #548) - Added some test_vm for the flip-flop too :-) Modified Paths: -------------- MacRuby/trunk/compiler.cpp MacRuby/trunk/compiler.h MacRuby/trunk/spec/frozen/language/if_spec.rb MacRuby/trunk/spec/frozen/language/precedence_spec.rb Added Paths: ----------- MacRuby/trunk/test_vm/flip.rb Modified: MacRuby/trunk/compiler.cpp =================================================================== --- MacRuby/trunk/compiler.cpp 2010-02-03 02:36:08 UTC (rev 3419) +++ MacRuby/trunk/compiler.cpp 2010-02-03 20:51:32 UTC (rev 3420) @@ -146,6 +146,8 @@ setCurrentClassFunc = NULL; getCacheFunc = NULL; debugTrapFunc = NULL; + getFFStateFunc = NULL; + setFFStateFunc = NULL; VoidTy = Type::getVoidTy(context); Int1Ty = Type::getInt1Ty(context); @@ -4518,6 +4520,21 @@ } break; + case NODE_FLIP2: + case NODE_FLIP3: + { + assert(node->nd_beg != NULL); + assert(node->nd_end != NULL); + + if (nd_type(node) == NODE_FLIP2) { + return compile_ff2(node); + } + else { + return compile_ff3(node); + } + } + break; + case NODE_BLOCK: { NODE *n = node; @@ -7299,3 +7316,149 @@ return f; } + +Value * +RoxorCompiler::compile_get_ffstate(GlobalVariable *ffstate) +{ + return new LoadInst(ffstate, "", bb); +} + +Value * +RoxorCompiler::compile_set_ffstate(Value *val, Value *expected, + GlobalVariable *ffstate, BasicBlock *mergeBB, Function *f) +{ + BasicBlock *valEqExpectedBB = BasicBlock::Create(context, + "value_equal_expected", f); + BasicBlock *valNotEqExpectedBB = BasicBlock::Create(context, + "value_not_equal_expected", f); + + Value *valueEqExpectedCond = new ICmpInst(*bb, ICmpInst::ICMP_EQ, val, + expected); + BranchInst::Create(valEqExpectedBB, valNotEqExpectedBB, + valueEqExpectedCond, bb); + + new StoreInst(trueVal, ffstate, valEqExpectedBB); + new StoreInst(falseVal, ffstate, valNotEqExpectedBB); + + BranchInst::Create(mergeBB, valEqExpectedBB); + BranchInst::Create(mergeBB, valNotEqExpectedBB); + + PHINode *pn = PHINode::Create(RubyObjTy, "", mergeBB); + pn->addIncoming(trueVal, valEqExpectedBB); + pn->addIncoming(falseVal, valNotEqExpectedBB); + + return pn; +} + +Value * +RoxorCompiler::compile_ff2(NODE *node) +{ + /* + * if ($state == true || nd_beg == true) + * $state = (nd_end == false) + * return true + * else + * return false + * end + */ + + GlobalVariable *ffstate = new GlobalVariable(*RoxorCompiler::module, + RubyObjTy, false, GlobalValue::InternalLinkage, falseVal, ""); + + Function *f = bb->getParent(); + + BasicBlock *stateNotTrueBB = BasicBlock::Create(context, + "state_not_true", f); + BasicBlock *stateOrBegIsTrueBB = BasicBlock::Create(context, + "state_or_beg_is_true", f); + BasicBlock *returnTrueBB = BasicBlock::Create(context, "return_true", f); + BasicBlock *returnFalseBB = BasicBlock::Create(context, "return_false", f); + BasicBlock *mergeBB = BasicBlock::Create(context, "merge", f); + + // `if $state == true` + Value *stateVal = compile_get_ffstate(ffstate); + Value *stateIsTrueCond = new ICmpInst(*bb, ICmpInst::ICMP_EQ, stateVal, + trueVal); + BranchInst::Create(stateOrBegIsTrueBB, stateNotTrueBB, stateIsTrueCond, + bb); + + // `or if nd_beg == true` + bb = stateNotTrueBB; + Value *beginValue = compile_node(node->nd_beg); + stateNotTrueBB = bb; + Value *begIsTrueCond = new ICmpInst(*bb, ICmpInst::ICMP_EQ, + beginValue, trueVal); + BranchInst::Create(stateOrBegIsTrueBB, returnFalseBB, begIsTrueCond, bb); + + // `$state = (nd_end == false)` + bb = stateOrBegIsTrueBB; + Value *endValue = compile_node(node->nd_end); + stateOrBegIsTrueBB = bb; + compile_set_ffstate(endValue, falseVal, ffstate, returnTrueBB, f); + + BranchInst::Create(mergeBB, returnTrueBB); + BranchInst::Create(mergeBB, returnFalseBB); + + bb = mergeBB; + PHINode *pn = PHINode::Create(RubyObjTy, "", mergeBB); + pn->addIncoming(trueVal, returnTrueBB); + pn->addIncoming(falseVal, returnFalseBB); + + return pn; +} + +Value * +RoxorCompiler::compile_ff3(NODE *node) +{ + /* + * if ($state == true) + * $state = (nd_end == false) + * return true + * else + * $state = (nd_beg == true) + * return $state + * end + */ + + GlobalVariable *ffstate = new GlobalVariable(*RoxorCompiler::module, + RubyObjTy, false, GlobalValue::InternalLinkage, falseVal, ""); + + Function *f = bb->getParent(); + + BasicBlock *stateIsTrueBB = BasicBlock::Create(context, "state_is_true", + f); + BasicBlock *stateIsFalseBB = BasicBlock::Create(context, "state_is_false", + f); + BasicBlock *returnTrueBB = BasicBlock::Create(context, "return_true", f); + BasicBlock *returnStateBB = BasicBlock::Create(context, "return_state", f); + BasicBlock *mergeBB = BasicBlock::Create(context, "merge", f); + + + // `if $state == true` + Value *stateVal = compile_get_ffstate(ffstate); + Value *stateIsTrueCond = new ICmpInst(*bb, ICmpInst::ICMP_EQ, stateVal, + trueVal); + BranchInst::Create(stateIsTrueBB, stateIsFalseBB, stateIsTrueCond, bb); + + // `$state = (nd_end == false)` + bb = stateIsTrueBB; + Value *endValue = compile_node(node->nd_end); + stateIsTrueBB = bb; + compile_set_ffstate(endValue, falseVal, ffstate, returnTrueBB, f); + + // `$state = (nd_beg == true)` + bb = stateIsFalseBB; + Value *beginValue = compile_node(node->nd_beg); + stateIsFalseBB = bb; + stateVal = compile_set_ffstate(beginValue, trueVal, ffstate, returnStateBB, f); + + BranchInst::Create(mergeBB, returnTrueBB); + BranchInst::Create(mergeBB, returnStateBB); + + bb = mergeBB; + PHINode *pn = PHINode::Create(RubyObjTy, "", mergeBB); + pn->addIncoming(trueVal, returnTrueBB); + pn->addIncoming(stateVal, returnStateBB); + + return pn; +} Modified: MacRuby/trunk/compiler.h =================================================================== --- MacRuby/trunk/compiler.h 2010-02-03 02:36:08 UTC (rev 3419) +++ MacRuby/trunk/compiler.h 2010-02-03 20:51:32 UTC (rev 3420) @@ -193,6 +193,9 @@ Function *setCurrentClassFunc; Function *getCacheFunc; Function *debugTrapFunc; + // flip-flop + Function *getFFStateFunc; + Function *setFFStateFunc; Constant *zeroVal; Constant *oneVal; @@ -357,6 +360,11 @@ SEL mid_to_sel(ID mid, int arity); + Value *compile_get_ffstate(GlobalVariable *ffstate); + Value *compile_set_ffstate(Value *val, Value *expected, GlobalVariable *ffstate, BasicBlock *mergeBB, Function *f); + Value *compile_ff2(NODE *current_node); + Value *compile_ff3(NODE *current_node); + void attach_current_line_metadata(Instruction *insn); }; Modified: MacRuby/trunk/spec/frozen/language/if_spec.rb =================================================================== --- MacRuby/trunk/spec/frozen/language/if_spec.rb 2010-02-03 02:36:08 UTC (rev 3419) +++ MacRuby/trunk/spec/frozen/language/if_spec.rb 2010-02-03 20:51:32 UTC (rev 3420) @@ -213,37 +213,35 @@ if false then 123; else 456; end.should == 456 end - # MacRuby TODO: causes a compile error - # - # describe "with a boolean range ('flip-flop' operator)" do - # before :each do - # ScratchPad.record [] - # end - # - # after :each do - # ScratchPad.clear - # end - # - # it "mimics an awk conditional with a single-element inclusive-end range" do - # 10.times { |i| ScratchPad << i if (i == 4)..(i == 4) } - # ScratchPad.recorded.should == [4] - # end - # - # it "mimics an awk conditional with a many-element inclusive-end range" do - # 10.times { |i| ScratchPad << i if (i == 4)..(i == 7) } - # ScratchPad.recorded.should == [4, 5, 6, 7] - # end - # - # it "mimics a sed conditional with a zero-element exclusive-end range" do - # 10.times { |i| ScratchPad << i if (i == 4)...(i == 4) } - # ScratchPad.recorded.should == [4, 5, 6, 7, 8, 9] - # end - # - # it "mimics a sed conditional with a many-element exclusive-end range" do - # 10.times { |i| ScratchPad << i if (i == 4)...(i == 5) } - # ScratchPad.recorded.should == [4, 5] - # end - # end + describe "with a boolean range ('flip-flop' operator)" do + before :each do + ScratchPad.record [] + end + + after :each do + ScratchPad.clear + end + + it "mimics an awk conditional with a single-element inclusive-end range" do + 10.times { |i| ScratchPad << i if (i == 4)..(i == 4) } + ScratchPad.recorded.should == [4] + end + + it "mimics an awk conditional with a many-element inclusive-end range" do + 10.times { |i| ScratchPad << i if (i == 4)..(i == 7) } + ScratchPad.recorded.should == [4, 5, 6, 7] + end + + it "mimics a sed conditional with a zero-element exclusive-end range" do + 10.times { |i| ScratchPad << i if (i == 4)...(i == 4) } + ScratchPad.recorded.should == [4, 5, 6, 7, 8, 9] + end + + it "mimics a sed conditional with a many-element exclusive-end range" do + 10.times { |i| ScratchPad << i if (i == 4)...(i == 5) } + ScratchPad.recorded.should == [4, 5] + end + end end describe "The postfix if form" do Modified: MacRuby/trunk/spec/frozen/language/precedence_spec.rb =================================================================== --- MacRuby/trunk/spec/frozen/language/precedence_spec.rb 2010-02-03 02:36:08 UTC (rev 3419) +++ MacRuby/trunk/spec/frozen/language/precedence_spec.rb 2010-02-03 20:51:32 UTC (rev 3420) @@ -330,15 +330,10 @@ lambda { eval("1...2...3") }.should raise_error(SyntaxError) end -# XXX: this is commented now due to a bug in compiler, which cannot -# distinguish between range and flip-flop operator so far. zenspider is -# currently working on a new lexer, which will be able to do that. -# As soon as it's done, these piece should be reenabled. -# -# it ".. ... have higher precedence than ? :" do -# (1..2 ? 3 : 4).should == 3 -# (1...2 ? 3 : 4).should == 3 -# end + it ".. ... have higher precedence than ? :" do + (1..2 ? 3 : 4).should == 3 + (1...2 ? 3 : 4).should == 3 + end it "? : is right-associative" do (true ? 2 : 3 ? 4 : 5).should == 2 Added: MacRuby/trunk/test_vm/flip.rb =================================================================== --- MacRuby/trunk/test_vm/flip.rb (rev 0) +++ MacRuby/trunk/test_vm/flip.rb 2010-02-03 20:51:32 UTC (rev 3420) @@ -0,0 +1,107 @@ +# flip-flop 2 should preserve its state accross different calls to the block it is in +# Test from Tomáš Matoušek of IronRuby +assert "0", %{ + def y *a; yield *a; end + $a = 0 + def test + $p = proc do |b, e| + if b..e + $a += 1 + else + $a -= 1 + end + end + + y false, &$p + y true, true, &$p + y false, &$p + y true, false, &$p + end + test + p $a +} + +# flip-flop 3 should preserve its state accross different calls to the block it is in +# Test from Tomáš Matoušek of IronRuby +assert "2", %{ + def y *a; yield *a; end + $a = 0 + def test + $p = proc do |b, e| + if b...e + $a += 1 + else + $a -= 1 + end + end + + y false, &$p + y true, true, &$p + y false, &$p + y true, false, &$p + end + test + p $a +} + +# General flip-flop test: +# Not only does it test the general use of a flip-flop operator, +# it also tests the `begin` and `end` block are only evaluated if needed +# Test from Tomáš Matoušek of IronRuby +assert "bfbetetetbetbfbfbf", %{ + F = false + T = true + x = X = '!' + B = [F,T,x,x,x,T,x,F,F] + E = [x,x,F,F,T,x,T,x,x] + + def b; step('b',B); end + def e; step('e',E); end + + def step name, value + r = value[$j] + putc name + $j += 1 + $continue = !r.nil? + r == X ? raise : r + end + + $j = 0 + $continue = true + while $continue + putc (b..e ? 't' : 'f') + end +} + +# Recursive flip-flop test +assert "dadbaddb", %{ + def y *a; yield *a; end + def test + $p = proc { |b, e| + if b..e + if e..b + putc 'a' + else + putc 'b' + end + else + if e..b + putc 'c' + else + putc 'd' + end + end + } + + y false, &$p + y true, true, &$p + y false, &$p + y true, false, &$p + y true, true, &$p + y false, &$p + y false, &$p + y true, false, &$p + puts + end + test +}