Junicon is a Java-based interpreter for the Unicon programming language, implemented using program transformation. Unicon in turn is a unique object-oriented scripting language that supports goal-directed evaluation of generator expressions. Junicon's novel implementation uses XML-based program transformation to translate Unicon into another scripting language, Groovy, that runs under Java. The result is a transformational interpreter for goal-directed evaluation that, because it runs on Java, is portable and has access to the full range of Java support for concurrency and graphics.
A key feature of Junicon is its seamless interoperability with Java, which allows Java methods, class fields, and data types to be accessed from Unicon, and vice-versa. Junicon can function in several modes, either as an interactive line-by-line interpreter, or as a tool that can translate its input into Groovy or directly to Java for later compilation. When run interactively, Java methods can be used transparently. When compiling to Java, however, casts may be needed, and Java method invocation must be indicated using "::" to distinguish it from the lambda expression invocation used by Junicon methods.
Junicon also supports multi-language embedding using scoped annotations. Junicon classes, methods, and expressions can be embedded in Java or Groovy, and vice-versa, using annotations of the form @<script lang="junicon"> code @</script> (or "java" or "groovy", respectively). Scoped annotations can be nested, in which case the code is transformed and then embedded from the innermost to the outermost annotation. Goal-directed evaluation can thus be embedded into Java or Groovy in a familiar and minimally invasive manner. Similarly, Java code can be injected into Junicon for native evaluation.
Junicon has a small number of additional constructs that extend Unicon syntax with new features. These extensions are conservative in that they preserve the original Unicon syntax. The new features of Junicon include scoped annotations for expressions, integration with the Java package model, familiar block notation for classes and methods, support for multi-language embedding, and supplemental features such as generator expressions as first class citizens, map and set literals, lambda expressions, concurrent generators using a lazy proxy for a thread, and function invocation without parenthesis.
Junicon has a small number of differences from Unicon that are required to cleanly interface with Java and Groovy.
Below is a summary of Junicon's extensions to Unicon.
@<tag attr=x ...> expr @</tag> # Annotations can be applied to expressions as well as to types @<tag attr=x .../> # Can use abbreviated empty-element tags #@<tag attr=x attri=y ...> # Commented annotations are also allowed
@<index origin="0"> # Directives are scoped annotations on a single line #@<index origin="0"> # Generated code below this will have origin 0
package some.package # Junicon follows Java package notation and scoping rules import some.other.package # All variables must be imported or declared import static some.other.package.* # Brings all globals, i.e., class statics, into the namespace
class foo : superFoo { # Familiar block notation is allowed. Class parenthesis may be omitted. local field; # Local variable declarations in any block are allowed method m(x) { # Local variables must be declared local foo : = expr; } # Unlike Unicon, Junicon does not treat undeclared variables as local static method s(x) { } # Static methods are allowed method main(args) { } # Automatically invoked by static main(String[] args) } # Class name should match file name
new C(args) # Can use "new" for instance creation o::f(x) # Need to explicitly indicate any Java methods when compiling to Java
local type x := e, y := e; # Facilitate cut-through to Java static type x := e, y := e; global type x, y; method foo(int x=3, y:int=3, z:int:3) # Default parameters can use = method foo(Object... args) # Allow Java-style types and varargs
global G => globalsMap.get("G") procedure P(x) {body} => class P { static method P(x) {body} } import static P.P; # Multiple procedures in a script are grouped into one class class C { } => class C { static C=()->new C(); } import static C.C; method M(x) {body} => M = this::M; # Exposed as variadic method reference M (Object... args) {body};
package "/usr/foo" => package usr.foo import "/usr/foo" => import static usr.foo.* link foo => import static foo.*
class C { var x; # Variables defined using "var" def f(args) { # Methods defined using "def" x = 1; # Assignment is "=" not ":=" if (x==2) then write("match") } # Equality is "==" not "=" }
@<script lang="junicon"> # If a line starts with @<script lang="junicon"> { (x) -> return every(!x); } # contents up to a matching end tag @</script> # produce a generator with an implicit next() after it
@<script lang="java"> # If a line starts with @<script lang="java"> contents up to a matching end tag y = "hello"+"world"; # are not transformed, and are injected for native evaluation using ()->{<code>} @</script> # Multiline comments /* */ must contain matching group delimiters
((List) x)::add((String) y); # Allow cast in arguments and first field System.out::println(x); # Java methods are indicated by "::", only needed when compiling to Java
x := {< code >} # Section of code that is turned into a quoted string
[ 1,2,3, [4,5] ] # List creation [ key:value, ..., key:value ] # Map creation [:] # Empty map { 1,2,3 } # Set literal
x[1:2] := [3,4]; # Can use slicing in assignment
[: (1 to 5) :] # Creates list by evaluating every(expr)
(x,y) -> { expr } # Like Java, lambdas do not allow locals or parameters to be redeclared. # However, referenced locals do not need to be effectively final. write x y # Can omit parenthesis in simple function invocation
g := <> expression # Create generator expression that is not yet evaluated. !g # Use generator expression, or promote Java iterator or collection.
|<> coexpression # Creates a co-expression. Use @coexpr to get the next element. |> pipe # Creates a co-expression proxy to the generator running in a separate thread. # The thread on demand using @ returns results to the waiting proxy. |>> proxy # Creates an in-place proxy to the generator running in a separate thread. # Equivalent to (! |> expr) |<>| f(e) # Creates a data-parallel proxy in a separate thread. # Equivalent to |> flatten(spawn(<>f, chunk(|> e)))
x.next() # Relaxed to allow keywords in object references null # Java null allowed // comment # Java line comments are allowed "string \ " # Escapes preserve newlines and tabs
Scoped annotations. Annotations provide hints that guide the behavior of the interpreter. Junicon provides a novel form of annotations that combine features of Java annotations with XML-style tags. Unlike Java, the XML-style annotations can be applied to expressions as well as to types. Like XML, annotations can surround text and can also be nested. Currently the scoped annotations are used for transform directives, providing control over code generation in addition to Unicon's $-prefixed preprocessor directives. They could also conceivably be used to mix XML tags into sections of code.
Close integration with Java. New features to support seamless integration include using types in variable declarations to facilitate cut-through to Java, casts for type coercion, package dot notation, instance creation using "new", and explicitly indicating which methods are native Java invocation. The latter is only needed when directly compiling to Java.
Multi-language embedding using scoped annotations. Scoped annotations can be used to embed Junicon within Java and Groovy, and vice-versa. Embedding Junicon into Java or Groovy can occur at the class, method, field, or expression level, and leaves code outside the embedding unchanged. Embedding Java into Junicon will exempt sections of code from being transformed, and so are directly passed through to the substrate interpreter. Block quotes are sections of code that are turned into quoted strings.
Generator expressions as first class citizens. We allow generator expressions to be used as first-class citizens that can be assigned to variables and passed as parameters. We also support generator creation using familiar (x in E) syntax. Generator expressions are distinct from Java iterators that may otherwise be passed to and returned from native Java code. To be used as a generator, a Java iterator must be explicitly promoted to a generator using an "!" expression.
Powerful features otherwise missing from Unicon. These features include map and set literals, lambda expressions, a natural style of method invocation that omits parenthesis, familiar block notation for classes and procedures, static methods, and local variable declarations within blocks.