Lathe’s Arc-to-Implementation-Language FFI

$ cd /path/to/jarc

$ java -server -cp \
>   "jarc.jar;$GROOVY_HOME/embeddable/groovy-all-1.7.2.jar" jarc.Jarc

Jarc> (= lathe-dir* "path/to/lathe/arc/")
Jarc> (load:+ lathe-dir* "loadfirst.arc")
Jarc> (use-rels-as jv (+ lathe-dir* "imp/jvm.arc"))
#3(tagged mac #<procedure>)
Jarc> (jv.jvm!groovy-util-Eval-me "2 + 2")

It’s on now.

After about a month of me submitting bugs to the Arc forum and Jarc’s creator, JD Brennan, fixing them, Jarc is now compatible enough with Arc 3.1 that my Lathe (the library I introduced last time) now wholeheartedly supports the Jarc implementation of Arc. This means it works on four Arc setups: Jarc, official Arc 3.1 (which itself needs a Windows fix), Anarki (which needs the same fix), and Rainbow. Most of you reading this are probably from the Arc forum, in which case you knew all that already. :-p

Those are all the setups I want Lathe to support for now, but I might consider arc3f, arc2c, or another Arc implementation if I realize it’s actually active. Another complication is that quite a lot of Arc users modify one of the PLT Scheme versions of Arc (Arc or Anarki) to suit their needs. In those cases, I figure the burden of Lathe compatibility is on them if they need it.

Still, there is a bit of a need to add new features to Arc. To this end, Anarki, Jarc, and Rainbow have provided ways to call from Arc into the host platform (PLT Scheme, the JVM, and the JVM respectively).

In fact, if you know Jarc, the above example isn’t all that impressive. You can already do this:

$ cd /path/to/jarc

$ java -server -cp \
>   "jarc.jar;$GROOVY_HOME/embeddable/groovy-all-1.7.2.jar" jarc.Jarc

Jarc> ( "2 + 2")

On Rainbow, you can accomplish the same thing in a couple of ways:

$ cd /path/to/rainbow/src/arc

$ java -server \
>   -cp "rainbow.jar;$GROOVY_HOME/embeddable/groovy-all-1.7.2.jar" \
>   rainbow.Console

arc> (java-static-invoke "groovy.util.Eval" 'me "2 + 2")
arc> (java-import groovy.util.Eval)
[Rainbow displays the source of the resulting macro.]
arc> (Eval me "2 + 2")

So what does Lathe get you here? Not much, I guess, but it actually does give you the ability to assign to JVM fields:

arc> (jv.jvm!height= (jv.jvm!java-awt-Rectangle-new 1 2 3 4) 5)

That’s a feature that’s lacking in Jarc and in Rainbow. Lathe’s jvm.arc accomplishes it through reflection calls on java.lang.reflect.Field objects, and it has to do one of the worst kinds of JVM reflection: Branching on primitive types. I’m glad that’s behind me!

Lathe also gives sort of a nice syntax, in my opinion. The syntax prioritizes (my) intuition over consistency, in ways that unfortunately make for a monstrous implementation that takes just as long to fully explain. Here’s the important part of the documentation, which assumes you’ve put jv.jvm into the global variable jvm through something like (= jvm jv.jvm):

; [blah, blah, blah, paragraphs and paragraphs of text]
; At long last, here are some examples:
; getting a static field
; jvm!javax-swing-JFrame-EXIT_ON_CLOSE
; jvm!EXIT_ON_CLOSE.jframe-instance
; setting a static field
; jvm!
; jvm!
; (jvm!staticField= someclass-instance new-value)
; calling a static method
; jvm!java-util-Arrays-asList.array
; getting a top-level class as a Class instance
; jvm!java-lang-Object-class
; getting a static nested class as a Class instance
; jvm!java-util-HashMap-Entry-class
; calling a constructor
; (jvm!java-util-ArrayList-new)
; calling an instance method (same as a static one, but shortenable)
; (jvm!java-util-HashMap-get map key)
; (jvm!get map key)
; getting an instance field
; jvm!java-awt-Rectangle-x.rect
; jvm!x.rect
; setting an instance field
; (jvm!java-awt-Rectangle-x= rect newx)
; jvm!java-awt-Rectangle-x=.rect.newx
; (jvm!x= rect newx)
; jvm!x=.rect.newx
; accessing an inner class (a non-static nested class) as a Class
;   instance
; jvm!com-example-OuterClass-InnerClass-class
; jvm!InnerClass.outer!class
; constructing an instance of an inner class
; jvm!com-example-OuterClass-InnerClass-new.outer
; (jvm!InnerClass.outer!new)
; some makeshift importing
; (= jutil jvm!java-util
;    weakhash jutil!WeakHashMap-new)
; (def jweak (x)
;   (if (ajava x jutil!Map-class)
;     weakhash.x
;       (isa x 'table)
;     (let result (weakhash)
;       (each (k v) x
;         (jvm!put result k v))
;       result)
;     (err "A non-map was passed to 'jweak.")))

But what this inconsistent interface gets us most of all is a different form of consistency: Greater consistency between Jarc and Rainbow. Now that the jvm function exists, if I want to add some other feature to Lathe that requires me to make JVM calls, I may be able to get away with writing the same code on both platforms.

It’s not quite as easy as that. Both platforms do certain things for you automatically, namely converting between Arc types and JVM types and deciding which JVM method/constructor best suits the Arc parameters you’ve given. An Arc library can’t easily overcome that; it can’t get its hands on certain kinds of JVM value, since they’re converted before Arc even sees them. Therefore, rather than putting abstraction inversion complexity on top of jv.jvm‘s other complexity, I’ve had it go with the flow for now.

But if you happen to have Groovy in the classpath, you can drop to the JVM and rise back up to Groovy by calling, and you can use Groovy as a more Arc-agnostic basis for manipulating JVM objects. You can also do additional things like extending JVM classes (not just interfaces) at runtime, which otherwise you would probably have to do by fiddling with bytecode and ClassLoaders. Time will tell if Lathe will provide extra features when Groovy is available, but it’s pretty likely considering how much I like Groovy. ^_^

That was a lot of focus on the JVM side of Arc. What about PLT Scheme? Well, thanks to a bug I discovered in Arc, Anarki isn’t the only setup that can drop to PLT Scheme. So Lathe’s sniff.arc, which is a home for platform detection flags, now includes four lines that implement a plt macro. It treats its argument as a PLT Scheme expression and returns its result, regardless of whether official Arc or Anarki is in use.

Both plt and jvm are set to nil when on an incompatible platform, and I expect that to allow for some nicely idiomatic cross-platform library code.

Well, it will once I discover a library I actually want to write with these things! My two ideas are a desktop GUI library, which I’m not sure I need, and a dynamic binding library, something I’m really interested in which seems to be a tough nut to crack on Rainbow.

P.S.: There’s another bit of progress on Lathe worth mentioning. In order to get multivals working on Jarc, which doesn’t support reinvokable continuations, I ported over an iteration library I had originally written in Groovy. It’s a pretty substantial library for building iterators when continuations aren’t available, and when they are, it also supports the yield coroutine style used in Python, C#, and so forth. It’s Artistic License 2.0 like the rest of Lathe. Check it out.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s