From a3e1693a752d4b021aa4a1dd8d8d8e85a91b5039 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 7 Mar 2010 19:07:37 -0500 Subject: [PATCH] waypoint -- docc'd down to the SplatNode --- Cakefile | 8 +- documentation/docs/nodes.html | 158 ++++++++++++++++++---------------- src/nodes.coffee | 105 +++++++++++++++------- 3 files changed, 158 insertions(+), 113 deletions(-) diff --git a/Cakefile b/Cakefile index f9dc0549..a11b83cd 100644 --- a/Cakefile +++ b/Cakefile @@ -41,10 +41,6 @@ task 'build:ultraviolet', 'build and install the Ultraviolet syntax highlighter' exec 'sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax' -task 'build:underscore', 'rebuild the Underscore.coffee documentation page', -> - exec 'uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html' - - task 'build:browser', 'rebuild the merged script for inclusion in the browser', -> exec 'rake browser', (err) -> throw err if err @@ -59,6 +55,10 @@ task 'doc:source', 'rebuild the internal documentation', -> throw err if err +task 'doc:underscore', 'rebuild the Underscore.coffee documentation page', -> + exec 'uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html' + + task 'test', 'run the CoffeeScript language test suite', -> process.mixin require 'assert' test_count: 0 diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html index 6c8e2bb6..e316e6cb 100644 --- a/documentation/docs/nodes.html +++ b/documentation/docs/nodes.html @@ -23,7 +23,11 @@ compiling it, or to compile directly. We need to wrap if this node is a statement, and it's not a pure_statement, and we're not at the top level of a block (which would be unnecessary), and we haven't already been asked to return the result (because statements know how to -return results).

  compile: (o) ->
+return results).

+ +

If a Node is top_sensitive, that means that it needs to compile differently +depending on whether it's being used as part of a larger expression, or is a +top-level statement within the function body.

  compile: (o) ->
     @options: merge o or {}
     @tab:     o.indent
     del @options, 'operation' unless @operation_sensitive()
@@ -305,8 +309,8 @@ it instead of wrapping nodes.

name: o.scope.free_variable() body: Expressions.wrap([literal(name)]) arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))]) - (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
#

An array slice literal. Unlike JavaScript's Array#slice, the second parameter -specifies the index of the end of the slice (just like the first parameter) + (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)

#

SliceNode

#

An array slice literal. Unlike JavaScript's Array#slice, the second parameter +specifies the index of the end of the slice, just as the first parameter is the index of the beginning.

exports.SliceNode: class SliceNode extends BaseNode
   type: 'Slice'
 
@@ -318,13 +322,15 @@ is the index of the beginning.

from: @range.from.compile(o) to: @range.to.compile(o) plus_part: if @range.exclusive then '' else ' + 1' - ".slice($from, $to$plus_part)"
#

An object literal.

exports.ObjectNode: class ObjectNode extends BaseNode
+    ".slice($from, $to$plus_part)"
#

ObjectNode

#

An object literal, nothing fancy.

exports.ObjectNode: class ObjectNode extends BaseNode
   type: 'Object'
 
   constructor: (props) ->
-    @children: @objects: @properties: props or []
#

All the mucking about with commas is to make sure that CommentNodes and + @children: @objects: @properties: props or []

#

All the mucking about with commas is to make sure that CommentNodes and AssignNodes get interleaved correctly, with no trailing commas or -commas affixed to comments. TODO: Extract this and add it to ArrayNode.

  compile_node: (o) ->
+commas affixed to comments.

+ +

TODO: Extract this and add it to ArrayNode.

  compile_node: (o) ->
     o.indent: @idt(1)
     non_comments: prop for prop in @properties when not (prop instanceof CommentNode)
     last_noncom:  non_comments[non_comments.length - 1]
@@ -336,13 +342,30 @@ commas affixed to comments. TODO: Extract this and add it to ArrayNode.

indent + prop.compile(o) + join props: props.join('') inner: if props then '\n' + props + '\n' + @idt() else '' - "{$inner}"
#

A class literal, including optional superclass and constructor.

exports.ClassNode: class ClassNode extends BaseNode
-  type: 'Class'
+    "{$inner}"
#

ArrayNode

#

An array literal.

exports.ArrayNode: class ArrayNode extends BaseNode
+  type: 'Array'
 
-  constructor: (variable, parent, props) ->
-    @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
+  constructor: (objects) ->
+    @children: @objects: objects or []
 
   compile_node: (o) ->
+    o.indent: @idt(1)
+    objects: for obj, i in @objects
+      code: obj.compile(o)
+      if obj instanceof CommentNode
+        "\n$code\n${o.indent}"
+      else if i is @objects.length - 1
+        code
+      else
+        "$code, "
+    objects: objects.join('')
+    ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']'
+    "[$objects$ending"
#

ClassNode

#

The CoffeeScript class definition.

exports.ClassNode: class ClassNode extends BaseNode
+  type: 'Class'
#

Initialize a ClassNode with its name, an optional superclass, and a +list of prototype property assignments.

  constructor: (variable, parent, props) ->
+    @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
#

Instead of generating the JavaScript string directly, we build up the +equivalent syntax tree and compile that, in pieces. You can see the +constructor, property assignments, and inheritance getting built out below.

  compile_node: (o) ->
     extension:   @parent and new ExtendsNode(@variable, @parent)
     constructor: null
     props:       new Expressions()
@@ -375,26 +398,9 @@ commas affixed to comments. TODO: Extract this and add it to ArrayNode.

returns: if ret then '\n' + @idt() + 'return ' + @variable.compile(o) + ';' else '' "$construct$extension$props$returns" -statement ClassNode
#

An array literal.

exports.ArrayNode: class ArrayNode extends BaseNode
-  type: 'Array'
-
-  constructor: (objects) ->
-    @children: @objects: objects or []
-
-  compile_node: (o) ->
-    o.indent: @idt(1)
-    objects: for obj, i in @objects
-      code: obj.compile(o)
-      if obj instanceof CommentNode
-        "\n$code\n${o.indent}"
-      else if i is @objects.length - 1
-        code
-      else
-        "$code, "
-    objects: objects.join('')
-    ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']'
-    "[$objects$ending"
#

A faux-node that is never created by the grammar, but is used during -code generation to generate a quick "array.push(value)" tree of nodes.

PushNode: exports.PushNode: {
+statement ClassNode
#

PushNode

#

A faux-node that is never created by the grammar, but is used during +code generation to generate a quick array.push(value) tree of nodes. +Helpful for recording the result arrays from comprehensions.

PushNode: exports.PushNode: {
 
   wrap: (array, expressions) ->
     expr: expressions.unwrap()
@@ -403,17 +409,16 @@ code generation to generate a quick "array.push(value)" tree of nodes.

new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr] )]) -}
#

A faux-node used to wrap an expressions body in a closure.

ClosureNode: exports.ClosureNode: {
+}
#

ClosureNode

#

A faux-node used to wrap an expressions body in a closure.

ClosureNode: exports.ClosureNode: {
 
   wrap: (expressions, statement) ->
     func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
     call: new CallNode(new ValueNode(func, [new AccessorNode(literal('call'))]), [literal('this')])
     if statement then Expressions.wrap([call]) else call
 
-}
#

Setting the value of a local variable, or the value of an object property.

exports.AssignNode: class AssignNode extends BaseNode
-  type: 'Assign'
-
-  PROTO_ASSIGN: /^(\S+)\.prototype/
+}
#

AssignNode

#

The AssignNode is used to assign a local variable to value, or to set the +property of an object -- including within object literals.

exports.AssignNode: class AssignNode extends BaseNode
+  type: 'Assign'
#

Matchers for detecting prototype assignments.

  PROTO_ASSIGN: /^(\S+)\.prototype/
   LEADING_DOT:  /^\.(prototype\.)?/
 
   constructor: (variable, value, context) ->
@@ -427,9 +432,10 @@ code generation to generate a quick "array.push(value)" tree of nodes.

@variable instanceof ValueNode is_statement: -> - @is_value() and (@variable.is_array() or @variable.is_object()) - - compile_node: (o) -> + @is_value() and (@variable.is_array() or @variable.is_object())
#

Compile an assignment, delegating to compile_pattern_match or +compile_splice if appropriate. Keep track of the name of the base object +we've been assigned to, for correct internal references. If the variable +has not been seen yet within the current scope, declare it.

  compile_node: (o) ->
     top:    del o, 'top'
     return  @compile_pattern_match(o) if @is_statement()
     return  @compile_splice(o) if @is_value() and @variable.is_splice()
@@ -448,9 +454,10 @@ code generation to generate a quick "array.push(value)" tree of nodes.

return "$@tab$val;" if stmt val: "($val)" if not top or o.returns val: "${@tab}return $val" if o.returns - val
#

Implementation of recursive pattern matching, when assigning array or + val

#

Brief implementation of recursive pattern matching, when assigning array or object literals to a value. Peeks at their properties to assign inner names. -See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring

  compile_pattern_match: (o) ->
+See the ECMAScript Harmony Wiki
+for details.

  compile_pattern_match: (o) ->
     val_var: o.scope.free_variable()
     value: if @value.is_statement() then ClosureNode.wrap(@value) else @value
     assigns: ["$@tab$val_var = ${ value.compile(o) };"]
@@ -468,9 +475,8 @@ See: http
       assigns.push(new AssignNode(obj, val).compile(o))
     code: assigns.join("\n")
     code += "\n${@tab}return ${ @variable.compile(o) };" if o.returns
-    code
-
-  compile_splice: (o) ->
+    code

Compile the assignment from an array splice literal, using JavaScript's +Array#splice method.

  compile_splice: (o) ->
     name:   @variable.compile(merge(o, {only_first: true}))
     l:      @variable.properties.length
     range:  @variable.properties[l - 1].range
@@ -478,16 +484,19 @@ See: http
     from:   range.from.compile(o)
     to:     range.to.compile(o) + ' - ' + from + plus
     val:    @value.compile(o)
-    "$name.splice.apply($name, [$from, $to].concat($val))"

A function definition. The only node that creates a new Scope. -A CodeNode does not have any children -- they're within the new scope.

exports.CodeNode: class CodeNode extends BaseNode
+    "$name.splice.apply($name, [$from, $to].concat($val))"
#

CodeNode

#

A function definition. This is the only node that creates a new Scope. +When for the purposes of walking the contents of a function body, the CodeNode +has no children -- they're within the inner scope.

exports.CodeNode: class CodeNode extends BaseNode
   type: 'Code'
 
   constructor: (params, body, tag) ->
     @params:  params or []
     @body:    body or new Expressions()
-    @bound:   tag is 'boundfunc'
-
-  compile_node: (o) ->
+    @bound:   tag is 'boundfunc'
#

Compilation creates a new scope unless explicitly asked to share with the +outer scope. Handles splat parameters in the parameter list by peeking at +the JavaScript arguments objects. If the function is bound with the => +arrow, generates a wrapper that saves the current value of this through +a closure.

  compile_node: (o) ->
     shared_scope: del o, 'shared_scope'
     top:          del o, 'top'
     o.scope:      shared_scope or new Scope(o.scope, @body, this)
@@ -511,19 +520,16 @@ A CodeNode does not have any children -- they're within the new scope.

"(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n$@tab})(this)" top_sensitive: -> - true - - real_children: -> - flatten [@params, @body.expressions] - - traverse: (block) -> + true
#

When traversing (for printing or inspecting), return the real children of +the function -- the parameters and body of expressions.

  real_children: ->
+    flatten [@params, @body.expressions]
#

Custom traverse implementation that uses the real_children.

  traverse: (block) ->
     block this
     block(child) for child in @real_children()
 
   toString: (idt) ->
     idt ||= ''
     children: (child.toString(idt + TAB) for child in @real_children()).join('')
-    "\n$idt$children"
#

A splat, either as a parameter to a function, an argument to a call, + "\n$idt$children"

#

SplatNode

#

A splat, either as a parameter to a function, an argument to a call, or in a destructuring assignment.

exports.SplatNode: class SplatNode extends BaseNode
   type: 'Splat'
 
@@ -540,7 +546,7 @@ or in a destructuring assignment.

"$name = Array.prototype.slice.call(arguments, $@index)" compile_value: (o, name, index) -> - "Array.prototype.slice.call($name, $index)"
#

A while loop, the only sort of low-level loop exposed by CoffeeScript. From + "Array.prototype.slice.call($name, $index)"

#

A while loop, the only sort of low-level loop exposed by CoffeeScript. From it, all other loops can be manufactured.

exports.WhileNode: class WhileNode extends BaseNode
   type: 'While'
 
@@ -572,7 +578,7 @@ it, all other loops can be manufactured.

@body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter "$pre {\n${ @body.compile(o) }\n$@tab}$post" -statement WhileNode
#

Simple Arithmetic and logical operations. Performs some conversion from +statement WhileNode

#

Simple Arithmetic and logical operations. Performs some conversion from CoffeeScript operations into their JavaScript equivalents.

exports.OpNode: class OpNode extends BaseNode
   type: 'Op'
 
@@ -608,7 +614,7 @@ CoffeeScript operations into their JavaScript equivalents.

return @compile_assignment(o) if @ASSIGNMENT.indexOf(@operator) >= 0 return @compile_unary(o) if @is_unary() return @compile_existence(o) if @operator is '?' - [@first.compile(o), @operator, @second.compile(o)].join ' '
#

Mimic Python's chained comparisons. See: + [@first.compile(o), @operator, @second.compile(o)].join ' '

#

Mimic Python's chained comparisons. See: http://docs.python.org/reference/expressions.html#notin

  compile_chain: (o) ->
     shared: @first.unwrap().second
     [@first.second, shared]: shared.compile_reference(o) if shared instanceof CallNode
@@ -630,7 +636,7 @@ CoffeeScript operations into their JavaScript equivalents.

space: if @PREFIX_OPERATORS.indexOf(@operator) >= 0 then ' ' else '' parts: [@operator, space, @first.compile(o)] parts: parts.reverse() if @flip - parts.join('')
#

A try/catch/finally block.

exports.TryNode: class TryNode extends BaseNode
+    parts.join('')
#

A try/catch/finally block.

exports.TryNode: class TryNode extends BaseNode
   type: 'Try'
 
   constructor: (attempt, error, recovery, ensure) ->
@@ -647,7 +653,7 @@ CoffeeScript operations into their JavaScript equivalents.

finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + "\n$@tab}" "${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part" -statement TryNode
#

Throw an exception.

exports.ThrowNode: class ThrowNode extends BaseNode
+statement TryNode
#

Throw an exception.

exports.ThrowNode: class ThrowNode extends BaseNode
   type: 'Throw'
 
   constructor: (expression) ->
@@ -656,7 +662,7 @@ CoffeeScript operations into their JavaScript equivalents.

compile_node: (o) -> "${@tab}throw ${@expression.compile(o)};" -statement ThrowNode, true
#

Check an expression for existence (meaning not null or undefined).

exports.ExistenceNode: class ExistenceNode extends BaseNode
+statement ThrowNode, true
#

Check an expression for existence (meaning not null or undefined).

exports.ExistenceNode: class ExistenceNode extends BaseNode
   type: 'Existence'
 
   constructor: (expression) ->
@@ -670,7 +676,7 @@ CoffeeScript operations into their JavaScript equivalents.

if variable instanceof CallNode or (variable instanceof ValueNode and variable.has_properties()) [first, second]: variable.compile_reference(o) [first, second]: [first.compile(o), second.compile(o)] - "(typeof $first !== \"undefined\" && $second !== null)"
#

An extra set of parentheses, specified explicitly in the source.

exports.ParentheticalNode: class ParentheticalNode extends BaseNode
+  "(typeof $first !== \"undefined\" && $second !== null)"
#

An extra set of parentheses, specified explicitly in the source.

exports.ParentheticalNode: class ParentheticalNode extends BaseNode
   type: 'Paren'
 
   constructor: (expression) ->
@@ -684,7 +690,7 @@ CoffeeScript operations into their JavaScript equivalents.

return code if @is_statement() l: code.length code: code.substr(o, l-1) if code.substr(l-1, 1) is ';' - "($code)"
#

The replacement for the for loop is an array comprehension (that compiles) + "($code)"

#

The replacement for the for loop is an array comprehension (that compiles) into a for loop. Also acts as an expression, able to return the result of the comprehenion. Unlike Python array comprehensions, it's able to pass the current index of the loop as a second parameter.

exports.ForNode: class ForNode extends BaseNode
@@ -751,7 +757,7 @@ the current index of the loop as a second parameter.

close: if @object then '}}\n' else '}\n' "$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$@tab$return_result" -statement ForNode
#

If/else statements. Switch/whens get compiled into these. Acts as an +statement ForNode

#

If/else statements. Switch/whens get compiled into these. Acts as an expression by pushing down requested returns to the expression bodies. Single-expression IfNodes are compiled into ternary operators if possible, because ternaries are first-class returnable assignable expressions.

exports.IfNode: class IfNode extends BaseNode
@@ -773,9 +779,9 @@ because ternaries are first-class returnable assignable expressions.

force_statement: -> @tags.statement: true - this
#

Tag a chain of IfNodes with their switch condition for equality.

  rewrite_condition: (expression) ->
+    this
#

Tag a chain of IfNodes with their switch condition for equality.

  rewrite_condition: (expression) ->
     @switcher: expression
-    this
#

Rewrite a chain of IfNodes with their switch condition for equality.

  rewrite_switch: (o) ->
+    this
#

Rewrite a chain of IfNodes with their switch condition for equality.

  rewrite_switch: (o) ->
     assigner: @switcher
     if not (@switcher.unwrap() instanceof LiteralNode)
       variable: literal(o.scope.free_variable())
@@ -787,14 +793,14 @@ because ternaries are first-class returnable assignable expressions.

else new OpNode('is', assigner, @condition) @else_body.rewrite_condition(@switcher) if @is_chain() - this
#

Rewrite a chain of IfNodes to add a default case as the final else.

  add_else: (exprs, statement) ->
+    this
#

Rewrite a chain of IfNodes to add a default case as the final else.

  add_else: (exprs, statement) ->
     if @is_chain()
       @else_body.add_else exprs, statement
     else
       exprs: exprs.unwrap() unless statement
       @children.push @else_body: exprs
-    this
#

If the else_body is an IfNode itself, then we've got an if-else chain.

  is_chain: ->
-    @chain ||= @else_body and @else_body instanceof IfNode
#

The IfNode only compiles into a statement if either of the bodies needs + this

#

If the else_body is an IfNode itself, then we've got an if-else chain.

  is_chain: ->
+    @chain ||= @else_body and @else_body instanceof IfNode
#

The IfNode only compiles into a statement if either of the bodies needs to be a statement.

  is_statement: ->
     @statement ||= !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement()))
 
@@ -802,7 +808,7 @@ to be a statement.

(cond.compile(o) for cond in flatten([@condition])).join(' || ') compile_node: (o) -> - if @is_statement() then @compile_statement(o) else @compile_ternary(o)
#

Compile the IfNode as a regular if-else statement. Flattened chains + if @is_statement() then @compile_statement(o) else @compile_ternary(o)

#

Compile the IfNode as a regular if-else statement. Flattened chains force sub-else bodies into statement form.

  compile_statement: (o) ->
     @rewrite_switch(o) if @switcher
     child:        del o, 'chain_child'
@@ -820,22 +826,22 @@ force sub-else bodies into statement form.

' else ' + @else_body.compile(merge(o, {indent: @idt(), chain_child: true})) else " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n$@tab}" - "$if_part$else_part"
#

Compile the IfNode into a ternary operator.

  compile_ternary: (o) ->
+    "$if_part$else_part"
#

Compile the IfNode into a ternary operator.

  compile_ternary: (o) ->
     if_part:    @condition.compile(o) + ' ? ' + @body.compile(o)
     else_part:  if @else_body then @else_body.compile(o) else 'null'
-    "$if_part : $else_part"
#

Constants

#

Tabs are two spaces for pretty printing.

TAB: '  '
-TRAILING_WHITESPACE: /\s+$/gm
#

Keep the identifier regex in sync with the Lexer.

IDENTIFIER:   /^[a-zA-Z$_](\w|\$)*$/
#

Utility Functions

#

Merge objects.

merge: (options, overrides) ->
+    "$if_part : $else_part"
#

Constants

#

Tabs are two spaces for pretty printing.

TAB: '  '
+TRAILING_WHITESPACE: /\s+$/gm
#

Keep the identifier regex in sync with the Lexer.

IDENTIFIER:   /^[a-zA-Z$_](\w|\$)*$/
#

Utility Functions

#

Merge objects.

merge: (options, overrides) ->
   fresh: {}
   (fresh[key]: val) for key, val of options
   (fresh[key]: val) for key, val of overrides if overrides
-  fresh
#

Trim out all falsy values from an array.

compact: (array) -> item for item in array when item
#

Return a completely flattened version of an array.

flatten: (array) ->
+  fresh
#

Trim out all falsy values from an array.

compact: (array) -> item for item in array when item
#

Return a completely flattened version of an array.

flatten: (array) ->
   memo: []
   for item in array
     if item instanceof Array then memo: memo.concat(item) else memo.push(item)
-  memo
#

Delete a key from an object, returning the value.

del: (obj, key) ->
+  memo
#

Delete a key from an object, returning the value.

del: (obj, key) ->
   val: obj[key]
   delete obj[key]
-  val
#

Quickie helper for a generated LiteralNode.

literal: (name) ->
+  val
#

Quickie helper for a generated LiteralNode.

literal: (name) ->
   new LiteralNode(name)
 
 
\ No newline at end of file diff --git a/src/nodes.coffee b/src/nodes.coffee index 5292c4a0..0c50dc44 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -37,6 +37,10 @@ exports.BaseNode: class BaseNode # the top level of a block (which would be unnecessary), and we haven't # already been asked to return the result (because statements know how to # return results). + # + # If a Node is *top_sensitive*, that means that it needs to compile differently + # depending on whether it's being used as part of a larger expression, or is a + # top-level statement within the function body. compile: (o) -> @options: merge o or {} @tab: o.indent @@ -471,9 +475,10 @@ exports.RangeNode: class RangeNode extends BaseNode arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))]) (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o) +#### SliceNode -# An array slice literal. Unlike JavaScript's Array#slice, the second parameter -# specifies the index of the end of the slice (just like the first parameter) +# An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter +# specifies the index of the end of the slice, just as the first parameter # is the index of the beginning. exports.SliceNode: class SliceNode extends BaseNode type: 'Slice' @@ -488,8 +493,9 @@ exports.SliceNode: class SliceNode extends BaseNode plus_part: if @range.exclusive then '' else ' + 1' ".slice($from, $to$plus_part)" +#### ObjectNode -# An object literal. +# An object literal, nothing fancy. exports.ObjectNode: class ObjectNode extends BaseNode type: 'Object' @@ -498,7 +504,9 @@ exports.ObjectNode: class ObjectNode extends BaseNode # All the mucking about with commas is to make sure that CommentNodes and # AssignNodes get interleaved correctly, with no trailing commas or - # commas affixed to comments. TODO: Extract this and add it to ArrayNode. + # commas affixed to comments. + # + # *TODO: Extract this and add it to ArrayNode*. compile_node: (o) -> o.indent: @idt(1) non_comments: prop for prop in @properties when not (prop instanceof CommentNode) @@ -513,14 +521,43 @@ exports.ObjectNode: class ObjectNode extends BaseNode inner: if props then '\n' + props + '\n' + @idt() else '' "{$inner}" +#### ArrayNode -# A class literal, including optional superclass and constructor. +# An array literal. +exports.ArrayNode: class ArrayNode extends BaseNode + type: 'Array' + + constructor: (objects) -> + @children: @objects: objects or [] + + compile_node: (o) -> + o.indent: @idt(1) + objects: for obj, i in @objects + code: obj.compile(o) + if obj instanceof CommentNode + "\n$code\n${o.indent}" + else if i is @objects.length - 1 + code + else + "$code, " + objects: objects.join('') + ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']' + "[$objects$ending" + +#### ClassNode + +# The CoffeeScript class definition. exports.ClassNode: class ClassNode extends BaseNode type: 'Class' + # Initialize a **ClassNode** with its name, an optional superclass, and a + # list of prototype property assignments. constructor: (variable, parent, props) -> @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []] + # Instead of generating the JavaScript string directly, we build up the + # equivalent syntax tree and compile that, in pieces. You can see the + # constructor, property assignments, and inheritance getting built out below. compile_node: (o) -> extension: @parent and new ExtendsNode(@variable, @parent) constructor: null @@ -556,31 +593,11 @@ exports.ClassNode: class ClassNode extends BaseNode statement ClassNode - -# An array literal. -exports.ArrayNode: class ArrayNode extends BaseNode - type: 'Array' - - constructor: (objects) -> - @children: @objects: objects or [] - - compile_node: (o) -> - o.indent: @idt(1) - objects: for obj, i in @objects - code: obj.compile(o) - if obj instanceof CommentNode - "\n$code\n${o.indent}" - else if i is @objects.length - 1 - code - else - "$code, " - objects: objects.join('') - ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']' - "[$objects$ending" - +#### PushNode # A faux-node that is never created by the grammar, but is used during -# code generation to generate a quick "array.push(value)" tree of nodes. +# code generation to generate a quick `array.push(value)` tree of nodes. +# Helpful for recording the result arrays from comprehensions. PushNode: exports.PushNode: { wrap: (array, expressions) -> @@ -592,6 +609,7 @@ PushNode: exports.PushNode: { } +#### ClosureNode # A faux-node used to wrap an expressions body in a closure. ClosureNode: exports.ClosureNode: { @@ -603,11 +621,14 @@ ClosureNode: exports.ClosureNode: { } +#### AssignNode -# Setting the value of a local variable, or the value of an object property. +# The **AssignNode** is used to assign a local variable to value, or to set the +# property of an object -- including within object literals. exports.AssignNode: class AssignNode extends BaseNode type: 'Assign' + # Matchers for detecting prototype assignments. PROTO_ASSIGN: /^(\S+)\.prototype/ LEADING_DOT: /^\.(prototype\.)?/ @@ -624,6 +645,10 @@ exports.AssignNode: class AssignNode extends BaseNode is_statement: -> @is_value() and (@variable.is_array() or @variable.is_object()) + # Compile an assignment, delegating to `compile_pattern_match` or + # `compile_splice` if appropriate. Keep track of the name of the base object + # we've been assigned to, for correct internal references. If the variable + # has not been seen yet within the current scope, declare it. compile_node: (o) -> top: del o, 'top' return @compile_pattern_match(o) if @is_statement() @@ -645,9 +670,10 @@ exports.AssignNode: class AssignNode extends BaseNode val: "${@tab}return $val" if o.returns val - # Implementation of recursive pattern matching, when assigning array or + # Brief implementation of recursive pattern matching, when assigning array or # object literals to a value. Peeks at their properties to assign inner names. - # See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring + # See the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring) + # for details. compile_pattern_match: (o) -> val_var: o.scope.free_variable() value: if @value.is_statement() then ClosureNode.wrap(@value) else @value @@ -668,6 +694,8 @@ exports.AssignNode: class AssignNode extends BaseNode code += "\n${@tab}return ${ @variable.compile(o) };" if o.returns code + # Compile the assignment from an array splice literal, using JavaScript's + # `Array#splice` method. compile_splice: (o) -> name: @variable.compile(merge(o, {only_first: true})) l: @variable.properties.length @@ -678,9 +706,11 @@ exports.AssignNode: class AssignNode extends BaseNode val: @value.compile(o) "$name.splice.apply($name, [$from, $to].concat($val))" +#### CodeNode -# A function definition. The only node that creates a new Scope. -# A CodeNode does not have any children -- they're within the new scope. +# A function definition. This is the only node that creates a new Scope. +# When for the purposes of walking the contents of a function body, the CodeNode +# has no *children* -- they're within the inner scope. exports.CodeNode: class CodeNode extends BaseNode type: 'Code' @@ -689,6 +719,11 @@ exports.CodeNode: class CodeNode extends BaseNode @body: body or new Expressions() @bound: tag is 'boundfunc' + # Compilation creates a new scope unless explicitly asked to share with the + # outer scope. Handles splat parameters in the parameter list by peeking at + # the JavaScript `arguments` objects. If the function is bound with the `=>` + # arrow, generates a wrapper that saves the current value of `this` through + # a closure. compile_node: (o) -> shared_scope: del o, 'shared_scope' top: del o, 'top' @@ -715,9 +750,12 @@ exports.CodeNode: class CodeNode extends BaseNode top_sensitive: -> true + # When traversing (for printing or inspecting), return the real children of + # the function -- the parameters and body of expressions. real_children: -> flatten [@params, @body.expressions] + # Custom `traverse` implementation that uses the `real_children`. traverse: (block) -> block this block(child) for child in @real_children() @@ -727,6 +765,7 @@ exports.CodeNode: class CodeNode extends BaseNode children: (child.toString(idt + TAB) for child in @real_children()).join('') "\n$idt$children" +#### SplatNode # A splat, either as a parameter to a function, an argument to a call, # or in a destructuring assignment.