require 'jruby' require 'java' require 'test/minirunit' StandardASMCompiler = org.jruby.compiler.impl.StandardASMCompiler ASTCompiler = org.jruby.compiler.ASTCompiler ASTInspector = org.jruby.compiler.ASTInspector Block = org.jruby.runtime.Block IRubyObject = org.jruby.runtime.builtin.IRubyObject def silence_warnings verb = $VERBOSE $VERBOSE = nil yield ensure $VERBOSE = verb end def compile_to_class(src) node = JRuby.parse(src, "testCompiler#{src.object_id}", false) filename = node.position.file classname = filename.sub("/", ".").sub("\\", ".").sub(".rb", "") inspector = ASTInspector.new inspector.inspect(node) context = StandardASMCompiler.new(classname, filename) compiler = ASTCompiler.new compiler.compileRoot(node, context, inspector) context.loadClass(JRuby.runtime.getJRubyClassLoader) end def compile_and_run(src) cls = compile_to_class(src) cls.new_instance.load(JRuby.runtime.current_context, JRuby.runtime.top_self, IRubyObject[0].new, Block::NULL_BLOCK) end asgnFixnumCode = "a = 5; a" asgnFloatCode = "a = 5.5; a" asgnStringCode = "a = 'hello'; a" asgnDStringCode = 'a = "hello#{42}"; a' asgnEvStringCode = 'a = "hello#{1+42}"; a' arrayCode = "['hello', 5, ['foo', 6]]" fcallCode = "foo('bar')" callCode = "'bar'.capitalize" ifCode = "if 1 == 1; 2; else; 3; end" unlessCode = "unless 1 == 1; 2; else; 3; end" whileCode = "a = 0; while a < 5; a = a + 2; end; a" whileNoBody = "$foo = false; def flip; $foo = !$foo; $foo; end; while flip; end" andCode = "1 && 2" andShortCode = "nil && 3" beginCode = "begin; a = 4; end; a" regexpLiteral = "/foo/" match1 = "/foo/ =~ 'foo'" match2 = "'foo' =~ /foo/" match3 = ":aaa =~ /foo/" iterBasic = "foo2('baz') { 4 }" defBasic = "def foo3(arg); arg + '2'; end" test_no_exception { compile_to_class(asgnFixnumCode); } # clone this since we're generating classnames based on object_id above test_equal(5, compile_and_run(asgnFixnumCode.clone)) test_equal(5.5, compile_and_run(asgnFloatCode)) test_equal('hello', compile_and_run(asgnStringCode)) test_equal('hello42', compile_and_run(asgnDStringCode)) test_equal('hello43', compile_and_run(asgnEvStringCode)) test_equal(/foo/, compile_and_run(regexpLiteral)) test_equal(nil, compile_and_run('$2')) test_equal(0, compile_and_run(match1)) test_equal(0, compile_and_run(match2)) test_equal(false, compile_and_run(match3)) def foo(arg) arg + '2' end def foo2(arg) arg end test_equal(['hello', 5, ['foo', 6]], compile_and_run(arrayCode)) test_equal('bar2', compile_and_run(fcallCode)) test_equal('Bar', compile_and_run(callCode)) test_equal(2, compile_and_run(ifCode)) test_equal(2, compile_and_run("2 if true")) test_equal(3, compile_and_run(unlessCode)) test_equal(3, compile_and_run("3 unless false")) test_equal(6, compile_and_run(whileCode)) test_equal('baz', compile_and_run(iterBasic)) compile_and_run(defBasic) test_equal('hello2', foo3('hello')) test_equal(2, compile_and_run(andCode)) test_equal(nil, compile_and_run(andShortCode)); test_equal(4, compile_and_run(beginCode)); class << Object alias :old_method_added :method_added def method_added(sym) $method_added = sym old_method_added(sym) end end test_no_exception { compile_and_run("alias :to_string :to_s") to_string test_equal(:to_string, $method_added) } # Some complicated block var stuff blocksCode = <<-EOS def a yield 3 end arr = [] x = 1 1.times { y = 2 arr << x x = 3 a { arr << y y = 4 arr << x x = 5 } arr << y arr << x x = 6 } arr << x EOS test_equal([1,2,3,4,5,6], compile_and_run(blocksCode)) yieldInBlock = < :bar}, compile_and_run("{:foo => :bar}")) test_equal(1..2, compile_and_run("1..2")) # FIXME: These tests aren't quite right..only the first one should allow self.a to be accessed # The other two should fail because the a accessor is private at top level. Only attr assigns # should be made into "variable" call types and allowed through. I need a better way to # test these, and the too-permissive visibility should be fixed. test_equal(1, compile_and_run("def a=(x); 2; end; self.a = 1")) test_equal(1, compile_and_run("def a; 1; end; def a=(arg); fail; end; self.a ||= 2")) test_equal([1, 1], compile_and_run("def a; @a; end; def a=(arg); @a = arg; 4; end; x = self.a ||= 1; [x, self.a]")) test_equal(nil, compile_and_run("def a; nil; end; def a=(arg); fail; end; self.a &&= 2")) test_equal([1, 1], compile_and_run("def a; @a; end; def a=(arg); @a = arg; end; @a = 3; x = self.a &&= 1; [x, self.a]")) test_equal(1, compile_and_run("def foo; $_ = 1; bar; $_; end; def bar; $_ = 2; end; foo")) # test empty bodies test_no_exception { test_equal(nil, compile_and_run(whileNoBody)) } test_no_exception { # fcall with empty block test_equal(nil, compile_and_run("def myfcall; yield; end; myfcall {}")) # call with empty block test_equal(nil, compile_and_run("def mycall; yield; end; public :mycall; self.mycall {}")) } # blocks with some basic single arguments test_no_exception { test_equal(1, compile_and_run("a = 0; [1].each {|a|}; a")) test_equal(1, compile_and_run("a = 0; [1].each {|x| a = x}; a")) test_equal(1, compile_and_run("[1].each {|@a|}; @a")) # make sure incoming array isn't treated as args array test_equal([1], compile_and_run("[[1]].each {|@a|}; @a")) } # blocks with tail (rest) arguments test_no_exception { test_equal([2,3], compile_and_run("[[1,2,3]].each {|x,*y| break y}")) test_equal([], compile_and_run("1.times {|x,*y| break y}")) test_no_exception { compile_and_run("1.times {|x,*|}")} } compile_and_run("1.times {|@@a|}") compile_and_run("a = []; 1.times {|a[0]|}") class_string = < 1, :b => 2, :c => 3}; a << c; end; a.sort")) # ensure blocks test_equal(1, compile_and_run("a = 2; begin; a = 3; ensure; a = 1; end; a")) test_equal(1, compile_and_run("$a = 2; def foo; return; ensure; $a = 1; end; foo; $a")) # op element assign test_equal([4, 4], compile_and_run("a = []; [a[0] ||= 4, a[0]]")) test_equal([4, 4], compile_and_run("a = [4]; [a[0] ||= 5, a[0]]")) test_equal([4, 4], compile_and_run("a = [1]; [a[0] += 3, a[0]]")) test_equal([1], compile_and_run("a = {}; a[0] ||= [1]; a[0]")) test_equal(2, compile_and_run("a = [1]; a[0] &&= 2; a[0]")) # non-local return test_equal(3, compile_and_run("def foo; loop {return 3}; return 4; end; foo")) # class var declaration test_equal(3, compile_and_run("class Foo; @@foo = 3; end")) test_equal(3, compile_and_run("class Bar; @@bar = 3; def self.bar; @@bar; end; end; Bar.bar")) # rescue test_no_exception { test_equal(2, compile_and_run("x = begin; 1; raise; rescue; 2; end")) test_equal(3, compile_and_run("x = begin; 1; raise; rescue TypeError; 2; rescue; 3; end")) test_equal(4, compile_and_run("x = begin; 1; rescue; 2; else; 4; end")) test_equal(4, compile_and_run("def foo; begin; return 4; rescue; end; return 3; end; foo")) # test that $! is getting reset/cleared appropriately $! = nil test_equal(nil, compile_and_run("begin; raise; rescue; end; $!")) test_equal(nil, compile_and_run("1.times { begin; raise; rescue; next; end }; $!")) test_ok(nil != compile_and_run("begin; raise; rescue; begin; raise; rescue; end; $!; end")) test_ok(nil != compile_and_run("begin; raise; rescue; 1.times { begin; raise; rescue; next; end }; $!; end")) } # break in a while in an ensure test_no_exception { test_equal(5, compile_and_run("begin; x = while true; break 5; end; ensure; end")) } # JRUBY-1388, Foo::Bar broke in the compiler test_no_exception { test_equal(5, compile_and_run("module Foo2; end; Foo2::Foo3 = 5; Foo2::Foo3")) } test_equal(5, compile_and_run("def foo; yield; end; x = false; foo { break 5 if x; begin; ensure; x = true; redo; end; break 6}")) # END block test_no_exception { compile_and_run("END {}") } # BEGIN block test_equal(5, compile_and_run("BEGIN { $begin = 5 }; $begin")) # nothing at all! test_no_exception { test_equal(nil, compile_and_run("")) } # JRUBY-2043 test_equal(5, compile_and_run("def foo; 1.times { a, b = [], 5; a[1] = []; return b; }; end; foo")) test_equal({"1" => 2}, compile_and_run("def foo; x = {1 => 2}; x.inject({}) do |hash, (key, value)|; hash[key.to_s] = value; hash; end; end; foo")) # JRUBY-2246 long_src = "a = 1\n" 5000.times { long_src << "a += 1\n" } test_equal(5001, compile_and_run(long_src)) # variable assignment of various types from loop results test_equal(1, compile_and_run("a = while true; break 1; end; a")) test_equal(1, compile_and_run("@a = while true; break 1; end; @a")) test_equal(1, compile_and_run("@@a = while true; break 1; end; @@a")) test_equal(1, compile_and_run("$a = while true; break 1; end; $a")) test_equal(1, compile_and_run("a = until false; break 1; end; a")) test_equal(1, compile_and_run("@a = until false; break 1; end; @a")) test_equal(1, compile_and_run("@@a = until false; break 1; end; @@a")) test_equal(1, compile_and_run("$a = until false; break 1; end; $a")) # same assignments but loop is within a begin test_equal(1, compile_and_run("a = begin; while true; break 1; end; end; a")) test_equal(1, compile_and_run("@a = begin; while true; break 1; end; end; @a")) test_equal(1, compile_and_run("@@a = begin; while true; break 1; end; end; @@a")) test_equal(1, compile_and_run("$a = begin; while true; break 1; end; end; $a")) test_equal(1, compile_and_run("a = begin; until false; break 1; end; end; a")) test_equal(1, compile_and_run("@a = begin; until false; break 1; end; end; @a")) test_equal(1, compile_and_run("@@a = begin; until false; break 1; end; end; @@a")) test_equal(1, compile_and_run("$a = begin; until false; break 1; end; end; $a")) # other contexts that require while to preserve stack test_equal(2, compile_and_run("1 + while true; break 1; end")) test_equal(2, compile_and_run("1 + begin; while true; break 1; end; end")) test_equal(2, compile_and_run("1 + until false; break 1; end")) test_equal(2, compile_and_run("1 + begin; until false; break 1; end; end")) def foo(a); a; end test_equal(nil, compile_and_run("foo(while false; end)")) test_equal(nil, compile_and_run("foo(until true; end)")) # test that 100 symbols compiles ok; that hits both types of symbol caching/creation syms = [:a] 99.times { syms << syms[-1].to_s.succ.intern } # 100 first instances of a symbol test_equal(syms, compile_and_run(syms.inspect)) # 100 first instances and 100 second instances (caching) test_equal([syms,syms], compile_and_run("[#{syms.inspect},#{syms.inspect}]"))