A read-eval-print-loop for JavaFX
JavaFX now supports a read-eval-print-loop ("repl"). This will be in the next ("Marina") release, but you can get it now from the Mercurial repository - see the compiler home page, which links to these instructions.A read-eval-print-loop is a well-known feature of many language implementations, but isn't that common with compiled languages, especially ones that have static typing and lexical name lookup.
See this companion article on the scripting API and implementation issues.
The class com.sun.tools.javafx.script.ScriptShell
implements the
actual repl. It is an application that was based on
the language-independent jrunscript
command.
but you can also subclass it if you want to customize it.
To run the REPL, start the ScriptShell
application - for example:
$ CLASSPATH=/tmp:dist/lib/shared/javafxc.jar \ java com.sun.tools.javafx.script.ScriptShell
Then you type commands at it:
/*fx1*/ var xy = 12 12 /*fx2*/ xy+10 12The prompt has the form of a comment, to make it easier to cut-and-paste. The name inside the comments is used as the name of a "scriptlet" which shows up in error messages and exceptions.
You can define functions, modify variables, and call functions:
/*fx3*/ function xy_plus (n:Integer):Integer { xy + n } /*fx4*/ ++xy 13 /*fx5*/ xy_plus(100) 113
You get warnings:
/*fx6*/ xy=2.5 fx6:1: possible loss of precision found : Number required: Integer 2
and errors:
/*fx7*/ xy="str" fx7:1: incompatible types found : String required: Integer
and exceptions:
/*fx8*/ xy/0 java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:616) at com.sun.tools.javafx.script.JavaFXCompiledScript.eval(JavaFXCompiledScript.java:59) at com.sun.tools.javafx.script.ScriptShell.evaluate(ScriptShell.java:350) at com.sun.tools.javafx.script.ScriptShell.evaluateString(ScriptShell.java:311) at com.sun.tools.javafx.script.ScriptShell.processSource(ScriptShell.java:244) at com.sun.tools.javafx.script.ScriptShell$1.run(ScriptShell.java:177) at com.sun.tools.javafx.script.ScriptShell.main(ScriptShell.java:51) Caused by: java.lang.ArithmeticException: / by zero at fx8.javafx$run$(fx8]:1) ... 10 more
Each command
is single line. In the future I hope we'll be able to
tell when a command is incomplete
, so the reader can keep reading
more lines until it has a complete command.
Re-definition
You can redefine variables (and functions and classes), but previously-compiled references refer to the earlier definition:/*fx9*/ var xy:String="str" str /*fx10*/ xy str /*fx11*/ xy_plus(100) 102
Now this is correct
in terms of static name-binding,
but perhaps not the most useful. What you probably want
is for xy_plus
to use the new definition of xy
.
This is what would happen in a typical dynamic language with
dynamic typing and dynamic name-lookup, but with
of compilation and static name-lookup xy_plus
uses the
the defintion seen when it was compiled.
Fixing
this would involve some combination of automatic
re-compilation (if xy_plus
depends on xy
,
and xy
is redefined, then re-compile xy_plus
),
and extra indirection.
A related issue is that forward references aren't allowed, which means you can't declare mutually dependent functions or classes - unless you type them all in the same command - i.e. on one line! Automatic recompilation could alleviate this: When a command is compiled, we not only remember the declarations it depends on, but also any missing symbols, so we can re-compile it if those later get declared.
Memory and start-up issues
Be aware that executing the first command causes a
noticable pause. While I haven't profiled this, I'm guessing
it is just because we need to load most of javafxc
(which
includes most of (a hacked-up version of) javac
), and that takes
some time. Subsequent commands run much faster.
Right now we don't do any smart reclaiming of classes. Each command compiles into one or more classes, which are kept around because they may be needed for future commands. Also, the bytecode array of a command is also kept around, since it may be needed when compiling future commands. Hopefully in the future we'll be more clever, so we can get-rid of no-longer-useful classes.