UNPKG

15.5 kBtext/coffeescriptView Raw
1kit = require "./kit"
2config = require "./config"
3root = module.exports = {}
4assert = require "assert"
5
6CAPTURE_SET = true
7
8# local variables dependencies tracking
9class VarUsage
10 constructor: ->
11 @sets = {}
12 @upds = {}
13 @mods = {}
14 @uses = {}
15 @after = {}
16 @before = {}
17 @vars = {}
18 # compiles stored information into environment used by builder
19 init: ->
20 deps = {}
21 for i, v of @vars when v and not @refs[i]
22 w = deps[i] = {}
23 w.fin = true unless @after[i]
24 w.use = true if @uses[i]
25 if @sets[i]
26 if @before[i]
27 w.mod = true
28 else
29 w.set = true
30 if @upds[i]
31 w.use = w.mod = true
32 deps
33 # sets map of variables used before the current node on LHS
34 setBefore: (known) ->
35 for i of known
36 @before[i] = true
37 if @sets[i]
38 @sets[i] = false
39 @upds[i] = true
40 # marks variable with the `name` as used on RHS
41 addAssign: (name) ->
42 if @uses[name]
43 @upds[name] = true
44 else
45 @sets[name] = true
46 @mods[name] = true
47 @mod = true
48 @
49 # marks variable with the `name` as used in LHS and RHS in the node
50 addUpd: (name) ->
51 @upds[name] = true
52 @uses[name] = true
53 @mods[name] = true
54 @mod = true
55 @upd = true
56 @
57 skipMods: -> @_skipMods = true; @
58 # adds information from child node
59 # aggregates used and modified variables
60 addInner: (d) ->
61 @mod = true if d.mod
62 @upd = true if d.upd
63 unless @_skipMods
64 @mods[i] = true for i,v of d.mods when v
65 @uses[i] = true for i,v of d.uses when v
66 return
67 # transfer all information from child node
68 # before that child node is embedded into the parent
69 addInnerDeep: (d) ->
70 @addInner(d)
71 @mod = true if d.mod
72 @upd = true if d.upd
73 @upds[i] = true for i,v of d.upds when v
74 @uses[i] = true for i,v of d.uses when v
75 for i,v of d.sets when v
76 if @uses[i]
77 @upds[i] = true
78 @upd = true
79 else
80 @sets[i] = true
81 return
82 threadOutMap: (res) ->
83 res ?= {}
84 for i, v of @mods when v
85 continue unless @after[i]
86 continue unless @vars[i] and not @refs[i]
87 res[i] = true
88 res
89
90envPush = (dst, src) ->
91 for i, v of src
92 w = dst[i] ?= {}
93 w.mod = true if v.mod or v.set and w.use
94 w.use = true if v.use
95 w.set = true if v.set and not w.mod
96 w.fin = true if v.fin
97 w.thread = true if v.thread and not v.fin
98 dst
99
100envMerge = (dst, src) ->
101 for i, v of src
102 w = dst[i] ?= {}
103 w.use = true if v.use
104 w.mod = true if v.mod
105 w.fin = true if v.fin
106 w.thread = true if v.thread and not v.fin
107 dst
108
109envThread = (env) ->
110 (i for i, v of env when v.thread and not v.fin).sort().map(kit.id)
111envBefore = (f, s) ->
112 for i, v of s when (w = f[i]) and w.use
113 v.use = true
114 return
115
116root.VarUsage = VarUsage
117
118$debId = 0
119
120# JS AST builder
121# two builders are joined with `append` or `prepend` methods
122class Builder
123 constructor: ->
124 @env = {}
125 @opts = {}
126 @$debId = $debId++
127 setOpts: (opts) ->
128 o = @opts
129 if o?
130 o[i] = v for i,v of opts when not o[i]?
131 else
132 @opts = opts
133 @
134 # sets if the built expression needs also to be coerced
135 needCoerce: (v) ->
136 v ?= true
137 @_needCoerce = v
138 @
139 # returns builder returning coerced expression
140 coerceObj: ->
141 return @ unless @_needCoerce
142 @toExprBuilder()._coerceObj()
143 _coerceObj: -> @
144 # sets if the built expression needs also to be coerced with exceptions wrap
145 noCoerceTry: -> @_noCoerceTry = true; @
146 # returns builder with coerced exceptions
147 coerceTry: ->
148 return @ if @_noCoerceTry and @opts.coerce is "all"
149 b = @capture().toBlockBuilder()
150 root.expr(kit.coerceThunk(b.block), b.eff).morph(b).noCoerceTry()
151 # sets/gets variable name the builder needs to bind value to
152 setBindVar: (bv) ->
153 if bv is false
154 delete @_bindvar
155 return @
156 @_bindVar = bv if bv?
157 @
158 getBindVar: -> @_bindVar
159 # marks builder as not requiring local variables capturing
160 noCapture: -> @_captured = true; @
161 # returns builder with local variables modifications captured
162 capture: ->
163 return @ if @_captured
164 @_captured = true
165 return @_capture()
166 _capture: -> @
167 # copies various options and state data from another builder
168 morph: (other,env) ->
169 @setBindVar(other.getBindVar())
170 assert.ok(other.opts)
171 @opts = other.opts
172 @_captured ?= other._captured
173 env = other.env unless env?
174 envPush(@env,env) if env
175 brk = other.isBrk()
176 @setBrk(brk) if brk?
177 @_needCoerce = other._needCoerce if not @_needCoerce?
178 @
179 # returns builder with `block` member with list of JS AST Statements
180 # representing current builder value
181 toBlockBuilder: (eff) ->
182 n = @toExprBuilder(eff)
183 block = [kit.ret(n.expr)]
184 (if n.eff then root.blockEff(block) else root.pureBlock(block)).morph(n)
185 # returns builder with `block` member with a JS AST Expression
186 # representing current builder value
187 toExprBuilder: (eff) ->
188 assert.ok(@toBlockBuilder isnt Builder::toBlockBuilder)
189 n = @toBlockBuilder(eff)
190 expr = kit.call(kit.fun([], n.block),[])
191 (if n.eff then root.exprEff(expr) else root.exprPure(expr)).morph(n)
192 # JS AST list of statements representing current value
193 toBlock: (eff) -> @capture().toBlockBuilder(eff).block
194 # JS AST expression representing current value
195 toExpr: (eff) -> @capture().toExprBuilder(eff).expr
196 # joins local variable environments
197 pushEnv: (env) -> envPush(@env, env); @
198 # joins local variables environment with `VarUsage`
199 mergeEnv: (env) ->
200 envPush(@env, env.init())
201 @
202 # returns builder representing current builder value bound to arguments
203 # given to the function
204 bindTo: ->
205 vars = []
206 exprs = []
207 eff = false
208 assert.ok(arguments.length)
209 eff or= i.eff for i in arguments
210 env = {}
211 threadVars = []
212 captVars = {}
213 threadIn = {}
214 lhs = {}
215 for i,x in arguments
216 capt = i
217 capt = capt.coerceObj() if arguments.length is 1
218 capt = capt.capture().toExprBuilder(eff)
219 tvars = []
220 for j, v of capt.env
221 w = env[j] ?= {}
222 w.use = true if v.use
223 w.mod = true if v.mod or w.use and v.set
224 w.set = true if v.set and not w.mod
225 w.fin = true if v.fin
226 if v.thread and not v.fin
227 w.thread = true
228 threadIn[j] = true
229 tvars.push j
230 if tvars.length isnt 0
231 assert.ok(arguments.length is 1)
232 threadVars.push(tvars...)
233 exprs.push(capt.expr)
234 bv = capt.getBindVar()
235 vars.push(bv) if bv?
236 vars.push(tvars.sort().map(kit.id)...)
237 threadOut = {}
238 for i, v of env
239 w = @env[i] ? {}
240 threadOut[i] = true if not v.fin and (v.thread or v.mod or w.mod)
241 thisCapt = @addThread(threadOut).capture()
242 block = thisCapt.toBlock()
243 thisCapt.setBrk(true) if @isBrk()
244 closVars = []
245 for i, v of thisCapt.env when v.mod
246 v.mod = false
247 closVars.push i unless threadIn[i]
248 closVars = closVars.sort().map(kit.id)
249 if closVars.length
250 block = [kit.ret(kit.call(kit.fun(closVars,block),closVars))]
251 for i,v of env when v.mod
252 w = thisCapt.env[i] ?= {fin:true}
253 w.use = w.mod = true
254 cont = kit.fun(vars, block)
255 return thisCapt if exprs.length is 0
256 root.exprEffCaptured(thisCapt.mkMap(exprs,cont))
257 .needCoerce(false).morph(thisCapt)
258 # specifies execution control will leave current scope (function)
259 # so no needs to thread any vars
260 setBrk: (@brk) -> @
261 # check is execution control will leave current scope
262 isBrk: -> @brk
263 # adds a map of variable to thread out of the current value
264 addThread: (thread) ->
265 if @isBrk()
266 for i, v of thread
267 (w = @env[i] ?= {fin:true}).thread = true
268 @
269 unless @_captured
270 for i, v of thread when v
271 (w = @env[i] ?= {fin:true}).thread = true
272 return @
273 env = {}
274 if thread?
275 for i,v of thread when v
276 w = @env[i]
277 continue if w? and w.fin
278 (env[i] = {}).thread = true
279 any = true if w? and not w.thread
280 for i, v of @env when not v.fin
281 w = env[i] ?= {}
282 if v.thread and not thread[i]
283 some = true
284 w.thread = true
285 return @ unless any
286 root.purePostfix(@,root.pure([]).pushEnv(env)).morph(@)
287
288# represents JS list of statements without effects
289class BlockPure extends Builder
290 constructor: (@block) -> super()
291 mkMap: (args, fun) ->
292 kit.mapply(args, fun)
293 pureBlock: -> @block
294 toPrefix: -> @
295 toBlockBuilder: (eff) ->
296 {block} = @
297 expr = @ret
298 nocoerce = @opts.coerce is "none"
299 if expr?
300 expr = kit.pure(expr) if eff and nocoerce
301 block.push(kit.ret(expr))
302 return @needCoerce(not nocoerce) unless eff
303 unless expr
304 block.push(kit.ret(kit.pure())) if nocoerce
305 root.blockEff(block).morph(@).needCoerce(not nocoerce)
306 prepend: (other) ->
307 (if @opts.bindAssoc is "left"
308 root.postfixLeft(other,@)
309 else root.purePostfix(other,@)).setOpts(@opts)
310 _append: (other) ->
311 if other.pureBlock?
312 @block.push(other.pureBlock()...)
313 envPush(@env,other.env)
314 @setBindVar(other._bindVar)
315 return @
316 root.purePrefix(@,other).setOpts(@opts)
317 append: (other) -> @_append(other)
318 coerceTry: ->
319 w = @opts.coerce
320 if w isnt "all"
321 return @
322 return root.exprEff(kit.coerceThunk(@block)).morph(@)
323 _capture: ->
324 ov = envThread(@env)
325 if ov.length
326 assert.ok(not @ret)
327 @ret = if ov.length is 1 then ov[0] else kit.arr(ov)
328 return @
329
330root.pure = (block) -> new BlockPure(block)
331
332# builder producing nothing
333class Empty extends BlockPure
334 constructor: ->
335 super([])
336 append: (other) -> other
337 prepend: (other) -> other
338
339root.empty = -> new Empty()
340
341# common parent for effectful builders
342class Eff extends Builder
343 constructor: -> super()
344 eff: true
345 mkMap: (args, fun) ->
346 kit.mbind args, fun
347 append: (other) ->
348 envBefore(@env,other.env)
349 if other.prepend?
350 res = other.prepend(@)
351 return res if res?
352 @_append other
353 _append: (other) ->
354 if not @_assocDone && @opts.bindAssoc is "left" and other.eff
355 @_assocDone = true
356 return root.postfixLeft(@,other).setOpts(other.opts)
357 other.bindTo(@)
358 _capture: ->
359 ov = envThread(@env)
360 if ov.length
361 if @getBindVar()?
362 fn = "munshiftTo"
363 val = kit.arr(ov)
364 else
365 fn = "mconst"
366 val = if ov.length is 1 then ov[0] else kit.arr(ov)
367 return root.exprEff(kit.call(
368 kit.mem(@coerceObj().toExpr(),kit.id(fn)),[val])).morph(@).noCapture()
369 return @
370
371# builder representing delayed `append`
372# the `append` will be executed after isFinal will return true
373class Cont extends Eff
374 constructor: (@inner, @cont, isFinal) ->
375 super()
376 @isFinal ?= isFinal
377 addThread: (thread) ->
378 @cont = @cont.addThread(thread)
379 @
380 setBindVar: (bv) -> @cont.setBindVar(bv); @
381 setBrk: (bv) -> @cont.setBrk(bv); @
382 isBrk: -> @cont.isBrk()
383 result: ->
384 @inner._append(@cont).morph(@)
385 append: (other) ->
386 @cont = @cont.append(other)
387 if @isFinal() then @result() else @
388 toExprBuilder: (eff) ->
389 @result().toExprBuilder(eff)
390 toBlockBuilder: (eff) ->
391 @result().toBlockBuilder(eff)
392 capture: ->
393 return @result()
394
395root.cont = (inner, cont, isFinal) -> new Cont(inner, cont, isFinal)
396
397# delayed append with right part is pure for left associative binds
398# or any for right ones
399class Postfix extends Cont
400 constructor: (inner, cont, @left) ->
401 super(inner,cont)
402 isFinal: -> not @left and @cont.eff
403
404root.purePostfix = (inner, cont) -> new Postfix(inner, cont)
405root.postfixLeft = (inner, cont) -> new Postfix(inner, cont, true)
406
407# parallel bind of several arguments
408class Par extends Eff
409 constructor: (@args, @cont) ->
410 super()
411 setBindVar: (bv) -> @cont.setBindVar(bv); @
412 addThread: (thread) ->
413 return @ if @isBrk()
414 @cont = @cont.addThread(thread)
415 @
416 setBrk: (bv) -> @cont.setBrk(bv); @
417 isBrk: -> @cont.isBrk()
418 append: (other) ->
419 if @cont.eff
420 return super(other)
421 @cont = @cont.append(other)
422 @
423 getDepVars: ->
424 @vars
425 toExprBuilder: ->
426 @cont.bindTo(@args...).morph(@).toExprBuilder()
427 capture: -> @toExprBuilder()
428
429root.par = (vars, args, cont) -> new Par(vars, args, cont)
430
431# delayed `append` where first part is pure
432class PurePrefix extends Eff
433 constructor: (@prefix, @cont) ->
434 super()
435 assert.ok(@cont.eff)
436 assert.ok(not @prefix.eff)
437 return
438 addThread: (thread) ->
439 return @ if @isBrk()
440 if @last?
441 @last = @last.addThread(thread)
442 else
443 @cont = @cont.addThread(thread)
444 @
445 setBindVar: (bv) -> (@last ? @cont).setBindVar(bv); @
446 setBrk: (bv) -> (@last ? @cont).setBrk(bv); @
447 isBrk: -> (@last ? @cont).isBrk()
448 prepend: (other) ->
449 unless other.eff
450 @prefix = other.append(@prefix)
451 return @
452 @.bindTo(other)
453 append: (other) ->
454 if @last
455 @cont = @cont.append(@last)
456 @last = other
457 @
458 toBlockBuilder: -> @capture()
459 capture: ->
460 if @last
461 last = @last
462 else
463 last = @cont
464 threadOut = {}
465 for i, v of @prefix.env when v.mod and not v.fin
466 threadOut[i] = true
467 last = last.addThread(threadOut)
468 cont = last.capture()
469 if @last
470 bv = cont._bindVar
471 cont = @cont.append(cont).capture()
472 env = {}
473 envMerge(env = {},@prefix.env)
474 envMerge(env,cont.env)
475 pblock = @prefix.toPrefix()
476 cblock = cont.toBlockBuilder()
477 for i,v of cblock.env when v.thread
478 w = env[i] ?= {fin:true}
479 w.thread = true
480 root.blockEff(pblock.block.concat(cblock.block)).morph(@,env)
481 .needCoerce(cblock._needCoerce)
482 .setBindVar(cont.getBindVar()).noCapture().setBrk(@isBrk())
483
484root.purePrefix = (prefix, cont) -> new PurePrefix(prefix, cont)
485
486# effectful AST statements list (a typical result of `toExprBuilder`)
487class BlockEff extends Eff
488 constructor: (@block) -> super()
489 toBlockBuilder: -> @
490
491root.blockEff = (block) ->
492 new BlockEff(block)
493
494root.block = (eff, block) ->
495 if eff then root.blockEff(block)
496 else root.pure(block)
497
498# effectful expression (a typical result of `toExprBuilder`)
499class ExprEff extends Eff
500 constructor: (@expr) ->
501 super()
502 _coerceObj: ->
503 w = @opts.coerce
504 return @ if w is "none"
505 root.exprEff(kit.coerceVal(@expr)).morph(@)
506 toExprBuilder: -> @
507
508root.exprEffCoerce = (expr) -> (new ExprEff(expr)).needCoerce()
509root.exprEff = (expr) -> (new ExprEff(expr)).needCoerce(false)
510root.exprEffCaptured = (expr) -> (new ExprEff(expr)).noCapture()
511
512# represents pure Expression AST (a typical result of `toExprBuilder`
513# for pure builders)
514class ExprPure extends BlockPure
515 constructor: (@expr) ->
516 super([kit.ret(@expr)])
517 toExprBuilder: (eff) ->
518 return super(eff) unless @expr
519 return @ unless eff
520 expr = if @opts.coerce is "none" then kit.pure(@expr) else @expr
521 return root.exprEff(expr).morph(@)
522 toBlockBuilder: (eff) ->
523 res = root.pure([])
524 res.ret = @expr
525 res.morph(@).toBlockBuilder(eff)
526 toPrefix: ->
527 root.pure(
528 [if @_bindVar?
529 kit.varDecl(@_bindVar,@expr)
530 else kit.exprStmt(@expr)]).morph(@).setBindVar(false)
531 _capture: ->
532 return super() unless @expr
533 ov = envThread(@env)
534 if ov.length
535 @expr = kit.arr([@expr,ov...])
536 @block = [kit.ret(@expr)]
537 return @
538
539root.exprPure = (expr) -> new ExprPure(expr)
540
541root.expr = (expr, eff) ->
542 if eff then root.exprEffCoerce(expr) else root.exprPure(expr)
543