VimUnDoEZNcnw!hE2TE _3 TC24$ $y ... ($x.resolver(0));5_3TE35 355_4TG35 if (5_4TL35 if (p.then)5_4TM35 if (p.then)5_4TQ35 if (p && p.then)5_4TQ35 if (p && p.then) {5_ 4Tf35 if (p && p.then) { }5_ 4 Tf34 if (p && p.then) { }5_ 3 Tm24, var p = $y ... ($x.resolver(0));5_ 3 Tm24+ ar p = $y ... ($x.resolver(0));5_ 3 Tm24* r p = $y ... ($x.resolver(0));5_  3 Tm24) p = $y ... ($x.resolver(0));5_ 3 Tn24( p = $y ... ($x.resolver(0));5_3 Tn24' = $y ... ($x.resolver(0));5_3 Tn24& = $y ... ($x.resolver(0));5_3 Tn24% $y ... ($x.resolver(0));5_3 To24$ $y ... ($x.resolver(0));5_3!Ty249 $state_machine.pbind($y ... ($x.resolver(0));5_3<T24= $state_machine.pbind($x, $y ... ($x.resolver(0));5_>3T>@>?5_> > >2v2T=?4 $y ... ($args (,) ... , $x.resolver(0));5_?$> >2v2T>@> $state_machine.pbind($x, $y ... ($x.resolver(0)));?@5_?L> >2v2T>Ae $state_machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0))$y ... ($x.resolver(0)));5_@ > >2v2T?@% $y ... ($x.resolver(0)));5_> > >2v2T=> ;5_3 > >2v2TV24> $state_machine.pbind($x, $y ... ($x.resolver(0)));5_3 > >2v2TW24= state_machine.pbind($x, $y ... ($x.resolver(0)));5_3 > >2v2TW24< tate_machine.pbind($x, $y ... ($x.resolver(0)));5_3 > >2v2TW24; ate_machine.pbind($x, $y ... ($x.resolver(0)));5_ 3 > >2v2TW24: te_machine.pbind($x, $y ... ($x.resolver(0)));5_! 3 > >2v2TW249 e_machine.pbind($x, $y ... ($x.resolver(0)));5_ "!3 > >2v2TW248 _machine.pbind($x, $y ... ($x.resolver(0)));5_!#"3 > >2v2TW247 machine.pbind($x, $y ... ($x.resolver(0)));5_"$#3 > >2v2TW246 achine.pbind($x, $y ... ($x.resolver(0)));5_#%$3 > >2v2TW245 chine.pbind($x, $y ... ($x.resolver(0)));5_$&%3 > >2v2TW244 hine.pbind($x, $y ... ($x.resolver(0)));5_%'&3 > >2v2TW243 ine.pbind($x, $y ... ($x.resolver(0)));5_&('3 > >2v2TX242 ne.pbind($x, $y ... ($x.resolver(0)));5_')(3 > >2v2TX241 e.pbind($x, $y ... ($x.resolver(0)));5_(*)3 > >2v2TX240 .pbind($x, $y ... ($x.resolver(0)));5_)+*3 > >2v2TX24/ pbind($x, $y ... ($x.resolver(0)));5_*,+3 > >2v2TX24. bind($x, $y ... ($x.resolver(0)));5_+-,3 > >2v2TX24- ind($x, $y ... ($x.resolver(0)));5_,.-3 > >2v2TX24, nd($x, $y ... ($x.resolver(0)));5_-/.3 > >2v2TX24+ d($x, $y ... ($x.resolver(0)));5_.0/3 > >2v2TX24* ($x, $y ... ($x.resolver(0)));5_/103 > >2v2TX24) $x, $y ... ($x.resolver(0)));5_0213 > >2v2TY24( x, $y ... ($x.resolver(0)));5_1323 > >2v2TY24' , $y ... ($x.resolver(0)));5_2433 > >2v2TY24& $y ... ($x.resolver(0)));5_3543#> >2v2T[24% $y ... ($x.resolver(0)));5_465> > >2v2T]=?N $state_machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_576> > >2v2T^=?M state_machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_687> > >2v2T^=?L tate_machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_798> > >2v2T^=?K ate_machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_8:9> > >2v2T^=?J te_machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_9;:> > >2v2T^=?I e_machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_:<;> > >2v2T^=?H _machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_;=<> > >2v2T^=?G machine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_<>=> > >2v2T^=?F achine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_=?>> > >2v2T^=?E chine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_>@?> > >2v2T^=?D hine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_?A@> > >2v2T^=?C ine.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_@BA> > >2v2T_=?B ne.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_ACB> > >2v2T_=?A e.pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_BDC> > >2v2T_=?@ .pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_CED> > >2v2T_=?? pbind($x, $y ... ($args (,) ... , $x.resolver(0)));5_DFE> > >2v2T_=?> bind($x, $y ... ($args (,) ... , $x.resolver(0)));5_EGF> > >2v2T_=?= ind($x, $y ... ($args (,) ... , $x.resolver(0)));5_FHG> > >2v2T_=?< nd($x, $y ... ($args (,) ... , $x.resolver(0)));5_GIH> > >2v2T_=?; d($x, $y ... ($args (,) ... , $x.resolver(0)));5_HJI> > >2v2T_=?: ($x, $y ... ($args (,) ... , $x.resolver(0)));5_IKJ> > >2v2T_=?9 $x, $y ... ($args (,) ... , $x.resolver(0)));5_JLK> > >2v2T`=?8 x, $y ... ($args (,) ... , $x.resolver(0)));5_KML> > >2v2T`=?7 , $y ... ($args (,) ... , $x.resolver(0)));5_LNM> > >2v2T`=?6 $y ... ($args (,) ... , $x.resolver(0)));5_MON>2> >2v2Tb=?5 $y ... ($args (,) ... , $x.resolver(0)));5_NPO3 > >2v2T24$ $y ... ($x.resolver(0));5_OQP>0> >2v2T=?4 $y ... ($args (,) ... , $x.resolver(0));5_PRQ9A:vTh8A9:5_QSR8HAvTi8:5_RTSAIBvTk@C } 5_SUT:JCvTo9;D case { $_ $taskname:ident($x:ident (,) ...) { $body ... } } => {5_TVU=JCvTt<>8 function $taskname($x (,) ... , $callback) {5_UWV=JCvTt<>7 function $taskname(x (,) ... , $callback) {5_VXW=JCvTu<>6 function $taskname( (,) ... , $callback) {5_WYX=JCvTu<>5 function $taskname((,) ... , $callback) {5_XZY=JCvTu<>4 function $taskname(,) ... , $callback) {5_Y[Z=JCvTu<>3 function $taskname() ... , $callback) {5_Z\[=JCvTu<>2 function $taskname( ... , $callback) {5_[]\=JCvTu<>1 function $taskname(... , $callback) {5_\^]=JCvTu<>0 function $taskname(.. , $callback) {5_]_^=JCvTu<>/ function $taskname(. , $callback) {5_^`_=JCvTu<>. function $taskname( , $callback) {5__a`=JCvTv<>- function $taskname(, $callback) {5_`ba=JCvTv<>, function $taskname( $callback) {5_acb>2JCvTy=?W setup_state_machine $_ $callback ($x (,) ... , $callback) { $body ... }5_bdc>2JCvTy=?V setup_state_machine $_ $callback (x (,) ... , $callback) { $body ... }5_ced>2JCvTz=?U setup_state_machine $_ $callback ( (,) ... , $callback) { $body ... }5_dfe>2JCvTz=?T setup_state_machine $_ $callback ((,) ... , $callback) { $body ... }5_egf>2JCvTz=?S setup_state_machine $_ $callback (,) ... , $callback) { $body ... }5_fhg>2JCvTz=?R setup_state_machine $_ $callback () ... , $callback) { $body ... }5_gih>2JCvTz=?Q setup_state_machine $_ $callback ( ... , $callback) { $body ... }5_hji>2JCvTz=?P setup_state_machine $_ $callback (... , $callback) { $body ... }5_ikj>2JCvTz=?O setup_state_machine $_ $callback (.. , $callback) { $body ... }5_jlk>2JCvTz=?N setup_state_machine $_ $callback (. , $callback) { $body ... }5_kul>2JCvTz=?M setup_state_machine $_ $callback ( , $callback) { $body ... }5_lvmu>2JCvT=?L setup_state_machine $_ $callback (, $callback) { $body ... }5_uwv>2JCvT=?K setup_state_machine $_ $callback ( $callback) { $body ... }5_vxw091vT/:015_wyx/B:vT/15_xzy9C;vT79 }8:5_y{z1B:vT025 case { $_ ($x:ident (,) ...) { $body ... } } => {5_z|{4B:vT350 (function ($x (,) ... , $callback) {5_{}|4B:vT35/ (function (x (,) ... , $callback) {5_|~}4B:vT35. (function ( (,) ... , $callback) {5_}~4B:vT35- (function ((,) ... , $callback) {5_~4B:vT35, (function (,) ... , $callback) {5_4B:vT35+ (function () ... , $callback) {5_4B:vT35* (function ( ... , $callback) {5_4B:vT35) (function (... , $callback) {5_4B:vT35( (function (.. , $callback) {5_4B:vT35' (function (. , $callback) {5_4B:vT35& (function ( , $callback) {5_4B:vT35% (function (, $callback) {5_4B:vT35$ (function ( $callback) {5_52B:vT46W setup_state_machine $_ $callback ($x (,) ... , $callback) { $body ... }5_52B:vT46V setup_state_machine $_ $callback (x (,) ... , $callback) { $body ... }5_52B:vT46U setup_state_machine $_ $callback ( (,) ... , $callback) { $body ... }5_52B:vT46T setup_state_machine $_ $callback ((,) ... , $callback) { $body ... }5_52B:vT46S setup_state_machine $_ $callback (,) ... , $callback) { $body ... }5_52B:vT46R setup_state_machine $_ $callback () ... , $callback) { $body ... }5_52B:vT46Q setup_state_machine $_ $callback ( ... , $callback) { $body ... }5_52B:vT46P setup_state_machine $_ $callback (... , $callback) { $body ... }5_52B:vT46O setup_state_machine $_ $callback (.. , $callback) { $body ... }5_52B:vT46N setup_state_machine $_ $callback (. , $callback) { $body ... }5_52B:vT46M setup_state_machine $_ $callback ( , $callback) { $body ... }5_52B:vT46L setup_state_machine $_ $callback (, $callback) { $body ... }5_52B:vT 46K setup_state_machine $_ $callback ( $callback) { $body ... }5_B:vTD// # Macro `task`//P// `task` is a macro that takes a body that describes a sequence of asynchronousR// operations and expands it to a state machine with very little runtime overhead.R// It is designed so that it can be used with functions that obey the NodeJS style^// callback convention where a callback function of the form `function (err, result) { ... }` U// is passed as the last argument of async calls. A "task" is itself such a function.//B// In general, a compiled task looks like a function of the form -// .// function (arg1, arg2, ... , callback) {%// ... state machine code ...// }//L// The macro supports the following four forms to provide easy expression of,// pure no-argument scripts and named tasks. macro task { Q // 1. `task { body ... }` produces a `function (callback) { ... }` expression\ // 2. `task name { body ... }` produces a `function name(callback) { ... }` declaration.k // 3. `task (arg1, arg2) { body ... }` produces a `function (arg1, arg2, callback) { ... }` expression.t // 4. `task name(arg1, arg2) { body ... }` produces a `function name(arg1, arg2, callback) { ... }` declaration. //X // The `task` macro goes hand-in-hand with the `Channel` and `StateMachine` modules.V // While the `StateMachine` module is internal and the macro user doesn't need to \ // bother about it, the `Channel` module offers a simple way to coordinate multi-taskingN // in JS - in the CSP style of the `Haskell`, `Erlang` and `Go` languages." case { $_ { $body ... } } => {: letstx $callback = [makeIdent("callback", #{$_})]; return #{ # (function ($callback) {J setup_state_machine $_ $callback ($callback) { $body ... } }) }; }2 case { $_ $taskname:ident { $body ... } } => {: letstx $callback = [makeIdent("callback", #{$_})]; return #{ + function $taskname($callback) {J setup_state_machine $_ $callback ($callback) { $body ... } } }; }% case { $_ () { $body ... } } => {: letstx $callback = [makeIdent("callback", #{$_})]; return #{# (function ($callback) {J setup_state_machine $_ $callback ($callback) { $body ... } }) }; }5 case { $_ ($x:ident (,) ...) { $body ... } } => {: letstx $callback = [makeIdent("callback", #{$_})]; return #{0 (function ($x (,) ... , $callback) {W setup_state_machine $_ $callback ($x (,) ... , $callback) { $body ... } }) }; }4 case { $_ $taskname:ident() { $body ... } } => {: letstx $callback = [makeIdent("callback", #{$_})]; return #{+ function $taskname($callback) {J setup_state_machine $_ $callback ($callback) { $body ... } } }; } D case { $_ $taskname:ident($x:ident (,) ...) { $body ... } } => {: letstx $callback = [makeIdent("callback", #{$_})]; return #{8 function $taskname($x (,) ... , $callback) {W setup_state_machine $_ $callback ($x (,) ... , $callback) { $body ... } } }; } }J// A "task" consists of a sequence of "statements" separated by ";". EachH// statement may be a synchronous action or an asynchronous one, but allH// statements are treated the same by `task`, by inserting an async stepF// between them. The following control structures are also supported -//0// 1. `if { ... }` and `if { ... } else { ... }`// 2. `while (...) { ... }`!// 3. `for (...;...;...) { ... }`$// 4. `catch (ErrorClass e) { ... }`// 5. `catch (e) { ... }`// 6. `finally { ... }`// 7. `finally func(args ...);`I// 8. `switch (val) { case v1: { } case v2,v3,v4: { } case v5: { } ... }`// 9. `throw expr;`%// 10. `return expr1 , expr2 , ... ;`// H// There is no separate `try` statement supported since in my experienceG// code that requires a local try-catch within a function almost alwaysE// has a bad design decision in it regarding error management, and/orG// could easily be refactored to make the error concerns clearer. Also,G// syntactically, placing the error handling code encourages postponingI// thinking about error conditions whereas putting catch clauses up frontG// forces thinking about them early on .. and close to the code that is?// actually relevant. For example, it is much clearer to state E// "begin a transaction now, if there is any error later on, rollback?// the transaction." which is expressed with this approach as -//// var tx = db.begin();// catch (e) {// tx.rollback(); // }-// ...256 lines of code that can fail...//"// as opposed to the traditional -//// var tx = db.begin(); // try {1// ...256 lines of code that can fail...// } catch (e) {// tx.rollback();// throw e; // }//B// Note: While there is a `throw e` in the traditional code above,G// there is none in the `catch` clause within a `task`. This is becauseG// if a catch clause doesn't "handle" the error, it automatically gets H// rethrown. "Handling" an error amounts to `return`ing without an error // from within a `catch` clause.//F// The following statement forms are supported within the task body as>// well as within the bodies of the above control structures -//J// 1. `var x = expr1, y = expr2, ... ;` This is interpreted as declarationK// and initialization of state variables. The initialization part is not// optional.//K// 2. `x, y, z <- blah[42].bling().asyncMethod(arg1, arg2);` will insert anL// additional `callback` argument to the method (or function) invocation,=// collect the results passed to the callback of the form N// `function (err, x, y, z) { ... }` and assign them to the state variables// `x`, `y` and `z`.//K// 3. `<- blah[42].bling().asyncMethod(arg1, arg2);` will insert a callbackJ// function of the form `function (err) { ... }` - i.e. no result valueJ// is expected of the callback. To make this form clearer, you can also.// use `await` instead of the leading `<-`.//F// 4. `x <- chan EXPR;` expects the expression `EXPR` to evaluate to aG// `Channel` object (see `channel.js`). `x` will be assigned to the C// value produced by the channel when `.take()` is called on it.C// This is a simpler syntax for `var ch = EXPR; x <- ch.take();`//G// All other statements separated by ";" are treated as synchronous and"// passed through the macro as is.//F// If you want to work with concurrently executing tasks, use channelsA// to coordinate them. Notable, `Channel.merge([ch1, ch2, ...])` F// will make a channel into which all the given channels will be setupP// to pipe their results. The merged channel will yield `{chan: ch, val: value}`H// objects so that you can do different things based on the channel that// produced the value.//I// Sometimes, you want to be able to handle an error in a recoverable wayJ// after the async operation completes. You can use the `<<-` operator forG// that. It works in the same way as the `<-` operator, except that theO// first variable is bound to the error. No async exception is raised with this// operator. For example -//A// err, result <<- fs.readFile("somewhere/file.txt", 'utf8');// if (err) {%// result = "Default value";// }//"// ## Setting up the state machine// I// To setup a state machine, we scan the body to find the machine's stateE// variables and declare them up front. This simplifies the need for F// local var declarations in the generated JS ... which are not really// local anyway.macro setup_state_machine {8 rule { $task $callback $formals { $body ... } } => { var StateMachine = arguments.callee.StateMachine || (arguments.callee.StateMachine = require('cspjs/src/state_machine'));* declare_state_arguments $formals ;b var state_machine = new StateMachine(this, $callback, state_machine_fn, arguments.callee);P declare_state_variables $task state_machine 0 ($callback) { $body ... } ( function state_machine_fn(err) {` if (err && !state_machine.state.isUnwinding) { return state_machine.callback(err); } try {1 switch (state_machine.state.id) { case 1:E // `step_state` is the real work horse, whichC // walks through each statement in the taskC // body and compiles it to a single step in- // the state machine.F step_state $task state_machine 1 { $body ... } } } catch (e) {* state_machine.callback(e); } } state_machine.start();- return state_machine.controlAPIMaker; }}// ## Declaring state variables//N// To do this, we scan the code and collect all the state variable identifiersP// into a pseudo list syntax that looks like `(x y z ...)`. The `$vars` argumentD// to the `declare_state_variables` macro is expected to match this.macro declare_state_variables {l rule { $task $state_machine $fin $vars { if ($x ...) { $then ... } else { $else ... } $rest ... } } => {a declare_state_variables $task $state_machine $fin $vars { $then ... $else ... $rest ... } }Z rule { $task $state_machine $fin $vars { if ($x ...) { $then ... } $rest ... } } => {W declare_state_variables $task $state_machine $fin $vars { $then ... $rest ... } }% // Rewrite for loops using while.v rule { $task $state_machine $fin $vars { for ($init ... ; $cond ... ; $next ...) { $body ... } $rest ... } } => { declare_state_variables $task $state_machine $fin $vars { $init ... ; while ($cond ...) { $body ... $next ... ; } $rest ... } }] rule { $task $state_machine $fin $vars { while ($x ...) { $body ... } $rest ... } } => {W declare_state_variables $task $state_machine $fin $vars { $body ... $rest ... } }G // If a finally block is encountered somewhere in the body, then weR // need to be able to save and restore state variables. So keep track of that.X rule { $task $state_machine $fin $vars { finally { $cleanup ... } $rest ... } } => {W declare_state_variables $task $state_machine 1 $vars { $cleanup ... $rest ... } }k rule { $task $state_machine $fin $vars { finally $cleanup ... ($args:expr (,) ...) ; $rest ... } } => {M declare_state_variables $task $state_machine $fin $vars { $rest ... } }o rule { $task $state_machine $fin $vars { catch ($eclass:ident $e:ident) { $handler ... } $rest ... } } => {j declare_state_variables $task $state_machine $fin $vars { var $e = null ; $handler ... $rest ... } }a rule { $task $state_machine $fin $vars { catch ($e:ident) { $handler ... } $rest ... } } => {j declare_state_variables $task $state_machine $fin $vars { var $e = null ; $handler ... $rest ... } } rule { $task $state_machine $fin $vars { switch ($x ...) { $(case $ix:lit (,) ... : { $body ... }) ... } $rest ... } } => {^ declare_state_variables $task $state_machine $fin $vars { $($body ...) ... $rest ... } }K rule { $task $state_machine $fin $vars { $step ... ; $rest ... } } => {b declare_state_variables_step $task $state_machine $fin $vars { $step ... ; } { $rest ... } }6 rule { $task $state_machine $fin $vars { } } => { ? declare_unique_varset $task $state_machine $fin $vars ; }3 rule { $task $state_machine $fin () { } } => {  }}G// After scanning the entire body, we uniquify the variable set becauseF// the body may contain multiple declarations of the same variable andM// we don't want to pollute the generated code with repeated var declarations// as much as we can.macro declare_unique_varset {3 case { _ $task $state_machine $fin ($v ...) } => { var vars = #{$v ...};( var varnames = vars.map(unwrapSyntax); var uniqvarnames = {};C varnames.forEach(function (v) { uniqvarnames['%' + v] = true; });r letstx $uvars ... = Object.keys(uniqvarnames).map(function (v) { return makeIdent(v.substring(1), #{$task}); });F return #{ declare_varset $task $state_machine $fin ($uvars ...) ; }; }}macro declare_varset {3 rule { $task $state_machine 0 ($v ...) ; } => { var $v (,) ... ; }3 rule { $task $state_machine 1 ($v ...) ; } => { var $v (,) ... ;8 $state_machine.captureStateVars = function () { ! return [$v (,) ...]; };< $state_machine.restoreStateVars = function (state) { var i = 0;# $($v = state[i++];) ... }; }}$macro declare_state_variables_step {X rule { $task $state_machine $fin ($v ...) { $x:ident := $y ... ; } { $rest ... } } => {M declare_state_variables $task $state_machine $fin ($x $v ...) { $rest ... } }X rule { $task $state_machine $fin ($v ...) { $x:ident <- $y ... ; } { $rest ... } } => {M declare_state_variables $task $state_machine $fin ($x $v ...) { $rest ... } }Y rule { $task $state_machine $fin ($v ...) { $x:ident <<- $y ... ; } { $rest ... } } => {M declare_state_variables $task $state_machine $fin ($x $v ...) { $rest ... } }` rule { $task $state_machine $fin ($v ...) { $x:ident (,) ... <- $y ... ; } { $rest ... } } => {Q declare_state_variables $task $state_machine $fin ($x ... $v ...) { $rest ... } }a rule { $task $state_machine $fin ($v ...) { $x:ident (,) ... <<- $y ... ; } { $rest ... } } => {Q declare_state_variables $task $state_machine $fin ($x ... $v ...) { $rest ... } }g rule { $task $state_machine $fin ($v ...) { var $($x:ident = $y:expr) (,) ... ; } { $rest ... } } => {Q declare_state_variables $task $state_machine $fin ($x ... $v ...) { $rest ... } }[ rule { $task $state_machine $fin ($v ...) { chan $x:ident (,) ... ; } { $rest ... } } => {Q declare_state_variables $task $state_machine $fin ($x ... $v ...) { $rest ... } }G rule { $task $state_machine $fin $vs { $x ... ; } { $rest ... } } => {E declare_state_variables $task $state_machine $fin $vs { $rest ... } }}macro declare_state_arguments {! rule { ($x:ident (,) ...) } => {2 var argi = 0, $($x = arguments[argi++]) (,) ...; }}.// ## Compiling the steps of the state machine//O// The `step_state` macro extracts the relevant bit of code to be compiled intoO// a "step" and passes it over to the `step_state_line` macro. This extra layerO// is useful since not all of the syntax in the body of a task are separated byN// ";" markers. The control structures `if`, `while`, `finally` and `catch` doM// not use ";" as separators to keep the code body of a task looking as close)// to traditional javascript as possible.macro step_state {e rule { $task $state_machine $id { if ($x ...) { $then ... } else { $else ... } $rest ... } } => {w step_state_line_if_else $task $state_machine $id { if ($x ...) { $then ... } else { $else ... } } { $rest ... } }S rule { $task $state_machine $id { if ($x ...) { $then ... } $rest ... } } => {_ step_state_line_if $task $state_machine $id { if ($x ...) { $then ... } } { $rest ... } }% // Rewrite for loops using while.o rule { $task $state_machine $id { for ($init ... ; $cond ... ; $next ...) { $body ... } $rest ... } } => {q step_state $task $state_machine $id { $init ... ; while ($cond ...) { $body ... $next ... ; } $rest ... } }V rule { $task $state_machine $id { while ($x ...) { $body ... } $rest ... } } => {e step_state_line_while $task $state_machine $id { while ($x ...) { $body ... } } { $rest ... } }R rule { $task $state_machine $id { finally { $cleanup ... } $rest ... } } => {i step_state_line_finally_block $task $state_machine $id { finally { $cleanup ... } } { $rest ... } }e rule { $task $state_machine $id { finally $cleanup ... ($args:expr (,) ...) ; $rest ... } } => {v step_state_line_finally_expr $task $state_machine $id { finally $cleanup ... ($args (,) ...) ; } { $rest ... } }Y rule { $task $state_machine $id { catch ($x ...) { $handler ... } $rest ... } } => {h step_state_line_catch $task $state_machine $id { catch ($x ...) { $handler ... } } { $rest ... } }T rule { $task $state_machine $id { switch ($x:expr) { $b ... } $rest ... } } => {` step_state_line_switch $task $state_machine $id { switch ($x) { $b ... } } { $rest ... } }D rule { $task $state_machine $id { $step ... ; $rest ... } } => {N step_state_line $task $state_machine $id { $step ... ; } { $rest ... } }. rule { $task $state_machine $id { } } => {, $state_machine.callback(null, true); break; }}// ## Counting states//T// For the control structures that perform branching to different parts of the code,T// we need to be able to determine the state ids of the branch and merge statements.W// `count_states` will count the number of states added by a given block of statements,S// including control structures, so that the jump ahead positions can be determined// during compilation.//R// The second argument to `count_states` is a pseudo list of the form `(m n ...)` Z// where `m`, `n` are plain integers. The list is summed up at the end by `sumpup_counts` // to produce the final count.macro count_states {[ rule { $task ($n ...) { if ($x ...) { $then ... } else { $else ... } $rest ... } } => {G count_states $task (3 $n ...) { $then ... $else ... $rest ... } }H rule { $task ($n ...) { if ($x ...) { $then ... } $rest ... } } => {= count_states $task (2 $n ...) { $then ... $rest ... } }% // Rewrite for loops using while.^ rule { $task $n { for ($init ... ; $cond ... ; $next ...) { $body ... } $rest ... } } => {c count_states $task $n { $init ... ; while ($cond ...) { $body ... $next ... ; } $rest ... } }K rule { $task ($n ...) { while ($x ...) { $body ... } $rest ... } } => {= count_states $task (2 $n ...) { $body ... $rest ... } }G rule { $task ($n ...) { finally { $cleanup ... } $rest ... } } => {@ count_states $task (2 $n ...) { $cleanup ... $rest ... } }Z rule { $task ($n ...) { finally $cleanup ... ($args:expr (,) ...) ; $rest ... } } => {3 count_states $task (1 $n ...) { $rest ... } }N rule { $task ($n ...) { catch ($e ...) { $handler ... } $rest ... } } => {@ count_states $task (2 $n ...) { $handler ... $rest ... } }o rule { $task ($n ...) { switch ($x ...) { $(case $ix:lit (,) ... : { $body ... }) ... } $rest ... } } => {Y count_states $task (1 $n ...) { $($body ... phi $state_machine ;) ... $rest ... } }4 rule { $task $n { $step ... ; $rest ... } } => {@ count_states_line $task $n { $step ... ; } { $rest ... } }% rule { $task ($n ...) { } } => {  sumup_counts ($n ...) }}U// BUG in sweetjs? Theoretically, it should be possible to merge these into the aboveG// count_states macro itself, but only this separation works correctly!macro count_states_line {T rule { $task ($n ...) { $x:ident (,) ... <- chan $y ... ; } { $rest ... } } => {3 count_states $task (2 $n ...) { $rest ... } }Q rule { $task ($n ...) { $x:ident (,) ... <- $y ... (); } { $rest ... } } => {3 count_states $task (2 $n ...) { $rest ... } }R rule { $task ($n ...) { $x:ident (,) ... <<- $y ... (); } { $rest ... } } => {3 count_states $task (2 $n ...) { $rest ... } }c rule { $task ($n ...) { $x:ident (,) ... <- $y ... ($args:expr (,) ...); } { $rest ... } } => {3 count_states $task (2 $n ...) { $rest ... } }d rule { $task ($n ...) { $x:ident (,) ... <<- $y ... ($args:expr (,) ...); } { $rest ... } } => {3 count_states $task (2 $n ...) { $rest ... } }I rule { $task ($n ...) { $x:ident := $y ... (); } { $rest ... } } => {3 count_states $task (1 $n ...) { $rest ... } }[ rule { $task ($n ...) { $x:ident := $y ... ($args:expr (,) ...); } { $rest ... } } => {3 count_states $task (1 $n ...) { $rest ... } }> rule { $task ($n ...) { $step ... ; } { $rest ... } } => {3 count_states $task (1 $n ...) { $rest ... } }}macro sumup_counts { case { $_ ($n ...) } => {W var sum = #{$n ...}.map(unwrapSyntax).reduce(function (a,b) { return a + b; });. letstx $sum = [makeValue(sum, #{$_})]; return #{$sum}; }}// ### Branching on conditions//V// `if { ... } else { ... }` blocks work as expected in normal javascript, except that1// async statements can also be used within them.macro step_state_line_if_else {n case { $me $task $state_machine $id { if ($x:expr) { $then ... } else { $else ... } } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{F var jumpThen = 1 + (count_states $task (0) { $then ... });F var jumpElse = 1 + (count_states $task (0) { $else ... });? $state_machine.pushPhi($id2 + jumpThen + jumpElse); if (!($x)) {5 $state_machine.goTo($id2 + jumpThen); break; } case $id2:| step_state $task $state_machine $id2 { $then ... phi $state_machine ; $else ... phi $state_machine ; $rest ... } }; }}macro step_state_line_if {[ case { $me $task $state_machine $id { if ($x:expr) { $then ... } } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{B var jump = 1 + (count_states $task (0) { $then ... }); if ($x) {4 $state_machine.pushPhi($id2 + jump); } else {1 $state_machine.goTo($id2 + jump); break; } case $id2:\ step_state $task $state_machine $id2 { $then ... phi $state_machine; $rest ... } }; }}macro step_state_line_switch { // ### Multi-tasking //O // `switch (expr) { case 0: { ... } case 1: { ... }}` can be used to manageN // coordination of multiple tasks. The `expr` is an expression whose valueO // is matched with the case literals to decide where to branch. The value L // coming in on such a "merged channel" has a `chan` property that givesQ // the channel itself that produced the value and a `val` property containingN // the value. You can attach identifiers to your channels and switch basedH // on them, or you can using `===` tests on the channels themselves. //M // There MUST be one `case` clause for each channel in the merge list, or* // an error will be raised at runtime. //% // You'd use `switch` like this - // ) // function addIndex(chan, ix) { // chan.ix = ix; // return chan; // }C // mch = Channel.merge([ch1, ch2, ... chN].map(addIndex)); // while (true) { // x <- chan mch;$ // switch (x.chan.ix) {- // case 0: { ... x.val ... }- // case 1: { ... x.val ... } // } // } // O // i.e., for the most part `switch` works like normal in Javascript, exceptM // that `break;` statements are not needed, and an exception is raised if+ // an unhandled case occurs at runtime. case { $me $task $state_machine $id { switch ($c:expr) { $(case $ix:lit (,) ... : { $body ... }) ... } } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{ var tmp1;: if (!(tmp1 = $state_machine.jumpTable($id))) { tmp1 = $state_machine.jumpTable($id, [$([$ix (,) ...]) (,) ...], [$((count_states $task (0) { $body ... })) (,) ...]); }0 tmp1.jumpToCase($state_machine, $c); break; case $id2:2 step_state $task $state_machine $id2 {5 $($body ... phi $state_machine ;) ... $rest ... } }; }}// ### Looping using `while`//U// The usual `while (cond) { body... }` is supported as well, except that there is no// `break;' statement support.macro step_state_line_while {^ case { $me $task $state_machine $id { while ($x:expr) { $body ... } } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{@ var jumpBody = count_states $task (0) { $body ... }; if ($x) {, $state_machine.pushPhi($id); } else {9 $state_machine.goTo($id2 + 1 + jumpBody); break; } case $id2:] step_state $task $state_machine $id2 { $body ... phi $state_machine ; $rest ... } };9 } }// ### Exception mechanism//K// Error handling inside tasks uses a different and more expressive form ofH// exceptions. There is no `try` clause since any statement may throw anI// exception that will be forwarded to the callback provided to the task.//O// `finally` statements/blocks can be placed anywhere and will register actionsM// to be executed before a) reaching the catch clause immediately above or b)L// exiting the block in which they occur. These statements/blocks execute inH// the order opposite to the order in which they were encountered duringJ// running. If these occur within a loop, then the statements/blocks willO// execute as many times as the loop did, once for every loop iteration. (So be,// aware of what you want to be cleaned up.)]// `finally funcExpr(args...);` statement causes the `funcExpr` and `args...` to be evaluated_// at the time the statement is encountered, but defers the call itself to be made at unwinding// time.//^// `finally obj.method(args...);` is also a supported form. The `obj` and `args` are evaluated`// when the `finally` statement is encountered, but the call itself is performed at cleanup time// (obviously).$macro step_state_line_finally_expr {{ case { $me $task $state_machine $id { finally $cleanup ... . $methId:ident ($arg:expr (,) ...) ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})];T /* Evaluate the arguments right now, but call the cleanup function later. */ return #{% var tmp1 = $cleanup ... ;Q $state_machine.pushCleanupAction(tmp1, tmp1.$methId, [$arg (,) ...]); case $id2:> step_state $task $state_machine $id2 { $rest ... } }; }~ case { $me $task $state_machine $id { finally $cleanup ... [ $methExpr:expr ] ($arg:expr (,) ...) ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})];T /* Evaluate the arguments right now, but call the cleanup function later. */ return #{% var tmp1 = $cleanup ... ;T $state_machine.pushCleanupAction(tmp1, tmp1[$methExpr], [$arg (,) ...]); case $id2:> step_state $task $state_machine $id2 { $rest ... } }; }k case { $me $task $state_machine $id { finally $cleanup ... ($arg:expr (,) ...) ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})];T /* Evaluate the arguments right now, but call the cleanup function later. */ return #{R $state_machine.pushCleanupAction(this, $cleanup ... , [$arg (,) ...]); case $id2:> step_state $task $state_machine $id2 { $rest ... } }; }}F// `finally { ... }` mark blocks of steps to be run at unwinding time.%macro step_state_line_finally_block {Y case { $me $task $state_machine $id { finally { $cleanup ... } } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{F var jumpHandler = count_states $task (0) { $cleanup ... };I $state_machine.pushCleanupStep($id2, $id2 + 1 + jumpHandler); break; case $id2:` step_state $task $state_machine $id2 { $cleanup ... phi $state_machine ; $rest ... } }; }}L// `catch (e) { ... }` blocks will catch all exceptions thrown by statementsK// that follow the block up to the end of the block's scope, bind the error@// to `e` and run the sequence of statements within the `{...}`.//K// `catch (ErrorClass e) {...}` will catch and handle only those errors `e`M// that satisfy `e instanceof ErrorClass`. Other errors propagate up to catch// clauses above.macro step_state_line_catch {p case { $me $task $state_machine $id { catch ($eclass:ident $e:ident) { $handler ... } } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{F var jumpHandler = count_states $task (0) { $handler ... };G $state_machine.pushErrorStep($id2, $id2 + 1 + jumpHandler); break; case $id2:* $e = $state_machine.state.err;1 if (!($e && $e instanceof $eclass)) {% $state_machine.phi(); break; }` step_state $task $state_machine $id2 { $handler ... phi $state_machine ; $rest ... } }; }b case { $me $task $state_machine $id { catch ($e:ident) { $handler ... } } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{F var jumpHandler = count_states $task (0) { $handler ... };G $state_machine.pushErrorStep($id2, $id2 + 1 + jumpHandler); break; case $id2:* $e = $state_machine.state.err;` step_state $task $state_machine $id2 { $handler ... phi $state_machine ; $rest ... } }; }}// ## step_state_line//N// This is the real work horse which walks through each statement and compiles5// it into an asynchronous step in the state machine.macro step_state_line { // ### await //N // The `await func(args...);` clause is a synonym for `<- func(args...);`.Q case { $me $task $state_machine $id { await $y ... (); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{1 $y ... ($state_machine.thenTo($id2)); break; case $id2:> step_state $task $state_machine $id2 { $rest ... } }; }c case { $me $task $state_machine $id { await $y ... ($args:expr (,) ...); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{A $y ... ($args (,) ... , $state_machine.thenTo($id2)); break; case $id2:> step_state $task $state_machine $id2 { $rest ... } }; }U case { $me $task $state_machine $id { await $x:ident ... ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{U $state_machine.resolve([$x (,) ...], false, $state_machine.thenTo($id2)); break; case $id2:0 var chans = arguments[1], i = 0;0 $($x = chans[i++].resolve();)...B step_state $task $state_machine $id2 { $rest ... } }; } & // ### Taking values from channels //[ // If you have functions that return channels on which they will produce their results,\ // then you can use this expression as syntax sugar to get the value out of the returned // channel. //0 // val <- chan someProcess(arg1, arg1);b case { $me $task $state_machine $id { $x:ident (,) ... <- chan $y ... ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});V letstx $id2 = [makeValue(id + 1, #{$id})], $id3 = [makeValue(id + 2, #{$id})];Y // In this form (ex: z <- chan blah[32].bling(); ), the expression is expected to@ // produce a channel, from which a value will be taken. //T // Type detection is done by looking for a `take` method, so any object thatA // has the same `take` protocol as a channel can be used. return #{ var tmp1 = $y ...;$ if (tmp1 && tmp1.take) {7 tmp1.take($state_machine.thenTo($id2)); } else {E throw new Error('Expected a channel in step ' + $id); } break; case $id2: var i = 1;' $($x = arguments[i++];) ... case $id3:B step_state $task $state_machine $id3 { $rest ... } }; } // ### Retrieving values //P // Values are retrieved from async steps using the `<-` clause of the form - //> // x, y, z <- coll[42].thing.asyncMethod(arg1, arg2); //\ // This block and the following are basically the same. The problem is that I don't knowS // how to insert the additional callback argument with a preceding comma in one) // case and without one in the other. //T // If you use ':=' instead of '<-', the operation is started off in parallel and] // the variable on the LHS (only one allowed in this case) will be bound to a new channelW // on which the result can be received. You can subsequently do "await x;" to causeU // x to be bound to the value received on the new channel, and further statementsX // can use the value directly. If you have multiple such channels bound to variablesX // x, y, z, you can await for a single value from each of them using "await x y z;".9 // If any errors occur, an exception will be raised. _ case { $me $task $state_machine $id { $x:ident (,) ... <- $y ... (); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});V letstx $id2 = [makeValue(id + 1, #{$id})], $id3 = [makeValue(id + 2, #{$id})]; return #{1 $y ... ($state_machine.thenTo($id2)); break; case $id2: var i = 1;' $($x = arguments[i++];) ... case $id3:B step_state $task $state_machine $id3 { $rest ... } }; }` case { $me $task $state_machine $id { $x:ident (,) ... <<- $y ... (); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});V letstx $id2 = [makeValue(id + 1, #{$id})], $id3 = [makeValue(id + 2, #{$id})]; return #{8 $y ... ($state_machine.thenToWithErr($id2)); break; case $id2: var i = 1;' $($x = arguments[i++];) ... case $id3:B step_state $task $state_machine $id3 { $rest ... } }; }q case { $me $task $state_machine $id { $x:ident (,) ... <- $y ... ($args:expr (,) ...); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});V letstx $id2 = [makeValue(id + 1, #{$id})], $id3 = [makeValue(id + 2, #{$id})]; return #{A $y ... ($args (,) ... , $state_machine.thenTo($id2)); break; case $id2: var i = 1;' $($x = arguments[i++];) ... case $id3:B step_state $task $state_machine $id3 { $rest ... } }; }r case { $me $task $state_machine $id { $x:ident (,) ... <<- $y ... ($args:expr (,) ...); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});V letstx $id2 = [makeValue(id + 1, #{$id})], $id3 = [makeValue(id + 2, #{$id})]; return #{H $y ... ($args (,) ... , $state_machine.thenToWithErr($id2)); break; case $id2: var i = 1;' $($x = arguments[i++];) ... case $id3:B step_state $task $state_machine $id3 { $rest ... } }; }W case { $me $task $state_machine $id { $x:ident := $y ... (); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{0 $x = $x || $state_machine.channel();# $y ... ($x.resolver()); case $id2:B step_state $task $state_machine $id2 { $rest ... } }; }i case { $me $task $state_machine $id { $x:ident := $y ... ($args:expr (,) ...); } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{0 $x = $x || $state_machine.channel();3 $y ... ($args (,) ... , $x.resolver()); case $id2:B step_state $task $state_machine $id2 { $rest ... } }; }% // ### State variable declaration //P // State variables are shared with expressions in the entire task and can beK // declared anywhere using var statements. Initializers are compulsory.d case { $me $task $state_machine $id { var $($x:ident = $y:expr) (,) ... ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{ $($x = $y;) ... case $id2:A step_state $task $state_machine $id2 { $rest ... } }; } 7 // Bad idea to use uninitialized vars for channels.F // Now you use "chan x, y, z;" to declare and initialize channels.X case { $me $task $state_machine $id { chan $x:ident (,) ... ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{7 $($x = $x || $state_machine.channel();) ... case $id2:A step_state $task $state_machine $id2 { $rest ... } }; } ' // ### Returning values from a task //F // `return x, y, ...;` will result in the task winding back up anyM // `finally` actions and then providing the given values to the next taskJ // by calling the last callback argument to the task. Such a statementO // will, obviously, return from within any block within control structures. //O // Though you can return from anywhere in this implementation, don't return# // from within finally clauses.Y case { $me $task $state_machine $id { return $x:expr (,) ... ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{6 $state_machine.callback(null, $x (,) ...); break; case $id2:A step_state $task $state_machine $id2 { $rest ... } }; } // ### Raising errors //J // The usual `throw err;` form will cause the error to first bubble upJ // the `finally` actions and the installed `catch` sequence and if theI // error survives them all, will be passed on to the task's callback. //@ // Hack: "throw object.err;" can be used as a short hand forH // "if (object.err) { throw object.err; }". i.e. the error is thrownM // only if it is not null or undefined or false. This fits with Node.js'sI // callback convention where `err === null` tests whether there is an= // error or not. So throwing a `null` doesn't make sense.P case { $me $task $state_machine $id { throw $e:expr ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{ var tmp1 = $e;? if (tmp1) { $state_machine.callback(tmp1); break; } case $id2:A step_state $task $state_machine $id2 { $rest ... } }; }' // ### Retrying a failed operation. //= // Within a catch block, you can use the retry statement  // // retry; // > // to jump control again to the beginning of the code that@ // the catch block traps errors for ... which is immediately1 // after the ending brace of the catch block.H case { $me $task $state_machine $id { retry ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{# $state_machine.retry(); break; case $id2:? step_state $task $state_machine $id2 { $rest ... } }; } // ## Internals // // ### `phi` //L // Used to merge states when branching using `if`, `while` and `switch`.U case { $me $task $state_machine $id { phi $state_machine ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{! $state_machine.phi(); break; case $id2:A step_state $task $state_machine $id2 { $rest ... } }; }! // ### Synchronous statements //K // Any statement that doesn't match the above structures are consideredN // to be executed synchronously. While each sync step is given its own id,Q // there isn't an async separation between these steps. The plus side of thatM // is that one more event-loop cycle is avoided, but the minus is that weA // lose the otherwise more fine grained multi-tasking we get. //K // I may change my mind about whether or not to introduce an additionalJ // async step, but that decision won't impact the meaning of the code.I case { $me $task $state_machine $id { $x ... ; } { $rest ... } } => {& var id = unwrapSyntax(#{$id});2 letstx $id2 = [makeValue(id + 1, #{$id})]; return #{ $x ... ; case $id2:> step_state $task $state_machine $id2 { $rest ... } }; }} export task5_lnum>2JCvT{=?K setup_state_machine $_ $callback ( $callback) { $body ... }5_mon>2'vT{=?J setup_state_machine $_ $callback ($callback) { $body ... }5_npo''vT'(&( " case { $_ { $body ... } } => {: letstx $callback = [makeIdent("callback", #{$_})]; return #{ # (function ($callback) {J setup_state_machine $_ $callback ($callback) { $body ... } }) }; }5_oqp&(vT&'5_prq0(vT/1.1 }5_qsr((vT')# case { $_ ({ $body ... } } => {5_rts((vT')$ case { $_ (){ $body ... } } => {5_st((vT')% case { $_ () { $body ... } } => {5