1 | {all, any, concat, concatMap, difference, foldl, foldl1, union} = require './functional-helpers'
|
2 | {beingDeclared, declarationsFor, usedAsExpression, envEnrichments} = require './helpers'
|
3 | CS = require './nodes'
|
4 | exports = module?.exports ? this
|
5 |
|
6 | makeDispatcher = (defaultValue, handlers, defaultHandler = (->)) ->
|
7 | handlers_ = {}
|
8 | for [ctors..., handler] in handlers
|
9 | handlers_[ctor::className] = handler for ctor in ctors
|
10 | (node, args...) ->
|
11 | return defaultValue unless node?
|
12 | handler =
|
13 | if Object::hasOwnProperty.call handlers_, node.className
|
14 | handlers_[node.className]
|
15 | else defaultHandler
|
16 | handler.apply node, args
|
17 |
|
18 |
|
19 | isTruthy =
|
20 | makeDispatcher no, [
|
21 | [
|
22 | CS.ArrayInitialiser, CS.Class, CS.DeleteOp, CS.ForIn, CS.ForOf
|
23 | CS.Function, CS.BoundFunction, CS.HeregExp, CS.ObjectInitialiser, CS.Range
|
24 | CS.RegExp, CS.Slice, CS.TypeofOp, CS.While
|
25 | -> yes
|
26 | ]
|
27 | [CS.AssignOp, -> isTruthy @expression]
|
28 | [CS.Block, ->
|
29 | if @statements.length is 0 then no
|
30 | else isTruthy @statements[@statements.length - 1]
|
31 | ]
|
32 | [CS.Bool, CS.Float, CS.Int, CS.String, -> !!@data]
|
33 | [CS.Conditional, ->
|
34 | (isTruthy @condition) and (isTruthy @consequent) or
|
35 | (isFalsey @condition) and isTruthy @alternate
|
36 | ]
|
37 | [CS.LogicalAndOp, -> (isTruthy @left) and isTruthy @right]
|
38 | [CS.LogicalNotOp, -> isFalsey @expression]
|
39 | [CS.LogicalOrOp, -> (isTruthy @left) or isTruthy @right]
|
40 | [CS.Program, -> isTruthy @body]
|
41 | [CS.SeqOp, -> isTruthy @right]
|
42 | [CS.Switch, ->
|
43 | (all @cases, isTruthy) and
|
44 | if @alternate? then isTruthy @alternate else yes
|
45 | ]
|
46 | [CS.SwitchCase, -> isTruthy @consequent]
|
47 | [CS.UnaryExistsOp, ->
|
48 | (isTruthy @expression) or
|
49 |
|
50 | @expression.instanceof CS.Int, CS.Float, CS.String, CS.UnaryPlusOp, CS.UnaryNegateOp, CS.LogicalNotOp
|
51 | ]
|
52 | ], -> no
|
53 |
|
54 | isFalsey =
|
55 | makeDispatcher no, [
|
56 | [CS.Null, CS.Undefined, -> yes]
|
57 | [CS.AssignOp, -> isFalsey @expression]
|
58 | [CS.Block, ->
|
59 | if @statements.length is 0 then yes
|
60 | else isFalsey @statements[@statements.length - 1]
|
61 | ]
|
62 | [CS.Bool, CS.Float, CS.Int, CS.String, -> not @data]
|
63 | [CS.Conditional, ->
|
64 | (isTruthy @condition) and (isFalsey @consequent) or
|
65 | (isFalsey @condition) and isFalsey @alternate
|
66 | ]
|
67 | [CS.LogicalAndOp, -> (isFalsey @left) or isFalsey @right]
|
68 | [CS.LogicalNotOp, -> isTruthy @expression]
|
69 | [CS.LogicalOrOp, -> (isFalsey @left) and isFalsey @right]
|
70 | [CS.Program, -> isFalsey @body]
|
71 | [CS.SeqOp, -> isFalsey @right]
|
72 | [CS.Switch, ->
|
73 | (all @cases, isFalsey) and
|
74 | if @alternate? then isFalsey @alternate else yes
|
75 | ]
|
76 | [CS.SwitchCase, -> isFalsey @block]
|
77 | [CS.UnaryExistsOp, -> @expression.instanceof CS.Null, CS.Undefined]
|
78 | ], -> no
|
79 |
|
80 | mayHaveSideEffects =
|
81 | makeDispatcher no, [
|
82 | [
|
83 | CS.Function, CS.BoundFunction, CS.Null, CS.RegExp, CS.This, CS.Undefined
|
84 | -> no
|
85 | ]
|
86 | [
|
87 | CS.Break, CS.Continue, CS.Debugger, CS.DeleteOp, CS.NewOp, CS.Return, CS.Super
|
88 | CS.PreDecrementOp, CS.PreIncrementOp, CS.PostDecrementOp, CS.PostIncrementOp
|
89 | CS.ClassProtoAssignOp, CS.Constructor, CS.Throw, CS.JavaScript, CS.ExtendsOp
|
90 | -> yes
|
91 | ]
|
92 | [CS.Class, (inScope) ->
|
93 | (mayHaveSideEffects @parent, inScope) or
|
94 | @nameAssignee? and (@name or (beingDeclared @nameAssignee).length > 0)
|
95 | ]
|
96 | [CS.Conditional, (inScope) ->
|
97 | (mayHaveSideEffects @condition, inScope) or
|
98 | (not isFalsey @condition) and (mayHaveSideEffects @consequent, inScope) or
|
99 | (not isTruthy @condition) and mayHaveSideEffects @alternate, inScope
|
100 | ]
|
101 | [CS.DoOp, (inScope) ->
|
102 | return yes unless @expression.instanceof CS.Functions
|
103 | newScope = difference inScope, concatMap @expression.parameters, beingDeclared
|
104 | args = for p in @expression.parameters
|
105 | if p.instanceof CS.AssignOp then p.expression else p
|
106 | return yes if any args, (a) -> mayHaveSideEffects a, newScope
|
107 | mayHaveSideEffects @expression.body, newScope
|
108 | ]
|
109 | [CS.ExistsOp, (inScope) ->
|
110 | return yes if mayHaveSideEffects @left, inScope
|
111 | return no if @left.instanceof CS.Undefined, CS.Null
|
112 | mayHaveSideEffects @right, inScope
|
113 | ]
|
114 | [CS.FunctionApplication, CS.SoakedFunctionApplication, (inScope) ->
|
115 | return yes unless @function.instanceof CS.Function, CS.BoundFunction
|
116 | newScope = difference inScope, concatMap @function.parameters, beingDeclared
|
117 | return yes if any @arguments, (a) -> mayHaveSideEffects a, newScope
|
118 | mayHaveSideEffects @function.body, newScope
|
119 | ]
|
120 | [CS.LogicalAndOp, (inScope) ->
|
121 | return yes if mayHaveSideEffects @left, inScope
|
122 | return no if isFalsey @left
|
123 | mayHaveSideEffects @right, inScope
|
124 | ]
|
125 | [CS.LogicalOrOp, (inScope) ->
|
126 | return yes if mayHaveSideEffects @left, inScope
|
127 | return no if isTruthy @left
|
128 | mayHaveSideEffects @right, inScope
|
129 | ]
|
130 | [CS.While, (inScope) ->
|
131 | (mayHaveSideEffects @condition, inScope) or
|
132 | (not isFalsey @condition) and mayHaveSideEffects @body, inScope
|
133 | ]
|
134 |
|
135 | [CS.AssignOp, CS.ClassProtoAssignOp, CS.CompoundAssignOp, (inScope) ->
|
136 |
|
137 | yes
|
138 | ]
|
139 |
|
140 | [CS.Bool, CS.Float, CS.Identifier, CS.Int, CS.String, -> no]
|
141 | ], (inScope) ->
|
142 | any @childNodes, (child) =>
|
143 | if child in @listMembers
|
144 | then any @[child], (m) -> mayHaveSideEffects m, inScope
|
145 | else mayHaveSideEffects @[child], inScope
|
146 |
|
147 |
|
148 |
|
149 | class exports.Optimiser
|
150 |
|
151 | @optimise = => (new this).optimise arguments...
|
152 |
|
153 |
|
154 | @isTruthy = isTruthy
|
155 | @isFalsey = isFalsey
|
156 | @mayHaveSideEffects = mayHaveSideEffects
|
157 |
|
158 | defaultRules = [
|
159 |
|
160 |
|
161 | [CS.Program, ->
|
162 | if @body? and mayHaveSideEffects @body, [] then this
|
163 | else new CS.Program null
|
164 | ]
|
165 |
|
166 |
|
167 | [CS.Block, ({inScope}) ->
|
168 | switch @statements.length
|
169 | when 0 then (new CS.Undefined).g()
|
170 | when 1 then @statements[0]
|
171 | else
|
172 | foldl @statements[0], @statements[1..], (expr, s) ->
|
173 | new CS.SeqOp expr, s
|
174 | ]
|
175 |
|
176 |
|
177 |
|
178 | [CS.SeqOp, ({inScope, ancestry}) ->
|
179 | canDropLast = not usedAsExpression this, ancestry
|
180 | if mayHaveSideEffects @left, inScope
|
181 | if mayHaveSideEffects @right, inScope then this
|
182 | else if not canDropLast then this
|
183 | else if @right.instanceof CS.Undefined then @left
|
184 | else new CS.SeqOp @left, declarationsFor @right, union inScope, envEnrichments @left, inScope
|
185 | else if (@right.instanceof CS.Identifier) and @right.data is 'eval' and
|
186 | ((ancestry[0]?.instanceof CS.FunctionApplication) and ancestry[0].function is this or
|
187 | (ancestry[0]?.instanceof CS.DoOp) and ancestry[0].expression is this)
|
188 | if (@left.instanceof CS.Int) and 0 <= @left.data <= 9 then this
|
189 | else if mayHaveSideEffects @left, inScope then this
|
190 | else new CS.SeqOp (new CS.Int 0).g(), @right
|
191 | else
|
192 | if mayHaveSideEffects @right, inScope
|
193 | decls = declarationsFor @left, inScope
|
194 | if decls.instanceof CS.Undefined then @right
|
195 |
|
196 | else this
|
197 | else if canDropLast
|
198 | declarationsFor this, inScope
|
199 | else @right
|
200 | ]
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | [CS.AssignOp, ->
|
206 | return this unless @expression.instanceof CS.SeqOp
|
207 | new CS.SeqOp @expression.left, new CS.AssignOp @assignee, @expression.right
|
208 | ]
|
209 |
|
210 |
|
211 |
|
212 |
|
213 | [CS.While, ({inScope}) ->
|
214 | if isFalsey @condition
|
215 | new CS.Block [
|
216 | if mayHaveSideEffects @condition, inScope
|
217 | new CS.SeqOp @condition, declarationsFor @body
|
218 | else
|
219 | if @body? then declarationsFor @body, inScope else new CS.Undefined
|
220 | new CS.ArrayInitialiser []
|
221 | ]
|
222 | else if isTruthy @condition
|
223 | if mayHaveSideEffects @condition, inScope then this
|
224 | else if @body?
|
225 | if this instanceof CS.Loop then this else (new CS.Loop @body).g()
|
226 | else new CS.ArrayInitialiser []
|
227 | else this
|
228 | ]
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | [CS.Conditional, ({inScope}) ->
|
234 | if isFalsey @condition
|
235 | [removedBlock, block] = [@consequent, @alternate]
|
236 | else if isTruthy @condition
|
237 | [block, removedBlock] = [@consequent, @alternate]
|
238 | else
|
239 | return this
|
240 | decls = declarationsFor removedBlock, inScope
|
241 | block = if block? then new CS.SeqOp decls, block else decls
|
242 | if mayHaveSideEffects @condition, inScope
|
243 | block = new CS.SeqOp @condition, block
|
244 | block
|
245 | ]
|
246 |
|
247 |
|
248 | [CS.ForIn, ({inScope}) ->
|
249 | return this unless (@target.instanceof CS.ArrayInitialiser) and @target.members.length is 0
|
250 | new CS.SeqOp (declarationsFor this, inScope), (new CS.ArrayInitialiser []).g()
|
251 | ]
|
252 |
|
253 |
|
254 | [CS.ForOf, ({inScope}) ->
|
255 | return this unless @isOwn and (@target.instanceof CS.ObjectInitialiser) and @target.members.length is 0
|
256 | new CS.SeqOp (declarationsFor this, inScope), (new CS.ArrayInitialiser []).g()
|
257 | ]
|
258 |
|
259 |
|
260 | [CS.ForIn, CS.ForOf, ({inScope}) ->
|
261 | return this unless isFalsey @filter
|
262 | new CS.SeqOp (declarationsFor this, inScope), (new CS.ArrayInitialiser []).g()
|
263 | ]
|
264 |
|
265 |
|
266 | [CS.ForIn, ->
|
267 | return this unless isTruthy @filter
|
268 | new CS.ForIn @valAssignee, @keyAssignee, @target, @step, null, @body
|
269 | ]
|
270 | [CS.ForOf, ->
|
271 | return this unless isTruthy @filter
|
272 | new CS.ForOf @isOwn, @keyAssignee, @valAssignee, @target, null, @body
|
273 | ]
|
274 |
|
275 |
|
276 | [CS.ArrayInitialiser, ({inScope, ancestry}) ->
|
277 | if usedAsExpression this, ancestry then this
|
278 | else
|
279 | foldl (new CS.Undefined).g(), @members, (expr, m) ->
|
280 | new CS.SeqOp expr, m
|
281 | ]
|
282 |
|
283 |
|
284 | [CS.ExistsOp, -> if @left.instanceof CS.Null, CS.Undefined then @right else this]
|
285 |
|
286 |
|
287 | [CS.UnaryExistsOp, -> if @expression.instanceof CS.Null, CS.Undefined then (new CS.Bool false).g() else this]
|
288 |
|
289 |
|
290 | [CS.LogicalNotOp, ({inScope}) ->
|
291 | switch
|
292 | when @expression.instanceof CS.Int, CS.Float, CS.String, CS.Bool
|
293 | (new CS.Bool !@expression.data).g()
|
294 | when @expression.instanceof CS.Functions then (new CS.Bool false).g()
|
295 | when @expression.instanceof CS.Null, CS.Undefined then (new CS.Bool true).g()
|
296 | when @expression.instanceof CS.ArrayInitialiser, CS.ObjectInitialiser
|
297 | if mayHaveSideEffects @expression, inScope then this
|
298 | else new CS.SeqOp (declarationsFor @expression, inScope), (new CS.Bool false).g()
|
299 | when @expression.instanceof CS.LogicalNotOp
|
300 | if @expression.expression.instanceof CS.LogicalNotOp then @expression.expression
|
301 | else this
|
302 | else this
|
303 | ]
|
304 |
|
305 |
|
306 | [CS.TypeofOp, ->
|
307 | switch
|
308 | when @expression.instanceof CS.Int, CS.Float, CS.UnaryNegateOp, CS.UnaryPlusOp
|
309 | (new CS.String 'number').g()
|
310 | when @expression.instanceof CS.String then (new CS.String 'string').g()
|
311 | when @expression.instanceof CS.Functions then (new CS.String 'function').g()
|
312 | when @expression.instanceof CS.Undefined then (new CS.String 'undefined').g()
|
313 |
|
314 | else this
|
315 | ]
|
316 |
|
317 |
|
318 | [CS.SeqOp, ({ancestry}) ->
|
319 | return this unless (ancestry[0]?.instanceof CS.Functions) and ancestry[0].body is this
|
320 | if (@right.instanceof CS.Return) and @right.expression?
|
321 | new CS.SeqOp @left, @right.expression
|
322 | else if @right.instanceof CS.Undefined
|
323 | new CS.SeqOp @left, new CS.Return
|
324 | else this
|
325 | ]
|
326 |
|
327 |
|
328 | [CS.Function, CS.BoundFunction, ->
|
329 | return this unless @block? and (
|
330 | (@block.instanceof CS.Undefined) or
|
331 | (@block.instanceof CS.Return) and not @block.expression?
|
332 | )
|
333 | new @constructor @parameters, null
|
334 | ]
|
335 |
|
336 |
|
337 | [CS.Return, -> if @expression?.instanceof CS.Undefined then new CS.Return else this]
|
338 |
|
339 | [CS.Slice, ->
|
340 | if (@left?.instanceof CS.Int, CS.String) and +@left.data is 0
|
341 | new CS.Slice @expression, @isInclusive, null, @right
|
342 | else if @isInclusive and (@right?.instanceof CS.UnaryNegateOp) and (@right.expression.instanceof CS.Int) and @right.expression.data is 1
|
343 | new CS.Slice @expression, yes, @left, null
|
344 | else this
|
345 | ]
|
346 | ]
|
347 |
|
348 | constructor: ->
|
349 | @rules = {}
|
350 | for [ctors..., handler] in defaultRules
|
351 | for ctor in ctors
|
352 | @addRule ctor::className, handler
|
353 |
|
354 | addRule: (ctor, handler) ->
|
355 | (@rules[ctor] ?= []).push handler
|
356 | this
|
357 |
|
358 | optimise: do ->
|
359 |
|
360 | walk = (fn, inScope = [], ancestry = []) ->
|
361 | if not this? or this is global
|
362 | throw new Error 'Optimiser rules must produce a node. `null` is not a node.'
|
363 | return this if this in ancestry
|
364 | ancestry.unshift this
|
365 | for childName in @childNodes when @[childName]?
|
366 | if childName in @listMembers
|
367 | for member, n in @[childName]
|
368 | while @[childName][n] isnt walk.call (@[childName][n] = fn.call @[childName][n], {inScope, ancestry}), fn, inScope, ancestry then
|
369 | inScope = union inScope, envEnrichments @[childName][n], inScope
|
370 | else
|
371 | while @[childName] isnt walk.call (@[childName] = fn.call @[childName], {inScope, ancestry}), fn, inScope, ancestry then
|
372 | inScope = union inScope, envEnrichments @[childName], inScope
|
373 | do ancestry.shift
|
374 | jsNode = fn.call this, {inScope, ancestry}
|
375 | jsNode[p] = @[p] for p in ['raw', 'line', 'column', 'offset']
|
376 | jsNode
|
377 |
|
378 | (ast) ->
|
379 | rules = @rules
|
380 | walk.call ast, ->
|
381 |
|
382 | memo = this
|
383 | oldClassName = null
|
384 | while oldClassName isnt memo.className
|
385 | for rule in rules[oldClassName = memo.className] ? []
|
386 | memo = rule.apply memo, arguments
|
387 | break unless oldClassName is memo.className
|
388 | memo
|