For those who only want to play with simple collaborations to see how the typing rules work, or are curious anout how we desugar aspectual methods, then an interpreted version based around a superset of featherweight java might be of interest.
It is good for absolutely no practical uses, as it doesn't have side-effects or conditionals.
Think of it as a lambda calculus with java syntax.
A sample program, illustrating subtyping and method invocation, is simple.input:
// define an atomic collaboration
atom
class Foo extends Object {
Bar makeBar() { return new Grok; }
}
class Bar extends Object { }
class Grok extends Bar { }
// evaluate an expression in the context of collaboration
!new Foo.makeBar();
The syntax is java-like, with a few modifications to make it easier to
parse and evaluate. More on this below. Run it like so (more than a
little snipped, to save space):
caph.ccs.neu.edu(14): ./fjacc.sh simple.input
=> new Grok()
fjacc.sh is a shell script included in the tarball.
It is a one liner that builds the classpath and connects the argument to std-in.
It should be trivial to port to windows.
The syntax modifications are that an expression is
Expr = ! Expr . m (Expr*) (method call)
| ? Expr . f (field ref)
| ( Type Expr ) (cast)
| = Expr [f -> Expr] (functional update of field)
| new Type (instantiation)
| x (variable)
The main differences from Featherweight Java are that the syntax has
been made LL1 by addition of prefix operators, and that constructors
have been replaced by functional update. Functional update returns a
copy of the object will all fields unchanged, appart from "f", which
has value "Expr".
We can define composite collaborations as well (simple2.input)
collab one = atom
class X extends Object {
expected X makeX(Y y);
X makeComplicated() { return !this.makeX(==new Y[x1 -> new X][x2 -> this]); }
}
class Y extends Object {
X x1; X x2;
X getX2() { return ?this.x2; }
}
collab two = atom
class A extends Object {
A makeA(B b) { return !b.getA(); }
}
class B extends Object {
expected A getA();
}
// put them together, linking up required and provided.
composite
attach [one two]
participant M extends Object <= [one.X two.A]
participant N extends Object <= [one.Y two.B]
with
provide two.A.makeA => one.X.makeX;
provide one.Y.getX2 => two.B.getA;
// composite collabs by default export nothing, so we need to export makeComplicated
export one.X.makeComplicated => main;
!new M.main();
Lastly, we also provide around methods. Since we don't have side effects, an "around" method can only choose whether to invoke the captured method or not (it can of course do this in any way as long as it satisfies its type signature). We provide the behavior to be done in the not case by giving a "default" method in the around link. The default method must have the same signature as the arounded method.
collab b = atom
class B extends Object {
B someMethod() { return new B; }
aspectual RV nodont(JP jp) {
return !jp.dontInvoke();
}
B dontDef(Object o) { return this; }
B someOtherMethod() { return this; }
}
collab c = atom
class C extends Object {
C oblivious(Object o) { return (C o); }
aspectual RetVal yesdo(JoinPoint thisjoinpoint) {
return !thisjoinpoint.invoke();
}
}
composite attach [b c]
participant D extends Object <= [b.B c.C]
with
around b.B.someMethod => c.C.yesdo default b.B.someOtherMethod;
around c.C.oblivious => b.B.nodont default b.B.dontDef;
export b.B.someMethod => one;
export c.C.oblivious => two;
!new D.two( !new D.one());