Thanks to Yoko’s recent message (http://tinyurl.com/3qwnnu), I was able
to
try out JSR 223 in Java 5 for the first time recently. I noticed some
differences in the way these two engines behave, and I’m wondering if
one of
them is more correct than the other. The included code (see the bottom
of
this message) compares the following Ruby statements when executed as
separate calls to eval():
$x=‘engine_name global’
puts “$x = #{$x}”
x=‘engine_name local’
puts “x = #{x}”
It outputs the following:
$x = bsf global
x = bsf local
$x = sun global
Failed to evaluate: puts “x = #{x}”
javax.script.ScriptException: org.jruby.exceptions.RaiseException
at com.sun.script.jruby.JRubyScriptEngine.evalNode(
JRubyScriptEngine.java:456)
at
com.sun.script.jruby.JRubyScriptEngine.eval(JRubyScriptEngine.java:180)
at
playground.CompareBsfAndSunScopes.evalSun(CompareBsfAndSunScopes.java:35)
at
playground.CompareBsfAndSunScopes.main(CompareBsfAndSunScopes.java:56)
Caused by: org.jruby.exceptions.RaiseException
The failure is caused by x being undefined. It appears top-level local
variables do not remain in scope between repeated calls to the Sun
engine. I
think this makes a lot of sense, but it threw me off because I was not
used
to this behavior from BSF. Is the Sun behavior expected? Is there a way
to
change the calls to BSF to make it behave like the Sun engine for
consistency?
I realize making separate eval() calls like this is probably an unusual
use
case, but it is common in the environment where I am embedding Ruby. I
noticed a few ruby programs I have written under BSF are not working
correctly with the Sun engine. I’m hoping it’s caused by this scope
issue.
I’ll report back if I find out it’s another difference between the
engines.
Also I am wondering if the Sun engine would generally be preferred over
the
BSF engine, since JSR 223 is now a standard part of Java 6. Regardless,
it
seems best to use globals if I need a variable remembered between eval()
calls even though BSF does not require it.
Here’s the code. It works for Java 5 once you put script-api.jar and
jruby-engine.jar on your classpath (see http://tinyurl.com/3qwnnu). On
Java
6, I believe you can
replace com.sun.script.jruby.JRubyScriptEngineManager with
javax.script.ScriptEngineManager,
but I don’t think it’s necessary.
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import com.sun.script.jruby.JRubyScriptEngineManager;
public class CompareBsfAndSunScopes {
BSFManager bsf;
ScriptEngine sun;
public CompareBsfAndSunScopes() {
BSFManager.registerScriptingEngine(“ruby”,
“org.jruby.javasupport.bsf.JRubyEngine”, new String[] { “rb” });
bsf = new BSFManager();
sun = new JRubyScriptEngineManager().getEngineByName(“jruby”);
}
public void evalBsf(String script) {
try {
bsf.eval(“ruby”, “(java)”, 1, 1, script);
}
catch (BSFException e) {
System.err.println("Failed to evaluate: " + script);
e.printStackTrace();
}
}
public void evalSun(String script) {
try {
sun.eval(script, sun.getContext());
}
catch (ScriptException e) {
System.err.println("Failed to evaluate: " + script);
e.printStackTrace();
}
}
public static void main(String args[]) {
CompareBsfAndSunScopes compare = new CompareBsfAndSunScopes();
compare.evalBsf(“$x=‘bsf global’”);
compare.evalBsf(“puts "$x = #{$x}"”);
compare.evalBsf(“x=‘bsf local’”);
compare.evalBsf(“puts "x = #{x}"”);
compare.evalSun(“$x=‘sun global’”);
compare.evalSun(“puts "$x = #{$x}"”);
compare.evalSun(“x=‘sun local’”);
compare.evalSun(“puts "x = #{x}"”);
}
}
–Adam