#2127 Support for JSR-223?

pjl Fri 5 Apr 2013

I'd like to embed Fantom into a Java servlet application. The obvious way to approach this is through JSR-223 (javax.script). Does Fantom include an implementation of JSR-223, or if not, is there a third-party implementation available?

pjl Fri 5 Apr 2013

Alternatively, is there a Java API of any flavour for executing Fantom scripts?

brian Sat 6 Apr 2013

You can execute Fantom scripts in the simplest way using Env.cur.compileScript.

pjl Tue 9 Apr 2013

I investigated this a bit further. In particular, I've been thinking about creating a JSR-223 implementation for Fantom. However, it seems that the impedance mismatch between Fantom and JSR-223 (that is, the javax.script interfaces) is too great, at the moment, for any sensible implementation. IMHO.

I thought I'd make a few notes here.

  • Actually writing subclasses/implementation for JSR-223 ought to be simple. Whether they are coded in Java (or Fantom, I think) using them from Java should be easy enough.
  • The basic cycle for JSR-223 is: a) provide a binding (equiv. Map<String,Object>) of vars, b) script these vars, c) return the last expression evaluated. For Fantom, it seems to be much more like running a Java application: a) load class(es), b) execute main(), c) return error code. There doesn't seem to be a good way to fit the one into the other.
  • Fantom seems more like a cleaner, terser Java than a scripting language; would it be possible (advantageous?) to allow Fantom to be used in a more "scriptable" way? For instance, the other four languages that I have investigated using via JSR-223 (JRuby, Jython, Clojure and Groovy) all have "hello world" scripts that are one-liners, akin to echo "Hello world". That is, their smallest unit of executable code (at the source level) is a statement/function - not a class.
  • I looked at the various classes that implement fansh (Evaluator and Shell) but it didn't seem like a good idea to depend on them (e.g. not changing) for something like a JSR-223 implementation. I don't think that could be used to implement the javax.script contract, anyway.
  • The fact that sys.Env.compileScript() takes a File doesn't work well for JSR-223. Some of the JSR-223 eval() methods take a File - but others take Readers, or a String. Is there any particular reason that compileScript() couldn't take an InStream?
  • I thought about wrapping some code generation about a "Fantom scriptlet" (akin to the one-line hello world examples). The generated code would have a method with an argument, appropriately named, for each object in the binding. I can't see a clean way to expose the bindings to the Fantom code, however, since the bindings might vary with each invocation of the same "scriptlet", so it would have to be parsed and code generated every invocation. (Alternatively, the binds would have to be provided en mass - e.g. in the map; this would make there use in Fantom code cumbersome.) Any way I looked at it, it looked like the implementation would be ugly. (Also, of course, any such "scriptlet" wouldn't be "real" Fantom code.)
  • Finally, these issues remind me (to a degree) of some of the problems that I've read about in relation to fitting Scala into the context of JSR-223.

http://www.slideshare.net/michid/scala-for-scriptinh

brian Thu 18 Apr 2013

I think the cleanest way you would implement that is to use the compiler classes directly to create transient pods. A good example is how testCompiler compile's snippets of code:

input := CompilerInput.make
input.podName     = podName
input.summary     = "test"
input.version     = Version.defVal
input.log.level   = LogLevel.err
input.isScript    = true
input.output      = CompilerOutputMode.transientPod
input.mode        = CompilerInputMode.str
input.srcStr      = src
input.srcStrLoc   = Loc.make("Script")

compiler = Compiler.make(input)
pod = compiler.compile.transientPod

LightDye Thu 2 May 2013

Hi Brian,

Using your hint above, I created the runnable example shown below. All what I need is to run a Fantom scriptlet (headless Fantom code) passed in as a Str parameter to a Fantom method. This example works, but I'm wondering if you would do this differently...

using compiler

class Runner {

	Int i := 0

	static Void main() {
		// Instantiates this class
		runner := Runner.make

		// Returns Int constant 1 defined as a script
		echo(runner.run("1"))

		// Prints "Hello, World! (1)" and returns null
		// Returning null is requred to prevent "Cannot return 'sys::Void' as 'sys::Obj?'"
		runner.run(""" echo("Hello, World! (1)"); return null """)

		// Returns "Hello, World! (2)"
		echo(runner.run(""" "Hello, " + params["name"] """, ["name" : "World! (2)"]))

		// Returns the pow(3) of Int numbers from 0 to 4
		[0,1,2,3,4].each { 
			echo(runner.run(""" ((Int)params["base"]).pow((Int)params["exponent"]) """, ["base" : it, "exponent" : 3]))
		}
	}

	Obj? run(Str script, [Str:Obj?]? params := null) {
		input := CompilerInput.make
		input.podName = "A${++i}" 		// Different podName prevents "sys::Err: Duplicate pod name: <podName>"
 		input.summary = "test"
		input.version = Version.defVal
		input.log.level = LogLevel.err
		input.isScript = true
		input.output = CompilerOutputMode.transientPod
		input.mode = CompilerInputMode.str
		input.srcStr = "class $input.podName { static Obj? func([Str:Obj?]? params) { " + script + " } }"
		input.srcStrLoc = Loc.make("Script")

		compiler := Compiler.make(input)
		pod := compiler.compile.transientPod

		// Invokes "func" on the compiled script, binding the given parameters
		return pod.type(input.podName).method("func").call(params)
	}

}

brian Fri 3 May 2013

That looks pretty great to me. Probably turning in expression into a full class might need to be more complicated as you run into different use cases (I've hack fansh a bit to handle various stuff, but its not quite perfect)

Login or Signup to reply.