Permalink
Browse files

- Adds the flip-flop operator (fixes #548)

 - Added some test_vm for the flip-flop too :-)



git-svn-id: http://svn.macosforge.org/repository/ruby/MacRuby/trunk@3420 23306eb0-4c56-4727-a40e-e92c0eb68959
  • Loading branch information...
1 parent c177343 commit c437b6200739e7da6ed24f52b738ec494786d491 Thibault Martin-Lagardette committed Feb 3, 2010
Showing with 311 additions and 40 deletions.
  1. +163 −0 compiler.cpp
  2. +8 −0 compiler.h
  3. +29 −31 spec/frozen/language/if_spec.rb
  4. +4 −9 spec/frozen/language/precedence_spec.rb
  5. +107 −0 test_vm/flip.rb
View
@@ -146,6 +146,8 @@ RoxorCompiler::RoxorCompiler(bool _debug_mode)
setCurrentClassFunc = NULL;
getCacheFunc = NULL;
debugTrapFunc = NULL;
+ getFFStateFunc = NULL;
+ setFFStateFunc = NULL;
VoidTy = Type::getVoidTy(context);
Int1Ty = Type::getInt1Ty(context);
@@ -4518,6 +4520,21 @@ RoxorCompiler::compile_node(NODE *node)
}
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 @@ RoxorCompiler::compile_to_ocval_convertor(const char *type)
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;
+}
View
@@ -193,6 +193,9 @@ class RoxorCompiler {
Function *setCurrentClassFunc;
Function *getCacheFunc;
Function *debugTrapFunc;
+ // flip-flop
+ Function *getFFStateFunc;
+ Function *setFFStateFunc;
Constant *zeroVal;
Constant *oneVal;
@@ -357,6 +360,11 @@ class RoxorCompiler {
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);
};
@@ -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
@@ -330,15 +330,10 @@ class FalseClass; undef_method :=~; end
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
View
@@ -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
+}

0 comments on commit c437b62

Please sign in to comment.