haml-more 0.4.0.a

Sign up to get free protection for your applications and to get access to all the features.
Files changed (225) hide show
  1. data/README.md +79 -0
  2. data/lib/haml/more/coffee_script.rb +137 -0
  3. data/lib/haml/more/content_for.rb +25 -0
  4. data/lib/haml/more.rb +45 -0
  5. data/lib/haml-more.rb +1 -0
  6. data/lib/sass/more.rb +16 -0
  7. data/lib/sass-more.rb +1 -0
  8. data/spec/sass/more_spec.rb +21 -0
  9. data/vendor/coffee-script/Cakefile +57 -0
  10. data/vendor/coffee-script/LICENSE +22 -0
  11. data/vendor/coffee-script/README +41 -0
  12. data/vendor/coffee-script/Rakefile +20 -0
  13. data/vendor/coffee-script/bin/cake +7 -0
  14. data/vendor/coffee-script/bin/coffee +7 -0
  15. data/vendor/coffee-script/documentation/coffee/aliases.coffee +9 -0
  16. data/vendor/coffee-script/documentation/coffee/arguments.coffee +4 -0
  17. data/vendor/coffee-script/documentation/coffee/array_comprehensions.coffee +7 -0
  18. data/vendor/coffee-script/documentation/coffee/assignment.coffee +2 -0
  19. data/vendor/coffee-script/documentation/coffee/cake_tasks.coffee +5 -0
  20. data/vendor/coffee-script/documentation/coffee/comparisons.coffee +5 -0
  21. data/vendor/coffee-script/documentation/coffee/conditionals.coffee +9 -0
  22. data/vendor/coffee-script/documentation/coffee/embedded.coffee +5 -0
  23. data/vendor/coffee-script/documentation/coffee/existence.coffee +8 -0
  24. data/vendor/coffee-script/documentation/coffee/expressions.coffee +9 -0
  25. data/vendor/coffee-script/documentation/coffee/expressions_assignment.coffee +1 -0
  26. data/vendor/coffee-script/documentation/coffee/expressions_comprehension.coffee +3 -0
  27. data/vendor/coffee-script/documentation/coffee/expressions_try.coffee +6 -0
  28. data/vendor/coffee-script/documentation/coffee/fat_arrow.coffee +6 -0
  29. data/vendor/coffee-script/documentation/coffee/functions.coffee +2 -0
  30. data/vendor/coffee-script/documentation/coffee/heredocs.coffee +5 -0
  31. data/vendor/coffee-script/documentation/coffee/multiple_return_values.coffee +5 -0
  32. data/vendor/coffee-script/documentation/coffee/object_comprehensions.coffee +4 -0
  33. data/vendor/coffee-script/documentation/coffee/object_extraction.coffee +13 -0
  34. data/vendor/coffee-script/documentation/coffee/objects_and_arrays.coffee +13 -0
  35. data/vendor/coffee-script/documentation/coffee/overview.coffee +29 -0
  36. data/vendor/coffee-script/documentation/coffee/parallel_assignment.coffee +4 -0
  37. data/vendor/coffee-script/documentation/coffee/range_comprehensions.coffee +6 -0
  38. data/vendor/coffee-script/documentation/coffee/scope.coffee +5 -0
  39. data/vendor/coffee-script/documentation/coffee/slices.coffee +6 -0
  40. data/vendor/coffee-script/documentation/coffee/soaks.coffee +1 -0
  41. data/vendor/coffee-script/documentation/coffee/splats.coffee +25 -0
  42. data/vendor/coffee-script/documentation/coffee/splices.coffee +5 -0
  43. data/vendor/coffee-script/documentation/coffee/strings.coffee +8 -0
  44. data/vendor/coffee-script/documentation/coffee/super.coffee +34 -0
  45. data/vendor/coffee-script/documentation/coffee/switch.coffee +10 -0
  46. data/vendor/coffee-script/documentation/coffee/try.coffee +7 -0
  47. data/vendor/coffee-script/documentation/coffee/while.coffee +10 -0
  48. data/vendor/coffee-script/documentation/css/docs.css +213 -0
  49. data/vendor/coffee-script/documentation/css/idle.css +63 -0
  50. data/vendor/coffee-script/documentation/css/logo.png +0 -0
  51. data/vendor/coffee-script/documentation/index.html.erb +967 -0
  52. data/vendor/coffee-script/documentation/js/aliases.js +14 -0
  53. data/vendor/coffee-script/documentation/js/arguments.js +8 -0
  54. data/vendor/coffee-script/documentation/js/array_comprehensions.js +26 -0
  55. data/vendor/coffee-script/documentation/js/assignment.js +5 -0
  56. data/vendor/coffee-script/documentation/js/cake_tasks.js +14 -0
  57. data/vendor/coffee-script/documentation/js/comparisons.js +5 -0
  58. data/vendor/coffee-script/documentation/js/conditionals.js +12 -0
  59. data/vendor/coffee-script/documentation/js/embedded.js +6 -0
  60. data/vendor/coffee-script/documentation/js/existence.js +7 -0
  61. data/vendor/coffee-script/documentation/js/expressions.js +13 -0
  62. data/vendor/coffee-script/documentation/js/expressions_assignment.js +4 -0
  63. data/vendor/coffee-script/documentation/js/expressions_comprehension.js +12 -0
  64. data/vendor/coffee-script/documentation/js/expressions_try.js +9 -0
  65. data/vendor/coffee-script/documentation/js/fat_arrow.js +15 -0
  66. data/vendor/coffee-script/documentation/js/functions.js +9 -0
  67. data/vendor/coffee-script/documentation/js/heredocs.js +4 -0
  68. data/vendor/coffee-script/documentation/js/intro.js +7 -0
  69. data/vendor/coffee-script/documentation/js/multiple_return_values.js +11 -0
  70. data/vendor/coffee-script/documentation/js/object_comprehensions.js +17 -0
  71. data/vendor/coffee-script/documentation/js/object_extraction.js +17 -0
  72. data/vendor/coffee-script/documentation/js/objects_and_arrays.js +10 -0
  73. data/vendor/coffee-script/documentation/js/overview.js +43 -0
  74. data/vendor/coffee-script/documentation/js/parallel_assignment.js +8 -0
  75. data/vendor/coffee-script/documentation/js/punctuation.js +8 -0
  76. data/vendor/coffee-script/documentation/js/range_comprehensions.js +21 -0
  77. data/vendor/coffee-script/documentation/js/scope.js +10 -0
  78. data/vendor/coffee-script/documentation/js/slices.js +6 -0
  79. data/vendor/coffee-script/documentation/js/soaks.js +4 -0
  80. data/vendor/coffee-script/documentation/js/splats.js +16 -0
  81. data/vendor/coffee-script/documentation/js/splices.js +5 -0
  82. data/vendor/coffee-script/documentation/js/strings.js +9 -0
  83. data/vendor/coffee-script/documentation/js/super.js +37 -0
  84. data/vendor/coffee-script/documentation/js/switch.js +18 -0
  85. data/vendor/coffee-script/documentation/js/try.js +10 -0
  86. data/vendor/coffee-script/documentation/js/while.js +22 -0
  87. data/vendor/coffee-script/documentation/underscore.html +627 -0
  88. data/vendor/coffee-script/examples/beautiful_code/binary_search.coffee +16 -0
  89. data/vendor/coffee-script/examples/beautiful_code/quicksort_runtime.coffee +13 -0
  90. data/vendor/coffee-script/examples/beautiful_code/regular_expression_matcher.coffee +34 -0
  91. data/vendor/coffee-script/examples/blocks.coffee +57 -0
  92. data/vendor/coffee-script/examples/code.coffee +173 -0
  93. data/vendor/coffee-script/examples/computer_science/README +4 -0
  94. data/vendor/coffee-script/examples/computer_science/binary_search.coffee +25 -0
  95. data/vendor/coffee-script/examples/computer_science/bubble_sort.coffee +11 -0
  96. data/vendor/coffee-script/examples/computer_science/linked_list.coffee +106 -0
  97. data/vendor/coffee-script/examples/computer_science/luhn_algorithm.coffee +36 -0
  98. data/vendor/coffee-script/examples/computer_science/merge_sort.coffee +19 -0
  99. data/vendor/coffee-script/examples/computer_science/selection_sort.coffee +23 -0
  100. data/vendor/coffee-script/examples/poignant.coffee +186 -0
  101. data/vendor/coffee-script/examples/potion.coffee +205 -0
  102. data/vendor/coffee-script/examples/underscore.coffee +603 -0
  103. data/vendor/coffee-script/examples/web_server.coffee +12 -0
  104. data/vendor/coffee-script/extras/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences +24 -0
  105. data/vendor/coffee-script/extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +361 -0
  106. data/vendor/coffee-script/extras/CoffeeScript.tmbundle/info.plist +10 -0
  107. data/vendor/coffee-script/extras/EXTRAS +20 -0
  108. data/vendor/coffee-script/extras/coffee.vim +117 -0
  109. data/vendor/coffee-script/index.html +1847 -0
  110. data/vendor/coffee-script/lib/bin/cake +7 -0
  111. data/vendor/coffee-script/lib/bin/coffee +7 -0
  112. data/vendor/coffee-script/lib/cake.js +80 -0
  113. data/vendor/coffee-script/lib/coffee-script.js +61 -0
  114. data/vendor/coffee-script/lib/command_line.js +201 -0
  115. data/vendor/coffee-script/lib/grammar.js +564 -0
  116. data/vendor/coffee-script/lib/lexer.js +405 -0
  117. data/vendor/coffee-script/lib/narwhal.js +44 -0
  118. data/vendor/coffee-script/lib/nodes.js +1328 -0
  119. data/vendor/coffee-script/lib/optparse.js +117 -0
  120. data/vendor/coffee-script/lib/parser.js +536 -0
  121. data/vendor/coffee-script/lib/repl.js +32 -0
  122. data/vendor/coffee-script/lib/rewriter.js +383 -0
  123. data/vendor/coffee-script/lib/scope.js +114 -0
  124. data/vendor/coffee-script/package.json +7 -0
  125. data/vendor/coffee-script/src/cake.coffee +45 -0
  126. data/vendor/coffee-script/src/coffee-script.coffee +45 -0
  127. data/vendor/coffee-script/src/command_line.coffee +130 -0
  128. data/vendor/coffee-script/src/grammar.coffee +456 -0
  129. data/vendor/coffee-script/src/lexer.coffee +327 -0
  130. data/vendor/coffee-script/src/narwhal.coffee +42 -0
  131. data/vendor/coffee-script/src/nodes.coffee +1045 -0
  132. data/vendor/coffee-script/src/optparse.coffee +79 -0
  133. data/vendor/coffee-script/src/repl.coffee +23 -0
  134. data/vendor/coffee-script/src/rewriter.coffee +253 -0
  135. data/vendor/coffee-script/src/scope.coffee +75 -0
  136. data/vendor/coffee-script/test/test_arguments.coffee +34 -0
  137. data/vendor/coffee-script/test/test_array_comprehension.coffee +42 -0
  138. data/vendor/coffee-script/test/test_assignment.coffee +26 -0
  139. data/vendor/coffee-script/test/test_blocks.coffee +4 -0
  140. data/vendor/coffee-script/test/test_calling_super.coffee +42 -0
  141. data/vendor/coffee-script/test/test_chained_calls.coffee +25 -0
  142. data/vendor/coffee-script/test/test_destructuring_assignment.coffee +62 -0
  143. data/vendor/coffee-script/test/test_everything.coffee +29 -0
  144. data/vendor/coffee-script/test/test_exceptions.coffee +2 -0
  145. data/vendor/coffee-script/test/test_existence.coffee +81 -0
  146. data/vendor/coffee-script/test/test_expressions.coffee +30 -0
  147. data/vendor/coffee-script/test/test_fancy_if_statement.coffee +26 -0
  148. data/vendor/coffee-script/test/test_functions.coffee +80 -0
  149. data/vendor/coffee-script/test/test_funky_comments.coffee +25 -0
  150. data/vendor/coffee-script/test/test_heredocs.coffee +46 -0
  151. data/vendor/coffee-script/test/test_lexical_scope.coffee +10 -0
  152. data/vendor/coffee-script/test/test_literals.coffee +56 -0
  153. data/vendor/coffee-script/test/test_nested_comprehensions.coffee +11 -0
  154. data/vendor/coffee-script/test/test_newline_escaping.coffee +6 -0
  155. data/vendor/coffee-script/test/test_operations.coffee +18 -0
  156. data/vendor/coffee-script/test/test_range_comprehension.coffee +20 -0
  157. data/vendor/coffee-script/test/test_ranges_and_slices.coffee +16 -0
  158. data/vendor/coffee-script/test/test_splats.coffee +47 -0
  159. data/vendor/coffee-script/test/test_splices.coffee +5 -0
  160. data/vendor/coffee-script/test/test_switch.coffee +64 -0
  161. data/vendor/coffee-script/test/test_while.coffee +30 -0
  162. data/vendor/coffee-script/vendor/jison/Jakefile +31 -0
  163. data/vendor/coffee-script/vendor/jison/README.md +347 -0
  164. data/vendor/coffee-script/vendor/jison/bin/jison +3 -0
  165. data/vendor/coffee-script/vendor/jison/bin/json2jison +3 -0
  166. data/vendor/coffee-script/vendor/jison/examples/ansic.jison +415 -0
  167. data/vendor/coffee-script/vendor/jison/examples/basic.json +8 -0
  168. data/vendor/coffee-script/vendor/jison/examples/basic2.json +9 -0
  169. data/vendor/coffee-script/vendor/jison/examples/basic2_lex.json +16 -0
  170. data/vendor/coffee-script/vendor/jison/examples/basic_lex.json +15 -0
  171. data/vendor/coffee-script/vendor/jison/examples/calculator.jison +38 -0
  172. data/vendor/coffee-script/vendor/jison/examples/calculator.jisonlex +14 -0
  173. data/vendor/coffee-script/vendor/jison/examples/calculator.json +42 -0
  174. data/vendor/coffee-script/vendor/jison/examples/classy.json +105 -0
  175. data/vendor/coffee-script/vendor/jison/examples/classy_ast.json +126 -0
  176. data/vendor/coffee-script/vendor/jison/examples/dism.json +25 -0
  177. data/vendor/coffee-script/vendor/jison/examples/dism_lr0.json +26 -0
  178. data/vendor/coffee-script/vendor/jison/examples/json.js +80 -0
  179. data/vendor/coffee-script/vendor/jison/examples/json_ast.js +83 -0
  180. data/vendor/coffee-script/vendor/jison/examples/precedence.json +26 -0
  181. data/vendor/coffee-script/vendor/jison/examples/reduce_conflict.json +13 -0
  182. data/vendor/coffee-script/vendor/jison/lib/jison/bnf.js +43 -0
  183. data/vendor/coffee-script/vendor/jison/lib/jison/jisonlex.js +18 -0
  184. data/vendor/coffee-script/vendor/jison/lib/jison/json2jison.js +146 -0
  185. data/vendor/coffee-script/vendor/jison/lib/jison/lexer.js +224 -0
  186. data/vendor/coffee-script/vendor/jison/lib/jison/util/bnf-parser.js +383 -0
  187. data/vendor/coffee-script/vendor/jison/lib/jison/util/lex-parser.js +407 -0
  188. data/vendor/coffee-script/vendor/jison/lib/jison/util/set.js +94 -0
  189. data/vendor/coffee-script/vendor/jison/lib/jison/util/typal.js +90 -0
  190. data/vendor/coffee-script/vendor/jison/lib/jison.js +1414 -0
  191. data/vendor/coffee-script/vendor/jison/package.json +14 -0
  192. data/vendor/coffee-script/vendor/jison/src/bnf.jison +110 -0
  193. data/vendor/coffee-script/vendor/jison/src/bnf.jisonlex +25 -0
  194. data/vendor/coffee-script/vendor/jison/src/bnf.lex.json +24 -0
  195. data/vendor/coffee-script/vendor/jison/src/jisonlex.jison +129 -0
  196. data/vendor/coffee-script/vendor/jison/src/jisonlex.jisonlex +31 -0
  197. data/vendor/coffee-script/vendor/jison/src/jisonlex.lex.json +30 -0
  198. data/vendor/coffee-script/vendor/jison/tests/all-tests.js +8 -0
  199. data/vendor/coffee-script/vendor/jison/tests/grammar/bnf.js +91 -0
  200. data/vendor/coffee-script/vendor/jison/tests/grammar/bnf_parse.js +65 -0
  201. data/vendor/coffee-script/vendor/jison/tests/grammar/grammar-tests.js +10 -0
  202. data/vendor/coffee-script/vendor/jison/tests/grammar/json2jison.js +24 -0
  203. data/vendor/coffee-script/vendor/jison/tests/grammar/lex/ansic.jisonlex +115 -0
  204. data/vendor/coffee-script/vendor/jison/tests/grammar/lex/bnf.jisonlex +25 -0
  205. data/vendor/coffee-script/vendor/jison/tests/grammar/lex/bnf.lex.json +24 -0
  206. data/vendor/coffee-script/vendor/jison/tests/grammar/lex/lex_grammar.jisonlex +31 -0
  207. data/vendor/coffee-script/vendor/jison/tests/grammar/lex/lex_grammar.lex.json +30 -0
  208. data/vendor/coffee-script/vendor/jison/tests/grammar/lex.jison +119 -0
  209. data/vendor/coffee-script/vendor/jison/tests/grammar/lex.js +58 -0
  210. data/vendor/coffee-script/vendor/jison/tests/grammar/lex_parse.js +117 -0
  211. data/vendor/coffee-script/vendor/jison/tests/lexer/lexer-tests.js +6 -0
  212. data/vendor/coffee-script/vendor/jison/tests/lexer/regexplexer.js +417 -0
  213. data/vendor/coffee-script/vendor/jison/tests/parser/actions.js +311 -0
  214. data/vendor/coffee-script/vendor/jison/tests/parser/api.js +236 -0
  215. data/vendor/coffee-script/vendor/jison/tests/parser/generator.js +196 -0
  216. data/vendor/coffee-script/vendor/jison/tests/parser/lalr.js +183 -0
  217. data/vendor/coffee-script/vendor/jison/tests/parser/lr0.js +72 -0
  218. data/vendor/coffee-script/vendor/jison/tests/parser/lr1.js +119 -0
  219. data/vendor/coffee-script/vendor/jison/tests/parser/parser-tests.js +14 -0
  220. data/vendor/coffee-script/vendor/jison/tests/parser/precedence.js +237 -0
  221. data/vendor/coffee-script/vendor/jison/tests/parser/slr.js +52 -0
  222. data/vendor/coffee-script/vendor/jison/tests/parser/tables.js +126 -0
  223. data/vendor/coffee-script/vendor/jison/tests/performance.js +110 -0
  224. data/vendor/coffee-script/vendor/jison/tests/setup.js +3 -0
  225. metadata +324 -0
@@ -0,0 +1,1045 @@
1
+ if process?
2
+ process.mixin require 'scope'
3
+ else
4
+ this.exports: this
5
+
6
+ # Some helper functions
7
+
8
+ # Tabs are two spaces for pretty printing.
9
+ TAB: ' '
10
+ TRAILING_WHITESPACE: /\s+$/gm
11
+
12
+ # Keep the identifier regex in sync with the Lexer.
13
+ IDENTIFIER: /^[a-zA-Z$_](\w|\$)*$/
14
+
15
+ # Merge objects.
16
+ merge: (options, overrides) ->
17
+ fresh: {}
18
+ (fresh[key]: val) for key, val of options
19
+ (fresh[key]: val) for key, val of overrides if overrides
20
+ fresh
21
+
22
+ # Trim out all falsy values from an array.
23
+ compact: (array) -> item for item in array when item
24
+
25
+ # Return a completely flattened version of an array.
26
+ flatten: (array) ->
27
+ memo: []
28
+ for item in array
29
+ if item instanceof Array then memo: memo.concat(item) else memo.push(item)
30
+ memo
31
+
32
+ # Delete a key from an object, returning the value.
33
+ del: (obj, key) ->
34
+ val: obj[key]
35
+ delete obj[key]
36
+ val
37
+
38
+ # Quickie inheritance convenience wrapper to reduce typing.
39
+ inherit: (parent, props) ->
40
+ klass: del(props, 'constructor')
41
+ klass extends parent
42
+ (klass.prototype[name]: prop) for name, prop of props
43
+ klass
44
+
45
+ # Mark a node as a statement, or a statement only.
46
+ statement: (klass, only) ->
47
+ klass::is_statement: -> true
48
+ (klass::is_statement_only: -> true) if only
49
+
50
+ # The abstract base class for all CoffeeScript nodes.
51
+ # All nodes are implement a "compile_node" method, which performs the
52
+ # code generation for that node. To compile a node, call the "compile"
53
+ # method, which wraps "compile_node" in some extra smarts, to know when the
54
+ # generated code should be wrapped up in a closure. An options hash is passed
55
+ # and cloned throughout, containing messages from higher in the AST,
56
+ # information about the current scope, and indentation level.
57
+ Node: exports.Node: ->
58
+
59
+ # This is extremely important -- we convert JS statements into expressions
60
+ # by wrapping them in a closure, only if it's possible, and we're not at
61
+ # the top level of a block (which would be unnecessary), and we haven't
62
+ # already been asked to return the result.
63
+ Node::compile: (o) ->
64
+ @options: merge o or {}
65
+ @indent: o.indent
66
+ del @options, 'operation' unless @operation_sensitive()
67
+ top: if @top_sensitive() then @options.top else del @options, 'top'
68
+ closure: @is_statement() and not @is_statement_only() and not top and
69
+ not @options.returns and not (this instanceof CommentNode) and
70
+ not @contains (node) -> node.is_statement_only()
71
+ if closure then @compile_closure(@options) else @compile_node(@options)
72
+
73
+ # Statements converted into expressions share scope with their parent
74
+ # closure, to preserve JavaScript-style lexical scope.
75
+ Node::compile_closure: (o) ->
76
+ @indent: o.indent
77
+ o.shared_scope: o.scope
78
+ ClosureNode.wrap(this).compile(o)
79
+
80
+ # If the code generation wishes to use the result of a complex expression
81
+ # in multiple places, ensure that the expression is only ever evaluated once.
82
+ Node::compile_reference: (o) ->
83
+ reference: new LiteralNode(o.scope.free_variable())
84
+ compiled: new AssignNode(reference, this)
85
+ [compiled, reference]
86
+
87
+ # Quick short method for the current indentation level, plus tabbing in.
88
+ Node::idt: (tabs) ->
89
+ idt: (@indent || '')
90
+ idt += TAB for i in [0...(tabs or 0)]
91
+ idt
92
+
93
+ # Does this node, or any of its children, contain a node of a certain kind?
94
+ Node::contains: (block) ->
95
+ for node in @children
96
+ return true if block(node)
97
+ return true if node instanceof Node and node.contains block
98
+ false
99
+
100
+ # toString representation of the node, for inspecting the parse tree.
101
+ Node::toString: (idt) ->
102
+ idt ||= ''
103
+ '\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
104
+
105
+ # Default implementations of the common node methods.
106
+ Node::unwrap: -> this
107
+ Node::children: []
108
+ Node::is_statement: -> false
109
+ Node::is_statement_only: -> false
110
+ Node::top_sensitive: -> false
111
+ Node::operation_sensitive: -> false
112
+
113
+ # A collection of nodes, each one representing an expression.
114
+ Expressions: exports.Expressions: inherit Node, {
115
+ type: 'Expressions'
116
+
117
+ constructor: (nodes) ->
118
+ @children: @expressions: compact flatten nodes or []
119
+ this
120
+
121
+ # Tack an expression on to the end of this expression list.
122
+ push: (node) ->
123
+ @expressions.push(node)
124
+ this
125
+
126
+ # Tack an expression on to the beginning of this expression list.
127
+ unshift: (node) ->
128
+ @expressions.unshift(node)
129
+ this
130
+
131
+ # If this Expressions consists of a single node, pull it back out.
132
+ unwrap: ->
133
+ if @expressions.length is 1 then @expressions[0] else this
134
+
135
+ # Is this an empty block of code?
136
+ empty: ->
137
+ @expressions.length is 0
138
+
139
+ # Is the node last in this block of expressions?
140
+ is_last: (node) ->
141
+ l: @expressions.length
142
+ last_index: if @expressions[l - 1] instanceof CommentNode then 2 else 1
143
+ node is @expressions[l - last_index]
144
+
145
+ compile: (o) ->
146
+ o ||= {}
147
+ if o.scope then Node::compile.call(this, o) else @compile_root(o)
148
+
149
+ # Compile each expression in the Expressions body.
150
+ compile_node: (o) ->
151
+ (@compile_expression(node, merge(o)) for node in @expressions).join("\n")
152
+
153
+ # If this is the top-level Expressions, wrap everything in a safety closure.
154
+ compile_root: (o) ->
155
+ o.indent: @indent: indent: if o.no_wrap then '' else TAB
156
+ o.scope: new Scope(null, this, null)
157
+ code: if o.globals then @compile_node(o) else @compile_with_declarations(o)
158
+ code: code.replace(TRAILING_WHITESPACE, '')
159
+ if o.no_wrap then code else "(function(){\n"+code+"\n})();"
160
+
161
+ # Compile the expressions body, with declarations of all inner variables
162
+ # pushed up to the top.
163
+ compile_with_declarations: (o) ->
164
+ code: @compile_node(o)
165
+ args: @contains (node) -> node instanceof ValueNode and node.is_arguments()
166
+ code: @idt() + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code if args
167
+ code: @idt() + 'var ' + o.scope.compiled_assignments() + ";\n" + code if o.scope.has_assignments(this)
168
+ code: @idt() + 'var ' + o.scope.compiled_declarations() + ";\n" + code if o.scope.has_declarations(this)
169
+ code
170
+
171
+ # Compiles a single expression within the expressions body.
172
+ compile_expression: (node, o) ->
173
+ @indent: o.indent
174
+ stmt: node.is_statement()
175
+ # We need to return the result if this is the last node in the expressions body.
176
+ returns: del(o, 'returns') and @is_last(node) and not node.is_statement_only()
177
+ # Return the regular compile of the node, unless we need to return the result.
178
+ return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns
179
+ # If it's a statement, the node knows how to return itself.
180
+ return node.compile(merge(o, {returns: true})) if node.is_statement()
181
+ # Otherwise, we can just return the value of the expression.
182
+ return @idt() + 'return ' + node.compile(o) + ';'
183
+
184
+ }
185
+
186
+ # Wrap up a node as an Expressions, unless it already is one.
187
+ Expressions.wrap: (nodes) ->
188
+ return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
189
+ new Expressions(nodes)
190
+
191
+ statement Expressions
192
+
193
+ # Literals are static values that can be passed through directly into
194
+ # JavaScript without translation, eg.: strings, numbers, true, false, null...
195
+ LiteralNode: exports.LiteralNode: inherit Node, {
196
+ type: 'Literal'
197
+
198
+ constructor: (value) ->
199
+ @value: value
200
+ this
201
+
202
+ # Break and continue must be treated as statements -- they lose their meaning
203
+ # when wrapped in a closure.
204
+ is_statement: ->
205
+ @value is 'break' or @value is 'continue'
206
+
207
+ compile_node: (o) ->
208
+ idt: if @is_statement() then @idt() else ''
209
+ end: if @is_statement() then ';' else ''
210
+ idt + @value + end
211
+
212
+ toString: (idt) ->
213
+ ' "' + @value + '"'
214
+
215
+ }
216
+
217
+ LiteralNode::is_statement_only: LiteralNode::is_statement
218
+
219
+ # Return an expression, or wrap it in a closure and return it.
220
+ ReturnNode: exports.ReturnNode: inherit Node, {
221
+ type: 'Return'
222
+
223
+ constructor: (expression) ->
224
+ @children: [@expression: expression]
225
+ this
226
+
227
+ compile_node: (o) ->
228
+ return @expression.compile(merge(o, {returns: true})) if @expression.is_statement()
229
+ @idt() + 'return ' + @expression.compile(o) + ';'
230
+
231
+ }
232
+
233
+ statement ReturnNode, true
234
+
235
+ # A value, indexed or dotted into, or vanilla.
236
+ ValueNode: exports.ValueNode: inherit Node, {
237
+ type: 'Value'
238
+
239
+ SOAK: " == undefined ? undefined : "
240
+
241
+ constructor: (base, properties) ->
242
+ @children: flatten [@base: base, @properties: (properties or [])]
243
+ this
244
+
245
+ push: (prop) ->
246
+ @properties.push(prop)
247
+ @children.push(prop)
248
+ this
249
+
250
+ operation_sensitive: ->
251
+ true
252
+
253
+ has_properties: ->
254
+ !!@properties.length
255
+
256
+ is_array: ->
257
+ @base instanceof ArrayNode and not @has_properties()
258
+
259
+ is_object: ->
260
+ @base instanceof ObjectNode and not @has_properties()
261
+
262
+ is_splice: ->
263
+ @has_properties() and @properties[@properties.length - 1] instanceof SliceNode
264
+
265
+ is_arguments: ->
266
+ @base.value is 'arguments'
267
+
268
+ unwrap: ->
269
+ if @properties.length then this else @base
270
+
271
+ # Values are statements if their base is a statement.
272
+ is_statement: ->
273
+ @base.is_statement and @base.is_statement() and not @has_properties()
274
+
275
+ compile_node: (o) ->
276
+ soaked: false
277
+ only: del(o, 'only_first')
278
+ op: del(o, 'operation')
279
+ props: if only then @properties[0...@properties.length - 1] else @properties
280
+ baseline: @base.compile o
281
+ baseline: '(' + baseline + ')' if @base instanceof ObjectNode and @has_properties()
282
+ complete: @last: baseline
283
+
284
+ for prop in props
285
+ @source: baseline
286
+ if prop.soak_node
287
+ soaked: true
288
+ if @base instanceof CallNode and prop is props[0]
289
+ temp: o.scope.free_variable()
290
+ complete: '(' + temp + ' = ' + complete + ')' + @SOAK + (baseline: temp + prop.compile(o))
291
+ else
292
+ complete: complete + @SOAK + (baseline += prop.compile(o))
293
+ else
294
+ part: prop.compile(o)
295
+ baseline += part
296
+ complete += part
297
+ @last: part
298
+
299
+ if op and soaked then '(' + complete + ')' else complete
300
+
301
+ }
302
+
303
+ # Pass through CoffeeScript comments into JavaScript comments at the
304
+ # same position.
305
+ CommentNode: exports.CommentNode: inherit Node, {
306
+ type: 'Comment'
307
+
308
+ constructor: (lines) ->
309
+ @lines: lines
310
+ this
311
+
312
+ compile_node: (o) ->
313
+ @idt() + '//' + @lines.join('\n' + @idt() + '//')
314
+
315
+ }
316
+
317
+ statement CommentNode
318
+
319
+ # Node for a function invocation. Takes care of converting super() calls into
320
+ # calls against the prototype's function of the same name.
321
+ CallNode: exports.CallNode: inherit Node, {
322
+ type: 'Call'
323
+
324
+ constructor: (variable, args) ->
325
+ @children: flatten [@variable: variable, @args: (args or [])]
326
+ @prefix: ''
327
+ this
328
+
329
+ new_instance: ->
330
+ @prefix: 'new '
331
+ this
332
+
333
+ push: (arg) ->
334
+ @args.push(arg)
335
+ @children.push(arg)
336
+ this
337
+
338
+ # Compile a vanilla function call.
339
+ compile_node: (o) ->
340
+ return @compile_splat(o) if @args[@args.length - 1] instanceof SplatNode
341
+ args: (arg.compile(o) for arg in @args).join(', ')
342
+ return @compile_super(args, o) if @variable is 'super'
343
+ @prefix + @variable.compile(o) + '(' + args + ')'
344
+
345
+ # Compile a call against the superclass's implementation of the current function.
346
+ compile_super: (args, o) ->
347
+ methname: o.scope.method.name
348
+ arg_part: if args.length then ', ' + args else ''
349
+ meth: if o.scope.method.proto
350
+ o.scope.method.proto + '.__superClass__.' + methname
351
+ else
352
+ methname + '.__superClass__.constructor'
353
+ meth + '.call(this' + arg_part + ')'
354
+
355
+ # Compile a function call being passed variable arguments.
356
+ compile_splat: (o) ->
357
+ meth: @variable.compile o
358
+ obj: @variable.source or 'this'
359
+ args: for arg, i in @args
360
+ code: arg.compile o
361
+ code: if arg instanceof SplatNode then code else '[' + code + ']'
362
+ if i is 0 then code else '.concat(' + code + ')'
363
+ @prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')'
364
+
365
+ }
366
+
367
+ # Node to extend an object's prototype with an ancestor object.
368
+ # After goog.inherits from the Closure Library.
369
+ ExtendsNode: exports.ExtendsNode: inherit Node, {
370
+ type: 'Extends'
371
+
372
+ constructor: (child, parent) ->
373
+ @children: [@child: child, @parent: parent]
374
+ this
375
+
376
+ # Hooking one constructor into another's prototype chain.
377
+ compile_node: (o) ->
378
+ construct: o.scope.free_variable()
379
+ child: @child.compile(o)
380
+ parent: @parent.compile(o)
381
+ prefix: ''
382
+ if not (@child instanceof ValueNode) or @child.has_properties() or not (@child.unwrap() instanceof LiteralNode)
383
+ child_var: o.scope.free_variable()
384
+ prefix += @idt() + child_var + ' = ' + child + ';\n'
385
+ child: child_var
386
+ if not (@parent instanceof ValueNode) or @parent.has_properties() or not (@parent.unwrap() instanceof LiteralNode)
387
+ parent_var: o.scope.free_variable()
388
+ prefix += @idt() + parent_var + ' = ' + parent + ';\n'
389
+ parent: parent_var
390
+ prefix + @idt() + construct + ' = function(){};\n' + @idt() +
391
+ construct + '.prototype = ' + parent + ".prototype;\n" + @idt() +
392
+ child + '.__superClass__ = ' + parent + ".prototype;\n" + @idt() +
393
+ child + '.prototype = new ' + construct + "();\n" + @idt() +
394
+ child + '.prototype.constructor = ' + child + ';'
395
+
396
+ }
397
+
398
+ statement ExtendsNode
399
+
400
+ # A dotted accessor into a part of a value, or the :: shorthand for
401
+ # an accessor into the object's prototype.
402
+ AccessorNode: exports.AccessorNode: inherit Node, {
403
+ type: 'Accessor'
404
+
405
+ constructor: (name, tag) ->
406
+ @children: [@name: name]
407
+ @prototype: tag is 'prototype'
408
+ @soak_node: tag is 'soak'
409
+ this
410
+
411
+ compile_node: (o) ->
412
+ '.' + (if @prototype then 'prototype.' else '') + @name.compile(o)
413
+
414
+ }
415
+
416
+ # An indexed accessor into a part of an array or object.
417
+ IndexNode: exports.IndexNode: inherit Node, {
418
+ type: 'Index'
419
+
420
+ constructor: (index, tag) ->
421
+ @children: [@index: index]
422
+ @soak_node: tag is 'soak'
423
+ this
424
+
425
+ compile_node: (o) ->
426
+ '[' + @index.compile(o) + ']'
427
+
428
+ }
429
+
430
+ # A range literal. Ranges can be used to extract portions (slices) of arrays,
431
+ # or to specify a range for list comprehensions.
432
+ RangeNode: exports.RangeNode: inherit Node, {
433
+ type: 'Range'
434
+
435
+ constructor: (from, to, exclusive) ->
436
+ @children: [@from: from, @to: to]
437
+ @exclusive: !!exclusive
438
+ this
439
+
440
+ compile_variables: (o) ->
441
+ @indent: o.indent
442
+ @from_var: o.scope.free_variable()
443
+ @to_var: o.scope.free_variable()
444
+ @from_var + ' = ' + @from.compile(o) + '; ' + @to_var + ' = ' + @to.compile(o) + ";\n" + @idt()
445
+
446
+ compile_node: (o) ->
447
+ return @compile_array(o) unless o.index
448
+ idx: del o, 'index'
449
+ step: del o, 'step'
450
+ vars: idx + '=' + @from_var
451
+ step: if step then step.compile(o) else '1'
452
+ equals: if @exclusive then '' else '='
453
+ intro: '(' + @from_var + ' <= ' + @to_var + ' ? ' + idx
454
+ compare: intro + ' <' + equals + ' ' + @to_var + ' : ' + idx + ' >' + equals + ' ' + @to_var + ')'
455
+ incr: intro + ' += ' + step + ' : ' + idx + ' -= ' + step + ')'
456
+ vars + '; ' + compare + '; ' + incr
457
+
458
+ # Expand the range into the equivalent array, if it's not being used as
459
+ # part of a comprehension, slice, or splice.
460
+ # TODO: This generates pretty ugly code ... shrink it.
461
+ compile_array: (o) ->
462
+ name: o.scope.free_variable()
463
+ body: Expressions.wrap([new LiteralNode(name)])
464
+ arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, new LiteralNode(name))])
465
+ (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
466
+
467
+ }
468
+
469
+ # An array slice literal. Unlike JavaScript's Array#slice, the second parameter
470
+ # specifies the index of the end of the slice (just like the first parameter)
471
+ # is the index of the beginning.
472
+ SliceNode: exports.SliceNode: inherit Node, {
473
+ type: 'Slice'
474
+
475
+ constructor: (range) ->
476
+ @children: [@range: range]
477
+ this
478
+
479
+ compile_node: (o) ->
480
+ from: @range.from.compile(o)
481
+ to: @range.to.compile(o)
482
+ plus_part: if @range.exclusive then '' else ' + 1'
483
+ ".slice(" + from + ', ' + to + plus_part + ')'
484
+
485
+ }
486
+
487
+ # An object literal.
488
+ ObjectNode: exports.ObjectNode: inherit Node, {
489
+ type: 'Object'
490
+
491
+ constructor: (props) ->
492
+ @children: @objects: @properties: props or []
493
+ this
494
+
495
+ # All the mucking about with commas is to make sure that CommentNodes and
496
+ # AssignNodes get interleaved correctly, with no trailing commas or
497
+ # commas affixed to comments. TODO: Extract this and add it to ArrayNode.
498
+ compile_node: (o) ->
499
+ o.indent: @idt(1)
500
+ non_comments: prop for prop in @properties when not (prop instanceof CommentNode)
501
+ last_noncom: non_comments[non_comments.length - 1]
502
+ props: for prop, i in @properties
503
+ join: ",\n"
504
+ join: "\n" if (prop is last_noncom) or (prop instanceof CommentNode)
505
+ join: '' if i is @properties.length - 1
506
+ indent: if prop instanceof CommentNode then '' else @idt(1)
507
+ indent + prop.compile(o) + join
508
+ props: props.join('')
509
+ inner: if props then '\n' + props + '\n' + @idt() else ''
510
+ '{' + inner + '}'
511
+
512
+ }
513
+
514
+ # An array literal.
515
+ ArrayNode: exports.ArrayNode: inherit Node, {
516
+ type: 'Array'
517
+
518
+ constructor: (objects) ->
519
+ @children: @objects: objects or []
520
+ this
521
+
522
+ compile_node: (o) ->
523
+ o.indent: @idt(1)
524
+ objects: for obj, i in @objects
525
+ code: obj.compile(o)
526
+ if obj instanceof CommentNode
527
+ '\n' + code + '\n' + o.indent
528
+ else if i is @objects.length - 1
529
+ code
530
+ else
531
+ code + ', '
532
+ objects: objects.join('')
533
+ ending: if objects.indexOf('\n') >= 0 then "\n" + @idt() + ']' else ']'
534
+ '[' + objects + ending
535
+
536
+ }
537
+
538
+ # A faux-node that is never created by the grammar, but is used during
539
+ # code generation to generate a quick "array.push(value)" tree of nodes.
540
+ PushNode: exports.PushNode: {
541
+
542
+ wrap: (array, expressions) ->
543
+ expr: expressions.unwrap()
544
+ return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
545
+ Expressions.wrap([new CallNode(
546
+ new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr]
547
+ )])
548
+
549
+ }
550
+
551
+ # A faux-node used to wrap an expressions body in a closure.
552
+ ClosureNode: exports.ClosureNode: {
553
+
554
+ wrap: (expressions, statement) ->
555
+ func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
556
+ call: new CallNode(new ValueNode(func, [new AccessorNode(new LiteralNode('call'))]), [new LiteralNode('this')])
557
+ if statement then Expressions.wrap([call]) else call
558
+
559
+ }
560
+
561
+ # Setting the value of a local variable, or the value of an object property.
562
+ AssignNode: exports.AssignNode: inherit Node, {
563
+ type: 'Assign'
564
+
565
+ PROTO_ASSIGN: /^(\S+)\.prototype/
566
+ LEADING_DOT: /^\.(prototype\.)?/
567
+
568
+ constructor: (variable, value, context) ->
569
+ @children: [@variable: variable, @value: value]
570
+ @context: context
571
+ this
572
+
573
+ top_sensitive: ->
574
+ true
575
+
576
+ is_value: ->
577
+ @variable instanceof ValueNode
578
+
579
+ is_statement: ->
580
+ @is_value() and (@variable.is_array() or @variable.is_object())
581
+
582
+ compile_node: (o) ->
583
+ top: del o, 'top'
584
+ return @compile_pattern_match(o) if @is_statement()
585
+ return @compile_splice(o) if @is_value() and @variable.is_splice()
586
+ stmt: del o, 'as_statement'
587
+ name: @variable.compile(o)
588
+ last: if @is_value() then @variable.last.replace(@LEADING_DOT, '') else name
589
+ match: name.match(@PROTO_ASSIGN)
590
+ proto: match and match[1]
591
+ if @value instanceof CodeNode
592
+ @value.name: last if last.match(IDENTIFIER)
593
+ @value.proto: proto if proto
594
+ return name + ': ' + @value.compile(o) if @context is 'object'
595
+ o.scope.find(name) unless @is_value() and @variable.has_properties()
596
+ val: name + ' = ' + @value.compile(o)
597
+ return @idt() + val + ';' if stmt
598
+ val: '(' + val + ')' if not top or o.returns
599
+ val: @idt() + 'return ' + val if o.returns
600
+ val
601
+
602
+ # Implementation of recursive pattern matching, when assigning array or
603
+ # object literals to a value. Peeks at their properties to assign inner names.
604
+ # See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
605
+ compile_pattern_match: (o) ->
606
+ val_var: o.scope.free_variable()
607
+ assigns: [@idt() + val_var + ' = ' + @value.compile(o) + ';']
608
+ o.top: true
609
+ o.as_statement: true
610
+ for obj, i in @variable.base.objects
611
+ idx: i
612
+ [obj, idx]: [obj.value, obj.variable.base] if @variable.is_object()
613
+ access_class: if @variable.is_array() then IndexNode else AccessorNode
614
+ if obj instanceof SplatNode
615
+ val: new LiteralNode(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
616
+ else
617
+ idx: new LiteralNode(idx) unless typeof idx is 'object'
618
+ val: new ValueNode(new LiteralNode(val_var), [new access_class(idx)])
619
+ assigns.push(new AssignNode(obj, val).compile(o))
620
+ assigns.join("\n")
621
+
622
+ compile_splice: (o) ->
623
+ name: @variable.compile(merge(o, {only_first: true}))
624
+ l: @variable.properties.length
625
+ range: @variable.properties[l - 1].range
626
+ plus: if range.exclusive then '' else ' + 1'
627
+ from: range.from.compile(o)
628
+ to: range.to.compile(o) + ' - ' + from + plus
629
+ name + '.splice.apply(' + name + ', [' + from + ', ' + to + '].concat(' + @value.compile(o) + '))'
630
+
631
+ }
632
+
633
+ # A function definition. The only node that creates a new Scope.
634
+ # A CodeNode does not have any children -- they're within the new scope.
635
+ CodeNode: exports.CodeNode: inherit Node, {
636
+ type: 'Code'
637
+
638
+ constructor: (params, body, tag) ->
639
+ @params: params
640
+ @body: body
641
+ @bound: tag is 'boundfunc'
642
+ this
643
+
644
+ compile_node: (o) ->
645
+ shared_scope: del o, 'shared_scope'
646
+ top: del o, 'top'
647
+ o.scope: shared_scope or new Scope(o.scope, @body, this)
648
+ o.returns: true
649
+ o.top: true
650
+ o.indent: @idt(if @bound then 2 else 1)
651
+ del o, 'no_wrap'
652
+ del o, 'globals'
653
+ if @params[@params.length - 1] instanceof SplatNode
654
+ splat: @params.pop()
655
+ splat.index: @params.length
656
+ @body.unshift(splat)
657
+ params: (param.compile(o) for param in @params)
658
+ (o.scope.parameter(param)) for param in params
659
+ code: if @body.expressions.length then '\n' + @body.compile_with_declarations(o) + '\n' else ''
660
+ name_part: if @name then ' ' + @name else ''
661
+ func: 'function' + (if @bound then '' else name_part) + '(' + params.join(', ') + ') {' + code + @idt(if @bound then 1 else 0) + '}'
662
+ func: '(' + func + ')' if top and not @bound
663
+ return func unless @bound
664
+ inner: '(function' + name_part + '() {\n' + @idt(2) + 'return __func.apply(__this, arguments);\n' + @idt(1) + '});'
665
+ '(function(__this) {\n' + @idt(1) + 'var __func = ' + func + ';\n' + @idt(1) + 'return ' + inner + '\n' + @idt() + '})(this)'
666
+
667
+ top_sensitive: ->
668
+ true
669
+
670
+ toString: (idt) ->
671
+ idt ||= ''
672
+ children: flatten [@params, @body.expressions]
673
+ '\n' + idt + @type + (child.toString(idt + TAB) for child in children).join('')
674
+
675
+ }
676
+
677
+ # A splat, either as a parameter to a function, an argument to a call,
678
+ # or in a destructuring assignment.
679
+ SplatNode: exports.SplatNode: inherit Node, {
680
+ type: 'Splat'
681
+
682
+ constructor: (name) ->
683
+ name: new LiteralNode(name) unless name.compile
684
+ @children: [@name: name]
685
+ this
686
+
687
+ compile_node: (o) ->
688
+ if @index? then @compile_param(o) else @name.compile(o)
689
+
690
+ compile_param: (o) ->
691
+ name: @name.compile(o)
692
+ o.scope.find name
693
+ name + ' = Array.prototype.slice.call(arguments, ' + @index + ')'
694
+
695
+ compile_value: (o, name, index) ->
696
+ "Array.prototype.slice.call(" + name + ', ' + index + ')'
697
+
698
+ }
699
+
700
+ # A while loop, the only sort of low-level loop exposed by CoffeeScript. From
701
+ # it, all other loops can be manufactured.
702
+ WhileNode: exports.WhileNode: inherit Node, {
703
+ type: 'While'
704
+
705
+ constructor: (condition, opts) ->
706
+ @children:[@condition: condition]
707
+ @filter: opts and opts.filter
708
+ this
709
+
710
+ add_body: (body) ->
711
+ @children.push @body: body
712
+ this
713
+
714
+ top_sensitive: ->
715
+ true
716
+
717
+ compile_node: (o) ->
718
+ returns: del(o, 'returns')
719
+ top: del(o, 'top') and not returns
720
+ o.indent: @idt(1)
721
+ o.top: true
722
+ cond: @condition.compile(o)
723
+ set: ''
724
+ if not top
725
+ rvar: o.scope.free_variable()
726
+ set: @idt() + rvar + ' = [];\n'
727
+ @body: PushNode.wrap(rvar, @body) if @body
728
+ post: if returns then '\n' + @idt() + 'return ' + rvar + ';' else ''
729
+ pre: set + @idt() + 'while (' + cond + ')'
730
+ return pre + ' null;' + post if not @body
731
+ @body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter
732
+ pre + ' {\n' + @body.compile(o) + '\n' + @idt() + '}' + post
733
+
734
+ }
735
+
736
+ statement WhileNode
737
+
738
+ # Simple Arithmetic and logical operations. Performs some conversion from
739
+ # CoffeeScript operations into their JavaScript equivalents.
740
+ OpNode: exports.OpNode: inherit Node, {
741
+ type: 'Op'
742
+
743
+ CONVERSIONS: {
744
+ '==': '==='
745
+ '!=': '!=='
746
+ 'and': '&&'
747
+ 'or': '||'
748
+ 'is': '==='
749
+ 'isnt': '!=='
750
+ 'not': '!'
751
+ }
752
+
753
+ CHAINABLE: ['<', '>', '>=', '<=', '===', '!==']
754
+ ASSIGNMENT: ['||=', '&&=', '?=']
755
+ PREFIX_OPERATORS: ['typeof', 'delete']
756
+
757
+ constructor: (operator, first, second, flip) ->
758
+ @type += ' ' + operator
759
+ @children: compact [@first: first, @second: second]
760
+ @operator: @CONVERSIONS[operator] or operator
761
+ @flip: !!flip
762
+ this
763
+
764
+ is_unary: ->
765
+ not @second
766
+
767
+ is_chainable: ->
768
+ @CHAINABLE.indexOf(@operator) >= 0
769
+
770
+ compile_node: (o) ->
771
+ o.operation: true
772
+ return @compile_chain(o) if @is_chainable() and @first.unwrap() instanceof OpNode and @first.unwrap().is_chainable()
773
+ return @compile_assignment(o) if @ASSIGNMENT.indexOf(@operator) >= 0
774
+ return @compile_unary(o) if @is_unary()
775
+ return @compile_existence(o) if @operator is '?'
776
+ @first.compile(o) + ' ' + @operator + ' ' + @second.compile(o)
777
+
778
+ # Mimic Python's chained comparisons. See:
779
+ # http://docs.python.org/reference/expressions.html#notin
780
+ compile_chain: (o) ->
781
+ shared: @first.unwrap().second
782
+ [@first.second, shared]: shared.compile_reference(o) if shared instanceof CallNode
783
+ '(' + @first.compile(o) + ') && (' + shared.compile(o) + ' ' + @operator + ' ' + @second.compile(o) + ')'
784
+
785
+ compile_assignment: (o) ->
786
+ [first, second]: [@first.compile(o), @second.compile(o)]
787
+ o.scope.find(first) if first.match(IDENTIFIER)
788
+ return first + ' = ' + ExistenceNode.compile_test(o, @first) + ' ? ' + first + ' : ' + second if @operator is '?='
789
+ first + ' = ' + first + ' ' + @operator.substr(0, 2) + ' ' + second
790
+
791
+ compile_existence: (o) ->
792
+ [first, second]: [@first.compile(o), @second.compile(o)]
793
+ ExistenceNode.compile_test(o, @first) + ' ? ' + first + ' : ' + second
794
+
795
+ compile_unary: (o) ->
796
+ space: if @PREFIX_OPERATORS.indexOf(@operator) >= 0 then ' ' else ''
797
+ parts: [@operator, space, @first.compile(o)]
798
+ parts: parts.reverse() if @flip
799
+ parts.join('')
800
+
801
+ }
802
+
803
+ # A try/catch/finally block.
804
+ TryNode: exports.TryNode: inherit Node, {
805
+ type: 'Try'
806
+
807
+ constructor: (attempt, error, recovery, ensure) ->
808
+ @children: compact [@attempt: attempt, @recovery: recovery, @ensure: ensure]
809
+ @error: error
810
+ this
811
+
812
+ compile_node: (o) ->
813
+ o.indent: @idt(1)
814
+ o.top: true
815
+ error_part: if @error then ' (' + @error.compile(o) + ') ' else ' '
816
+ catch_part: (@recovery or '') and ' catch' + error_part + '{\n' + @recovery.compile(o) + '\n' + @idt() + '}'
817
+ finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + '\n' + @idt() + '}'
818
+ @idt() + 'try {\n' + @attempt.compile(o) + '\n' + @idt() + '}' + catch_part + finally_part
819
+
820
+ }
821
+
822
+ statement TryNode
823
+
824
+ # Throw an exception.
825
+ ThrowNode: exports.ThrowNode: inherit Node, {
826
+ type: 'Throw'
827
+
828
+ constructor: (expression) ->
829
+ @children: [@expression: expression]
830
+ this
831
+
832
+ compile_node: (o) ->
833
+ @idt() + 'throw ' + @expression.compile(o) + ';'
834
+
835
+ }
836
+
837
+ statement ThrowNode, true
838
+
839
+ # Check an expression for existence (meaning not null or undefined).
840
+ ExistenceNode: exports.ExistenceNode: inherit Node, {
841
+ type: 'Existence'
842
+
843
+ constructor: (expression) ->
844
+ @children: [@expression: expression]
845
+ this
846
+
847
+ compile_node: (o) ->
848
+ ExistenceNode.compile_test(o, @expression)
849
+
850
+ }
851
+
852
+ ExistenceNode.compile_test: (o, variable) ->
853
+ [first, second]: [variable, variable]
854
+ if variable instanceof CallNode or (variable instanceof ValueNode and variable.has_properties())
855
+ [first, second]: variable.compile_reference(o)
856
+ '(typeof ' + first.compile(o) + ' !== "undefined" && ' + second.compile(o) + ' !== null)'
857
+
858
+ # An extra set of parentheses, specified explicitly in the source.
859
+ ParentheticalNode: exports.ParentheticalNode: inherit Node, {
860
+ type: 'Paren'
861
+
862
+ constructor: (expression) ->
863
+ @children: [@expression: expression]
864
+ this
865
+
866
+ is_statement: ->
867
+ @expression.is_statement()
868
+
869
+ compile_node: (o) ->
870
+ code: @expression.compile(o)
871
+ return code if @is_statement()
872
+ l: code.length
873
+ code: code.substr(o, l-1) if code.substr(l-1, 1) is ';'
874
+ '(' + code + ')'
875
+
876
+ }
877
+
878
+ # The replacement for the for loop is an array comprehension (that compiles)
879
+ # into a for loop. Also acts as an expression, able to return the result
880
+ # of the comprehenion. Unlike Python array comprehensions, it's able to pass
881
+ # the current index of the loop as a second parameter.
882
+ ForNode: exports.ForNode: inherit Node, {
883
+ type: 'For'
884
+
885
+ constructor: (body, source, name, index) ->
886
+ @body: body
887
+ @name: name
888
+ @index: index or null
889
+ @source: source.source
890
+ @filter: source.filter
891
+ @step: source.step
892
+ @object: !!source.object
893
+ [@name, @index]: [@index, @name] if @object
894
+ @children: compact [@body, @source, @filter]
895
+ this
896
+
897
+ top_sensitive: ->
898
+ true
899
+
900
+ compile_node: (o) ->
901
+ top_level: del(o, 'top') and not o.returns
902
+ range: @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length
903
+ source: if range then @source.base else @source
904
+ scope: o.scope
905
+ name: @name and @name.compile(o)
906
+ index: @index and @index.compile(o)
907
+ name_found: name and scope.find(name)
908
+ index_found: index and scope.find(index)
909
+ body_dent: @idt(1)
910
+ rvar: scope.free_variable() unless top_level
911
+ svar: scope.free_variable()
912
+ ivar: if range then name else index or scope.free_variable()
913
+ var_part: ''
914
+ body: Expressions.wrap([@body])
915
+ if range
916
+ index_var: scope.free_variable()
917
+ source_part: source.compile_variables(o)
918
+ for_part: index_var + ' = 0, ' + source.compile(merge(o, {index: ivar, step: @step})) + ', ' + index_var + '++'
919
+ else
920
+ index_var: null
921
+ source_part: svar + ' = ' + @source.compile(o) + ';\n' + @idt()
922
+ step_part: if @step then ivar + ' += ' + @step.compile(o) else ivar + '++'
923
+ for_part: ivar + ' = 0; ' + ivar + ' < ' + svar + '.length; ' + step_part
924
+ var_part: body_dent + name + ' = ' + svar + '[' + ivar + '];\n' if name
925
+ set_result: if rvar then @idt() + rvar + ' = []; ' else @idt()
926
+ return_result: rvar or ''
927
+ body: ClosureNode.wrap(body, true) if top_level and @contains (n) -> n instanceof CodeNode
928
+ body: PushNode.wrap(rvar, body) unless top_level
929
+ if o.returns
930
+ return_result: 'return ' + return_result
931
+ del o, 'returns'
932
+ body: new IfNode(@filter, body, null, {statement: true}) if @filter
933
+ else if @filter
934
+ body: Expressions.wrap([new IfNode(@filter, body)])
935
+ if @object
936
+ o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true)
937
+ for_part: ivar + ' in ' + svar + ') { if (__hasProp.call(' + svar + ', ' + ivar + ')'
938
+ return_result: '\n' + @idt() + return_result + ';' unless top_level
939
+ body: body.compile(merge(o, {indent: body_dent, top: true}))
940
+ vars: if range then name else name + ', ' + ivar
941
+ close: if @object then '}}\n' else '}\n'
942
+ set_result + source_part + 'for (' + for_part + ') {\n' + var_part + body + '\n' + @idt() + close + @idt() + return_result
943
+
944
+ }
945
+
946
+ statement ForNode
947
+
948
+ # If/else statements. Switch/whens get compiled into these. Acts as an
949
+ # expression by pushing down requested returns to the expression bodies.
950
+ # Single-expression IfNodes are compiled into ternary operators if possible,
951
+ # because ternaries are first-class returnable assignable expressions.
952
+ IfNode: exports.IfNode: inherit Node, {
953
+ type: 'If'
954
+
955
+ constructor: (condition, body, else_body, tags) ->
956
+ @condition: condition
957
+ @body: body and body.unwrap()
958
+ @else_body: else_body and else_body.unwrap()
959
+ @children: compact [@condition, @body, @else_body]
960
+ @tags: tags or {}
961
+ @multiple: true if @condition instanceof Array
962
+ @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert
963
+ this
964
+
965
+ push: (else_body) ->
966
+ eb: else_body.unwrap()
967
+ if @else_body then @else_body.push(eb) else @else_body: eb
968
+ this
969
+
970
+ force_statement: ->
971
+ @tags.statement: true
972
+ this
973
+
974
+ # Tag a chain of IfNodes with their switch condition for equality.
975
+ rewrite_condition: (expression) ->
976
+ @switcher: expression
977
+ this
978
+
979
+ # Rewrite a chain of IfNodes with their switch condition for equality.
980
+ rewrite_switch: (o) ->
981
+ assigner: @switcher
982
+ if not (@switcher.unwrap() instanceof LiteralNode)
983
+ variable: new LiteralNode(o.scope.free_variable())
984
+ assigner: new AssignNode(variable, @switcher)
985
+ @switcher: variable
986
+ @condition: if @multiple
987
+ for cond, i in @condition
988
+ new OpNode('is', (if i is 0 then assigner else @switcher), cond)
989
+ else
990
+ new OpNode('is', assigner, @condition)
991
+ @else_body.rewrite_condition(@switcher) if @is_chain()
992
+ this
993
+
994
+ # Rewrite a chain of IfNodes to add a default case as the final else.
995
+ add_else: (exprs, statement) ->
996
+ if @is_chain()
997
+ @else_body.add_else exprs, statement
998
+ else
999
+ exprs: exprs.unwrap() unless statement
1000
+ @children.push @else_body: exprs
1001
+ this
1002
+
1003
+ # If the else_body is an IfNode itself, then we've got an if-else chain.
1004
+ is_chain: ->
1005
+ @chain ||= @else_body and @else_body instanceof IfNode
1006
+
1007
+ # The IfNode only compiles into a statement if either of the bodies needs
1008
+ # to be a statement.
1009
+ is_statement: ->
1010
+ @statement ||= !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement()))
1011
+
1012
+ compile_condition: (o) ->
1013
+ (cond.compile(o) for cond in flatten([@condition])).join(' || ')
1014
+
1015
+ compile_node: (o) ->
1016
+ if @is_statement() then @compile_statement(o) else @compile_ternary(o)
1017
+
1018
+ # Compile the IfNode as a regular if-else statement. Flattened chains
1019
+ # force sub-else bodies into statement form.
1020
+ compile_statement: (o) ->
1021
+ @rewrite_switch(o) if @switcher
1022
+ child: del o, 'chain_child'
1023
+ cond_o: merge o
1024
+ del cond_o, 'returns'
1025
+ o.indent: @idt(1)
1026
+ o.top: true
1027
+ if_dent: if child then '' else @idt()
1028
+ com_dent: if child then @idt() else ''
1029
+ prefix: if @comment then @comment.compile(cond_o) + '\n' + com_dent else ''
1030
+ body: Expressions.wrap([@body]).compile(o)
1031
+ if_part: prefix + if_dent + 'if (' + @compile_condition(cond_o) + ') {\n' + body + '\n' + @idt() + '}'
1032
+ return if_part unless @else_body
1033
+ else_part: if @is_chain()
1034
+ ' else ' + @else_body.compile(merge(o, {indent: @idt(), chain_child: true}))
1035
+ else
1036
+ ' else {\n' + Expressions.wrap([@else_body]).compile(o) + '\n' + @idt() + '}'
1037
+ if_part + else_part
1038
+
1039
+ # Compile the IfNode into a ternary operator.
1040
+ compile_ternary: (o) ->
1041
+ if_part: @condition.compile(o) + ' ? ' + @body.compile(o)
1042
+ else_part: if @else_body then @else_body.compile(o) else 'null'
1043
+ if_part + ' : ' + else_part
1044
+
1045
+ }