spade-packager 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.gitmodules +6 -0
- data/bin/spadepkg +8 -0
- data/lib/libgems_ext.rb +8 -0
- data/lib/libgems_ext/config_file.rb +33 -0
- data/lib/libgems_ext/dependency_installer.rb +150 -0
- data/lib/libgems_ext/installer.rb +39 -0
- data/lib/libgems_ext/libgems.rb +39 -0
- data/lib/libgems_ext/spec_fetcher.rb +11 -0
- data/lib/spade-packager.rb +1 -0
- data/lib/spade/packager.rb +18 -0
- data/lib/spade/packager/cli.rb +9 -0
- data/lib/spade/packager/cli/base.rb +196 -0
- data/lib/spade/packager/cli/owner.rb +46 -0
- data/lib/spade/packager/cli/project_generator.rb +117 -0
- data/lib/spade/packager/credentials.rb +38 -0
- data/lib/spade/packager/local.rb +50 -0
- data/lib/spade/packager/package.rb +160 -0
- data/lib/spade/packager/remote.rb +98 -0
- data/lib/spade/packager/repository.rb +18 -0
- data/lib/spade/packager/version.rb +5 -0
- data/packages/coffee-script/.gitignore +8 -0
- data/packages/coffee-script/.npmignore +11 -0
- data/packages/coffee-script/Cakefile +229 -0
- data/packages/coffee-script/LICENSE +22 -0
- data/packages/coffee-script/README +47 -0
- data/packages/coffee-script/Rakefile +78 -0
- data/packages/coffee-script/bin/cake +7 -0
- data/packages/coffee-script/bin/coffee +7 -0
- data/packages/coffee-script/documentation/coffee/aliases.coffee +11 -0
- data/packages/coffee-script/documentation/coffee/array_comprehensions.coffee +2 -0
- data/packages/coffee-script/documentation/coffee/block_comment.coffee +6 -0
- data/packages/coffee-script/documentation/coffee/cake_tasks.coffee +9 -0
- data/packages/coffee-script/documentation/coffee/classes.coffee +25 -0
- data/packages/coffee-script/documentation/coffee/comparisons.coffee +5 -0
- data/packages/coffee-script/documentation/coffee/conditionals.coffee +13 -0
- data/packages/coffee-script/documentation/coffee/default_args.coffee +8 -0
- data/packages/coffee-script/documentation/coffee/do.coffee +4 -0
- data/packages/coffee-script/documentation/coffee/embedded.coffee +5 -0
- data/packages/coffee-script/documentation/coffee/existence.coffee +10 -0
- data/packages/coffee-script/documentation/coffee/expressions.coffee +9 -0
- data/packages/coffee-script/documentation/coffee/expressions_assignment.coffee +3 -0
- data/packages/coffee-script/documentation/coffee/expressions_comprehension.coffee +3 -0
- data/packages/coffee-script/documentation/coffee/expressions_try.coffee +7 -0
- data/packages/coffee-script/documentation/coffee/fat_arrow.coffee +6 -0
- data/packages/coffee-script/documentation/coffee/functions.coffee +2 -0
- data/packages/coffee-script/documentation/coffee/heredocs.coffee +7 -0
- data/packages/coffee-script/documentation/coffee/heregexes.coffee +11 -0
- data/packages/coffee-script/documentation/coffee/interpolation.coffee +6 -0
- data/packages/coffee-script/documentation/coffee/multiple_return_values.coffee +7 -0
- data/packages/coffee-script/documentation/coffee/object_comprehensions.coffee +4 -0
- data/packages/coffee-script/documentation/coffee/object_extraction.coffee +13 -0
- data/packages/coffee-script/documentation/coffee/objects_and_arrays.coffee +19 -0
- data/packages/coffee-script/documentation/coffee/objects_reserved.coffee +5 -0
- data/packages/coffee-script/documentation/coffee/overview.coffee +28 -0
- data/packages/coffee-script/documentation/coffee/parallel_assignment.coffee +6 -0
- data/packages/coffee-script/documentation/coffee/patterns_and_splats.coffee +7 -0
- data/packages/coffee-script/documentation/coffee/prototypes.coffee +3 -0
- data/packages/coffee-script/documentation/coffee/range_comprehensions.coffee +2 -0
- data/packages/coffee-script/documentation/coffee/scope.coffee +5 -0
- data/packages/coffee-script/documentation/coffee/slices.coffee +7 -0
- data/packages/coffee-script/documentation/coffee/soaks.coffee +1 -0
- data/packages/coffee-script/documentation/coffee/splats.coffee +27 -0
- data/packages/coffee-script/documentation/coffee/splices.coffee +5 -0
- data/packages/coffee-script/documentation/coffee/strings.coffee +8 -0
- data/packages/coffee-script/documentation/coffee/switch.coffee +10 -0
- data/packages/coffee-script/documentation/coffee/try.coffee +8 -0
- data/packages/coffee-script/documentation/coffee/while.coffee +10 -0
- data/packages/coffee-script/documentation/css/docs.css +374 -0
- data/packages/coffee-script/documentation/css/idle.css +64 -0
- data/packages/coffee-script/documentation/docs/browser.html +25 -0
- data/packages/coffee-script/documentation/docs/cake.html +43 -0
- data/packages/coffee-script/documentation/docs/coffee-script.html +51 -0
- data/packages/coffee-script/documentation/docs/command.html +161 -0
- data/packages/coffee-script/documentation/docs/docco.css +186 -0
- data/packages/coffee-script/documentation/docs/grammar.html +399 -0
- data/packages/coffee-script/documentation/docs/helpers.html +31 -0
- data/packages/coffee-script/documentation/docs/index.html +3 -0
- data/packages/coffee-script/documentation/docs/lexer.html +490 -0
- data/packages/coffee-script/documentation/docs/nodes.html +1338 -0
- data/packages/coffee-script/documentation/docs/optparse.html +78 -0
- data/packages/coffee-script/documentation/docs/repl.html +24 -0
- data/packages/coffee-script/documentation/docs/rewriter.html +251 -0
- data/packages/coffee-script/documentation/docs/scope.html +54 -0
- data/packages/coffee-script/documentation/docs/underscore.html +295 -0
- data/packages/coffee-script/documentation/images/background.png +0 -0
- data/packages/coffee-script/documentation/images/banding.png +0 -0
- data/packages/coffee-script/documentation/images/button_bg.png +0 -0
- data/packages/coffee-script/documentation/images/button_bg_dark.gif +0 -0
- data/packages/coffee-script/documentation/images/button_bg_green.gif +0 -0
- data/packages/coffee-script/documentation/images/favicon.ico +0 -0
- data/packages/coffee-script/documentation/images/logo.png +0 -0
- data/packages/coffee-script/documentation/images/screenshadow.png +0 -0
- data/packages/coffee-script/documentation/index.html.erb +1607 -0
- data/packages/coffee-script/documentation/js/aliases.js +17 -0
- data/packages/coffee-script/documentation/js/array_comprehensions.js +6 -0
- data/packages/coffee-script/documentation/js/block_comment.js +4 -0
- data/packages/coffee-script/documentation/js/cake_tasks.js +10 -0
- data/packages/coffee-script/documentation/js/classes.js +44 -0
- data/packages/coffee-script/documentation/js/comparisons.js +3 -0
- data/packages/coffee-script/documentation/js/conditionals.js +12 -0
- data/packages/coffee-script/documentation/js/default_args.js +7 -0
- data/packages/coffee-script/documentation/js/do.js +10 -0
- data/packages/coffee-script/documentation/js/embedded.js +4 -0
- data/packages/coffee-script/documentation/js/existence.js +6 -0
- data/packages/coffee-script/documentation/js/expressions.js +15 -0
- data/packages/coffee-script/documentation/js/expressions_assignment.js +2 -0
- data/packages/coffee-script/documentation/js/expressions_comprehension.js +9 -0
- data/packages/coffee-script/documentation/js/expressions_try.js +7 -0
- data/packages/coffee-script/documentation/js/fat_arrow.js +9 -0
- data/packages/coffee-script/documentation/js/functions.js +7 -0
- data/packages/coffee-script/documentation/js/heredocs.js +2 -0
- data/packages/coffee-script/documentation/js/heregexes.js +2 -0
- data/packages/coffee-script/documentation/js/interpolation.js +4 -0
- data/packages/coffee-script/documentation/js/multiple_return_values.js +5 -0
- data/packages/coffee-script/documentation/js/object_comprehensions.js +15 -0
- data/packages/coffee-script/documentation/js/object_extraction.js +10 -0
- data/packages/coffee-script/documentation/js/objects_and_arrays.js +17 -0
- data/packages/coffee-script/documentation/js/objects_reserved.js +4 -0
- data/packages/coffee-script/documentation/js/overview.js +35 -0
- data/packages/coffee-script/documentation/js/parallel_assignment.js +4 -0
- data/packages/coffee-script/documentation/js/patterns_and_splats.js +4 -0
- data/packages/coffee-script/documentation/js/prototypes.js +3 -0
- data/packages/coffee-script/documentation/js/range_comprehensions.js +9 -0
- data/packages/coffee-script/documentation/js/scope.js +8 -0
- data/packages/coffee-script/documentation/js/slices.js +4 -0
- data/packages/coffee-script/documentation/js/soaks.js +2 -0
- data/packages/coffee-script/documentation/js/splats.js +15 -0
- data/packages/coffee-script/documentation/js/splices.js +3 -0
- data/packages/coffee-script/documentation/js/strings.js +2 -0
- data/packages/coffee-script/documentation/js/switch.js +23 -0
- data/packages/coffee-script/documentation/js/try.js +8 -0
- data/packages/coffee-script/documentation/js/while.js +18 -0
- data/packages/coffee-script/documentation/vendor/jquery-1.4.2.js +6240 -0
- data/packages/coffee-script/examples/beautiful_code/binary_search.coffee +16 -0
- data/packages/coffee-script/examples/beautiful_code/quicksort_runtime.coffee +13 -0
- data/packages/coffee-script/examples/beautiful_code/regular_expression_matcher.coffee +34 -0
- data/packages/coffee-script/examples/blocks.coffee +54 -0
- data/packages/coffee-script/examples/code.coffee +167 -0
- data/packages/coffee-script/examples/computer_science/README +4 -0
- data/packages/coffee-script/examples/computer_science/binary_search.coffee +25 -0
- data/packages/coffee-script/examples/computer_science/bubble_sort.coffee +11 -0
- data/packages/coffee-script/examples/computer_science/linked_list.coffee +108 -0
- data/packages/coffee-script/examples/computer_science/luhn_algorithm.coffee +36 -0
- data/packages/coffee-script/examples/computer_science/merge_sort.coffee +19 -0
- data/packages/coffee-script/examples/computer_science/selection_sort.coffee +23 -0
- data/packages/coffee-script/examples/poignant.coffee +181 -0
- data/packages/coffee-script/examples/potion.coffee +206 -0
- data/packages/coffee-script/examples/underscore.coffee +682 -0
- data/packages/coffee-script/examples/web_server.coffee +12 -0
- data/packages/coffee-script/extras/EXTRAS +7 -0
- data/packages/coffee-script/extras/coffee-script.js +8 -0
- data/packages/coffee-script/extras/jsl.conf +44 -0
- data/packages/coffee-script/index.html +2515 -0
- data/packages/coffee-script/lib/browser.js +52 -0
- data/packages/coffee-script/lib/cake.js +76 -0
- data/packages/coffee-script/lib/coffee-script.js +82 -0
- data/packages/coffee-script/lib/command.js +263 -0
- data/packages/coffee-script/lib/grammar.js +581 -0
- data/packages/coffee-script/lib/helpers.js +66 -0
- data/packages/coffee-script/lib/index.js +8 -0
- data/packages/coffee-script/lib/lexer.js +633 -0
- data/packages/coffee-script/lib/nodes.js +2165 -0
- data/packages/coffee-script/lib/optparse.js +111 -0
- data/packages/coffee-script/lib/parser.js +649 -0
- data/packages/coffee-script/lib/repl.js +42 -0
- data/packages/coffee-script/lib/rewriter.js +353 -0
- data/packages/coffee-script/lib/scope.js +120 -0
- data/packages/coffee-script/lib/spade-format.js +45 -0
- data/packages/coffee-script/package.json +26 -0
- data/packages/coffee-script/src/browser.coffee +43 -0
- data/packages/coffee-script/src/cake.coffee +69 -0
- data/packages/coffee-script/src/coffee-script.coffee +92 -0
- data/packages/coffee-script/src/command.coffee +214 -0
- data/packages/coffee-script/src/grammar.coffee +590 -0
- data/packages/coffee-script/src/helpers.coffee +56 -0
- data/packages/coffee-script/src/index.coffee +2 -0
- data/packages/coffee-script/src/lexer.coffee +653 -0
- data/packages/coffee-script/src/nodes.coffee +1754 -0
- data/packages/coffee-script/src/optparse.coffee +99 -0
- data/packages/coffee-script/src/repl.coffee +42 -0
- data/packages/coffee-script/src/rewriter.coffee +326 -0
- data/packages/coffee-script/src/scope.coffee +94 -0
- data/packages/coffee-script/test/arguments.coffee +127 -0
- data/packages/coffee-script/test/assignment.coffee +98 -0
- data/packages/coffee-script/test/break.coffee +18 -0
- data/packages/coffee-script/test/comments.coffee +201 -0
- data/packages/coffee-script/test/conditionals.coffee +181 -0
- data/packages/coffee-script/test/exception_handling.coffee +90 -0
- data/packages/coffee-script/test/helpers.coffee +96 -0
- data/packages/coffee-script/test/importing.coffee +18 -0
- data/packages/coffee-script/test/operators.coffee +225 -0
- data/packages/coffee-script/test/ranges_slices_and_splices.coffee +186 -0
- data/packages/coffee-script/test/regular_expressions.coffee +56 -0
- data/packages/coffee-script/test/test.html +123 -0
- data/packages/coffee-script/test/test_chaining.coffee +77 -0
- data/packages/coffee-script/test/test_classes.coffee +372 -0
- data/packages/coffee-script/test/test_compilation.coffee +26 -0
- data/packages/coffee-script/test/test_comprehensions.coffee +318 -0
- data/packages/coffee-script/test/test_existence.coffee +165 -0
- data/packages/coffee-script/test/test_functions.coffee +379 -0
- data/packages/coffee-script/test/test_heredocs.coffee +111 -0
- data/packages/coffee-script/test/test_literals.coffee +270 -0
- data/packages/coffee-script/test/test_option_parser.coffee +27 -0
- data/packages/coffee-script/test/test_pattern_matching.coffee +162 -0
- data/packages/coffee-script/test/test_returns.coffee +63 -0
- data/packages/coffee-script/test/test_splats.coffee +102 -0
- data/packages/coffee-script/test/test_strings.coffee +118 -0
- data/packages/coffee-script/test/test_switch.coffee +103 -0
- data/packages/coffee-script/test/test_while.coffee +71 -0
- data/packages/ivory/LICENSE.txt +1 -0
- data/packages/ivory/README.md +19 -0
- data/packages/ivory/lib/buffer.js +111 -0
- data/packages/ivory/lib/events.js +137 -0
- data/packages/ivory/lib/fs.js +266 -0
- data/packages/ivory/lib/main.js +13 -0
- data/packages/ivory/lib/path.js +158 -0
- data/packages/ivory/lib/ruby/buffer.rb +145 -0
- data/packages/ivory/lib/ruby/constants.rb +585 -0
- data/packages/ivory/lib/ruby/events.rb +32 -0
- data/packages/ivory/lib/ruby/fs.rb +245 -0
- data/packages/ivory/lib/ruby/process.rb +28 -0
- data/packages/ivory/lib/stream.js +115 -0
- data/packages/ivory/lib/util.js +414 -0
- data/packages/ivory/package.json +11 -0
- data/packages/ivory/spade-boot.js +78 -0
- data/packages/jquery/main.js +7179 -0
- data/packages/jquery/package.json +10 -0
- data/packages/json/lib/main.js +14 -0
- data/packages/json/package.json +8 -0
- data/packages/lproj/README.md +77 -0
- data/packages/lproj/examples/demo-app/en.lproj/localized.strings +2 -0
- data/packages/lproj/examples/demo-app/fr.lproj/localized.strings +3 -0
- data/packages/lproj/examples/demo-app/index.html +8 -0
- data/packages/lproj/examples/demo-app/lib/main.js +7 -0
- data/packages/lproj/examples/demo-app/package.json +9 -0
- data/packages/lproj/lib/main.js +78 -0
- data/packages/lproj/lib/strings-format.js +6 -0
- data/packages/lproj/package.json +9 -0
- data/packages/optparse/README.md +161 -0
- data/packages/optparse/TODO +1 -0
- data/packages/optparse/examples/browser-test.html +75 -0
- data/packages/optparse/examples/nodejs-test.js +90 -0
- data/packages/optparse/lib/optparse.js +309 -0
- data/packages/optparse/package.json +13 -0
- data/packages/optparse/seed.yml +5 -0
- data/packages/text/lib/main.js +8 -0
- data/packages/text/package.json +9 -0
- data/packages/web-file/README.md +7 -0
- data/packages/web-file/lib/errors.js +32 -0
- data/packages/web-file/lib/file-reader.js +10 -0
- data/packages/web-file/lib/file-system.js +234 -0
- data/packages/web-file/lib/file-writer.js +10 -0
- data/packages/web-file/lib/file.js +9 -0
- data/packages/web-file/lib/main.js +34 -0
- data/packages/web-file/lib/platform.js +25 -0
- data/packages/web-file/lib/ruby/file.rb +252 -0
- data/packages/web-file/lib/ruby/file_reader.rb +69 -0
- data/packages/web-file/lib/ruby/file_system.rb +134 -0
- data/packages/web-file/lib/ruby/file_writer.rb +78 -0
- data/packages/web-file/package.json +12 -0
- data/packages/web-typed-array/README.md +7 -0
- data/packages/web-typed-array/lib/array-buffer-view.js +9 -0
- data/packages/web-typed-array/lib/array-buffer.js +7 -0
- data/packages/web-typed-array/lib/main.js +33 -0
- data/packages/web-typed-array/lib/platform.js +20 -0
- data/packages/web-typed-array/lib/ruby/array_buffer.rb +31 -0
- data/packages/web-typed-array/lib/ruby/array_buffer_view.rb +130 -0
- data/packages/web-typed-array/lib/ruby/typed_array.rb +133 -0
- data/packages/web-typed-array/lib/typed-array.js +26 -0
- data/packages/web-typed-array/package.json +9 -0
- data/spade-packager.gemspec +39 -0
- data/spec/cli/build_spec.rb +57 -0
- data/spec/cli/install_spec.rb +119 -0
- data/spec/cli/installed_spec.rb +55 -0
- data/spec/cli/list_spec.rb +74 -0
- data/spec/cli/login_spec.rb +75 -0
- data/spec/cli/new_spec.rb +5 -0
- data/spec/cli/owner_spec.rb +114 -0
- data/spec/cli/push_spec.rb +73 -0
- data/spec/cli/uninstall_spec.rb +58 -0
- data/spec/cli/unpack_spec.rb +72 -0
- data/spec/cli/unyank_spec.rb +73 -0
- data/spec/cli/yank_spec.rb +73 -0
- data/spec/credentials_spec.rb +23 -0
- data/spec/fixtures/badrake-0.8.7.spd +0 -0
- data/spec/fixtures/builder-3.0.0.spd +0 -0
- data/spec/fixtures/bundler-1.1.pre.spd +0 -0
- data/spec/fixtures/coffee-1.0.1.pre.spd +0 -0
- data/spec/fixtures/core-test-0.4.3.spd +0 -0
- data/spec/fixtures/core-test/bin/cot +3 -0
- data/spec/fixtures/core-test/lib/main.js +1 -0
- data/spec/fixtures/core-test/resources/runner.css +0 -0
- data/spec/fixtures/core-test/tests/test.js +1 -0
- data/spec/fixtures/highline-1.6.1.spd +0 -0
- data/spec/fixtures/ivory-0.0.1.spd +0 -0
- data/spec/fixtures/jquery-1.4.3.spd +0 -0
- data/spec/fixtures/optparse-1.0.1.spd +0 -0
- data/spec/fixtures/package.json +30 -0
- data/spec/fixtures/rake-0.8.6.spd +0 -0
- data/spec/fixtures/rake-0.8.7.spd +0 -0
- data/spec/gauntlet_spec.rb +27 -0
- data/spec/package_spec.rb +267 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/cli.rb +103 -0
- data/spec/support/fake.rb +48 -0
- data/spec/support/fake_gem_server.rb +67 -0
- data/spec/support/fake_gemcutter.rb +50 -0
- data/spec/support/matchers.rb +32 -0
- data/spec/support/path.rb +61 -0
- data/templates/project/LICENSE +19 -0
- data/templates/project/README.md +21 -0
- data/templates/project/lib/main.js +9 -0
- data/templates/project/project.json +31 -0
- data/templates/project/tests/main-test.js +8 -0
- metadata +484 -0
@@ -0,0 +1,1754 @@
|
|
1
|
+
# `nodes.coffee` contains all of the node classes for the syntax tree. Most
|
2
|
+
# nodes are created as the result of actions in the [grammar](grammar.html),
|
3
|
+
# but some are created by other nodes as a method of code generation. To convert
|
4
|
+
# the syntax tree into a string of JavaScript code, call `compile()` on the root.
|
5
|
+
|
6
|
+
{Scope} = require './scope'
|
7
|
+
|
8
|
+
# Import the helpers we plan to use.
|
9
|
+
{compact, flatten, extend, merge, del, starts, ends, last} = require './helpers'
|
10
|
+
|
11
|
+
exports.extend = extend # for parser
|
12
|
+
|
13
|
+
# Constant functions for nodes that don't need customization.
|
14
|
+
YES = -> yes
|
15
|
+
NO = -> no
|
16
|
+
THIS = -> this
|
17
|
+
NEGATE = -> @negated = not @negated; this
|
18
|
+
|
19
|
+
#### Base
|
20
|
+
|
21
|
+
# The **Base** is the abstract base class for all nodes in the syntax tree.
|
22
|
+
# Each subclass implements the `compileNode` method, which performs the
|
23
|
+
# code generation for that node. To compile a node to JavaScript,
|
24
|
+
# call `compile` on it, which wraps `compileNode` in some generic extra smarts,
|
25
|
+
# to know when the generated code needs to be wrapped up in a closure.
|
26
|
+
# An options hash is passed and cloned throughout, containing information about
|
27
|
+
# the environment from higher in the tree (such as if a returned value is
|
28
|
+
# being requested by the surrounding function), information about the current
|
29
|
+
# scope, and indentation level.
|
30
|
+
exports.Base = class Base
|
31
|
+
|
32
|
+
# Common logic for determining whether to wrap this node in a closure before
|
33
|
+
# compiling it, or to compile directly. We need to wrap if this node is a
|
34
|
+
# *statement*, and it's not a *pureStatement*, and we're not at
|
35
|
+
# the top level of a block (which would be unnecessary), and we haven't
|
36
|
+
# already been asked to return the result (because statements know how to
|
37
|
+
# return results).
|
38
|
+
compile: (o, lvl) ->
|
39
|
+
o = extend {}, o
|
40
|
+
o.level = lvl if lvl
|
41
|
+
node = @unfoldSoak(o) or this
|
42
|
+
node.tab = o.indent
|
43
|
+
if o.level is LEVEL_TOP or not node.isStatement(o)
|
44
|
+
node.compileNode o
|
45
|
+
else
|
46
|
+
node.compileClosure o
|
47
|
+
|
48
|
+
# Statements converted into expressions via closure-wrapping share a scope
|
49
|
+
# object with their parent closure, to preserve the expected lexical scope.
|
50
|
+
compileClosure: (o) ->
|
51
|
+
if @jumps()
|
52
|
+
throw SyntaxError 'cannot use a pure statement in an expression.'
|
53
|
+
o.sharedScope = yes
|
54
|
+
Closure.wrap(this).compileNode o
|
55
|
+
|
56
|
+
# If the code generation wishes to use the result of a complex expression
|
57
|
+
# in multiple places, ensure that the expression is only ever evaluated once,
|
58
|
+
# by assigning it to a temporary variable. Pass a level to precompile.
|
59
|
+
cache: (o, level, reused) ->
|
60
|
+
unless @isComplex()
|
61
|
+
ref = if level then @compile o, level else this
|
62
|
+
[ref, ref]
|
63
|
+
else
|
64
|
+
ref = new Literal reused or o.scope.freeVariable 'ref'
|
65
|
+
sub = new Assign ref, this
|
66
|
+
if level then [sub.compile(o, level), ref.value] else [sub, ref]
|
67
|
+
|
68
|
+
# Compile to a source/variable pair suitable for looping.
|
69
|
+
compileLoopReference: (o, name) ->
|
70
|
+
src = tmp = @compile o, LEVEL_LIST
|
71
|
+
unless -Infinity < +src < Infinity or IDENTIFIER.test(src) and o.scope.check(src, yes)
|
72
|
+
src = "#{ tmp = o.scope.freeVariable name } = #{src}"
|
73
|
+
[src, tmp]
|
74
|
+
|
75
|
+
# Construct a node that returns the current node's result.
|
76
|
+
# Note that this is overridden for smarter behavior for
|
77
|
+
# many statement nodes (e.g. If, For)...
|
78
|
+
makeReturn: ->
|
79
|
+
new Return this
|
80
|
+
|
81
|
+
# Does this node, or any of its children, contain a node of a certain kind?
|
82
|
+
# Recursively traverses down the *children* of the nodes, yielding to a block
|
83
|
+
# and returning true when the block finds a match. `contains` does not cross
|
84
|
+
# scope boundaries.
|
85
|
+
contains: (pred) ->
|
86
|
+
contains = no
|
87
|
+
@traverseChildren no, (node) ->
|
88
|
+
if pred node
|
89
|
+
contains = yes
|
90
|
+
return no
|
91
|
+
contains
|
92
|
+
|
93
|
+
# Is this node of a certain type, or does it contain the type?
|
94
|
+
containsType: (type) ->
|
95
|
+
this instanceof type or @contains (node) -> node instanceof type
|
96
|
+
|
97
|
+
# Pull out the last non-comment node of a node list.
|
98
|
+
lastNonComment: (list) ->
|
99
|
+
i = list.length
|
100
|
+
return list[i] while i-- when list[i] not instanceof Comment
|
101
|
+
null
|
102
|
+
|
103
|
+
# `toString` representation of the node, for inspecting the parse tree.
|
104
|
+
# This is what `coffee --nodes` prints out.
|
105
|
+
toString: (idt = '', name = @constructor.name) ->
|
106
|
+
tree = '\n' + idt + name
|
107
|
+
tree += '?' if @soak
|
108
|
+
@eachChild (node) -> tree += node.toString idt + TAB
|
109
|
+
tree
|
110
|
+
|
111
|
+
# Passes each child to a function, breaking when the function returns `false`.
|
112
|
+
eachChild: (func) ->
|
113
|
+
return this unless @children
|
114
|
+
for attr in @children when @[attr]
|
115
|
+
for child in flatten [@[attr]]
|
116
|
+
return this if func(child) is false
|
117
|
+
this
|
118
|
+
|
119
|
+
traverseChildren: (crossScope, func) ->
|
120
|
+
@eachChild (child) ->
|
121
|
+
return false if func(child) is false
|
122
|
+
child.traverseChildren crossScope, func
|
123
|
+
|
124
|
+
invert: ->
|
125
|
+
new Op '!', this
|
126
|
+
|
127
|
+
unwrapAll: ->
|
128
|
+
node = this
|
129
|
+
continue until node is node = node.unwrap()
|
130
|
+
node
|
131
|
+
|
132
|
+
# Default implementations of the common node properties and methods. Nodes
|
133
|
+
# will override these with custom logic, if needed.
|
134
|
+
children: []
|
135
|
+
|
136
|
+
isStatement : NO
|
137
|
+
jumps : NO
|
138
|
+
isComplex : YES
|
139
|
+
isChainable : NO
|
140
|
+
isAssignable : NO
|
141
|
+
|
142
|
+
unwrap : THIS
|
143
|
+
unfoldSoak : NO
|
144
|
+
|
145
|
+
# Is this node used to assign a certain variable?
|
146
|
+
assigns: NO
|
147
|
+
|
148
|
+
#### Expressions
|
149
|
+
|
150
|
+
# The expressions body is the list of expressions that forms the body of an
|
151
|
+
# indented block of code -- the implementation of a function, a clause in an
|
152
|
+
# `if`, `switch`, or `try`, and so on...
|
153
|
+
exports.Expressions = class Expressions extends Base
|
154
|
+
constructor: (nodes) ->
|
155
|
+
@expressions = compact flatten nodes or []
|
156
|
+
|
157
|
+
children: ['expressions']
|
158
|
+
|
159
|
+
# Tack an expression on to the end of this expression list.
|
160
|
+
push: (node) ->
|
161
|
+
@expressions.push node
|
162
|
+
this
|
163
|
+
|
164
|
+
# Remove and return the last expression of this expression list.
|
165
|
+
pop: ->
|
166
|
+
@expressions.pop()
|
167
|
+
|
168
|
+
# Add an expression at the beginning of this expression list.
|
169
|
+
unshift: (node) ->
|
170
|
+
@expressions.unshift node
|
171
|
+
this
|
172
|
+
|
173
|
+
# If this Expressions consists of just a single node, unwrap it by pulling
|
174
|
+
# it back out.
|
175
|
+
unwrap: ->
|
176
|
+
if @expressions.length is 1 then @expressions[0] else this
|
177
|
+
|
178
|
+
# Is this an empty block of code?
|
179
|
+
isEmpty: ->
|
180
|
+
not @expressions.length
|
181
|
+
|
182
|
+
isStatement: (o) ->
|
183
|
+
for exp in @expressions when exp.isStatement o
|
184
|
+
return yes
|
185
|
+
no
|
186
|
+
|
187
|
+
jumps: (o) ->
|
188
|
+
for exp in @expressions
|
189
|
+
return exp if exp.jumps o
|
190
|
+
|
191
|
+
# An Expressions node does not return its entire body, rather it
|
192
|
+
# ensures that the final expression is returned.
|
193
|
+
makeReturn: ->
|
194
|
+
len = @expressions.length
|
195
|
+
while len--
|
196
|
+
expr = @expressions[len]
|
197
|
+
if expr not instanceof Comment
|
198
|
+
@expressions[len] = expr.makeReturn()
|
199
|
+
break
|
200
|
+
this
|
201
|
+
|
202
|
+
# An **Expressions** is the only node that can serve as the root.
|
203
|
+
compile: (o = {}, level) ->
|
204
|
+
if o.scope then super o, level else @compileRoot o
|
205
|
+
|
206
|
+
# Compile all expressions within the **Expressions** body. If we need to
|
207
|
+
# return the result, and it's an expression, simply return it. If it's a
|
208
|
+
# statement, ask the statement to do so.
|
209
|
+
compileNode: (o) ->
|
210
|
+
@tab = o.indent
|
211
|
+
top = o.level is LEVEL_TOP
|
212
|
+
codes = []
|
213
|
+
for node in @expressions
|
214
|
+
node = node.unwrapAll()
|
215
|
+
node = (node.unfoldSoak(o) or node)
|
216
|
+
if top
|
217
|
+
node.front = true
|
218
|
+
code = node.compile o
|
219
|
+
codes.push if node.isStatement o then code else @tab + code + ';'
|
220
|
+
else
|
221
|
+
codes.push node.compile o, LEVEL_LIST
|
222
|
+
return codes.join '\n' if top
|
223
|
+
code = codes.join(', ') or 'void 0'
|
224
|
+
if codes.length > 1 and o.level >= LEVEL_LIST then "(#{code})" else code
|
225
|
+
|
226
|
+
# If we happen to be the top-level **Expressions**, wrap everything in
|
227
|
+
# a safety closure, unless requested not to.
|
228
|
+
# It would be better not to generate them in the first place, but for now,
|
229
|
+
# clean up obvious double-parentheses.
|
230
|
+
compileRoot: (o) ->
|
231
|
+
o.indent = @tab = if o.bare then '' else TAB
|
232
|
+
o.scope = new Scope null, this, null
|
233
|
+
o.level = LEVEL_TOP
|
234
|
+
code = @compileWithDeclarations o
|
235
|
+
code = code.replace TRAILING_WHITESPACE, ''
|
236
|
+
if o.bare then code else "(function() {\n#{code}\n}).call(this);\n"
|
237
|
+
|
238
|
+
# Compile the expressions body for the contents of a function, with
|
239
|
+
# declarations of all inner variables pushed up to the top.
|
240
|
+
compileWithDeclarations: (o) ->
|
241
|
+
code = post = ''
|
242
|
+
for exp, i in @expressions
|
243
|
+
exp = exp.unwrap()
|
244
|
+
break unless exp instanceof Comment or exp instanceof Literal
|
245
|
+
o = merge(o, level: LEVEL_TOP)
|
246
|
+
if i
|
247
|
+
rest = @expressions.splice i, @expressions.length
|
248
|
+
code = @compileNode o
|
249
|
+
@expressions = rest
|
250
|
+
post = @compileNode o
|
251
|
+
{scope} = o
|
252
|
+
if scope.expressions is this
|
253
|
+
if not o.globals and o.scope.hasDeclarations()
|
254
|
+
code += "#{@tab}var #{ scope.declaredVariables().join(', ') };\n"
|
255
|
+
if scope.hasAssignments
|
256
|
+
code += "#{@tab}var #{ multident scope.assignedVariables().join(', '), @tab };\n"
|
257
|
+
code + post
|
258
|
+
|
259
|
+
# Wrap up the given nodes as an **Expressions**, unless it already happens
|
260
|
+
# to be one.
|
261
|
+
@wrap: (nodes) ->
|
262
|
+
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
|
263
|
+
new Expressions nodes
|
264
|
+
|
265
|
+
#### Literal
|
266
|
+
|
267
|
+
# Literals are static values that can be passed through directly into
|
268
|
+
# JavaScript without translation, such as: strings, numbers,
|
269
|
+
# `true`, `false`, `null`...
|
270
|
+
exports.Literal = class Literal extends Base
|
271
|
+
constructor: (@value) ->
|
272
|
+
|
273
|
+
makeReturn: ->
|
274
|
+
if @isStatement() then this else new Return this
|
275
|
+
|
276
|
+
isAssignable: ->
|
277
|
+
IDENTIFIER.test @value
|
278
|
+
|
279
|
+
isStatement: ->
|
280
|
+
@value in ['break', 'continue', 'debugger']
|
281
|
+
|
282
|
+
isComplex: NO
|
283
|
+
|
284
|
+
assigns: (name) ->
|
285
|
+
name is @value
|
286
|
+
|
287
|
+
jumps: (o) ->
|
288
|
+
return no unless @isStatement()
|
289
|
+
if not (o and (o.loop or o.block and (@value isnt 'continue'))) then this else no
|
290
|
+
|
291
|
+
compileNode: (o) ->
|
292
|
+
code = if @value.reserved then "\"#{@value}\"" else @value
|
293
|
+
if @isStatement() then "#{@tab}#{code};" else code
|
294
|
+
|
295
|
+
toString: ->
|
296
|
+
' "' + @value + '"'
|
297
|
+
|
298
|
+
#### Return
|
299
|
+
|
300
|
+
# A `return` is a *pureStatement* -- wrapping it in a closure wouldn't
|
301
|
+
# make sense.
|
302
|
+
exports.Return = class Return extends Base
|
303
|
+
constructor: (@expression) ->
|
304
|
+
|
305
|
+
children: ['expression']
|
306
|
+
|
307
|
+
isStatement: YES
|
308
|
+
makeReturn: THIS
|
309
|
+
jumps: THIS
|
310
|
+
|
311
|
+
compile: (o, level) ->
|
312
|
+
expr = @expression?.makeReturn()
|
313
|
+
if expr and expr not instanceof Return then expr.compile o, level else super o, level
|
314
|
+
|
315
|
+
compileNode: (o) ->
|
316
|
+
@tab + "return#{ if @expression then ' ' + @expression.compile(o, LEVEL_PAREN) else '' };"
|
317
|
+
|
318
|
+
#### Value
|
319
|
+
|
320
|
+
# A value, variable or literal or parenthesized, indexed or dotted into,
|
321
|
+
# or vanilla.
|
322
|
+
exports.Value = class Value extends Base
|
323
|
+
constructor: (base, props, tag) ->
|
324
|
+
return base if not props and base instanceof Value
|
325
|
+
@base = base
|
326
|
+
@properties = props or []
|
327
|
+
@[tag] = true if tag
|
328
|
+
return this
|
329
|
+
|
330
|
+
children: ['base', 'properties']
|
331
|
+
|
332
|
+
# Add a property access to the list.
|
333
|
+
push: (prop) ->
|
334
|
+
@properties.push prop
|
335
|
+
this
|
336
|
+
|
337
|
+
hasProperties: ->
|
338
|
+
!!@properties.length
|
339
|
+
|
340
|
+
# Some boolean checks for the benefit of other nodes.
|
341
|
+
isArray : -> not @properties.length and @base instanceof Arr
|
342
|
+
isComplex : -> @hasProperties() or @base.isComplex()
|
343
|
+
isAssignable : -> @hasProperties() or @base.isAssignable()
|
344
|
+
isSimpleNumber : -> @base instanceof Literal and SIMPLENUM.test @base.value
|
345
|
+
isAtomic : ->
|
346
|
+
for node in @properties.concat @base
|
347
|
+
return no if node.soak or node instanceof Call
|
348
|
+
yes
|
349
|
+
|
350
|
+
isStatement : (o) -> not @properties.length and @base.isStatement o
|
351
|
+
assigns : (name) -> not @properties.length and @base.assigns name
|
352
|
+
jumps : (o) -> not @properties.length and @base.jumps o
|
353
|
+
|
354
|
+
isObject: (onlyGenerated) ->
|
355
|
+
return no if @properties.length
|
356
|
+
(@base instanceof Obj) and (not onlyGenerated or @base.generated)
|
357
|
+
|
358
|
+
isSplice: ->
|
359
|
+
last(@properties) instanceof Slice
|
360
|
+
|
361
|
+
makeReturn: ->
|
362
|
+
if @properties.length then super() else @base.makeReturn()
|
363
|
+
|
364
|
+
# The value can be unwrapped as its inner node, if there are no attached
|
365
|
+
# properties.
|
366
|
+
unwrap: ->
|
367
|
+
if @properties.length then this else @base
|
368
|
+
|
369
|
+
# A reference has base part (`this` value) and name part.
|
370
|
+
# We cache them separately for compiling complex expressions.
|
371
|
+
# `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`
|
372
|
+
cacheReference: (o) ->
|
373
|
+
name = last @properties
|
374
|
+
if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
|
375
|
+
return [this, this] # `a` `a.b`
|
376
|
+
base = new Value @base, @properties.slice 0, -1
|
377
|
+
if base.isComplex() # `a().b`
|
378
|
+
bref = new Literal o.scope.freeVariable 'base'
|
379
|
+
base = new Value new Parens new Assign bref, base
|
380
|
+
return [base, bref] unless name # `a()`
|
381
|
+
if name.isComplex() # `a[b()]`
|
382
|
+
nref = new Literal o.scope.freeVariable 'name'
|
383
|
+
name = new Index new Assign nref, name.index
|
384
|
+
nref = new Index nref
|
385
|
+
[base.push(name), new Value(bref or base.base, [nref or name])]
|
386
|
+
|
387
|
+
# We compile a value to JavaScript by compiling and joining each property.
|
388
|
+
# Things get much more interesting if the chain of properties has *soak*
|
389
|
+
# operators `?.` interspersed. Then we have to take care not to accidentally
|
390
|
+
# evaluate anything twice when building the soak chain.
|
391
|
+
compileNode: (o) ->
|
392
|
+
@base.front = @front
|
393
|
+
props = @properties
|
394
|
+
code = @base.compile o, if props.length then LEVEL_ACCESS else null
|
395
|
+
code = "(#{code})" if props[0] instanceof Access and @isSimpleNumber()
|
396
|
+
code += prop.compile o for prop in props
|
397
|
+
code
|
398
|
+
|
399
|
+
# Unfold a soak into an `If`: `a?.b` -> `a.b if a?`
|
400
|
+
unfoldSoak: (o) ->
|
401
|
+
if ifn = @base.unfoldSoak o
|
402
|
+
Array::push.apply ifn.body.properties, @properties
|
403
|
+
return ifn
|
404
|
+
for prop, i in @properties when prop.soak
|
405
|
+
prop.soak = off
|
406
|
+
fst = new Value @base, @properties.slice 0, i
|
407
|
+
snd = new Value @base, @properties.slice i
|
408
|
+
if fst.isComplex()
|
409
|
+
ref = new Literal o.scope.freeVariable 'ref'
|
410
|
+
fst = new Parens new Assign ref, fst
|
411
|
+
snd.base = ref
|
412
|
+
return new If new Existence(fst), snd, soak: on
|
413
|
+
null
|
414
|
+
|
415
|
+
#### Comment
|
416
|
+
|
417
|
+
# CoffeeScript passes through block comments as JavaScript block comments
|
418
|
+
# at the same position.
|
419
|
+
exports.Comment = class Comment extends Base
|
420
|
+
constructor: (@comment) ->
|
421
|
+
|
422
|
+
isStatement: YES
|
423
|
+
makeReturn: THIS
|
424
|
+
|
425
|
+
compileNode: (o, level) ->
|
426
|
+
code = '/*' + multident(@comment, @tab) + '*/'
|
427
|
+
code = o.indent + code if (level or o.level) is LEVEL_TOP
|
428
|
+
code
|
429
|
+
|
430
|
+
#### Call
|
431
|
+
|
432
|
+
# Node for a function invocation. Takes care of converting `super()` calls into
|
433
|
+
# calls against the prototype's function of the same name.
|
434
|
+
exports.Call = class Call extends Base
|
435
|
+
constructor: (variable, @args = [], @soak) ->
|
436
|
+
@isNew = false
|
437
|
+
@isSuper = variable is 'super'
|
438
|
+
@variable = if @isSuper then null else variable
|
439
|
+
|
440
|
+
children: ['variable', 'args']
|
441
|
+
|
442
|
+
# Tag this invocation as creating a new instance.
|
443
|
+
newInstance: ->
|
444
|
+
base = @variable.base or @variable
|
445
|
+
if base instanceof Call
|
446
|
+
base.newInstance()
|
447
|
+
else
|
448
|
+
@isNew = true
|
449
|
+
this
|
450
|
+
|
451
|
+
# Grab the reference to the superclass's implementation of the current
|
452
|
+
# method.
|
453
|
+
superReference: (o) ->
|
454
|
+
{method} = o.scope
|
455
|
+
throw SyntaxError 'cannot call super outside of a function.' unless method
|
456
|
+
{name} = method
|
457
|
+
throw SyntaxError 'cannot call super on an anonymous function.' unless name
|
458
|
+
if method.klass
|
459
|
+
"#{method.klass}.__super__.#{name}"
|
460
|
+
else
|
461
|
+
"#{name}.__super__.constructor"
|
462
|
+
|
463
|
+
# Soaked chained invocations unfold into if/else ternary structures.
|
464
|
+
unfoldSoak: (o) ->
|
465
|
+
if @soak
|
466
|
+
if @variable
|
467
|
+
return ifn if ifn = unfoldSoak o, this, 'variable'
|
468
|
+
[left, rite] = new Value(@variable).cacheReference o
|
469
|
+
else
|
470
|
+
left = new Literal @superReference o
|
471
|
+
rite = new Value left
|
472
|
+
rite = new Call rite, @args
|
473
|
+
rite.isNew = @isNew
|
474
|
+
left = new Literal "typeof #{ left.compile o } === \"function\""
|
475
|
+
return new If left, new Value(rite), soak: yes
|
476
|
+
call = this
|
477
|
+
list = []
|
478
|
+
loop
|
479
|
+
if call.variable instanceof Call
|
480
|
+
list.push call
|
481
|
+
call = call.variable
|
482
|
+
continue
|
483
|
+
break unless call.variable instanceof Value
|
484
|
+
list.push call
|
485
|
+
break unless (call = call.variable.base) instanceof Call
|
486
|
+
for call in list.reverse()
|
487
|
+
if ifn
|
488
|
+
if call.variable instanceof Call
|
489
|
+
call.variable = ifn
|
490
|
+
else
|
491
|
+
call.variable.base = ifn
|
492
|
+
ifn = unfoldSoak o, call, 'variable'
|
493
|
+
ifn
|
494
|
+
|
495
|
+
# Compile a vanilla function call.
|
496
|
+
compileNode: (o) ->
|
497
|
+
@variable?.front = @front
|
498
|
+
if code = Splat.compileSplattedArray o, @args, true
|
499
|
+
return @compileSplat o, code
|
500
|
+
args = (arg.compile o, LEVEL_LIST for arg in @args).join ', '
|
501
|
+
if @isSuper
|
502
|
+
@superReference(o) + ".call(this#{ args and ', ' + args })"
|
503
|
+
else
|
504
|
+
(if @isNew then 'new ' else '') + @variable.compile(o, LEVEL_ACCESS) + "(#{args})"
|
505
|
+
|
506
|
+
# `super()` is converted into a call against the superclass's implementation
|
507
|
+
# of the current function.
|
508
|
+
compileSuper: (args, o) ->
|
509
|
+
"#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#{args})"
|
510
|
+
|
511
|
+
# If you call a function with a splat, it's converted into a JavaScript
|
512
|
+
# `.apply()` call to allow an array of arguments to be passed.
|
513
|
+
# If it's a constructor, then things get real tricky. We have to inject an
|
514
|
+
# inner constructor in order to be able to pass the varargs.
|
515
|
+
compileSplat: (o, splatArgs) ->
|
516
|
+
return "#{ @superReference o }.apply(this, #{splatArgs})" if @isSuper
|
517
|
+
if @isNew
|
518
|
+
idt = @tab + TAB
|
519
|
+
return """
|
520
|
+
(function(func, args, ctor) {
|
521
|
+
#{idt}ctor.prototype = func.prototype;
|
522
|
+
#{idt}var child = new ctor, result = func.apply(child, args);
|
523
|
+
#{idt}return typeof result === "object" ? result : child;
|
524
|
+
#{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function() {})
|
525
|
+
"""
|
526
|
+
base = new Value @variable
|
527
|
+
if (name = base.properties.pop()) and base.isComplex()
|
528
|
+
ref = o.scope.freeVariable 'ref'
|
529
|
+
fun = "(#{ref} = #{ base.compile o, LEVEL_LIST })#{ name.compile o }"
|
530
|
+
else
|
531
|
+
fun = base.compile o, LEVEL_ACCESS
|
532
|
+
if name
|
533
|
+
ref = fun
|
534
|
+
fun += name.compile o
|
535
|
+
else
|
536
|
+
ref = 'null'
|
537
|
+
"#{fun}.apply(#{ref}, #{splatArgs})"
|
538
|
+
|
539
|
+
#### Extends
|
540
|
+
|
541
|
+
# Node to extend an object's prototype with an ancestor object.
|
542
|
+
# After `goog.inherits` from the
|
543
|
+
# [Closure Library](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.html).
|
544
|
+
exports.Extends = class Extends extends Base
|
545
|
+
constructor: (@child, @parent) ->
|
546
|
+
|
547
|
+
children: ['child', 'parent']
|
548
|
+
|
549
|
+
# Hooks one constructor into another's prototype chain.
|
550
|
+
compile: (o) ->
|
551
|
+
utility 'hasProp'
|
552
|
+
new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compile o
|
553
|
+
|
554
|
+
#### Access
|
555
|
+
|
556
|
+
# A `.` access into a property of a value, or the `::` shorthand for
|
557
|
+
# an access into the object's prototype.
|
558
|
+
exports.Access = class Access extends Base
|
559
|
+
constructor: (@name, tag) ->
|
560
|
+
@name.asKey = yes
|
561
|
+
@proto = if tag is 'proto' then '.prototype' else ''
|
562
|
+
@soak = tag is 'soak'
|
563
|
+
|
564
|
+
children: ['name']
|
565
|
+
|
566
|
+
compile: (o) ->
|
567
|
+
name = @name.compile o
|
568
|
+
@proto + if IS_STRING.test name then "[#{name}]" else ".#{name}"
|
569
|
+
|
570
|
+
isComplex: NO
|
571
|
+
|
572
|
+
#### Index
|
573
|
+
|
574
|
+
# A `[ ... ]` indexed access into an array or object.
|
575
|
+
exports.Index = class Index extends Base
|
576
|
+
constructor: (@index) ->
|
577
|
+
|
578
|
+
children: ['index']
|
579
|
+
|
580
|
+
compile: (o) ->
|
581
|
+
(if @proto then '.prototype' else '') + "[#{ @index.compile o, LEVEL_PAREN }]"
|
582
|
+
|
583
|
+
isComplex: ->
|
584
|
+
@index.isComplex()
|
585
|
+
|
586
|
+
#### Range
|
587
|
+
|
588
|
+
# A range literal. Ranges can be used to extract portions (slices) of arrays,
|
589
|
+
# to specify a range for comprehensions, or as a value, to be expanded into the
|
590
|
+
# corresponding array of integers at runtime.
|
591
|
+
exports.Range = class Range extends Base
|
592
|
+
|
593
|
+
children: ['from', 'to']
|
594
|
+
|
595
|
+
constructor: (@from, @to, tag) ->
|
596
|
+
@exclusive = tag is 'exclusive'
|
597
|
+
@equals = if @exclusive then '' else '='
|
598
|
+
|
599
|
+
# Compiles the range's source variables -- where it starts and where it ends.
|
600
|
+
# But only if they need to be cached to avoid double evaluation.
|
601
|
+
compileVariables: (o) ->
|
602
|
+
o = merge(o, top: true)
|
603
|
+
[@from, @fromVar] = @from.cache o, LEVEL_LIST
|
604
|
+
[@to, @toVar] = @to.cache o, LEVEL_LIST
|
605
|
+
[@fromNum, @toNum] = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)]
|
606
|
+
parts = []
|
607
|
+
parts.push @from if @from isnt @fromVar
|
608
|
+
parts.push @to if @to isnt @toVar
|
609
|
+
|
610
|
+
# When compiled normally, the range returns the contents of the *for loop*
|
611
|
+
# needed to iterate over the values in the range. Used by comprehensions.
|
612
|
+
compileNode: (o) ->
|
613
|
+
@compileVariables o
|
614
|
+
return @compileArray(o) unless o.index
|
615
|
+
return @compileSimple(o) if @fromNum and @toNum
|
616
|
+
idx = del o, 'index'
|
617
|
+
step = del o, 'step'
|
618
|
+
vars = "#{idx} = #{@from}" + if @to isnt @toVar then ", #{@to}" else ''
|
619
|
+
intro = "(#{@fromVar} <= #{@toVar} ? #{idx}"
|
620
|
+
compare = "#{intro} <#{@equals} #{@toVar} : #{idx} >#{@equals} #{@toVar})"
|
621
|
+
stepPart = if step then step.compile(o) else '1'
|
622
|
+
incr = if step then "#{idx} += #{stepPart}" else "#{intro} += #{stepPart} : #{idx} -= #{stepPart})"
|
623
|
+
"#{vars}; #{compare}; #{incr}"
|
624
|
+
|
625
|
+
# Compile a simple range comprehension, with integers.
|
626
|
+
compileSimple: (o) ->
|
627
|
+
[from, to] = [+@fromNum, +@toNum]
|
628
|
+
idx = del o, 'index'
|
629
|
+
step = del o, 'step'
|
630
|
+
step and= "#{idx} += #{step.compile(o)}"
|
631
|
+
if from <= to
|
632
|
+
"#{idx} = #{from}; #{idx} <#{@equals} #{to}; #{step or "#{idx}++"}"
|
633
|
+
else
|
634
|
+
"#{idx} = #{from}; #{idx} >#{@equals} #{to}; #{step or "#{idx}--"}"
|
635
|
+
|
636
|
+
# When used as a value, expand the range into the equivalent array.
|
637
|
+
compileArray: (o) ->
|
638
|
+
if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
|
639
|
+
range = [+@fromNum..+@toNum]
|
640
|
+
range.pop() if @exclusive
|
641
|
+
return "[#{ range.join(', ') }]"
|
642
|
+
idt = @tab + TAB
|
643
|
+
i = o.scope.freeVariable 'i'
|
644
|
+
result = o.scope.freeVariable 'results'
|
645
|
+
pre = "\n#{idt}#{result} = [];"
|
646
|
+
if @fromNum and @toNum
|
647
|
+
o.index = i
|
648
|
+
body = @compileSimple o
|
649
|
+
else
|
650
|
+
vars = "#{i} = #{@from}" + if @to isnt @toVar then ", #{@to}" else ''
|
651
|
+
clause = "#{@fromVar} <= #{@toVar} ?"
|
652
|
+
body = "var #{vars}; #{clause} #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{clause} #{i} += 1 : #{i} -= 1"
|
653
|
+
post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
|
654
|
+
"(function() {#{pre}\n#{idt}for (#{body})#{post}}).call(this)"
|
655
|
+
|
656
|
+
#### Slice
|
657
|
+
|
658
|
+
# An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
|
659
|
+
# specifies the index of the end of the slice, just as the first parameter
|
660
|
+
# is the index of the beginning.
|
661
|
+
exports.Slice = class Slice extends Base
|
662
|
+
|
663
|
+
children: ['range']
|
664
|
+
|
665
|
+
constructor: (@range) ->
|
666
|
+
super()
|
667
|
+
|
668
|
+
# We have to be careful when trying to slice through the end of the array,
|
669
|
+
# `9e9` is used because not all implementations respect `undefined` or `1/0`.
|
670
|
+
# `9e9` should be safe because `9e9` > `2**32`, the max array length.
|
671
|
+
compileNode: (o) ->
|
672
|
+
{to, from} = @range
|
673
|
+
fromStr = from and from.compile(o, LEVEL_PAREN) or '0'
|
674
|
+
compiled = to and to.compile o, LEVEL_PAREN
|
675
|
+
if to and not (not @range.exclusive and +compiled is -1)
|
676
|
+
toStr = ', ' + if @range.exclusive
|
677
|
+
compiled
|
678
|
+
else if SIMPLENUM.test compiled
|
679
|
+
(+compiled + 1).toString()
|
680
|
+
else
|
681
|
+
"(#{compiled} + 1) || 9e9"
|
682
|
+
".slice(#{ fromStr }#{ toStr or '' })"
|
683
|
+
|
684
|
+
#### Obj
|
685
|
+
|
686
|
+
# An object literal, nothing fancy.
|
687
|
+
exports.Obj = class Obj extends Base
|
688
|
+
constructor: (props, @generated = false) ->
|
689
|
+
@objects = @properties = props or []
|
690
|
+
|
691
|
+
children: ['properties']
|
692
|
+
|
693
|
+
compileNode: (o) ->
|
694
|
+
props = @properties
|
695
|
+
return (if @front then '({})' else '{}') unless props.length
|
696
|
+
idt = o.indent += TAB
|
697
|
+
lastNoncom = @lastNonComment @properties
|
698
|
+
props = for prop, i in props
|
699
|
+
join = if i is props.length - 1
|
700
|
+
''
|
701
|
+
else if prop is lastNoncom or prop instanceof Comment
|
702
|
+
'\n'
|
703
|
+
else
|
704
|
+
',\n'
|
705
|
+
indent = if prop instanceof Comment then '' else idt
|
706
|
+
if prop instanceof Value and prop.this
|
707
|
+
prop = new Assign prop.properties[0].name, prop, 'object'
|
708
|
+
if prop not instanceof Comment
|
709
|
+
if prop not instanceof Assign
|
710
|
+
prop = new Assign prop, prop, 'object'
|
711
|
+
(prop.variable.base or prop.variable).asKey = yes
|
712
|
+
indent + prop.compile(o, LEVEL_TOP) + join
|
713
|
+
props = props.join ''
|
714
|
+
obj = "{#{ props and '\n' + props + '\n' + @tab }}"
|
715
|
+
if @front then "(#{obj})" else obj
|
716
|
+
|
717
|
+
assigns: (name) ->
|
718
|
+
for prop in @properties when prop.assigns name then return yes
|
719
|
+
no
|
720
|
+
|
721
|
+
#### Arr
|
722
|
+
|
723
|
+
# An array literal.
|
724
|
+
exports.Arr = class Arr extends Base
|
725
|
+
constructor: (objs) ->
|
726
|
+
@objects = objs or []
|
727
|
+
|
728
|
+
children: ['objects']
|
729
|
+
|
730
|
+
compileNode: (o) ->
|
731
|
+
return '[]' unless @objects.length
|
732
|
+
o.indent += TAB
|
733
|
+
return code if code = Splat.compileSplattedArray o, @objects
|
734
|
+
code = (obj.compile o, LEVEL_LIST for obj in @objects).join ', '
|
735
|
+
if code.indexOf('\n') >= 0
|
736
|
+
"[\n#{o.indent}#{code}\n#{@tab}]"
|
737
|
+
else
|
738
|
+
"[#{code}]"
|
739
|
+
|
740
|
+
assigns: (name) ->
|
741
|
+
for obj in @objects when obj.assigns name then return yes
|
742
|
+
no
|
743
|
+
|
744
|
+
#### Class
|
745
|
+
|
746
|
+
# The CoffeeScript class definition.
|
747
|
+
# Initialize a **Class** with its name, an optional superclass, and a
|
748
|
+
# list of prototype property assignments.
|
749
|
+
exports.Class = class Class extends Base
|
750
|
+
constructor: (@variable, @parent, @body = new Expressions) ->
|
751
|
+
@boundFuncs = []
|
752
|
+
@body.classBody = yes
|
753
|
+
|
754
|
+
children: ['variable', 'parent', 'body']
|
755
|
+
|
756
|
+
# Figure out the appropriate name for the constructor function of this class.
|
757
|
+
determineName: ->
|
758
|
+
return null unless @variable
|
759
|
+
decl = if tail = last @variable.properties
|
760
|
+
tail instanceof Access and tail.name.value
|
761
|
+
else
|
762
|
+
@variable.base.value
|
763
|
+
decl and= IDENTIFIER.test(decl) and decl
|
764
|
+
|
765
|
+
# For all `this`-references and bound functions in the class definition,
|
766
|
+
# `this` is the Class being constructed.
|
767
|
+
setContext: (name) ->
|
768
|
+
@body.traverseChildren false, (node) ->
|
769
|
+
return false if node.classBody
|
770
|
+
if node instanceof Literal and node.value is 'this'
|
771
|
+
node.value = name
|
772
|
+
else if node instanceof Code
|
773
|
+
node.klass = name
|
774
|
+
node.context = name if node.bound
|
775
|
+
|
776
|
+
# Ensure that all functions bound to the instance are proxied in the
|
777
|
+
# constructor.
|
778
|
+
addBoundFunctions: (o) ->
|
779
|
+
if @boundFuncs.length
|
780
|
+
for bvar in @boundFuncs
|
781
|
+
bname = bvar.compile o
|
782
|
+
@ctor.body.unshift new Literal "this.#{bname} = #{utility 'bind'}(this.#{bname}, this);"
|
783
|
+
|
784
|
+
# Merge the properties from a top-level object as prototypal properties
|
785
|
+
# on the class.
|
786
|
+
addProperties: (node, name) ->
|
787
|
+
props = node.base.properties.slice 0
|
788
|
+
while assign = props.shift()
|
789
|
+
if assign instanceof Assign
|
790
|
+
base = assign.variable.base
|
791
|
+
delete assign.context
|
792
|
+
func = assign.value
|
793
|
+
if base.value is 'constructor'
|
794
|
+
if @ctor
|
795
|
+
throw new Error 'cannot define more than one constructor in a class'
|
796
|
+
if func.bound
|
797
|
+
throw new Error 'cannot define a constructor as a bound function'
|
798
|
+
if func instanceof Code
|
799
|
+
assign = @ctor = func
|
800
|
+
else
|
801
|
+
assign = @ctor = new Assign(new Value(new Literal name), func)
|
802
|
+
else
|
803
|
+
unless assign.variable.this
|
804
|
+
assign.variable = new Value(new Literal(name), [new Access(base, 'proto')])
|
805
|
+
if func instanceof Code and func.bound
|
806
|
+
@boundFuncs.push base
|
807
|
+
func.bound = no
|
808
|
+
assign
|
809
|
+
|
810
|
+
# Walk the body of the class, looking for prototype properties to be converted.
|
811
|
+
walkBody: (name) ->
|
812
|
+
@traverseChildren false, (child) =>
|
813
|
+
return false if child instanceof Class
|
814
|
+
if child instanceof Expressions
|
815
|
+
for node, i in exps = child.expressions
|
816
|
+
if node instanceof Value and node.isObject(true)
|
817
|
+
exps[i] = @addProperties node, name
|
818
|
+
child.expressions = exps = flatten exps
|
819
|
+
|
820
|
+
# Make sure that a constructor is defined for the class, and properly
|
821
|
+
# configured.
|
822
|
+
ensureConstructor: (name) ->
|
823
|
+
if not @ctor
|
824
|
+
@ctor = new Code
|
825
|
+
@ctor.body.push new Call 'super', [new Splat new Literal 'arguments'] if @parent
|
826
|
+
@body.expressions.unshift @ctor
|
827
|
+
@ctor.ctor = @ctor.name = name
|
828
|
+
@ctor.klass = null
|
829
|
+
@ctor.noReturn = yes
|
830
|
+
|
831
|
+
# Instead of generating the JavaScript string directly, we build up the
|
832
|
+
# equivalent syntax tree and compile that, in pieces. You can see the
|
833
|
+
# constructor, property assignments, and inheritance getting built out below.
|
834
|
+
compileNode: (o) ->
|
835
|
+
decl = @determineName()
|
836
|
+
name = decl or @name or '_Class'
|
837
|
+
lname = new Literal name
|
838
|
+
|
839
|
+
@setContext name
|
840
|
+
@walkBody name
|
841
|
+
@body.expressions.unshift new Extends lname, @parent if @parent
|
842
|
+
@ensureConstructor name
|
843
|
+
@body.expressions.push lname
|
844
|
+
@addBoundFunctions o
|
845
|
+
|
846
|
+
klass = new Parens Closure.wrap(@body), true
|
847
|
+
klass = new Assign @variable, klass if @variable
|
848
|
+
klass.compile o
|
849
|
+
|
850
|
+
#### Assign
|
851
|
+
|
852
|
+
# The **Assign** is used to assign a local variable to value, or to set the
|
853
|
+
# property of an object -- including within object literals.
|
854
|
+
exports.Assign = class Assign extends Base
|
855
|
+
constructor: (@variable, @value, @context, options) ->
|
856
|
+
@param = options and options.param
|
857
|
+
|
858
|
+
# Matchers for detecting class/method names
|
859
|
+
METHOD_DEF: /^(?:(\S+)\.prototype\.|\S+?)?\b([$A-Za-z_][$\w]*)$/
|
860
|
+
|
861
|
+
children: ['variable', 'value']
|
862
|
+
|
863
|
+
assigns: (name) ->
|
864
|
+
@[if @context is 'object' then 'value' else 'variable'].assigns name
|
865
|
+
|
866
|
+
unfoldSoak: (o) ->
|
867
|
+
unfoldSoak o, this, 'variable'
|
868
|
+
|
869
|
+
# Compile an assignment, delegating to `compilePatternMatch` or
|
870
|
+
# `compileSplice` if appropriate. Keep track of the name of the base object
|
871
|
+
# we've been assigned to, for correct internal references. If the variable
|
872
|
+
# has not been seen yet within the current scope, declare it.
|
873
|
+
compileNode: (o) ->
|
874
|
+
if isValue = @variable instanceof Value
|
875
|
+
return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
|
876
|
+
return @compileSplice o if @variable.isSplice()
|
877
|
+
return @compileConditional o if @context in ['||=', '&&=', '?=']
|
878
|
+
name = @variable.compile o, LEVEL_LIST
|
879
|
+
if @value instanceof Code and match = @METHOD_DEF.exec name
|
880
|
+
@value.name = match[2]
|
881
|
+
@value.klass = match[1] if match[1]
|
882
|
+
val = @value.compile o, LEVEL_LIST
|
883
|
+
return "#{name}: #{val}" if @context is 'object'
|
884
|
+
unless @variable.isAssignable()
|
885
|
+
throw SyntaxError "\"#{ @variable.compile o }\" cannot be assigned."
|
886
|
+
unless @context or isValue and (@variable.namespaced or @variable.hasProperties())
|
887
|
+
if @param
|
888
|
+
o.scope.add name, 'var'
|
889
|
+
else
|
890
|
+
o.scope.find name
|
891
|
+
val = name + " #{ @context or '=' } " + val
|
892
|
+
if o.level <= LEVEL_LIST then val else "(#{val})"
|
893
|
+
|
894
|
+
# Brief implementation of recursive pattern matching, when assigning array or
|
895
|
+
# object literals to a value. Peeks at their properties to assign inner names.
|
896
|
+
# See the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring)
|
897
|
+
# for details.
|
898
|
+
compilePatternMatch: (o) ->
|
899
|
+
top = o.level is LEVEL_TOP
|
900
|
+
{value} = this
|
901
|
+
{objects} = @variable.base
|
902
|
+
return value.compile o unless olen = objects.length
|
903
|
+
isObject = @variable.isObject()
|
904
|
+
if top and olen is 1 and (obj = objects[0]) not instanceof Splat
|
905
|
+
# Unroll simplest cases: `{v} = x` -> `v = x.v`
|
906
|
+
if obj instanceof Assign
|
907
|
+
{variable: {base: idx}, value: obj} = obj
|
908
|
+
else
|
909
|
+
if obj.base instanceof Parens
|
910
|
+
[obj, idx] = new Value(obj.unwrapAll()).cacheReference o
|
911
|
+
else
|
912
|
+
idx = if isObject
|
913
|
+
if obj.this then obj.properties[0].name else obj
|
914
|
+
else
|
915
|
+
new Literal 0
|
916
|
+
acc = IDENTIFIER.test idx.unwrap().value or 0
|
917
|
+
value = new Value value
|
918
|
+
value.properties.push new (if acc then Access else Index) idx
|
919
|
+
return new Assign(obj, value).compile o
|
920
|
+
vvar = value.compile o, LEVEL_LIST
|
921
|
+
assigns = []
|
922
|
+
splat = false
|
923
|
+
if not IDENTIFIER.test(vvar) or @variable.assigns(vvar)
|
924
|
+
assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{vvar}"
|
925
|
+
vvar = ref
|
926
|
+
for obj, i in objects
|
927
|
+
# A regular array pattern-match.
|
928
|
+
idx = i
|
929
|
+
if isObject
|
930
|
+
if obj instanceof Assign
|
931
|
+
# A regular object pattern-match.
|
932
|
+
{variable: {base: idx}, value: obj} = obj
|
933
|
+
else
|
934
|
+
# A shorthand `{a, b, @c} = val` pattern-match.
|
935
|
+
if obj.base instanceof Parens
|
936
|
+
[obj, idx] = new Value(obj.unwrapAll()).cacheReference o
|
937
|
+
else
|
938
|
+
idx = if obj.this then obj.properties[0].name else obj
|
939
|
+
if not splat and obj instanceof Splat
|
940
|
+
val = "#{olen} <= #{vvar}.length ? #{ utility 'slice' }.call(#{vvar}, #{i}"
|
941
|
+
if rest = olen - i - 1
|
942
|
+
ivar = o.scope.freeVariable 'i'
|
943
|
+
val += ", #{ivar} = #{vvar}.length - #{rest}) : (#{ivar} = #{i}, [])"
|
944
|
+
else
|
945
|
+
val += ") : []"
|
946
|
+
val = new Literal val
|
947
|
+
splat = "#{ivar}++"
|
948
|
+
else
|
949
|
+
if obj instanceof Splat
|
950
|
+
obj = obj.name.compile o
|
951
|
+
throw SyntaxError \
|
952
|
+
"multiple splats are disallowed in an assignment: #{obj} ..."
|
953
|
+
if typeof idx is 'number'
|
954
|
+
idx = new Literal splat or idx
|
955
|
+
acc = no
|
956
|
+
else
|
957
|
+
acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
|
958
|
+
val = new Value new Literal(vvar), [new (if acc then Access else Index) idx]
|
959
|
+
assigns.push new Assign(obj, val, null, param: @param).compile o, LEVEL_TOP
|
960
|
+
assigns.push vvar unless top
|
961
|
+
code = assigns.join ', '
|
962
|
+
if o.level < LEVEL_LIST then code else "(#{code})"
|
963
|
+
|
964
|
+
# When compiling a conditional assignment, take care to ensure that the
|
965
|
+
# operands are only evaluated once, even though we have to reference them
|
966
|
+
# more than once.
|
967
|
+
compileConditional: (o) ->
|
968
|
+
[left, rite] = @variable.cacheReference o
|
969
|
+
new Op(@context.slice(0, -1), left, new Assign(rite, @value, '=')).compile o
|
970
|
+
|
971
|
+
# Compile the assignment from an array splice literal, using JavaScript's
|
972
|
+
# `Array#splice` method.
|
973
|
+
compileSplice: (o) ->
|
974
|
+
{range: {from, to, exclusive}} = @variable.properties.pop()
|
975
|
+
name = @variable.compile o
|
976
|
+
[fromDecl, fromRef] = from?.cache(o, LEVEL_OP) or ['0', '0']
|
977
|
+
if to
|
978
|
+
if from?.isSimpleNumber() and to.isSimpleNumber()
|
979
|
+
to = +to.compile(o) - +fromRef
|
980
|
+
to += 1 unless exclusive
|
981
|
+
else
|
982
|
+
to = to.compile(o) + ' - ' + fromRef
|
983
|
+
to += ' + 1' unless exclusive
|
984
|
+
else
|
985
|
+
to = "9e9"
|
986
|
+
[valDef, valRef] = @value.cache o, LEVEL_LIST
|
987
|
+
code = "[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat(#{valDef})), #{valRef}"
|
988
|
+
if o.level > LEVEL_TOP then "(#{code})" else code
|
989
|
+
|
990
|
+
#### Code
|
991
|
+
|
992
|
+
# A function definition. This is the only node that creates a new Scope.
|
993
|
+
# When for the purposes of walking the contents of a function body, the Code
|
994
|
+
# has no *children* -- they're within the inner scope.
|
995
|
+
exports.Code = class Code extends Base
|
996
|
+
constructor: (params, body, tag) ->
|
997
|
+
@params = params or []
|
998
|
+
@body = body or new Expressions
|
999
|
+
@bound = tag is 'boundfunc'
|
1000
|
+
@context = 'this' if @bound
|
1001
|
+
|
1002
|
+
children: ['params', 'body']
|
1003
|
+
|
1004
|
+
isStatement: -> !!@ctor
|
1005
|
+
|
1006
|
+
jumps: NO
|
1007
|
+
|
1008
|
+
# Compilation creates a new scope unless explicitly asked to share with the
|
1009
|
+
# outer scope. Handles splat parameters in the parameter list by peeking at
|
1010
|
+
# the JavaScript `arguments` objects. If the function is bound with the `=>`
|
1011
|
+
# arrow, generates a wrapper that saves the current value of `this` through
|
1012
|
+
# a closure.
|
1013
|
+
compileNode: (o) ->
|
1014
|
+
o.scope = new Scope o.scope, @body, this
|
1015
|
+
o.scope.shared = del o, 'sharedScope'
|
1016
|
+
o.indent += TAB
|
1017
|
+
delete o.bare
|
1018
|
+
delete o.globals
|
1019
|
+
vars = []
|
1020
|
+
exprs = []
|
1021
|
+
for param in @params when param.splat
|
1022
|
+
splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
|
1023
|
+
new Value new Literal 'arguments'
|
1024
|
+
break
|
1025
|
+
for param in @params
|
1026
|
+
if param.isComplex()
|
1027
|
+
val = ref = param.asReference o
|
1028
|
+
val = new Op '?', ref, param.value if param.value
|
1029
|
+
exprs.push new Assign new Value(param.name), val, '=', param: yes
|
1030
|
+
else
|
1031
|
+
ref = param
|
1032
|
+
if param.value
|
1033
|
+
lit = new Literal ref.name.value + ' == null'
|
1034
|
+
val = new Assign new Value(param.name), param.value, '='
|
1035
|
+
exprs.push new If lit, val
|
1036
|
+
vars.push ref unless splats
|
1037
|
+
wasEmpty = @body.isEmpty()
|
1038
|
+
exprs.unshift splats if splats
|
1039
|
+
@body.expressions.unshift exprs... if exprs.length
|
1040
|
+
o.scope.parameter vars[i] = v.compile o for v, i in vars unless splats
|
1041
|
+
@body.makeReturn() unless wasEmpty or @noReturn
|
1042
|
+
idt = o.indent
|
1043
|
+
code = 'function'
|
1044
|
+
code += ' ' + @name if @ctor
|
1045
|
+
code += '(' + vars.join(', ') + ') {'
|
1046
|
+
code += "\n#{ @body.compileWithDeclarations o }\n#{@tab}" unless @body.isEmpty()
|
1047
|
+
code += '}'
|
1048
|
+
return @tab + code if @ctor
|
1049
|
+
return utility('bind') + "(#{code}, #{@context})" if @bound
|
1050
|
+
if @front or (o.level >= LEVEL_ACCESS) then "(#{code})" else code
|
1051
|
+
|
1052
|
+
# Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries
|
1053
|
+
# unless `crossScope` is `true`.
|
1054
|
+
traverseChildren: (crossScope, func) ->
|
1055
|
+
super(crossScope, func) if crossScope
|
1056
|
+
|
1057
|
+
#### Param
|
1058
|
+
|
1059
|
+
# A parameter in a function definition. Beyond a typical Javascript parameter,
|
1060
|
+
# these parameters can also attach themselves to the context of the function,
|
1061
|
+
# as well as be a splat, gathering up a group of parameters into an array.
|
1062
|
+
exports.Param = class Param extends Base
|
1063
|
+
constructor: (@name, @value, @splat) ->
|
1064
|
+
|
1065
|
+
children: ['name', 'value']
|
1066
|
+
|
1067
|
+
compile: (o) ->
|
1068
|
+
@name.compile o, LEVEL_LIST
|
1069
|
+
|
1070
|
+
asReference: (o) ->
|
1071
|
+
return @reference if @reference
|
1072
|
+
node = @name
|
1073
|
+
if node.this
|
1074
|
+
node = node.properties[0].name
|
1075
|
+
node = new Literal '_' + node.value if node.value.reserved
|
1076
|
+
else if node.isComplex()
|
1077
|
+
node = new Literal o.scope.freeVariable 'arg'
|
1078
|
+
node = new Value node
|
1079
|
+
node = new Splat node if @splat
|
1080
|
+
@reference = node
|
1081
|
+
|
1082
|
+
isComplex: ->
|
1083
|
+
@name.isComplex()
|
1084
|
+
|
1085
|
+
#### Splat
|
1086
|
+
|
1087
|
+
# A splat, either as a parameter to a function, an argument to a call,
|
1088
|
+
# or as part of a destructuring assignment.
|
1089
|
+
exports.Splat = class Splat extends Base
|
1090
|
+
|
1091
|
+
children: ['name']
|
1092
|
+
|
1093
|
+
isAssignable: YES
|
1094
|
+
|
1095
|
+
constructor: (name) ->
|
1096
|
+
@name = if name.compile then name else new Literal name
|
1097
|
+
|
1098
|
+
assigns: (name) ->
|
1099
|
+
@name.assigns name
|
1100
|
+
|
1101
|
+
compile: (o) ->
|
1102
|
+
if @index? then @compileParam o else @name.compile o
|
1103
|
+
|
1104
|
+
# Utility function that converts arbitrary number of elements, mixed with
|
1105
|
+
# splats, to a proper array.
|
1106
|
+
@compileSplattedArray: (o, list, apply) ->
|
1107
|
+
index = -1
|
1108
|
+
continue while (node = list[++index]) and node not instanceof Splat
|
1109
|
+
return '' if index >= list.length
|
1110
|
+
if list.length is 1
|
1111
|
+
code = list[0].compile o, LEVEL_LIST
|
1112
|
+
return code if apply
|
1113
|
+
return "#{ utility 'slice' }.call(#{code})"
|
1114
|
+
args = list.slice index
|
1115
|
+
for node, i in args
|
1116
|
+
code = node.compile o, LEVEL_LIST
|
1117
|
+
args[i] = if node instanceof Splat
|
1118
|
+
then "#{ utility 'slice' }.call(#{code})"
|
1119
|
+
else "[#{code}]"
|
1120
|
+
return args[0] + ".concat(#{ args.slice(1).join ', ' })" if index is 0
|
1121
|
+
base = (node.compile o, LEVEL_LIST for node in list.slice 0, index)
|
1122
|
+
"[#{ base.join ', ' }].concat(#{ args.join ', ' })"
|
1123
|
+
|
1124
|
+
#### While
|
1125
|
+
|
1126
|
+
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
|
1127
|
+
# it, all other loops can be manufactured. Useful in cases where you need more
|
1128
|
+
# flexibility or more speed than a comprehension can provide.
|
1129
|
+
exports.While = class While extends Base
|
1130
|
+
constructor: (condition, options) ->
|
1131
|
+
@condition = if options?.invert then condition.invert() else condition
|
1132
|
+
@guard = options?.guard
|
1133
|
+
|
1134
|
+
children: ['condition', 'guard', 'body']
|
1135
|
+
|
1136
|
+
isStatement: YES
|
1137
|
+
|
1138
|
+
makeReturn: ->
|
1139
|
+
@returns = yes
|
1140
|
+
this
|
1141
|
+
|
1142
|
+
addBody: (@body) ->
|
1143
|
+
this
|
1144
|
+
|
1145
|
+
jumps: ->
|
1146
|
+
{expressions} = @body
|
1147
|
+
return no unless expressions.length
|
1148
|
+
for node in expressions
|
1149
|
+
return node if node.jumps loop: yes
|
1150
|
+
no
|
1151
|
+
|
1152
|
+
# The main difference from a JavaScript *while* is that the CoffeeScript
|
1153
|
+
# *while* can be used as a part of a larger expression -- while loops may
|
1154
|
+
# return an array containing the computed result of each iteration.
|
1155
|
+
compileNode: (o) ->
|
1156
|
+
o.indent += TAB
|
1157
|
+
set = ''
|
1158
|
+
{body} = this
|
1159
|
+
if body.isEmpty()
|
1160
|
+
body = ''
|
1161
|
+
else
|
1162
|
+
if o.level > LEVEL_TOP or @returns
|
1163
|
+
rvar = o.scope.freeVariable 'results'
|
1164
|
+
set = "#{@tab}#{rvar} = [];\n"
|
1165
|
+
body = Push.wrap rvar, body if body
|
1166
|
+
body = Expressions.wrap [new If @guard, body] if @guard
|
1167
|
+
body = "\n#{ body.compile o, LEVEL_TOP }\n#{@tab}"
|
1168
|
+
code = set + @tab + "while (#{ @condition.compile o, LEVEL_PAREN }) {#{body}}"
|
1169
|
+
if @returns
|
1170
|
+
code += "\n#{@tab}return #{rvar};"
|
1171
|
+
code
|
1172
|
+
|
1173
|
+
#### Op
|
1174
|
+
|
1175
|
+
# Simple Arithmetic and logical operations. Performs some conversion from
|
1176
|
+
# CoffeeScript operations into their JavaScript equivalents.
|
1177
|
+
exports.Op = class Op extends Base
|
1178
|
+
constructor: (op, first, second, flip) ->
|
1179
|
+
return new In first, second if op is 'in'
|
1180
|
+
return new Call first, first.params or [] if op is 'do'
|
1181
|
+
if op is 'new'
|
1182
|
+
return first.newInstance() if first instanceof Call
|
1183
|
+
first = new Parens first if first instanceof Code and first.bound
|
1184
|
+
@operator = CONVERSIONS[op] or op
|
1185
|
+
@first = first
|
1186
|
+
@second = second
|
1187
|
+
@flip = !!flip
|
1188
|
+
return this
|
1189
|
+
|
1190
|
+
# The map of conversions from CoffeeScript to JavaScript symbols.
|
1191
|
+
CONVERSIONS =
|
1192
|
+
'==': '==='
|
1193
|
+
'!=': '!=='
|
1194
|
+
'of': 'in'
|
1195
|
+
|
1196
|
+
# The map of invertible operators.
|
1197
|
+
INVERSIONS =
|
1198
|
+
'!==': '==='
|
1199
|
+
'===': '!=='
|
1200
|
+
|
1201
|
+
children: ['first', 'second']
|
1202
|
+
|
1203
|
+
isSimpleNumber: NO
|
1204
|
+
|
1205
|
+
isUnary: ->
|
1206
|
+
not @second
|
1207
|
+
|
1208
|
+
# Am I capable of
|
1209
|
+
# [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin)?
|
1210
|
+
isChainable: ->
|
1211
|
+
@operator in ['<', '>', '>=', '<=', '===', '!==']
|
1212
|
+
|
1213
|
+
invert: ->
|
1214
|
+
if @isChainable() and @first.isChainable()
|
1215
|
+
allInvertable = yes
|
1216
|
+
curr = this
|
1217
|
+
while curr and curr.operator
|
1218
|
+
allInvertable and= (curr.operator of INVERSIONS)
|
1219
|
+
curr = curr.first
|
1220
|
+
return new Parens(this).invert() unless allInvertable
|
1221
|
+
curr = this
|
1222
|
+
while curr and curr.operator
|
1223
|
+
curr.invert = !curr.invert
|
1224
|
+
curr.operator = INVERSIONS[curr.operator]
|
1225
|
+
curr = curr.first
|
1226
|
+
this
|
1227
|
+
else if op = INVERSIONS[@operator]
|
1228
|
+
@operator = op
|
1229
|
+
if @first.unwrap() instanceof Op
|
1230
|
+
@first.invert()
|
1231
|
+
this
|
1232
|
+
else if @second
|
1233
|
+
new Parens(this).invert()
|
1234
|
+
else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
|
1235
|
+
fst.operator in ['!', 'in', 'instanceof']
|
1236
|
+
fst
|
1237
|
+
else
|
1238
|
+
new Op '!', this
|
1239
|
+
|
1240
|
+
unfoldSoak: (o) ->
|
1241
|
+
@operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
|
1242
|
+
|
1243
|
+
compileNode: (o) ->
|
1244
|
+
return @compileUnary o if @isUnary()
|
1245
|
+
return @compileChain o if @isChainable() and @first.isChainable()
|
1246
|
+
return @compileExistence o if @operator is '?'
|
1247
|
+
@first.front = @front
|
1248
|
+
code = @first.compile(o, LEVEL_OP) + ' ' + @operator + ' ' +
|
1249
|
+
@second.compile(o, LEVEL_OP)
|
1250
|
+
if o.level <= LEVEL_OP then code else "(#{code})"
|
1251
|
+
|
1252
|
+
# Mimic Python's chained comparisons when multiple comparison operators are
|
1253
|
+
# used sequentially. For example:
|
1254
|
+
#
|
1255
|
+
# bin/coffee -e 'console.log 50 < 65 > 10'
|
1256
|
+
# true
|
1257
|
+
compileChain: (o) ->
|
1258
|
+
[@first.second, shared] = @first.second.cache o
|
1259
|
+
fst = @first.compile o, LEVEL_OP
|
1260
|
+
code = "#{fst} #{if @invert then '&&' else '||'} #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL_OP }"
|
1261
|
+
"(#{code})"
|
1262
|
+
|
1263
|
+
compileExistence: (o) ->
|
1264
|
+
if @first.isComplex()
|
1265
|
+
ref = o.scope.freeVariable 'ref'
|
1266
|
+
fst = new Parens new Assign new Literal(ref), @first
|
1267
|
+
else
|
1268
|
+
fst = @first
|
1269
|
+
ref = fst.compile o
|
1270
|
+
new Existence(fst).compile(o) + " ? #{ref} : #{ @second.compile o, LEVEL_LIST }"
|
1271
|
+
|
1272
|
+
# Compile a unary **Op**.
|
1273
|
+
compileUnary: (o) ->
|
1274
|
+
parts = [op = @operator]
|
1275
|
+
parts.push ' ' if op in ['new', 'typeof', 'delete'] or
|
1276
|
+
op in ['+', '-'] and @first instanceof Op and @first.operator is op
|
1277
|
+
parts.push @first.compile o, LEVEL_OP
|
1278
|
+
parts.reverse() if @flip
|
1279
|
+
parts.join ''
|
1280
|
+
|
1281
|
+
toString: (idt) ->
|
1282
|
+
super idt, @constructor.name + ' ' + @operator
|
1283
|
+
|
1284
|
+
#### In
|
1285
|
+
exports.In = class In extends Base
|
1286
|
+
constructor: (@object, @array) ->
|
1287
|
+
|
1288
|
+
children: ['object', 'array']
|
1289
|
+
|
1290
|
+
invert: NEGATE
|
1291
|
+
|
1292
|
+
compileNode: (o) ->
|
1293
|
+
if @array instanceof Value and @array.isArray()
|
1294
|
+
@compileOrTest o
|
1295
|
+
else
|
1296
|
+
@compileLoopTest o
|
1297
|
+
|
1298
|
+
compileOrTest: (o) ->
|
1299
|
+
[sub, ref] = @object.cache o, LEVEL_OP
|
1300
|
+
[cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
|
1301
|
+
tests = for item, i in @array.base.objects
|
1302
|
+
(if i then ref else sub) + cmp + item.compile o, LEVEL_OP
|
1303
|
+
tests = tests.join cnj
|
1304
|
+
if o.level < LEVEL_OP then tests else "(#{tests})"
|
1305
|
+
|
1306
|
+
compileLoopTest: (o) ->
|
1307
|
+
[sub, ref] = @object.cache o, LEVEL_LIST
|
1308
|
+
code = utility('indexOf') + ".call(#{ @array.compile o, LEVEL_LIST }, #{ref}) " +
|
1309
|
+
if @negated then '< 0' else '>= 0'
|
1310
|
+
return code if sub is ref
|
1311
|
+
code = sub + ', ' + code
|
1312
|
+
if o.level < LEVEL_LIST then code else "(#{code})"
|
1313
|
+
|
1314
|
+
toString: (idt) ->
|
1315
|
+
super idt, @constructor.name + if @negated then '!' else ''
|
1316
|
+
|
1317
|
+
#### Try
|
1318
|
+
|
1319
|
+
# A classic *try/catch/finally* block.
|
1320
|
+
exports.Try = class Try extends Base
|
1321
|
+
constructor: (@attempt, @error, @recovery, @ensure) ->
|
1322
|
+
|
1323
|
+
children: ['attempt', 'recovery', 'ensure']
|
1324
|
+
|
1325
|
+
isStatement: YES
|
1326
|
+
|
1327
|
+
jumps: (o) -> @attempt.jumps(o) or @recovery?.jumps(o)
|
1328
|
+
|
1329
|
+
makeReturn: ->
|
1330
|
+
@attempt = @attempt .makeReturn() if @attempt
|
1331
|
+
@recovery = @recovery.makeReturn() if @recovery
|
1332
|
+
this
|
1333
|
+
|
1334
|
+
# Compilation is more or less as you would expect -- the *finally* clause
|
1335
|
+
# is optional, the *catch* is not.
|
1336
|
+
compileNode: (o) ->
|
1337
|
+
o.indent += TAB
|
1338
|
+
errorPart = if @error then " (#{ @error.compile o }) " else ' '
|
1339
|
+
catchPart = if @recovery
|
1340
|
+
" catch#{errorPart}{\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}}"
|
1341
|
+
else unless @ensure or @recovery
|
1342
|
+
' catch (_e) {}'
|
1343
|
+
"""
|
1344
|
+
#{@tab}try {
|
1345
|
+
#{ @attempt.compile o, LEVEL_TOP }
|
1346
|
+
#{@tab}}#{ catchPart or '' }
|
1347
|
+
""" + if @ensure then " finally {\n#{ @ensure.compile o, LEVEL_TOP }\n#{@tab}}" else ''
|
1348
|
+
|
1349
|
+
#### Throw
|
1350
|
+
|
1351
|
+
# Simple node to throw an exception.
|
1352
|
+
exports.Throw = class Throw extends Base
|
1353
|
+
constructor: (@expression) ->
|
1354
|
+
|
1355
|
+
children: ['expression']
|
1356
|
+
|
1357
|
+
isStatement: YES
|
1358
|
+
jumps: NO
|
1359
|
+
|
1360
|
+
# A **Throw** is already a return, of sorts...
|
1361
|
+
makeReturn: THIS
|
1362
|
+
|
1363
|
+
compileNode: (o) ->
|
1364
|
+
@tab + "throw #{ @expression.compile o };"
|
1365
|
+
|
1366
|
+
#### Existence
|
1367
|
+
|
1368
|
+
# Checks a variable for existence -- not *null* and not *undefined*. This is
|
1369
|
+
# similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth
|
1370
|
+
# table.
|
1371
|
+
exports.Existence = class Existence extends Base
|
1372
|
+
constructor: (@expression) ->
|
1373
|
+
|
1374
|
+
children: ['expression']
|
1375
|
+
|
1376
|
+
invert: NEGATE
|
1377
|
+
|
1378
|
+
compileNode: (o) ->
|
1379
|
+
code = @expression.compile o, LEVEL_OP
|
1380
|
+
code = if IDENTIFIER.test(code) and not o.scope.check code
|
1381
|
+
if @negated
|
1382
|
+
"typeof #{code} == \"undefined\" || #{code} === null"
|
1383
|
+
else
|
1384
|
+
"typeof #{code} != \"undefined\" && #{code} !== null"
|
1385
|
+
else
|
1386
|
+
sym = if @negated then '==' else '!='
|
1387
|
+
"#{code} #{sym} null"
|
1388
|
+
if o.level <= LEVEL_COND then code else "(#{code})"
|
1389
|
+
|
1390
|
+
#### Parens
|
1391
|
+
|
1392
|
+
# An extra set of parentheses, specified explicitly in the source. At one time
|
1393
|
+
# we tried to clean up the results by detecting and removing redundant
|
1394
|
+
# parentheses, but no longer -- you can put in as many as you please.
|
1395
|
+
#
|
1396
|
+
# Parentheses are a good way to force any statement to become an expression.
|
1397
|
+
exports.Parens = class Parens extends Base
|
1398
|
+
constructor: (@body) ->
|
1399
|
+
|
1400
|
+
children: ['body']
|
1401
|
+
|
1402
|
+
unwrap : -> @body
|
1403
|
+
isComplex : -> @body.isComplex()
|
1404
|
+
makeReturn: -> @body.makeReturn()
|
1405
|
+
|
1406
|
+
compileNode: (o) ->
|
1407
|
+
expr = @body.unwrap()
|
1408
|
+
if expr instanceof Value and expr.isAtomic()
|
1409
|
+
expr.front = @front
|
1410
|
+
return expr.compile o
|
1411
|
+
code = expr.compile o, LEVEL_PAREN
|
1412
|
+
bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
|
1413
|
+
(expr instanceof For and expr.returns))
|
1414
|
+
if bare then code else "(#{code})"
|
1415
|
+
|
1416
|
+
#### For
|
1417
|
+
|
1418
|
+
# CoffeeScript's replacement for the *for* loop is our array and object
|
1419
|
+
# comprehensions, that compile into *for* loops here. They also act as an
|
1420
|
+
# expression, able to return the result of each filtered iteration.
|
1421
|
+
#
|
1422
|
+
# Unlike Python array comprehensions, they can be multi-line, and you can pass
|
1423
|
+
# the current index of the loop as a second parameter. Unlike Ruby blocks,
|
1424
|
+
# you can map and filter in a single pass.
|
1425
|
+
exports.For = class For extends Base
|
1426
|
+
constructor: (body, source) ->
|
1427
|
+
{@source, @guard, @step, @name, @index} = source
|
1428
|
+
@body = Expressions.wrap [body]
|
1429
|
+
@own = !!source.own
|
1430
|
+
@object = !!source.object
|
1431
|
+
[@name, @index] = [@index, @name] if @object
|
1432
|
+
throw SyntaxError 'index cannot be a pattern matching expression' if @index instanceof Value
|
1433
|
+
@range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length
|
1434
|
+
@pattern = @name instanceof Value
|
1435
|
+
throw SyntaxError 'indexes do not apply to range loops' if @range and @index
|
1436
|
+
throw SyntaxError 'cannot pattern match over range loops' if @range and @pattern
|
1437
|
+
@returns = false
|
1438
|
+
|
1439
|
+
children: ['body', 'source', 'guard', 'step']
|
1440
|
+
|
1441
|
+
isStatement: YES
|
1442
|
+
|
1443
|
+
jumps: While::jumps
|
1444
|
+
|
1445
|
+
makeReturn: ->
|
1446
|
+
@returns = yes
|
1447
|
+
this
|
1448
|
+
|
1449
|
+
# Welcome to the hairiest method in all of CoffeeScript. Handles the inner
|
1450
|
+
# loop, filtering, stepping, and result saving for array, object, and range
|
1451
|
+
# comprehensions. Some of the generated code can be shared in common, and
|
1452
|
+
# some cannot.
|
1453
|
+
compileNode: (o) ->
|
1454
|
+
body = Expressions.wrap [@body]
|
1455
|
+
lastJumps = last(body.expressions)?.jumps()
|
1456
|
+
@returns = no if lastJumps and lastJumps instanceof Return
|
1457
|
+
source = if @range then @source.base else @source
|
1458
|
+
scope = o.scope
|
1459
|
+
name = @name and @name.compile o, LEVEL_LIST
|
1460
|
+
index = @index and @index.compile o, LEVEL_LIST
|
1461
|
+
scope.find(name, immediate: yes) if name and not @pattern
|
1462
|
+
scope.find(index, immediate: yes) if index
|
1463
|
+
rvar = scope.freeVariable 'results' if @returns
|
1464
|
+
ivar = (if @range then name else index) or scope.freeVariable 'i'
|
1465
|
+
name = ivar if @pattern
|
1466
|
+
varPart = ''
|
1467
|
+
guardPart = ''
|
1468
|
+
defPart = ''
|
1469
|
+
idt1 = @tab + TAB
|
1470
|
+
if @range
|
1471
|
+
forPart = source.compile merge(o, {index: ivar, @step})
|
1472
|
+
else
|
1473
|
+
svar = @source.compile o, LEVEL_LIST
|
1474
|
+
if (name or @own) and not IDENTIFIER.test svar
|
1475
|
+
defPart = "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
|
1476
|
+
svar = ref
|
1477
|
+
if name and not @pattern
|
1478
|
+
namePart = "#{name} = #{svar}[#{ivar}]"
|
1479
|
+
unless @object
|
1480
|
+
lvar = scope.freeVariable 'len'
|
1481
|
+
stepPart = if @step then "#{ivar} += #{ @step.compile(o, LEVEL_OP) }" else "#{ivar}++"
|
1482
|
+
forPart = "#{ivar} = 0, #{lvar} = #{svar}.length; #{ivar} < #{lvar}; #{stepPart}"
|
1483
|
+
if @returns
|
1484
|
+
resultPart = "#{@tab}#{rvar} = [];\n"
|
1485
|
+
returnResult = "\n#{@tab}return #{rvar};"
|
1486
|
+
body = Push.wrap rvar, body
|
1487
|
+
if @guard
|
1488
|
+
body = Expressions.wrap [new If @guard, body]
|
1489
|
+
if @pattern
|
1490
|
+
body.expressions.unshift new Assign @name, new Literal "#{svar}[#{ivar}]"
|
1491
|
+
defPart += @pluckDirectCall o, body
|
1492
|
+
varPart = "\n#{idt1}#{namePart};" if namePart
|
1493
|
+
if @object
|
1494
|
+
forPart = "#{ivar} in #{svar}"
|
1495
|
+
guardPart = "\n#{idt1}if (!#{utility('hasProp')}.call(#{svar}, #{ivar})) continue;" if @own
|
1496
|
+
body = body.compile merge(o, indent: idt1), LEVEL_TOP
|
1497
|
+
body = '\n' + body + '\n' if body
|
1498
|
+
"""
|
1499
|
+
#{defPart}#{resultPart or ''}#{@tab}for (#{forPart}) {#{guardPart}#{varPart}#{body}#{@tab}}#{returnResult or ''}
|
1500
|
+
"""
|
1501
|
+
|
1502
|
+
pluckDirectCall: (o, body) ->
|
1503
|
+
defs = ''
|
1504
|
+
for expr, idx in body.expressions
|
1505
|
+
expr = expr.unwrapAll()
|
1506
|
+
continue unless expr instanceof Call
|
1507
|
+
val = expr.variable.unwrapAll()
|
1508
|
+
continue unless (val instanceof Code) or
|
1509
|
+
(val instanceof Value and
|
1510
|
+
val.base?.unwrapAll() instanceof Code and
|
1511
|
+
val.properties.length is 1 and
|
1512
|
+
val.properties[0].name?.value in ['call', 'apply'])
|
1513
|
+
fn = val.base?.unwrapAll() or val
|
1514
|
+
ref = new Literal o.scope.freeVariable 'fn'
|
1515
|
+
base = new Value ref
|
1516
|
+
if val.base
|
1517
|
+
[val.base, base] = [base, val]
|
1518
|
+
args.unshift new Literal 'this'
|
1519
|
+
body.expressions[idx] = new Call base, expr.args
|
1520
|
+
defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n'
|
1521
|
+
defs
|
1522
|
+
|
1523
|
+
#### Switch
|
1524
|
+
|
1525
|
+
# A JavaScript *switch* statement. Converts into a returnable expression on-demand.
|
1526
|
+
exports.Switch = class Switch extends Base
|
1527
|
+
constructor: (@subject, @cases, @otherwise) ->
|
1528
|
+
|
1529
|
+
children: ['subject', 'cases', 'otherwise']
|
1530
|
+
|
1531
|
+
isStatement: YES
|
1532
|
+
|
1533
|
+
jumps: (o = {block: yes}) ->
|
1534
|
+
for [conds, block] in @cases
|
1535
|
+
return block if block.jumps o
|
1536
|
+
@otherwise?.jumps o
|
1537
|
+
|
1538
|
+
makeReturn: ->
|
1539
|
+
pair[1].makeReturn() for pair in @cases
|
1540
|
+
@otherwise?.makeReturn()
|
1541
|
+
this
|
1542
|
+
|
1543
|
+
compileNode: (o) ->
|
1544
|
+
idt1 = o.indent + TAB
|
1545
|
+
idt2 = o.indent = idt1 + TAB
|
1546
|
+
code = @tab + "switch (#{ @subject?.compile(o, LEVEL_PAREN) or false }) {\n"
|
1547
|
+
for [conditions, block], i in @cases
|
1548
|
+
for cond in flatten [conditions]
|
1549
|
+
cond = cond.invert() unless @subject
|
1550
|
+
code += idt1 + "case #{ cond.compile o, LEVEL_PAREN }:\n"
|
1551
|
+
code += body + '\n' if body = block.compile o, LEVEL_TOP
|
1552
|
+
break if i is @cases.length - 1 and not @otherwise
|
1553
|
+
expr = @lastNonComment block.expressions
|
1554
|
+
jumper = expr.jumps()
|
1555
|
+
if not expr or not jumper or (jumper instanceof Literal and jumper.value is 'debugger')
|
1556
|
+
code += idt2 + 'break;\n'
|
1557
|
+
code += idt1 + "default:\n#{ @otherwise.compile o, LEVEL_TOP }\n" if @otherwise and @otherwise.expressions.length
|
1558
|
+
code + @tab + '}'
|
1559
|
+
|
1560
|
+
#### If
|
1561
|
+
|
1562
|
+
# *If/else* statements. Acts as an expression by pushing down requested returns
|
1563
|
+
# to the last line of each clause.
|
1564
|
+
#
|
1565
|
+
# Single-expression **Ifs** are compiled into conditional operators if possible,
|
1566
|
+
# because ternaries are already proper expressions, and don't need conversion.
|
1567
|
+
exports.If = class If extends Base
|
1568
|
+
constructor: (condition, @body, options = {}) ->
|
1569
|
+
@condition = if options.type is 'unless' then condition.invert() else condition
|
1570
|
+
@elseBody = null
|
1571
|
+
@isChain = false
|
1572
|
+
{@soak} = options
|
1573
|
+
|
1574
|
+
children: ['condition', 'body', 'elseBody']
|
1575
|
+
|
1576
|
+
bodyNode: -> @body?.unwrap()
|
1577
|
+
elseBodyNode: -> @elseBody?.unwrap()
|
1578
|
+
|
1579
|
+
# Rewrite a chain of **Ifs** to add a default case as the final *else*.
|
1580
|
+
addElse: (elseBody) ->
|
1581
|
+
if @isChain
|
1582
|
+
@elseBodyNode().addElse elseBody
|
1583
|
+
else
|
1584
|
+
@isChain = elseBody instanceof If
|
1585
|
+
@elseBody = @ensureExpressions elseBody
|
1586
|
+
this
|
1587
|
+
|
1588
|
+
# The **If** only compiles into a statement if either of its bodies needs
|
1589
|
+
# to be a statement. Otherwise a conditional operator is safe.
|
1590
|
+
isStatement: (o) ->
|
1591
|
+
o?.level is LEVEL_TOP or
|
1592
|
+
@bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
|
1593
|
+
|
1594
|
+
jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
|
1595
|
+
|
1596
|
+
compileNode: (o) ->
|
1597
|
+
if @isStatement o then @compileStatement o else @compileExpression o
|
1598
|
+
|
1599
|
+
makeReturn: ->
|
1600
|
+
@body and= new Expressions [@body.makeReturn()]
|
1601
|
+
@elseBody and= new Expressions [@elseBody.makeReturn()]
|
1602
|
+
this
|
1603
|
+
|
1604
|
+
ensureExpressions: (node) ->
|
1605
|
+
if node instanceof Expressions then node else new Expressions [node]
|
1606
|
+
|
1607
|
+
# Compile the **If** as a regular *if-else* statement. Flattened chains
|
1608
|
+
# force inner *else* bodies into statement form.
|
1609
|
+
compileStatement: (o) ->
|
1610
|
+
child = del o, 'chainChild'
|
1611
|
+
cond = @condition.compile o, LEVEL_PAREN
|
1612
|
+
o.indent += TAB
|
1613
|
+
body = @ensureExpressions(@body).compile o
|
1614
|
+
body = "\n#{body}\n#{@tab}" if body
|
1615
|
+
ifPart = "if (#{cond}) {#{body}}"
|
1616
|
+
ifPart = @tab + ifPart unless child
|
1617
|
+
return ifPart unless @elseBody
|
1618
|
+
ifPart + ' else ' + if @isChain
|
1619
|
+
o.indent = @tab
|
1620
|
+
o.chainChild = yes
|
1621
|
+
@elseBody.unwrap().compile o, LEVEL_TOP
|
1622
|
+
else
|
1623
|
+
"{\n#{ @elseBody.compile o, LEVEL_TOP }\n#{@tab}}"
|
1624
|
+
|
1625
|
+
# Compile the If as a conditional operator.
|
1626
|
+
compileExpression: (o) ->
|
1627
|
+
cond = @condition.compile o, LEVEL_COND
|
1628
|
+
body = @bodyNode().compile o, LEVEL_LIST
|
1629
|
+
alt = if @elseBodyNode() then @elseBodyNode().compile(o, LEVEL_LIST) else 'void 0'
|
1630
|
+
code = "#{cond} ? #{body} : #{alt}"
|
1631
|
+
if o.level >= LEVEL_COND then "(#{code})" else code
|
1632
|
+
|
1633
|
+
unfoldSoak: ->
|
1634
|
+
@soak and this
|
1635
|
+
|
1636
|
+
# Faux-Nodes
|
1637
|
+
# ----------
|
1638
|
+
# Faux-nodes are never created by the grammar, but are used during code
|
1639
|
+
# generation to generate other combinations of nodes.
|
1640
|
+
|
1641
|
+
#### Push
|
1642
|
+
|
1643
|
+
# The **Push** creates the tree for `array.push(value)`,
|
1644
|
+
# which is helpful for recording the result arrays from comprehensions.
|
1645
|
+
Push =
|
1646
|
+
wrap: (name, exps) ->
|
1647
|
+
return exps if exps.isEmpty() or last(exps.expressions).jumps()
|
1648
|
+
exps.push new Call new Value(new Literal(name), [new Access new Literal 'push']), [exps.pop()]
|
1649
|
+
|
1650
|
+
#### Closure
|
1651
|
+
|
1652
|
+
# A faux-node used to wrap an expressions body in a closure.
|
1653
|
+
Closure =
|
1654
|
+
|
1655
|
+
# Wrap the expressions body, unless it contains a pure statement,
|
1656
|
+
# in which case, no dice. If the body mentions `this` or `arguments`,
|
1657
|
+
# then make sure that the closure wrapper preserves the original values.
|
1658
|
+
wrap: (expressions, statement, noReturn) ->
|
1659
|
+
return expressions if expressions.jumps()
|
1660
|
+
func = new Code [], Expressions.wrap [expressions]
|
1661
|
+
args = []
|
1662
|
+
if (mentionsArgs = expressions.contains @literalArgs) or
|
1663
|
+
( expressions.contains @literalThis)
|
1664
|
+
meth = new Literal if mentionsArgs then 'apply' else 'call'
|
1665
|
+
args = [new Literal 'this']
|
1666
|
+
args.push new Literal 'arguments' if mentionsArgs
|
1667
|
+
func = new Value func, [new Access meth]
|
1668
|
+
func.noReturn = noReturn
|
1669
|
+
call = new Call func, args
|
1670
|
+
if statement then Expressions.wrap [call] else call
|
1671
|
+
|
1672
|
+
literalArgs: (node) ->
|
1673
|
+
node instanceof Literal and node.value is 'arguments' and not node.asKey
|
1674
|
+
literalThis: (node) ->
|
1675
|
+
(node instanceof Literal and node.value is 'this' and not node.asKey) or
|
1676
|
+
(node instanceof Code and node.bound)
|
1677
|
+
|
1678
|
+
# Unfold a node's child if soak, then tuck the node under created `If`
|
1679
|
+
unfoldSoak = (o, parent, name) ->
|
1680
|
+
return unless ifn = parent[name].unfoldSoak o
|
1681
|
+
parent[name] = ifn.body
|
1682
|
+
ifn.body = new Value parent
|
1683
|
+
ifn
|
1684
|
+
|
1685
|
+
# Constants
|
1686
|
+
# ---------
|
1687
|
+
|
1688
|
+
UTILITIES =
|
1689
|
+
|
1690
|
+
# Correctly set up a prototype chain for inheritance, including a reference
|
1691
|
+
# to the superclass for `super()` calls, and copies of any static properties.
|
1692
|
+
extends: '''
|
1693
|
+
function(child, parent) {
|
1694
|
+
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
1695
|
+
function ctor() { this.constructor = child; }
|
1696
|
+
ctor.prototype = parent.prototype;
|
1697
|
+
child.prototype = new ctor;
|
1698
|
+
child.__super__ = parent.prototype;
|
1699
|
+
return child;
|
1700
|
+
}
|
1701
|
+
'''
|
1702
|
+
|
1703
|
+
# Create a function bound to the current value of "this".
|
1704
|
+
bind: '''
|
1705
|
+
function(fn, me){ return function(){ return fn.apply(me, arguments); }; }
|
1706
|
+
'''
|
1707
|
+
|
1708
|
+
# Discover if an item is in an array.
|
1709
|
+
indexOf: '''
|
1710
|
+
Array.prototype.indexOf || function(item) {
|
1711
|
+
for (var i = 0, l = this.length; i < l; i++) {
|
1712
|
+
if (this[i] === item) return i;
|
1713
|
+
}
|
1714
|
+
return -1;
|
1715
|
+
}
|
1716
|
+
'''
|
1717
|
+
|
1718
|
+
# Shortcuts to speed up the lookup time for native functions.
|
1719
|
+
hasProp: 'Object.prototype.hasOwnProperty'
|
1720
|
+
slice : 'Array.prototype.slice'
|
1721
|
+
|
1722
|
+
# Levels indicates a node's position in the AST. Useful for knowing if
|
1723
|
+
# parens are necessary or superfluous.
|
1724
|
+
LEVEL_TOP = 1 # ...;
|
1725
|
+
LEVEL_PAREN = 2 # (...)
|
1726
|
+
LEVEL_LIST = 3 # [...]
|
1727
|
+
LEVEL_COND = 4 # ... ? x : y
|
1728
|
+
LEVEL_OP = 5 # !...
|
1729
|
+
LEVEL_ACCESS = 6 # ...[0]
|
1730
|
+
|
1731
|
+
# Tabs are two spaces for pretty printing.
|
1732
|
+
TAB = ' '
|
1733
|
+
|
1734
|
+
# Trim out all trailing whitespace, so that the generated code plays nice
|
1735
|
+
# with Git.
|
1736
|
+
TRAILING_WHITESPACE = /[ \t]+$/gm
|
1737
|
+
|
1738
|
+
IDENTIFIER = /^[$A-Za-z_][$\w]*$/
|
1739
|
+
SIMPLENUM = /^[+-]?\d+$/
|
1740
|
+
|
1741
|
+
# Is a literal value a string?
|
1742
|
+
IS_STRING = /^['"]/
|
1743
|
+
|
1744
|
+
# Utility Functions
|
1745
|
+
# -----------------
|
1746
|
+
|
1747
|
+
# Helper for ensuring that utility functions are assigned at the top level.
|
1748
|
+
utility = (name) ->
|
1749
|
+
ref = "__#{name}"
|
1750
|
+
Scope.root.assign ref, UTILITIES[name]
|
1751
|
+
ref
|
1752
|
+
|
1753
|
+
multident = (code, tab) ->
|
1754
|
+
code.replace /\n/g, '$&' + tab
|