1 | {any, concat, concatMap, difference, divMod, foldl1, map, nub, owns, partition, span, union} = require './functional-helpers'
|
2 | {beingDeclared, usedAsExpression, envEnrichments} = require './helpers'
|
3 | CS = require './nodes'
|
4 | JS = require './js-nodes'
|
5 | exports = module?.exports ? this
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | jsReserved = [
|
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 |
|
19 | genSym = do ->
|
20 | genSymCounter = 0
|
21 | (pre) -> new JS.GenSym pre, ++genSymCounter
|
22 |
|
23 |
|
24 | stmt = (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 |
|
35 | new JS.IfStatement (expr e.test), (stmt e.consequent), stmt e.alternate
|
36 | else new JS.ExpressionStatement e
|
37 |
|
38 | expr = (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 |
|
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 |
|
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 |
|
75 | throw new Error "expr: Cannot use a #{s.type} as a value"
|
76 |
|
77 | makeReturn = (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 |
|
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 |
|
101 | generateMutatingWalker = (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 |
|
112 | declarationsNeeded = (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 |
|
117 | else []
|
118 |
|
119 | declarationsNeededRecursive = (node) ->
|
120 | return [] unless node?
|
121 |
|
122 | if node.instanceof JS.FunctionExpression, JS.FunctionDeclaration then []
|
123 | else union (declarationsNeeded node), concatMap node.childNodes, (childName) ->
|
124 |
|
125 | return [] unless node[childName]?
|
126 | if childName in node.listMembers then concatMap node[childName], declarationsNeededRecursive
|
127 | else declarationsNeededRecursive node[childName]
|
128 |
|
129 | collectIdentifiers = (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 |
|
142 | needsCaching = (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 |
|
149 | forceBlock = (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 |
|
155 | makeVarDeclaration = (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 |
|
165 | isIdentifierName = (name) ->
|
166 |
|
167 | name not in jsReserved and /^[$_a-z][$_a-z0-9]*$/i.test name
|
168 |
|
169 | memberAccess = (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 |
|
174 | dynamicMemberAccess = (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 |
|
180 | assignment = (assignee, expression, valueUsed = no) ->
|
181 | assignments = []
|
182 | switch
|
183 | when assignee.rest then
|
184 | when assignee.instanceof JS.ArrayExpression
|
185 | e = expr expression
|
186 |
|
187 |
|
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 |
|
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 |
|
228 |
|
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 |
|
246 | hasSoak = (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 |
|
256 | generateSoak = do ->
|
257 |
|
258 |
|
259 |
|
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 |
|
301 | helperNames = {}
|
302 | helpers =
|
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 =
|
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 |
|
360 | enabledHelpers = []
|
361 | for 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 |
|
367 | inlineHelpers =
|
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 |
|
372 | for own h, fn of inlineHelpers
|
373 | helpers[h] = fn
|
374 |
|
375 |
|
376 |
|
377 | class exports.Compiler
|
378 |
|
379 | @compile = => (new this).compile arguments...
|
380 |
|
381 |
|
382 | defaultRules = [
|
383 |
|
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 |
|
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 |
|
402 | block = [stmt new JS.UnaryExpression 'void', new JS.CallExpression (memberAccess (new JS.FunctionExpression null, [], new JS.BlockStatement block), 'call'), [new JS.ThisExpression]]
|
403 |
|
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 |
|
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 |
|
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 |
|
497 | [CS.Range, ({left: left_, right: right_}) ->
|
498 |
|
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
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
1004 | generatedSymbols[node.uniqueId]
|
1005 | else
|
1006 |
|
1007 | nsCounters[node.ns] = if owns nsCounters, node.ns then 1 + nsCounters[node.ns] else 0
|
1008 |
|
1009 | ++nsCounters[node.ns] while (formatted = format node.ns, nsCounters[node.ns]) in usedSymbols
|
1010 |
|
1011 | generatedSymbols[node.uniqueId] = formatted
|
1012 |
|
1013 |
|
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: {}
|