exports.generator = generator = (intern, compiletime, runtime) ->
Generate three categories of objects: intern -- for internal use only compiletime -- for compiletime lookups runtime -- for when it's actually run, either inlined or require'd
Eventually, we might want to call this generator to generate into different contexts (like back into inline code, but for now, we abandon this project).
exports.generator = generator = (intern, compiletime, runtime) ->
compiletime.transform = (x, options) ->
x.icedTransform options
compiletime.const = C =
k : "__iced_k"
k_noop : "__iced_k_noop"
param : "__iced_p_"
ns: "iced"
runtime : "runtime"
Deferrals : "Deferrals"
deferrals : "__iced_deferrals"
fulfill : "_fulfill"
b_while : "_break"
t_while : "_while"
c_while : "_continue"
n_while : "_next"
n_arg : "__iced_next_arg"
context : "context"
defer_method : "defer"
slot : "__slot"
assign_fn : "assign_fn"
autocb : "autocb"
retslot : "ret"
trace : "__iced_trace"
passed_deferral : "__iced_passed_deferral"
findDeferral : "findDeferral"
lineno : "lineno"
parent : "parent"
filename : "filename"
funcname : "funcname"
catchExceptions : 'catchExceptions'
runtime_modes : [ "node", "inline", "window", "none", "browserify" ]
trampoline : "trampoline"
intern.makeDeferReturn = (obj, defer_args, id, trace_template, multi) ->
trace = {}
for k,v of trace_template
trace[k] = v
trace[C.lineno] = defer_args?[C.lineno]
ret = (inner_args...) ->
defer_args?.assign_fn?.apply(null, inner_args)
if obj
o = obj
obj = null unless multi
o._fulfill id, trace
else
intern._warn "overused deferral at #{intern._trace_to_string trace}"
ret[C.trace] = trace
ret
intern.__c = 0
intern.tickCounter = (mod) ->
intern.__c++
if (intern.__c % mod) == 0
intern.__c = 0
true
else
false
intern.__active_trace = null
intern._trace_to_string = (tr) ->
fn = tr[C.funcname] || "<anonymous>"
"#{fn} (#{tr[C.filename]}:#{tr[C.lineno] + 1})"
intern._warn = (m) ->
console?.log "ICED warning: #{m}"
trampoline --- make a call to the next continuation... we can either do this directly, or every 500 ticks, from the main loop (so we don't overwhelm ourselves for stack space)..
runtime.trampoline = (fn) ->
if not intern.tickCounter 500
fn()
else if process?
process.nextTick fn
else
setTimeout fn
A collection of Deferrals; this is a better version than the one that's inline; it allows for iced tracing
runtime.Deferrals = class Deferrals
constructor: (k, @trace) ->
@continuation = k
@count = 1
@ret = null
_call : (trace) ->
if @continuation
intern.__active_trace = trace
c = @continuation
@continuation = null
c @ret
else
intern._warn "Entered dead await at #{intern._trace_to_string trace}"
_fulfill : (id, trace) ->
if --@count > 0
noop
else
runtime.trampoline ( () => @_call trace )
defer : (args) ->
@count++
self = this
return intern.makeDeferReturn self, args, null, @trace
As above, but won't get rewritten bythe compiler... This is in case custom defer implementations want to access the base implementation.
_defer : (args) -> @["defer"] args
runtime.findDeferral = findDeferral = (args) ->
for a in args
return a if a?[C.trace]
null
More flexible runtime behavior, can wait for the first deferral to fire, rather than just the last.
runtime.Rendezvous = class Rendezvous
constructor: ->
@completed = []
@waiters = []
@defer_id = 0
RvId -- A helper class the allows deferalls to take on an ID when used with Rendezvous
class RvId
constructor: (@rv,@id,@multi)->
defer: (defer_args) ->
@rv._deferWithId @id, defer_args, @multi
wait: (cb) ->
if @completed.length
x = @completed.shift()
cb(x)
else
@waiters.push cb
defer: (defer_args) ->
id = @defer_id++
@deferWithId id, defer_args
id -- assign an ID to a deferral, and also toggle the multi bit on the deferral. By default, this bit is off.
id: (i, multi) ->
multi = false unless multi?
new RvId(this, i, multi)
Private Interface
_fulfill: (id, trace) ->
if @waiters.length
cb = @waiters.shift()
cb id
else
@completed.push id
_deferWithId: (id, defer_args, multi) ->
@count++
intern.makeDeferReturn this, defer_args, id, {}, multi
Follow an iced-generated stack walk from the active trace up as far as we can. Output a vector of stack frames.
runtime.stackWalk = stackWalk = (cb) ->
ret = []
tr = if cb then cb[C.trace] else intern.__active_trace
while tr
line = " at #{intern._trace_to_string tr}"
ret.push line
tr = tr?[C.parent]?[C.trace]
ret
runtime.exceptionHandler = exceptionHandler = (err, logger) ->
logger = console.log unless logger
logger err.stack
stack = stackWalk()
if stack.length
logger "Iced callback trace:"
logger stack.join "\n"
Catch all uncaught exceptions with the iced exception handler. As mentioned here:
It's good idea to kill the service at this point, since state is probably horked. See his examples for more explanations.
runtime.catchExceptions = (logger) ->
process?.on 'uncaughtException', (err) ->
exceptionHandler err, logger
process.exit 1
exports.runtime = {}
generator this, exports, exports.runtime