UNPKG

51.2 kBtext/coffeescriptView Raw
1{any, concat, concatMap, difference, divMod, foldl1, map, nub, owns, partition, span, union} = require './functional-helpers'
2{beingDeclared, usedAsExpression, envEnrichments} = require './helpers'
3CS = require './nodes'
4JS = require './js-nodes'
5exports = module?.exports ? this
6
7# TODO: this whole file could use a general cleanup
8
9
10jsReserved = [
11 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
12 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'implements',
13 'import', 'in', 'instanceof', 'interface', 'let', 'native', 'new', 'null', 'package', 'private',
14 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'true', 'try',
15 'typeof', 'var', 'void', 'while', 'with', 'yield', 'arguments', 'eval'
16]
17
18
19genSym = do ->
20 genSymCounter = 0
21 (pre) -> new JS.GenSym pre, ++genSymCounter
22
23
24stmt = (e) ->
25 return e unless e?
26 if e.isStatement then e
27 else if e.instanceof JS.SequenceExpression
28 walk = (seq) ->
29 concatMap seq.expressions, (e) ->
30 if e.instanceof JS.SequenceExpression then walk e
31 else [stmt e]
32 new JS.BlockStatement walk e
33 else if e.instanceof JS.ConditionalExpression
34 # TODO: drop either the consequent or the alternate if they don't have side effects
35 new JS.IfStatement (expr e.test), (stmt e.consequent), stmt e.alternate
36 else new JS.ExpressionStatement e
37
38expr = (s) ->
39 return s unless s?
40 if s.isExpression then s
41 else if s.instanceof JS.BlockStatement
42 switch s.body.length
43 when 0 then helpers.undef()
44 when 1 then expr s.body[0]
45 else new JS.SequenceExpression map s.body, expr
46 else if s.instanceof JS.ExpressionStatement
47 s.expression
48 else if s.instanceof JS.ThrowStatement
49 new JS.CallExpression (new JS.FunctionExpression null, [], forceBlock s), []
50 else if s.instanceof JS.IfStatement
51 consequent = expr (s.consequent ? helpers.undef())
52 alternate = expr (s.alternate ? helpers.undef())
53 new JS.ConditionalExpression s.test, consequent, alternate
54 else if s.instanceof JS.ForInStatement, JS.ForStatement, JS.WhileStatement
55 accum = genSym 'accum'
56 # TODO: remove accidental mutation like this in these helpers
57 push = (x) -> stmt new JS.CallExpression (memberAccess accum, 'push'), [x]
58 s.body = forceBlock s.body
59 if s.body.body.length
60 lastExpression = s.body.body[-1..][0]
61 unless lastExpression.instanceof JS.ThrowStatement
62 # WARN: more mutation!
63 s.body.body[s.body.body.length - 1] = push expr lastExpression
64 else
65 s.body.body.push push helpers.undef()
66 block = new JS.BlockStatement [s, new JS.ReturnStatement accum]
67 iife = new JS.FunctionExpression null, [accum], block
68 new JS.CallExpression (memberAccess iife, 'call'), [new JS.ThisExpression, new JS.ArrayExpression []]
69 else if s.instanceof JS.SwitchStatement, JS.TryStatement
70 block = new JS.BlockStatement [makeReturn s]
71 iife = new JS.FunctionExpression null, [], block
72 new JS.CallExpression (memberAccess iife, 'call'), [new JS.ThisExpression]
73 else
74 # TODO: comprehensive
75 throw new Error "expr: Cannot use a #{s.type} as a value"
76
77makeReturn = (node) ->
78 return new JS.ReturnStatement unless node?
79 if node.instanceof JS.BlockStatement
80 new JS.BlockStatement [node.body[...-1]..., makeReturn node.body[-1..][0]]
81 else if node.instanceof JS.SequenceExpression
82 new JS.SequenceExpression [node.expressions[...-1]..., makeReturn node.expressions[-1..][0]]
83 else if node.instanceof JS.IfStatement
84 new JS.IfStatement node.test, (makeReturn node.consequent), if node.alternate? then makeReturn node.alternate else null
85 else if node.instanceof JS.SwitchStatement
86 new JS.SwitchStatement node.discriminant, map node.cases, makeReturn
87 else if node.instanceof JS.SwitchCase
88 return node unless node.consequent.length
89 stmts = if node.consequent[-1..][0].instanceof JS.BreakStatement then node.consequent[...-1] else node.consequent
90 new JS.SwitchCase node.test, [stmts[...-1]..., makeReturn stmts[-1..][0]]
91 else if node.instanceof JS.TryStatement
92 new JS.TryStatement (makeReturn node.block), (map node.handlers, makeReturn), if node.finalizer? then makeReturn node.finalizer else null
93 else if node.instanceof JS.CatchClause
94 new JS.CatchClause node.param, makeReturn node.body
95 # These are CoffeeScript statements. They can't be used in expression position and they don't trigger auto-return behaviour in functions.
96 else if node.instanceof JS.ThrowStatement, JS.ReturnStatement, JS.BreakStatement, JS.ContinueStatement, JS.DebuggerStatement then node
97 else if (node.instanceof JS.UnaryExpression) and node.operator is 'void' then new JS.ReturnStatement
98 else new JS.ReturnStatement expr node
99
100
101generateMutatingWalker = (fn) -> (node, args...) ->
102 for childName in node.childNodes
103 continue unless node[childName]?
104 node[childName] =
105 if childName in node.listMembers
106 for n in node[childName]
107 fn.apply n, args
108 else
109 fn.apply node[childName], args
110 node
111
112declarationsNeeded = (node) ->
113 return [] unless node?
114 if (node.instanceof JS.AssignmentExpression) and node.operator is '=' and node.left.instanceof JS.Identifier then [node.left]
115 else if node.instanceof JS.ForInStatement then [node.left]
116 #TODO: else if node.instanceof JS.CatchClause then [node.param]
117 else []
118
119declarationsNeededRecursive = (node) ->
120 return [] unless node?
121 # don't cross scope boundaries
122 if node.instanceof JS.FunctionExpression, JS.FunctionDeclaration then []
123 else union (declarationsNeeded node), concatMap node.childNodes, (childName) ->
124 # TODO: this should make use of an fmap method
125 return [] unless node[childName]?
126 if childName in node.listMembers then concatMap node[childName], declarationsNeededRecursive
127 else declarationsNeededRecursive node[childName]
128
129collectIdentifiers = (node) -> nub switch
130 when !node? then []
131 when node.instanceof JS.Identifier then [node.name]
132 when (node.instanceof JS.MemberExpression) and not node.computed
133 collectIdentifiers node.object
134 else concatMap node.childNodes, (childName) ->
135 return [] unless node[childName]?
136 if childName in node.listMembers
137 concatMap node[childName], collectIdentifiers
138 else
139 collectIdentifiers node[childName]
140
141# TODO: something like Optimiser.mayHaveSideEffects
142needsCaching = (node) ->
143 return no unless node?
144 (envEnrichments node, []).length > 0 or
145 (node.instanceof CS.FunctionApplications, CS.DoOp, CS.NewOp, CS.ArrayInitialiser, CS.ObjectInitialiser, CS.RegExp, CS.HeregExp, CS.PreIncrementOp, CS.PostIncrementOp, CS.PreDecrementOp, CS.PostDecrementOp) or
146 (any (difference node.childNodes, node.listMembers), (n) -> needsCaching node[n]) or
147 any node.listMembers, (n) -> any node[n], needsCaching
148
149forceBlock = (node) ->
150 return new JS.BlockStatement [] unless node?
151 node = stmt node
152 if node.instanceof JS.BlockStatement then node
153 else new JS.BlockStatement [node]
154
155makeVarDeclaration = (vars) ->
156 vars.sort (a, b) ->
157 a = a.name.toLowerCase()
158 b = b.name.toLowerCase()
159 if a < b then -1 else if a > b then 1 else 0
160 decls = for v in vars
161 new JS.VariableDeclarator v
162 new JS.VariableDeclaration 'var', decls
163
164# tests for the ES3 equivalent of ES5's IdentifierName
165isIdentifierName = (name) ->
166 # this regex can be made more permissive, allowing non-whitespace unicode characters
167 name not in jsReserved and /^[$_a-z][$_a-z0-9]*$/i.test name
168
169memberAccess = (e, member) ->
170 if isIdentifierName member
171 then new JS.MemberExpression no, (expr e), new JS.Identifier member
172 else new JS.MemberExpression yes, (expr e), new JS.Literal member
173
174dynamicMemberAccess = (e, index) ->
175 if (index.instanceof JS.Literal) and typeof index.value is 'string'
176 then memberAccess e, index.value
177 else new JS.MemberExpression yes, e, index
178
179# TODO: rewrite this whole thing using the CS AST nodes
180assignment = (assignee, expression, valueUsed = no) ->
181 assignments = []
182 switch
183 when assignee.rest then # do nothing for right now
184 when assignee.instanceof JS.ArrayExpression
185 e = expr expression
186 # TODO: only cache expression when it needs it
187 #if valueUsed or @assignee.members.length > 1 and needsCaching @expression
188 if valueUsed or assignee.elements.length > 1
189 e = genSym 'cache'
190 assignments.push new JS.AssignmentExpression '=', e, expr expression
191
192 elements = assignee.elements
193
194 for m, i in elements
195 break if m.rest
196 assignments.push assignment m, (dynamicMemberAccess e, new JS.Literal i), valueUsed
197
198 if elements.length > 0
199 # TODO: see if this logic can be combined with rest-parameter handling
200 if elements[-1..][0].rest
201 numElements = elements.length
202 restName = elements[numElements - 1] = elements[numElements - 1].expression
203 test = new JS.BinaryExpression '<=', (new JS.Literal numElements), memberAccess e, 'length'
204 consequent = helpers.slice e, new JS.Literal (numElements - 1)
205 alternate = new JS.ArrayExpression []
206 assignments.push stmt new JS.AssignmentExpression '=', restName, new JS.ConditionalExpression test, consequent, alternate
207 else if any elements, (p) -> p.rest
208 restName = index = null
209 for p, i in elements when p.rest
210 restName = p.expression
211 index = i
212 break
213 elements.splice index, 1
214 numElements = elements.length
215 size = genSym 'size'
216 assignments.push new JS.AssignmentExpression '=', size, memberAccess e, 'length'
217 test = new JS.BinaryExpression '>', size, new JS.Literal numElements
218 consequent = helpers.slice e, (new JS.Literal index), new JS.BinaryExpression '-', size, new JS.Literal numElements - index
219 assignments.push new JS.AssignmentExpression '=', restName, new JS.ConditionalExpression test, consequent, new JS.ArrayExpression []
220 for p, i in elements[index...]
221 assignments.push stmt new JS.AssignmentExpression '=', p, new JS.MemberExpression yes, e, new JS.BinaryExpression '-', size, new JS.Literal numElements - index - i
222 if any elements, (p) -> p.rest
223 throw new Error 'Positional destructuring assignments may not have more than one rest operator'
224
225 when assignee.instanceof JS.ObjectExpression
226 e = expression
227 # TODO: only cache expression when it needs it
228 #if valueUsed or @assignee.members.length > 1 and needsCaching @expression
229 if valueUsed or assignee.properties.length > 1
230 e = genSym 'cache'
231 assignments.push new JS.AssignmentExpression '=', e, expression
232
233 for m in assignee.properties
234 propName = if m.key.instanceof JS.Identifier then new JS.Literal m.key.name else m.key
235 assignments.push assignment m.value, (dynamicMemberAccess e, propName), valueUsed
236
237 when assignee.instanceof JS.Identifier, JS.GenSym, JS.MemberExpression
238 assignments.push new JS.AssignmentExpression '=', assignee, expr expression
239 else
240 throw new Error "compile: assignment: unassignable assignee: #{assignee.type}"
241 switch assignments.length
242 when 0 then if e is expression then helpers.undef() else expression
243 when 1 then assignments[0]
244 else new JS.SequenceExpression if valueUsed then [assignments..., e] else assignments
245
246hasSoak = (node) -> switch
247 when node.instanceof CS.SoakedFunctionApplication, CS.SoakedMemberAccessOp, CS.SoakedProtoMemberAccessOp, CS.SoakedDynamicMemberAccessOp, CS.SoakedDynamicProtoMemberAccessOp
248 yes
249 when node.instanceof CS.FunctionApplication
250 hasSoak node.function
251 when node.instanceof CS.MemberAccessOps
252 hasSoak node.expression
253 else
254 no
255
256generateSoak = do ->
257 # this function builds a tuple containing
258 # * a list of conjuncts for the conditional's test
259 # * the expression to be used as the consequent
260 fn = (node) -> switch
261 when node.instanceof CS.MemberAccessOp, CS.ProtoMemberAccessOp
262 [tests, e] = fn node.expression
263 [tests, new node.constructor e, node.memberName]
264 when node.instanceof CS.DynamicMemberAccessOp, CS.DynamicProtoMemberAccessOp
265 [tests, e] = fn node.expression
266 [tests, new node.constructor e, node.indexingExpr]
267 when node.instanceof CS.FunctionApplication
268 [tests, e] = fn node.function
269 [tests, new CS.FunctionApplication e, node.arguments]
270 when node.instanceof CS.SoakedFunctionApplication
271 [tests, e] = fn node.function
272 typeofTest = (e) -> new CS.EQOp (new CS.String 'function'), new CS.TypeofOp e
273 if needsCaching e
274 sym = new CS.GenSym 'cache'
275 [[tests..., typeofTest new CS.AssignOp sym, e], new CS.FunctionApplication sym, node.arguments]
276 else
277 [[tests..., typeofTest e], new CS.FunctionApplication e, node.arguments]
278 when node.instanceof CS.SoakedMemberAccessOp, CS.SoakedProtoMemberAccessOp, CS.SoakedDynamicMemberAccessOp, CS.SoakedDynamicProtoMemberAccessOp
279 memberName = switch
280 when node.instanceof CS.SoakedMemberAccessOp, CS.SoakedProtoMemberAccessOp then 'memberName'
281 when node.instanceof CS.SoakedDynamicMemberAccessOp, CS.SoakedDynamicProtoMemberAccessOp then 'indexingExpr'
282 ctor = switch
283 when node.instanceof CS.SoakedMemberAccessOp then CS.MemberAccessOp
284 when node.instanceof CS.SoakedProtoMemberAccessOp then CS.ProtoMemberAccessOp
285 when node.instanceof CS.SoakedDynamicMemberAccessOp then CS.DynamicMemberAccessOp
286 when node.instanceof CS.SoakedDynamicProtoMemberAccessOp then CS.DynamicProtoMemberAccessOp
287 [tests, e] = fn node.expression
288 if needsCaching e
289 sym = new CS.GenSym 'cache'
290 [[tests..., new CS.UnaryExistsOp new CS.AssignOp sym, e], new ctor sym, node[memberName]]
291 else
292 [[tests..., new CS.UnaryExistsOp e], new ctor e, node[memberName]]
293 else
294 [[], node]
295
296 (node) ->
297 [tests, e] = fn node
298 new CS.Conditional (foldl1 tests, (memo, t) -> new CS.LogicalAndOp memo, t), e
299
300
301helperNames = {}
302helpers =
303 extends: ->
304 protoAccess = (e) -> memberAccess e, 'prototype'
305 child = new JS.Identifier 'child'
306 parent = new JS.Identifier 'parent'
307 ctor = new JS.Identifier 'ctor'
308 key = new JS.Identifier 'key'
309 block = [
310 new JS.ForInStatement key, parent, new JS.IfStatement (helpers.isOwn parent, key), f = # TODO: figure out how we can allow this
311 stmt new JS.AssignmentExpression '=', (new JS.MemberExpression yes, child, key), new JS.MemberExpression yes, parent, key
312 new JS.FunctionDeclaration ctor, [], new JS.BlockStatement [
313 stmt new JS.AssignmentExpression '=', (memberAccess new JS.ThisExpression, 'constructor'), child
314 ]
315 new JS.AssignmentExpression '=', (protoAccess ctor), protoAccess parent
316 new JS.AssignmentExpression '=', (protoAccess child), new JS.NewExpression ctor, []
317 new JS.AssignmentExpression '=', (memberAccess child, '__super__'), protoAccess parent
318 new JS.ReturnStatement child
319 ]
320 new JS.FunctionDeclaration helperNames.extends, [child, parent], new JS.BlockStatement map block, stmt
321 construct: ->
322 child = new JS.Identifier 'child'
323 ctor = new JS.Identifier 'ctor'
324 fn = new JS.Identifier 'fn'
325 args = new JS.Identifier 'args'
326 result = new JS.Identifier 'result'
327 block = [
328 new JS.VariableDeclaration 'var', [
329 new JS.VariableDeclarator fn, new JS.FunctionExpression null, [], new JS.BlockStatement []
330 ]
331 new JS.AssignmentExpression '=', (memberAccess fn, 'prototype'), memberAccess ctor, 'prototype'
332 new JS.VariableDeclaration 'var', [
333 new JS.VariableDeclarator child, new JS.NewExpression fn, []
334 new JS.VariableDeclarator result, new JS.CallExpression (memberAccess ctor, 'apply'), [child, args]
335 ]
336 new JS.ReturnStatement new JS.ConditionalExpression (new JS.BinaryExpression '===', result, new JS.CallExpression (new JS.Identifier 'Object'), [result]), result, child
337 ]
338 new JS.FunctionDeclaration helperNames.construct, [ctor, args], new JS.BlockStatement map block, stmt
339 isOwn: ->
340 hop = memberAccess (new JS.ObjectExpression []), 'hasOwnProperty'
341 params = args = [(new JS.Identifier 'o'), new JS.Identifier 'p']
342 functionBody = [new JS.CallExpression (memberAccess hop, 'call'), args]
343 new JS.FunctionDeclaration helperNames.isOwn, params, makeReturn new JS.BlockStatement map functionBody, stmt
344 in: ->
345 member = new JS.Identifier 'member'
346 list = new JS.Identifier 'list'
347 i = new JS.Identifier 'i'
348 length = new JS.Identifier 'length'
349 varDeclaration = new JS.VariableDeclaration 'var', [
350 new JS.VariableDeclarator i, new JS.Literal 0
351 new JS.VariableDeclarator length, memberAccess list, 'length'
352 ]
353 loopBody = new JS.IfStatement (new JS.BinaryExpression '&&', (new JS.BinaryExpression 'in', i, list), (new JS.BinaryExpression '===', (new JS.MemberExpression yes, list, i), member)), new JS.ReturnStatement new JS.Literal yes
354 functionBody = [
355 new JS.ForStatement varDeclaration, (new JS.BinaryExpression '<', i, length), (new JS.UpdateExpression '++', yes, i), loopBody
356 new JS.Literal no
357 ]
358 new JS.FunctionDeclaration helperNames.in, [member, list], makeReturn new JS.BlockStatement map functionBody, stmt
359
360enabledHelpers = []
361for own h, fn of helpers
362 helperNames[h] = genSym h
363 helpers[h] = do (h, fn) -> ->
364 enabledHelpers.push fn()
365 (helpers[h] = -> new JS.CallExpression helperNames[h], arguments).apply this, arguments
366
367inlineHelpers =
368 exp: -> new JS.CallExpression (memberAccess (new JS.Identifier 'Math'), 'pow'), arguments
369 undef: -> new JS.UnaryExpression 'void', new JS.Literal 0
370 slice: -> new JS.CallExpression (memberAccess (memberAccess (new JS.ArrayExpression []), 'slice'), 'call'), arguments
371
372for own h, fn of inlineHelpers
373 helpers[h] = fn
374
375
376
377class exports.Compiler
378
379 @compile = => (new this).compile arguments...
380
381 # TODO: none of the default rules should need to use `compile`; fix it with functions
382 defaultRules = [
383 # control flow structures
384 [CS.Program, ({body, inScope, options}) ->
385 return new JS.Program [] unless body?
386 block = stmt body
387 block =
388 if block.instanceof JS.BlockStatement then block.body
389 else [block]
390
391 # Push function declaration helpers, unshift all other types (VariableDeclarations, etc.)
392 [fnDeclHelpers, otherHelpers] = partition enabledHelpers, (helper) -> helper.instanceof JS.FunctionDeclaration
393 [].push.apply block, fnDeclHelpers
394 [].unshift.apply block, otherHelpers
395
396 decls = nub concatMap block, declarationsNeededRecursive
397 if decls.length > 0
398 if options.bare
399 block.unshift makeVarDeclaration decls
400 else
401 # add a function wrapper
402 block = [stmt new JS.UnaryExpression 'void', new JS.CallExpression (memberAccess (new JS.FunctionExpression null, [], new JS.BlockStatement block), 'call'), [new JS.ThisExpression]]
403 # generate node
404 pkg = require './../../package.json'
405 program = new JS.Program block
406 program.leadingComments = [
407 type: 'Line'
408 value: " Generated by CoffeeScript #{pkg.version}"
409 ]
410 program
411 ]
412 [CS.Block, ({statements}) ->
413 switch statements.length
414 when 0 then new JS.EmptyStatement
415 when 1 then new stmt statements[0]
416 else new JS.BlockStatement map statements, stmt
417 ]
418 [CS.SeqOp, ({left, right})-> new JS.SequenceExpression [left, right]]
419 [CS.Conditional, ({condition, consequent, alternate, ancestry}) ->
420 if alternate?
421 throw new Error 'Conditional with non-null alternate requires non-null consequent' unless consequent?
422 alternate = forceBlock alternate unless alternate.instanceof JS.IfStatement
423 if alternate? or ancestry[0]?.instanceof CS.Conditional
424 consequent = forceBlock consequent
425 inspect = (o) -> require('util').inspect o, no, 2, yes
426 new JS.IfStatement (expr condition), (stmt consequent), alternate
427 ]
428 [CS.ForIn, ({valAssignee, keyAssignee, target, step, filter, body}) ->
429 i = genSym 'i'
430 length = genSym 'length'
431 block = forceBlock body
432 block.body.push stmt helpers.undef() unless block.body.length
433 e = if needsCaching @target then genSym 'cache' else target
434 varDeclaration = new JS.VariableDeclaration 'var', [
435 new JS.VariableDeclarator i, new JS.Literal 0
436 new JS.VariableDeclarator length, memberAccess e, 'length'
437 ]
438 unless e is target
439 varDeclaration.declarations.unshift new JS.VariableDeclarator e, target
440 if @filter?
441 # TODO: if block only has a single statement, wrap it instead of continuing
442 block.body.unshift stmt new JS.IfStatement (new JS.UnaryExpression '!', filter), new JS.ContinueStatement
443 if keyAssignee?
444 block.body.unshift stmt assignment keyAssignee, i
445 block.body.unshift stmt assignment valAssignee, new JS.MemberExpression yes, e, i
446 new JS.ForStatement varDeclaration, (new JS.BinaryExpression '<', i, length), (new JS.UpdateExpression '++', yes, i), block
447 ]
448 [CS.ForOf, ({keyAssignee, valAssignee, target, filter, body}) ->
449 block = forceBlock body
450 block.body.push stmt helpers.undef() unless block.body.length
451 e = if @isOwn and needsCaching @target then genSym 'cache' else expr target
452 if @filter?
453 # TODO: if block only has a single statement, wrap it instead of continuing
454 block.body.unshift stmt new JS.IfStatement (new JS.UnaryExpression '!', filter), new JS.ContinueStatement
455 if valAssignee?
456 block.body.unshift stmt assignment valAssignee, new JS.MemberExpression yes, e, keyAssignee
457 if @isOwn
458 block.body.unshift stmt new JS.IfStatement (new JS.UnaryExpression '!', helpers.isOwn e, keyAssignee), new JS.ContinueStatement
459 right = if e is target then e else new JS.AssignmentExpression '=', e, target
460 new JS.ForInStatement keyAssignee, right, block
461 ]
462 [CS.While, ({condition, body}) -> new JS.WhileStatement (expr condition), forceBlock body]
463 [CS.Switch, ({expression, cases, alternate}) ->
464 cases = concat cases
465 unless expression?
466 expression = new JS.Literal false
467 for c in cases
468 c.test = new JS.UnaryExpression '!', c.test
469 if alternate?
470 cases.push new JS.SwitchCase null, [stmt alternate]
471 for c in cases[...-1] when c.consequent?.length > 0
472 c.consequent.push new JS.BreakStatement
473 new JS.SwitchStatement expression, cases
474 ]
475 [CS.SwitchCase, ({conditions, consequent}) ->
476 cases = map conditions, (c) ->
477 new JS.SwitchCase c, []
478 block = stmt consequent
479 block = if block?
480 if block.instanceof JS.BlockStatement then block.body else [block]
481 else []
482 cases[cases.length - 1].consequent = block
483 cases
484 ]
485 [CS.Try, ({body, catchAssignee, catchBody, finallyBody}) ->
486 finallyBlock = if finallyBody? then forceBlock finallyBody else null
487 e = genSym 'e'
488 catchBlock = forceBlock catchBody
489 if catchAssignee?
490 catchBlock.body.unshift stmt assignment catchAssignee, e
491 handlers = [new JS.CatchClause e, catchBlock]
492 new JS.TryStatement (forceBlock body), handlers, finallyBlock
493 ]
494 [CS.Throw, ({expression}) -> new JS.ThrowStatement expression]
495
496 # data structures
497 [CS.Range, ({left: left_, right: right_}) ->
498 # enumerate small integral ranges
499 if ((@left.instanceof CS.Int) or ((@left.instanceof CS.UnaryNegateOp) and @left.expression.instanceof CS.Int)) and
500 ( (@right.instanceof CS.Int) or ((@right.instanceof CS.UnaryNegateOp) and @right.expression.instanceof CS.Int))
501 rawLeft = if @left.instanceof CS.UnaryNegateOp then -@left.expression.data else @left.data
502 rawRight = if @right.instanceof CS.UnaryNegateOp then -@right.expression.data else @right.data
503 if (Math.abs rawLeft - rawRight) <= 20
504 range = if @isInclusive then [rawLeft..rawRight] else [rawLeft...rawRight]
505 return new JS.ArrayExpression map range, (n) -> if n < 0 then new JS.UnaryExpression '-', new JS.Literal -n else new JS.Literal n
506
507 accum = genSym 'accum'
508 body = [stmt new JS.AssignmentExpression '=', accum, new JS.ArrayExpression []]
509
510 if needsCaching left_
511 left = genSym 'from'
512 body.push stmt new JS.AssignmentExpression '=', left, left_
513 else left = left_
514 if needsCaching right_
515 right = genSym 'to'
516 body.push stmt new JS.AssignmentExpression '=', right, right_
517 else right = right_
518
519 i = genSym 'i'
520 vars = new JS.VariableDeclaration 'var', [new JS.VariableDeclarator i, left]
521
522 conditionTest = new JS.BinaryExpression '<=', left, right
523 conditionConsequent = new JS.BinaryExpression (if @isInclusive then '<=' else '<'), i, right
524 conditionAlternate = new JS.BinaryExpression (if @isInclusive then '>=' else '>'), i, right
525 condition = new JS.ConditionalExpression conditionTest, conditionConsequent, conditionAlternate
526
527 update = new JS.ConditionalExpression conditionTest, (new JS.UpdateExpression '++', yes, i), new JS.UpdateExpression '--', yes, i
528
529 body.push new JS.ForStatement vars, condition, update, stmt new JS.CallExpression (memberAccess accum, 'push'), [i]
530 body.push new JS.ReturnStatement accum
531 new JS.CallExpression (memberAccess (new JS.FunctionExpression null, [], new JS.BlockStatement body), 'apply'), [new JS.ThisExpression, new JS.Identifier 'arguments']
532 ]
533 [CS.ArrayInitialiser, do ->
534 groupMembers = (members) ->
535 if members.length is 0 then []
536 else
537 [ys, zs] = span members, (x) -> not x.spread
538 if ys.length is 0
539 sliced = helpers.slice zs[0].expression
540 [ys, zs] = [sliced, zs[1..]]
541 else ys = new JS.ArrayExpression map ys, expr
542 [ys].concat groupMembers zs
543 ({members, compile}) ->
544 if any members, (m) -> m.spread
545 grouped = groupMembers members
546 new JS.CallExpression (memberAccess grouped[0], 'concat'), grouped[1..]
547 else new JS.ArrayExpression map members, expr
548 ]
549 [CS.Spread, ({expression}) -> {spread: yes, expression}]
550 [CS.ObjectInitialiser, ({members}) -> new JS.ObjectExpression members]
551 [CS.ObjectInitialiserMember, ({key, expression}) ->
552 keyName = @key.data
553 key = if isIdentifierName keyName then new JS.Identifier keyName else new JS.Literal keyName
554 new JS.Property key, expr expression
555 ]
556 [CS.DefaultParam, ({param, default: d}) -> {param, default: d}]
557 [CS.Function, CS.BoundFunction, do ->
558
559 handleParam = (param, original, block) -> switch
560 when original.instanceof CS.Rest then param # keep these for special processing later
561 when original.instanceof CS.Identifier then param
562 when original.instanceof CS.MemberAccessOps, CS.ObjectInitialiser, CS.ArrayInitialiser
563 p = genSym 'param'
564 block.body.unshift stmt assignment param, p
565 p
566 when original.instanceof CS.DefaultParam
567 block.body.unshift new JS.IfStatement (new JS.BinaryExpression '==', (new JS.Literal null), param.param), stmt new JS.AssignmentExpression '=', param.param, param.default
568 handleParam.call this, param.param, original.param, block
569 else throw new Error "Unsupported parameter type: #{original.className}"
570
571 ({parameters, body, ancestry}) ->
572 unless ancestry[0]?.instanceof CS.Constructor
573 body = makeReturn body
574 block = forceBlock body
575 last = block.body[-1..][0]
576 if (last?.instanceof JS.ReturnStatement) and not last.argument?
577 block.body = block.body[...-1]
578
579 parameters_ =
580 if parameters.length is 0 then []
581 else
582 pIndex = parameters.length
583 while pIndex--
584 handleParam.call this, parameters[pIndex], @parameters[pIndex], block
585 parameters = parameters_.reverse()
586
587 if parameters.length > 0
588 if parameters[-1..][0].rest
589 numParams = parameters.length
590 paramName = parameters[numParams - 1] = parameters[numParams - 1].expression
591 test = new JS.BinaryExpression '<=', (new JS.Literal numParams), memberAccess (new JS.Identifier 'arguments'), 'length'
592 consequent = helpers.slice (new JS.Identifier 'arguments'), new JS.Literal (numParams - 1)
593 alternate = new JS.ArrayExpression []
594 block.body.unshift stmt new JS.AssignmentExpression '=', paramName, new JS.ConditionalExpression test, consequent, alternate
595 else if any parameters, (p) -> p.rest
596 paramName = index = null
597 for p, i in parameters when p.rest
598 paramName = p.expression
599 index = i
600 break
601 parameters.splice index, 1
602 numParams = parameters.length
603 numArgs = genSym 'numArgs'
604 reassignments = new JS.IfStatement (new JS.BinaryExpression '>', (new JS.AssignmentExpression '=', numArgs, memberAccess (new JS.Identifier 'arguments'), 'length'), new JS.Literal numParams), (new JS.BlockStatement [
605 stmt new JS.AssignmentExpression '=', paramName, helpers.slice (new JS.Identifier 'arguments'), (new JS.Literal index), new JS.BinaryExpression '-', numArgs, new JS.Literal numParams - index
606 ]), new JS.BlockStatement [stmt new JS.AssignmentExpression '=', paramName, new JS.ArrayExpression []]
607 for p, i in parameters[index...]
608 reassignments.consequent.body.push stmt new JS.AssignmentExpression '=', p, new JS.MemberExpression yes, (new JS.Identifier 'arguments'), new JS.BinaryExpression '-', numArgs, new JS.Literal numParams - index - i
609 block.body.unshift reassignments
610 if any parameters, (p) -> p.rest
611 throw new Error 'Parameter lists may not have more than one rest operator'
612
613 performedRewrite = no
614 if @instanceof CS.BoundFunction
615 newThis = genSym 'this'
616 rewriteThis = generateMutatingWalker ->
617 if @instanceof JS.ThisExpression
618 performedRewrite = yes
619 newThis
620 else if @instanceof JS.FunctionExpression, JS.FunctionDeclaration then this
621 else rewriteThis this
622 rewriteThis block
623
624 fn = new JS.FunctionExpression null, parameters, block
625 if performedRewrite
626 new JS.SequenceExpression [
627 new JS.AssignmentExpression '=', newThis, new JS.ThisExpression
628 fn
629 ]
630 else fn
631 ]
632 [CS.Rest, ({expression}) -> {rest: yes, expression, isExpression: yes, isStatement: yes}]
633
634 # TODO: comment
635 [CS.Class, ({nameAssignee, parent, name, ctor, body, compile}) ->
636 args = []
637 params = []
638 parentRef = genSym 'super'
639 block = forceBlock body
640 if (name.instanceof JS.Identifier) and name.name in jsReserved
641 name = genSym name.name
642
643 if ctor?
644 # TODO: I'd really like to avoid searching for the constructor like this
645 for c, i in block.body when c.instanceof JS.FunctionDeclaration
646 ctorIndex = i
647 break
648 block.body.splice ctorIndex, 1, ctor
649 else
650 ctor = new JS.FunctionDeclaration name, [], new JS.BlockStatement []
651 ctorIndex = 0
652 block.body.unshift ctor
653 ctor.id = name
654 # handle external constructors
655 if @ctor? and not @ctor.expression.instanceof CS.Functions
656 ctorRef = genSym 'externalCtor'
657 ctor.body.body.push makeReturn new JS.CallExpression (memberAccess ctorRef, 'apply'), [new JS.ThisExpression, new JS.Identifier 'arguments']
658 block.body.splice ctorIndex, 0, stmt new JS.AssignmentExpression '=', ctorRef, expr compile @ctor.expression
659
660 if @boundMembers.length > 0
661 instance = genSym 'instance'
662 for protoAssignOp in @boundMembers
663 memberName = protoAssignOp.assignee.data.toString()
664 ps = (genSym() for _ in protoAssignOp.expression.parameters)
665 member = memberAccess new JS.ThisExpression, memberName
666 protoMember = memberAccess (memberAccess name, 'prototype'), memberName
667 fn = new JS.FunctionExpression null, ps, new JS.BlockStatement [
668 makeReturn new JS.CallExpression (memberAccess protoMember, 'apply'), [instance, new JS.Identifier 'arguments']
669 ]
670 ctor.body.body.unshift stmt new JS.AssignmentExpression '=', member, fn
671 ctor.body.body.unshift stmt new JS.AssignmentExpression '=', instance, new JS.ThisExpression
672
673 if parent?
674 params.push parentRef
675 args.push parent
676 block.body.unshift stmt helpers.extends name, parentRef
677 block.body.push new JS.ReturnStatement new JS.ThisExpression
678
679 rewriteThis = generateMutatingWalker ->
680 if @instanceof JS.ThisExpression then name
681 else if @instanceof JS.FunctionExpression, JS.FunctionDeclaration then this
682 else rewriteThis this
683 rewriteThis block
684
685 iife = new JS.CallExpression (new JS.FunctionExpression null, params, block), args
686 if nameAssignee? then assignment nameAssignee, iife else iife
687 ]
688 [CS.Constructor, ({expression}) ->
689 tmpName = genSym 'class'
690 if @expression.instanceof CS.Functions
691 new JS.FunctionDeclaration tmpName, expression.params, forceBlock expression.body
692 else
693 new JS.FunctionDeclaration tmpName, [], new JS.BlockStatement []
694 ]
695 [CS.ClassProtoAssignOp, ({assignee, expression, compile}) ->
696 if @expression.instanceof CS.BoundFunction
697 compile new CS.ClassProtoAssignOp @assignee, new CS.Function @expression.parameters, @expression.body
698 else
699 protoMember = memberAccess (memberAccess new JS.ThisExpression, 'prototype'), @assignee.data
700 new JS.AssignmentExpression '=', protoMember, expression
701 ]
702
703 # more complex operations
704 [CS.AssignOp, ({assignee, expression, ancestry}) ->
705 assignment assignee, expression, usedAsExpression this, ancestry
706 ]
707 [CS.CompoundAssignOp, ({assignee, expression, inScope}) ->
708 op = switch @op
709 when CS.LogicalAndOp::className then '&&'
710 when CS.LogicalOrOp::className then '||'
711 when CS.ExistsOp::className then '?'
712 when CS.BitOrOp::className then '|'
713 when CS.BitXorOp::className then '^'
714 when CS.BitAndOp::className then '&'
715 when CS.LeftShiftOp::className then '<<'
716 when CS.SignedRightShiftOp::className then '>>'
717 when CS.UnsignedRightShiftOp::className then '>>>'
718 when CS.PlusOp::className then '+'
719 when CS.SubtractOp::className then '-'
720 when CS.MultiplyOp::className then '*'
721 when CS.DivideOp::className then '/'
722 when CS.RemOp::className then '%'
723 when CS.ExpOp::className then '**'
724 else throw new Error 'Unrecognised compound assignment operator'
725
726 # if assignee is an identifier, fail unless assignee is in scope
727 if op in ['&&', '||', '?'] and (assignee.instanceof JS.Identifier) and assignee.name not in inScope
728 throw new Error "the variable \"#{assignee.name}\" can't be assigned with ?= because it has not been defined."
729
730 switch op
731 when '&&', '||'
732 new JS.BinaryExpression op, assignee, new JS.AssignmentExpression '=', assignee, expr expression
733 when '?'
734 condition = new JS.BinaryExpression '!=', (new JS.Literal null), assignee
735 new JS.ConditionalExpression condition, assignee, new JS.AssignmentExpression '=', assignee, expr expression
736 when '**'
737 new JS.AssignmentExpression '=', assignee, helpers.exp assignee, expr expression
738 else new JS.AssignmentExpression "#{op}=", assignee, expr expression
739 ]
740 [CS.ChainedComparisonOp, ({expression, compile}) ->
741 return expression unless @expression.left.instanceof CS.ComparisonOps
742 left = expression.left.right
743 lhs = compile new CS.ChainedComparisonOp @expression.left
744 if needsCaching @expression.left.right
745 left = genSym 'cache'
746 # WARN: mutation
747 if @expression.left.left.instanceof CS.ComparisonOps
748 lhs.right.right = new JS.AssignmentExpression '=', left, lhs.right.right
749 else lhs.right = new JS.AssignmentExpression '=', left, lhs.right
750 new JS.BinaryExpression '&&', lhs, new JS.BinaryExpression expression.operator, left, expression.right
751 ]
752 [CS.FunctionApplication, ({function: fn, arguments: args, compile}) ->
753 if any args, (m) -> m.spread
754 lhs = @function
755 context = new CS.Null
756 if needsCaching @function
757 context = new CS.GenSym 'cache'
758 lhs = if @function.instanceof CS.StaticMemberAccessOps
759 new @function.constructor (new CS.AssignOp context, lhs.expression), @function.memberName
760 else if @function.instanceof CS.DynamicMemberAccessOps
761 new @function.constructor (new CS.AssignOp context, lhs.expression), @function.indexingExpr
762 else new CS.AssignOp context, lhs
763 else if lhs.instanceof CS.MemberAccessOps
764 context = lhs.expression
765 if @function.instanceof CS.ProtoMemberAccessOp, CS.DynamicProtoMemberAccessOp
766 context = new CS.MemberAccessOp context, 'prototype'
767 else if @function.instanceof CS.SoakedProtoMemberAccessOp, CS.SoakedDynamicProtoMemberAccessOp
768 context = new CS.SoakedMemberAccessOp context, 'prototype'
769 compile new CS.FunctionApplication (new CS.MemberAccessOp lhs, 'apply'), [context, new CS.ArrayInitialiser @arguments]
770 else if hasSoak this then compile generateSoak this
771 else new JS.CallExpression (expr fn), map args, expr
772 ]
773 [CS.SoakedFunctionApplication, ({compile}) -> compile generateSoak this]
774 [CS.NewOp, ({ctor, arguments: args, compile}) ->
775 if any args, (m) -> m.spread
776 helpers.construct ctor, compile new CS.ArrayInitialiser @arguments
777 else new JS.NewExpression ctor, map args, expr
778 ]
779 [CS.HeregExp, ({expression}) ->
780 args = [expression]
781 if flags = (flag for flag in ['g', 'i', 'm', 'y'] when @flags[flag]).join ''
782 args.push new JS.Literal flags
783 new JS.NewExpression (new JS.Identifier 'RegExp'), args
784 ]
785 [CS.RegExp, ->
786 flags = (flag for flag in ['g', 'i', 'm', 'y'] when @flags[flag]).join ''
787 # TODO: try/catch for invalid regexps
788 re = new RegExp @data, flags
789 new JS.Literal re
790 ]
791 [CS.ConcatOp, ({left, right, ancestry}) ->
792 plusOp = new JS.BinaryExpression '+', (expr left), expr right
793 unless ancestry[0].instanceof CS.ConcatOp
794 leftmost = plusOp
795 leftmost = leftmost.left while leftmost.left?.left?
796 unless (leftmost.left.instanceof JS.Literal) and 'string' is typeof leftmost.left.value
797 leftmost.left = new JS.BinaryExpression '+', (new JS.Literal ''), leftmost.left
798 plusOp
799 ]
800 [CS.MemberAccessOp, CS.SoakedMemberAccessOp, ({expression, compile}) ->
801 if hasSoak this then expr compile generateSoak this
802 else memberAccess expression, @memberName
803 ]
804 [CS.ProtoMemberAccessOp, CS.SoakedProtoMemberAccessOp, ({expression, compile}) ->
805 if hasSoak this then expr compile generateSoak this
806 else memberAccess (memberAccess expression, 'prototype'), @memberName
807 ]
808 [CS.DynamicMemberAccessOp, CS.SoakedDynamicMemberAccessOp, ({expression, indexingExpr, compile}) ->
809 if hasSoak this then expr compile generateSoak this
810 else dynamicMemberAccess expression, indexingExpr
811 ]
812 [CS.DynamicProtoMemberAccessOp, CS.SoakedDynamicProtoMemberAccessOp, ({expression, indexingExpr, compile}) ->
813 if hasSoak this then expr compile generateSoak this
814 else dynamicMemberAccess (memberAccess expression, 'prototype'), indexingExpr
815 ]
816 [CS.Slice, ({expression, left, right}) ->
817 args = if left? then [left] else if right? then [new JS.Literal 0] else []
818 if right?
819 args.push if @isInclusive
820 if (right.instanceof JS.Literal) and typeof right.data is 'number'
821 then new JS.Literal right.data + 1
822 else new JS.BinaryExpression '||', (new JS.BinaryExpression '+', (new JS.UnaryExpression '+', right), new JS.Literal 1), new JS.Literal 9e9
823 else right
824 new JS.CallExpression (memberAccess expression, 'slice'), args
825 ]
826 [CS.ExistsOp, ({left, right, inScope}) ->
827 e = if needsCaching @left then genSym 'cache' else expr left
828 condition = new JS.BinaryExpression '!=', (new JS.Literal null), e
829 if (e.instanceof JS.Identifier) and e.name not in inScope
830 condition = new JS.BinaryExpression '&&', (new JS.BinaryExpression '!==', (new JS.Literal 'undefined'), new JS.UnaryExpression 'typeof', e), condition
831 node = new JS.ConditionalExpression condition, e, expr right
832 if e is left then node
833 else new JS.SequenceExpression [(new JS.AssignmentExpression '=', e, left), node]
834 ]
835 [CS.UnaryExistsOp, ({expression, inScope}) ->
836 nullTest = new JS.BinaryExpression '!=', (new JS.Literal null), expression
837 if (expression.instanceof JS.Identifier) and expression.name not in inScope
838 typeofTest = new JS.BinaryExpression '!==', (new JS.Literal 'undefined'), new JS.UnaryExpression 'typeof', expression
839 new JS.BinaryExpression '&&', typeofTest, nullTest
840 else nullTest
841 ]
842 [CS.DoOp, do ->
843 deriveArgsFromParams = (params) ->
844 args = for param, index in params
845 switch
846 when param.instanceof CS.DefaultParam
847 params[index] = param.param
848 param.default
849 when param.instanceof CS.Identifier, CS.MemberAccessOp then param
850 else helpers.undef()
851 ({expression, compile}) ->
852 args = []
853 if (@expression.instanceof CS.AssignOp) and @expression.expression.instanceof CS.Function
854 args = deriveArgsFromParams @expression.expression.parameters
855 else if @expression.instanceof CS.Function
856 args = deriveArgsFromParams @expression.parameters
857 compile new CS.FunctionApplication @expression, args
858 ]
859 [CS.Return, ({expression: e}) -> new JS.ReturnStatement expr e]
860 [CS.Break, -> new JS.BreakStatement]
861 [CS.Continue, -> new JS.ContinueStatement]
862 [CS.Debugger, -> new JS.DebuggerStatement]
863
864 # straightforward operators
865 [CS.ExpOp, ({left, right}) ->
866 helpers.exp (expr left), expr right
867 ]
868 [CS.DivideOp, ({left, right}) -> new JS.BinaryExpression '/', (expr left), expr right]
869 [CS.MultiplyOp, ({left, right}) -> new JS.BinaryExpression '*', (expr left), expr right]
870 [CS.RemOp, ({left, right}) -> new JS.BinaryExpression '%', (expr left), expr right]
871 [CS.PlusOp, ({left, right}) -> new JS.BinaryExpression '+', (expr left), expr right]
872 [CS.SubtractOp, ({left, right}) -> new JS.BinaryExpression '-', (expr left), expr right]
873
874 [CS.OfOp, ({left, right}) -> new JS.BinaryExpression 'in', (expr left), expr right]
875 [CS.InOp, ({left, right}) ->
876 if (right.instanceof JS.ArrayExpression) and right.elements.length < 5
877 switch right.elements.length
878 when 0
879 if needsCaching @left
880 # TODO: only necessary if value is used, which is almost always
881 new JS.SequenceExpression [left, new JS.Literal false]
882 else new JS.Literal false
883 when 1
884 new JS.BinaryExpression '===', left, right.elements[0]
885 else
886 if needsCaching @left
887 helpers.in (expr left), expr right
888 else
889 comparisons = map right.elements, (e) -> new JS.BinaryExpression '===', left, e
890 foldl1 comparisons, (l, r) -> new JS.BinaryExpression '||', l, r
891 else
892 helpers.in (expr left), expr right
893 ]
894 [CS.ExtendsOp, ({left, right}) -> helpers.extends (expr left), expr right]
895 [CS.InstanceofOp, ({left, right}) -> new JS.BinaryExpression 'instanceof', (expr left), expr right]
896
897 [CS.LogicalAndOp, ({left, right}) -> new JS.BinaryExpression '&&', (expr left), expr right]
898 [CS.LogicalOrOp, ({left, right}) -> new JS.BinaryExpression '||', (expr left), expr right]
899
900 [CS.EQOp , ({left, right}) -> new JS.BinaryExpression '===', (expr left), expr right]
901 [CS.NEQOp , ({left, right}) -> new JS.BinaryExpression '!==', (expr left), expr right]
902 [CS.GTEOp , ({left, right}) -> new JS.BinaryExpression '>=', (expr left), expr right]
903 [CS.GTOp , ({left, right}) -> new JS.BinaryExpression '>', (expr left), expr right]
904 [CS.LTEOp , ({left, right}) -> new JS.BinaryExpression '<=', (expr left), expr right]
905 [CS.LTOp , ({left, right}) -> new JS.BinaryExpression '<', (expr left), expr right]
906
907 [CS.BitAndOp , ({left, right}) -> new JS.BinaryExpression '&', (expr left), expr right]
908 [CS.BitOrOp , ({left, right}) -> new JS.BinaryExpression '|', (expr left), expr right]
909 [CS.BitXorOp , ({left, right}) -> new JS.BinaryExpression '^', (expr left), expr right]
910 [CS.LeftShiftOp , ({left, right}) -> new JS.BinaryExpression '<<', (expr left), expr right]
911 [CS.SignedRightShiftOp , ({left, right}) -> new JS.BinaryExpression '>>', (expr left), expr right]
912 [CS.UnsignedRightShiftOp , ({left, right}) -> new JS.BinaryExpression '>>>', (expr left), expr right]
913
914 [CS.PreDecrementOp, ({expression: e}) -> new JS.UpdateExpression '--', yes, expr e]
915 [CS.PreIncrementOp, ({expression: e}) -> new JS.UpdateExpression '++', yes, expr e]
916 [CS.PostDecrementOp, ({expression: e}) -> new JS.UpdateExpression '--', no, expr e]
917 [CS.PostIncrementOp, ({expression: e}) -> new JS.UpdateExpression '++', no, expr e]
918 [CS.UnaryPlusOp, ({expression: e}) -> new JS.UnaryExpression '+', expr e]
919 [CS.UnaryNegateOp, ({expression: e}) -> new JS.UnaryExpression '-', expr e]
920 [CS.LogicalNotOp, ({expression: e}) -> new JS.UnaryExpression '!', expr e]
921 [CS.BitNotOp, ({expression: e}) -> new JS.UnaryExpression '~', expr e]
922 [CS.TypeofOp, ({expression: e}) -> new JS.UnaryExpression 'typeof', expr e]
923 [CS.DeleteOp, ({expression: e}) -> new JS.UnaryExpression 'delete', expr e]
924
925 # primitives
926 [CS.Identifier, -> new JS.Identifier @data]
927 [CS.GenSym, do ->
928 symbols = []
929 memos = []
930 ->
931 if this in symbols then memos[symbols.indexOf this]
932 else
933 symbols.push this
934 memos.push memo = genSym @data
935 memo
936 ]
937 [CS.Bool, CS.Int, CS.Float, CS.String, -> new JS.Literal @data]
938 [CS.Null, -> new JS.Literal null]
939 [CS.Undefined, -> helpers.undef()]
940 [CS.This, -> new JS.ThisExpression]
941 [CS.JavaScript, -> new JS.CallExpression (new JS.Identifier 'eval'), [new JS.Literal @data]]
942 ]
943
944 constructor: ->
945 @rules = {}
946 for [ctors..., handler] in defaultRules
947 for ctor in ctors
948 @addRule ctor, handler
949
950 addRule: (ctor, handler) ->
951 @rules[ctor::className] = handler
952 this
953
954 # TODO: comment
955 compile: do ->
956 walk = (fn, inScope, ancestry, options) ->
957
958 if (ancestry[0]?.instanceof CS.Function, CS.BoundFunction) and this is ancestry[0].body
959 inScope = union inScope, concatMap ancestry[0].parameters, beingDeclared
960
961 ancestry.unshift this
962 children = {}
963
964 for childName in @childNodes when @[childName]?
965 children[childName] =
966 if childName in @listMembers
967 for member in @[childName]
968 jsNode = walk.call member, fn, inScope, ancestry
969 inScope = union inScope, envEnrichments member, inScope
970 jsNode
971 else
972 child = @[childName]
973 jsNode = walk.call child, fn, inScope, ancestry
974 inScope = union inScope, envEnrichments child, inScope
975 jsNode
976
977 children.inScope = inScope
978 children.ancestry = ancestry
979 children.options = options
980 children.compile = (node) ->
981 walk.call node.g(), fn, inScope, ancestry
982
983 do ancestry.shift
984 jsNode = fn.call this, children
985 jsNode[p] = @[p] for p in ['raw', 'line', 'column', 'offset']
986 jsNode
987
988 generateSymbols = do ->
989
990 generatedSymbols = {}
991 format = (pre, counter) ->
992 if pre
993 "#{pre}$#{counter or ''}"
994 else
995 if counter < 26
996 String.fromCharCode 0x61 + counter
997 else
998 [div, mod] = divMod counter, 26
999 (format pre, div - 1) + format pre, mod
1000
1001 generateName = (node, {usedSymbols, nsCounters}) ->
1002 if owns generatedSymbols, node.uniqueId
1003 # if we've already generated a name for this symbol, use it
1004 generatedSymbols[node.uniqueId]
1005 else
1006 # retrieve the next available counter in this symbol's namespace
1007 nsCounters[node.ns] = if owns nsCounters, node.ns then 1 + nsCounters[node.ns] else 0
1008 # avoid clashing with anything that is already in scope
1009 ++nsCounters[node.ns] while (formatted = format node.ns, nsCounters[node.ns]) in usedSymbols
1010 # save the name for future reference
1011 generatedSymbols[node.uniqueId] = formatted
1012
1013 # TODO: comments
1014 generateMutatingWalker (state) ->
1015 state.declaredSymbols = union state.declaredSymbols, map (declarationsNeeded this), (id) -> id.name
1016 {declaredSymbols, usedSymbols, nsCounters} = state
1017 newNode = if @instanceof JS.GenSym
1018 newNode = new JS.Identifier generateName this, state
1019 usedSymbols.push newNode.name
1020 newNode
1021 else if @instanceof JS.FunctionExpression, JS.FunctionDeclaration
1022 params = concatMap @params, collectIdentifiers
1023 nsCounters_ = {}
1024 nsCounters_[k] = v for own k, v of nsCounters
1025 newNode = generateSymbols this,
1026 declaredSymbols: union declaredSymbols, params
1027 usedSymbols: union usedSymbols, params
1028 nsCounters: nsCounters_
1029 newNode.body = forceBlock newNode.body
1030 declNames = nub difference (map (declarationsNeededRecursive @body), (id) -> id.name), union declaredSymbols, params
1031 decls = map declNames, (name) -> new JS.Identifier name
1032 newNode.body.body.unshift makeVarDeclaration decls if decls.length > 0
1033 newNode
1034 else generateSymbols this, state
1035 state.declaredSymbols = union declaredSymbols, map (declarationsNeededRecursive newNode), (id) -> id.name
1036 newNode
1037
1038 defaultRule = ->
1039 throw new Error "compile: Non-exhaustive patterns in case: #{@className}"
1040
1041 (ast, options = {}) ->
1042 options.bare ?= no
1043 rules = @rules
1044 jsAST = walk.call ast, (-> (rules[@className] ? defaultRule).apply this, arguments), [], [], options
1045 generateSymbols jsAST,
1046 declaredSymbols: []
1047 usedSymbols: union jsReserved[..], collectIdentifiers jsAST
1048 nsCounters: {}