haml-more 0.4.0.a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }