On Sat, 2006-08-26 at 12:07 +0300, Mark Wotton wrote:
> Unfortunately, my timeframe's a bit limited - if it's going to be
> more than a day's hacking, I'm going to have to do it by hand.
A control inverting parser for a simple language (like Basic)
is about 4 hours real work.
A simple scheduler, also about 4 hours work.
Parser
----------
There are four instructions:
CALL f x
RETURN
ENTER
LEAVE
The encoding is:
CALL f x
(a) create a new object "f" to hold local variables
set "f.pc" to 0
set "f.caller" to this
(b) set pc to 'next'
(c) return the object
(d) generate label 'next'
RETURN
(a) return "caller"
ENTER:
(a) computed jump on 'pc'
-- switch in C, goto *pc if you have gcc YMMV
(b) generate label 0
LEAVE
(a) For C, a "}" to finish off the switch
There are many optimisations and quirks, for example
for a tail call, you set 'f.caller' to 'caller'
instead of 'this'.
All of this assumes you have some aggregate type,
like a struct, in which to put all your variables,
including the pc and caller variables. You have
to write code that only uses local (auto, stack,
temporary) variables between the above instructions,
since, in particular CALL yields control, and thus
the stack is lost. It helps if your target language
supports some kind of class here.
The whole of the above translated code is a 'method'
called 'resume()'. To actually execute the code you
just say:
while(p) p = p->resume();
The initial call sets the 'caller' to 0,
which the 'while(p)' checks, and drops thru
when the procedure has finished. By this mechanism,
CALL translates to just returning the 'this' pointer
of the new top of stack, and RETURN just returns
the callers 'this' pointer. Each objects stores the
'function local' pc variable to keep track of where
it is up to, and the initial 'switch' jumps there.
In a GC free implementation, either RETURN should delete
the current object, or CALL should delete the called
object (callee or caller delete -- your choice).
This implementation will handle closures BUT
you have to copy them before calling them, to ensure
they're re-entrant AND to ensure that you don't delete
the closure and try to reuse it.
To add blocking, add a flag which is either
'run' or 'block'. In 'block' case you need to invent
a protocol to decide how to reschedule as events
come in, and you need to do some more work on
your scheduler to handle this.
In a dedicated application like yours this is usually
quite simple: you poll the event queue, and given an
event pick one of the blocked 'continuations' to resume
on some application specific basis.
If you have interrupts delivering events, you have
to make them queue notifications asynchronously,
so the scheduler can poll the queue.
If you actually try to do this one evening for fun
you'll find it all drops out. The first step is
to write a simple example using CALL RETURN etc,
and then transform it by hand .. test it in a
simple scheduler, and then you can mechanise
the transformation.
The main difference between this and Felix is
that Felix
(a) handles lexical scoping
(b) does a heap of optimisation, mainly inlining
to eliminate most of the closures: in many
simple applications it reduces the whole thing
down to the equivalent "C" function
(c) has an actual type system .. :)
You don't need any of that in a micro controller.
In fact, you probably don't need recursion or re-entrancy,
so you don't even need to 'create' any objects: global
storage will do.
--
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Felix-language mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/felix-language