Here it is. Compiling this Avail module causes 25 to be printed (as
expected)...
-----------------------------------------------------------------------
Module "Examples-Joy"
Extends
Uses "kernel"
Names
Body
/* Define a trivial Joy interpreter, directly *embedded* in the Avail
syntax. */
/* Some useful types... */
stack ::= tuple of all;
funct ::= [stack]->stack;
/* Polymorphic utility for evaluation... */
Method "_run_" is [a : all, s : stack | <a> then s; ]:stack;
Method "_run_" is [f : funct, s : stack | f(s); ]:stack;
Method "_runAll_" is [t : tuple, s : stack |
s2 : stack := s;
t do [x : all | s2 := x run s2; ];
s2;
];
/* Some useful primitive operations... */
pop : funct := [s:stack | s[2..||s||];];
dup : funct := [s:stack | <s[1]> then s;];
swap : funct := [s:stack | <s[2],s[1]> then (s[3..||s||]); ];
print : funct := [s:stack | Print s[1]; s[2..||s||];];
i : funct := [s:stack |
cast s[1] into [t : tuple |
t runAll (s[2..||s||]); ]; ];
is : funct := [s:stack |
cast s[1] into [str : string |
cast s[2] into [t : tuple |
Method str is [ [s:stack | t runAll s;]; ]; ]; ];
s[2..||s||]; /* leaves quoted program on stack */
];
Method "+" is [ [s:stack |
cast s[1..2] into [t : tuple like <integer, integer> |
<t[1]+t[2]> then (s[3..||s||]);]; ]; ];
Method "*" is [ [s:stack |
cast s[1..2] into [t : tuple like <integer, integer> |
<t[1]*t[2]> then (s[3..||s||]);]; ]; ];
/* Quoting (0, 1, >1)... */
Method "[]" is [<>;];
Method "[_]" is [x : all | <x>;];
Method "[_]" is [aList : list | <aList>;];
/* And, of course, the outer 'evaluator'... */
Method "_Joy run" is [t : tuple |
residue ::= t runAll <>;
Assert residue = <>;
]:void;
[[dup, *], "square", is, pop] Joy run;
[5, square, print] Joy run;
-----------------------------------------------------------------------
That was it. I didn't think it would take much code, but that was a little
smaller than I expected. A couple of things to note:
Avail is strongly typed, and Joy (this version at least) is not typed, so a
couple of dynamic casts were used (these are checked at runtime).
The 'is' operation actually defines a new Avail word taking no arguments and
returning a funct.
The commas are necessary in the current Avail implementation. Initially I
tried defining a juxtaposition operation "__" (i.e., an operation with no
keywords or punctuation, but taking two arguments) of type
[funct,funct]->funct, but that meant I had to quote literals somehow, (to
turn them into functs suitable for juxtaposing). I chose a prefix backquote
"`_". Then I tried implementing quotations and discovered I could do it,
but only if I pre-collapsed tuples (quotations) into a "compiled" form,
preventing all list-manipulation operations except concatenations. Oh,
well. I guess that's about as close a non-applicative language can come to
embedding an applicative language's syntax directly.
The "directness" of the embedding may not be obvious. Let me write one more
expression to explain...
[2+2, square, [s:stack | square(s) then s[2..||s||];], print] Joy run;
In this expression, the 2+2 is an ordinary Avail expression that yields the
Joy literal 4. The square funct is as defined previously. Then a closure
that matches the funct signature is constructed inline. Note the use of the
'square' operation in the Avail code, defined earlier by the Joy 'is'
operator. The inline funct takes a stack s, passes this to the square
operation, which then answers a stack with just the number 16 on it (in this
case), then it takes this stack, which is really just the tuple <16>, and
concatenates the remainder of the stack s onto it via the "_then_" operation
(top of stack is the leftmost tuple element for convenience).
I believe the K implementation Stevan Apter <
apter@...> submitted
earlier is more concise and much more comprehensive (neither feature was a
goal for me at this time). On the other hand, it required the Joy
expressions to be quoted (via 'I"'). I may have misunderstood the K code,
but it seemed that some Joy operations were defined via 'I"' quoted Joy
expresions, but others were defined via K code directly. I didn't see any
mixtures, and I don't understand the implementation well enough to hazard a
guess whether it would be possble to mix the Joy and K code in a definition.
Now, about typing. I should be able to piggyback a Joy type system on top
of Avail's type system. That is, type declarations made in a typed
extension of Joy should be checkable when the Joy-in-Avail code is compiled.
By maintaining type constraints in a stack-oriented manner, all quotations
can be type-checked by "simulating" the operations as mere type-stack
manipulation. The actual code should serve to clear up this concept, so I
won't discuss it further until I've had (A) some sleep, and (B) time to
write a statically type-checked Joy system in Avail.
Oh, by the way, the code in this post took less than half an hour to write,
despite frequent problems with my work-in-progress Avail interpreter (I
disabled the C++ DLL and just let it run 10x slower). This post itself took
much longer than that to compose.
If there are specific areas of the code (e.g., unfamiliar operators) that
are hard to understand, just ask.