From b0aec3cbe2131c12bbea68d4e63354ce947133c1 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 8 Mar 2010 05:42:57 -0500 Subject: [PATCH] Finishing off the docs for nodes.coffee -- almost ready to roll. --- documentation/docs/nodes.html | 84 +++++++++++++++++++++-------------- lib/nodes.js | 78 +++++++++++++++++++++----------- src/nodes.coffee | 84 ++++++++++++++++++++++++----------- 3 files changed, 161 insertions(+), 85 deletions(-) diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html index a99444f5..53715c05 100644 --- a/documentation/docs/nodes.html +++ b/documentation/docs/nodes.html @@ -647,7 +647,7 @@ is optional, the catch is not.

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
#

ThrowNode

#

Simple node to throw an exception.

exports.ThrowNode: class ThrowNode extends BaseNode
   type: 'Throw'
 
   constructor: (expression) ->
@@ -656,21 +656,27 @@ is optional, the catch is not.

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
#

ExistenceNode

#

Checks a variable for existence -- not null and not undefined. This is +similar to .nil? in Ruby, and avoids having to consult a JavaScript truth +table.

exports.ExistenceNode: class ExistenceNode extends BaseNode
   type: 'Existence'
 
   constructor: (expression) ->
     @children: [@expression: expression]
 
   compile_node: (o) ->
-    ExistenceNode.compile_test(o, @expression)
-
-ExistenceNode.compile_test: (o, variable) ->
+    ExistenceNode.compile_test(o, @expression)
#

The meat of the ExistenceNode is in this static compile_test method +because other nodes like to check the existence of their variables as well. +Be careful not to double-evaluate anything.

ExistenceNode.compile_test: (o, variable) ->
   [first, second]: [variable, variable]
   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)"
#

ParentheticalNode

#

An extra set of parentheses, specified explicitly in the source. At one time +we tried to clean up the results by detecting and removing redundant +parentheses, but no longer -- you can put in as many as you please.

+ +

Parentheses are a good way to force any statement to become an expression.

exports.ParentheticalNode: class ParentheticalNode extends BaseNode
   type: 'Paren'
 
   constructor: (expression) ->
@@ -684,10 +690,13 @@ is optional, the catch is not.

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) -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
+    "($code)"
#

ForNode

#

CoffeeScript's replacement for the for loop is our array and object +comprehensions, that compile into for loops here. They also act as an +expression, able to return the result of each filtered iteration.

+ +

Unlike Python array comprehensions, they can be multi-line, and you can pass +the current index of the loop as a second parameter. Unlike Ruby blocks, +you can map and filter in a single pass.

exports.ForNode: class ForNode extends BaseNode
   type: 'For'
 
   constructor: (body, source, name, index) ->
@@ -702,9 +711,10 @@ the current index of the loop as a second parameter.

@children: compact [@body, @source, @filter] top_sensitive: -> - true - - compile_node: (o) -> + true
#

Welcome to the hairiest method in all of CoffeeScript. Handles the inner +loop, filtering, stepping, and result saving for array, object, and range +comprehensions. Some of the generated code can be shared in common, and +some cannot.

  compile_node: (o) ->
     top_level:      del(o, 'top') and not o.returns
     range:          @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length
     source:         if range then @source.base else @source
@@ -751,10 +761,11 @@ 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 -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
+statement ForNode
#

IfNode

#

If/else statements. Our switch/when will be compiled into this. Acts as an +expression by pushing down requested returns to the last line of each clause.

+ +

Single-expression IfNodes are compiled into ternary operators if possible, +because ternaries are already proper expressions, and don't need conversion.

exports.IfNode: class IfNode extends BaseNode
   type: 'If'
 
   constructor: (condition, body, else_body, tags) ->
@@ -764,18 +775,19 @@ because ternaries are first-class returnable assignable expressions.

@children: compact [@condition, @body, @else_body] @tags: tags or {} @multiple: true if @condition instanceof Array - @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert - - push: (else_body) -> + @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert
#

Add a new else clause to this IfNode, or push it down to the bottom +of the chain recursively.

  push: (else_body) ->
     eb: else_body.unwrap()
     if @else_body then @else_body.push(eb) else @else_body: eb
     this
 
   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 object(s) to switch on for equality +tests. rewrite_switch will perform the actual change at compile time.

  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. +Ensure that the switch expression isn't evaluated more than once.

  rewrite_switch: (o) ->
     assigner: @switcher
     if not (@switcher.unwrap() instanceof LiteralNode)
       variable: literal(o.scope.free_variable())
@@ -787,23 +799,23 @@ 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 -to be a statement.

  is_statement: ->
+    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 its bodies needs +to be a statement. Otherwise a ternary is safe.

  is_statement: ->
     @statement ||= !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement()))
 
   compile_condition: (o) ->
     (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 -force sub-else bodies into statement form.

  compile_statement: (o) ->
+    if @is_statement() then @compile_statement(o) else @compile_ternary(o)
#

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

  compile_statement: (o) ->
     @rewrite_switch(o) if @switcher
     child:        del o, 'chain_child'
     cond_o:       merge o
@@ -820,22 +832,26 @@ 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 as 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: '  '
#

Trim out all trailing whitespace, so that the generated code plays nice +with Git.

TRAILING_WHITESPACE: /\s+$/gm
#

Keep this identifier regex in sync with the Lexer.

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

Utility Functions

#

Merge objects, returning a fresh copy with attributes from both sides. +Used every time compile is called, to allow properties in the options hash +to propagate down the tree without polluting other branches.

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. Handy for getting a +list of children.

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. Useful when a node is +looking for a particular method in an options hash.

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

Quickie helper for a generated LiteralNode.

literal: (name) ->
+  val
#

Handy helper for a generating LiteralNode.

literal: (name) ->
   new LiteralNode(name)
 
 
\ No newline at end of file diff --git a/lib/nodes.js b/lib/nodes.js index 8c52e2ef..3ea376a6 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1226,7 +1226,7 @@ idt += TAB }).call(this); statement(TryNode); //### ThrowNode - // Throw an exception. + // Simple node to throw an exception. exports.ThrowNode = (function() { ThrowNode = function ThrowNode(expression) { this.children = [(this.expression = expression)]; @@ -1240,7 +1240,10 @@ idt += TAB return ThrowNode; }).call(this); statement(ThrowNode); - // Check an expression for existence (meaning not null or undefined). + //### ExistenceNode + // Checks a variable for existence -- not *null* and not *undefined*. This is + // similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth + // table. exports.ExistenceNode = (function() { ExistenceNode = function ExistenceNode(expression) { this.children = [(this.expression = expression)]; @@ -1253,6 +1256,9 @@ idt += TAB }; return ExistenceNode; }).call(this); + // The meat of the **ExistenceNode** is in this static `compile_test` method + // because other nodes like to check the existence of their variables as well. + // Be careful not to double-evaluate anything. ExistenceNode.compile_test = function compile_test(o, variable) { var _a, _b, _c, first, second; _a = [variable, variable]; @@ -1268,7 +1274,11 @@ idt += TAB second = _c[1]; return "(typeof " + first + " !== \"undefined\" && " + second + " !== null)"; }; - // An extra set of parentheses, specified explicitly in the source. + //### ParentheticalNode + // An extra set of parentheses, specified explicitly in the source. At one time + // we tried to clean up the results by detecting and removing redundant + // parentheses, but no longer -- you can put in as many as you please. + // Parentheses are a good way to force any statement to become an expression. exports.ParentheticalNode = (function() { ParentheticalNode = function ParentheticalNode(expression) { this.children = [(this.expression = expression)]; @@ -1293,10 +1303,13 @@ idt += TAB }; return ParentheticalNode; }).call(this); - // 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. + //### ForNode + // CoffeeScript's replacement for the *for* loop is our array and object + // comprehensions, that compile into *for* loops here. They also act as an + // expression, able to return the result of each filtered iteration. + // Unlike Python array comprehensions, they can be multi-line, and you can pass + // the current index of the loop as a second parameter. Unlike Ruby blocks, + // you can map and filter in a single pass. exports.ForNode = (function() { ForNode = function ForNode(body, source, name, index) { var _a; @@ -1320,6 +1333,10 @@ idt += TAB ForNode.prototype.top_sensitive = function top_sensitive() { return true; }; + // Welcome to the hairiest method in all of CoffeeScript. Handles the inner + // loop, filtering, stepping, and result saving for array, object, and range + // comprehensions. Some of the generated code can be shared in common, and + // some cannot. ForNode.prototype.compile_node = function compile_node(o) { var body, body_dent, close, for_part, index, index_found, index_var, ivar, lvar, name, name_found, range, return_result, rvar, scope, set_result, source, source_part, step_part, svar, top_level, var_part, vars; top_level = del(o, 'top') && !o.returns; @@ -1397,10 +1414,11 @@ idt += TAB return ForNode; }).call(this); 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. + //### IfNode + // *If/else* statements. Our *switch/when* will be compiled into this. Acts as an + // expression by pushing down requested returns to the last line of each clause. + // Single-expression **IfNodes** are compiled into ternary operators if possible, + // because ternaries are already proper expressions, and don't need conversion. exports.IfNode = (function() { IfNode = function IfNode(condition, body, else_body, tags) { this.condition = condition; @@ -1418,6 +1436,8 @@ idt += TAB }; __extends(IfNode, BaseNode); IfNode.prototype.type = 'If'; + // Add a new *else* clause to this **IfNode**, or push it down to the bottom + // of the chain recursively. IfNode.prototype.push = function push(else_body) { var eb; eb = else_body.unwrap(); @@ -1428,12 +1448,14 @@ idt += TAB this.tags.statement = true; return this; }; - // Tag a chain of IfNodes with their switch condition for equality. + // Tag a chain of **IfNodes** with their object(s) to switch on for equality + // tests. `rewrite_switch` will perform the actual change at compile time. IfNode.prototype.rewrite_condition = function rewrite_condition(expression) { this.switcher = expression; return this; }; - // Rewrite a chain of IfNodes with their switch condition for equality. + // Rewrite a chain of **IfNodes** with their switch condition for equality. + // Ensure that the switch expression isn't evaluated more than once. IfNode.prototype.rewrite_switch = function rewrite_switch(o) { var _a, _b, _c, assigner, cond, i, variable; assigner = this.switcher; @@ -1459,7 +1481,7 @@ idt += TAB } return this; }; - // Rewrite a chain of IfNodes to add a default case as the final else. + // Rewrite a chain of **IfNodes** to add a default case as the final *else*. IfNode.prototype.add_else = function add_else(exprs, statement) { if (this.is_chain()) { this.else_body.add_else(exprs, statement); @@ -1471,12 +1493,12 @@ idt += TAB } return this; }; - // If the else_body is an IfNode itself, then we've got an if-else chain. + // If the `else_body` is an **IfNode** itself, then we've got an *if-else* chain. IfNode.prototype.is_chain = function is_chain() { return this.chain = this.chain || this.else_body && this.else_body instanceof IfNode; }; - // The IfNode only compiles into a statement if either of the bodies needs - // to be a statement. + // The **IfNode** only compiles into a statement if either of its bodies needs + // to be a statement. Otherwise a ternary is safe. IfNode.prototype.is_statement = function is_statement() { return this.statement = this.statement || !!(this.comment || this.tags.statement || this.body.is_statement() || (this.else_body && this.else_body.is_statement())); }; @@ -1494,8 +1516,8 @@ idt += TAB IfNode.prototype.compile_node = function compile_node(o) { return this.is_statement() ? this.compile_statement(o) : this.compile_ternary(o); }; - // Compile the IfNode as a regular if-else statement. Flattened chains - // force sub-else bodies into statement form. + // Compile the **IfNode** as a regular *if-else* statement. Flattened chains + // force inner *else* bodies into statement form. IfNode.prototype.compile_statement = function compile_statement(o) { var body, child, com_dent, cond_o, else_part, if_dent, if_part, prefix; if (this.switcher) { @@ -1520,7 +1542,7 @@ idt += TAB })) : " else {\n" + (Expressions.wrap([this.else_body]).compile(o)) + "\n" + this.tab + "}"; return if_part + else_part; }; - // Compile the IfNode into a ternary operator. + // Compile the IfNode as a ternary operator. IfNode.prototype.compile_ternary = function compile_ternary(o) { var else_part, if_part; if_part = this.condition.compile(o) + ' ? ' + this.body.compile(o); @@ -1533,12 +1555,16 @@ idt += TAB // --------- // Tabs are two spaces for pretty printing. TAB = ' '; + // Trim out all trailing whitespace, so that the generated code plays nice + // with Git. TRAILING_WHITESPACE = /\s+$/gm; - // Keep the identifier regex in sync with the Lexer. + // Keep this identifier regex in sync with the Lexer. IDENTIFIER = /^[a-zA-Z$_](\w|\$)*$/; // Utility Functions // ----------------- - // Merge objects. + // Merge objects, returning a fresh copy with attributes from both sides. + // Used every time `compile` is called, to allow properties in the options hash + // to propagate down the tree without polluting other branches. merge = function merge(options, overrides) { var _a, _b, fresh, key, val; fresh = {}; @@ -1568,7 +1594,8 @@ idt += TAB } return _a; }; - // Return a completely flattened version of an array. + // Return a completely flattened version of an array. Handy for getting a + // list of `children`. flatten = function flatten(array) { var _a, _b, _c, item, memo; memo = []; @@ -1579,14 +1606,15 @@ idt += TAB } return memo; }; - // Delete a key from an object, returning the value. + // Delete a key from an object, returning the value. Useful when a node is + // looking for a particular method in an options hash. del = function del(obj, key) { var val; val = obj[key]; delete obj[key]; return val; }; - // Quickie helper for a generated LiteralNode. + // Handy helper for a generating LiteralNode. literal = function literal(name) { return new LiteralNode(name); }; diff --git a/src/nodes.coffee b/src/nodes.coffee index ec89c0ae..142928f3 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -940,7 +940,7 @@ statement TryNode #### ThrowNode -# Throw an exception. +# Simple node to throw an exception. exports.ThrowNode: class ThrowNode extends BaseNode type: 'Throw' @@ -952,8 +952,11 @@ exports.ThrowNode: class ThrowNode extends BaseNode statement ThrowNode +#### ExistenceNode -# Check an expression for existence (meaning not null or undefined). +# Checks a variable for existence -- not *null* and not *undefined*. This is +# similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth +# table. exports.ExistenceNode: class ExistenceNode extends BaseNode type: 'Existence' @@ -963,6 +966,9 @@ exports.ExistenceNode: class ExistenceNode extends BaseNode compile_node: (o) -> ExistenceNode.compile_test(o, @expression) +# The meat of the **ExistenceNode** is in this static `compile_test` method +# because other nodes like to check the existence of their variables as well. +# Be careful not to double-evaluate anything. ExistenceNode.compile_test: (o, variable) -> [first, second]: [variable, variable] if variable instanceof CallNode or (variable instanceof ValueNode and variable.has_properties()) @@ -970,8 +976,13 @@ ExistenceNode.compile_test: (o, variable) -> [first, second]: [first.compile(o), second.compile(o)] "(typeof $first !== \"undefined\" && $second !== null)" +#### ParentheticalNode -# An extra set of parentheses, specified explicitly in the source. +# An extra set of parentheses, specified explicitly in the source. At one time +# we tried to clean up the results by detecting and removing redundant +# parentheses, but no longer -- you can put in as many as you please. +# +# Parentheses are a good way to force any statement to become an expression. exports.ParentheticalNode: class ParentheticalNode extends BaseNode type: 'Paren' @@ -988,11 +999,15 @@ exports.ParentheticalNode: class ParentheticalNode extends BaseNode code: code.substr(o, l-1) if code.substr(l-1, 1) is ';' "($code)" +#### ForNode -# 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. +# CoffeeScript's replacement for the *for* loop is our array and object +# comprehensions, that compile into *for* loops here. They also act as an +# expression, able to return the result of each filtered iteration. +# +# Unlike Python array comprehensions, they can be multi-line, and you can pass +# the current index of the loop as a second parameter. Unlike Ruby blocks, +# you can map and filter in a single pass. exports.ForNode: class ForNode extends BaseNode type: 'For' @@ -1010,6 +1025,10 @@ exports.ForNode: class ForNode extends BaseNode top_sensitive: -> true + # Welcome to the hairiest method in all of CoffeeScript. Handles the inner + # loop, filtering, stepping, and result saving for array, object, and range + # comprehensions. Some of the generated code can be shared in common, and + # some cannot. compile_node: (o) -> top_level: del(o, 'top') and not o.returns range: @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length @@ -1059,11 +1078,13 @@ exports.ForNode: class ForNode extends BaseNode statement ForNode +#### IfNode -# 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. +# *If/else* statements. Our *switch/when* will be compiled into this. Acts as an +# expression by pushing down requested returns to the last line of each clause. +# +# Single-expression **IfNodes** are compiled into ternary operators if possible, +# because ternaries are already proper expressions, and don't need conversion. exports.IfNode: class IfNode extends BaseNode type: 'If' @@ -1076,6 +1097,8 @@ exports.IfNode: class IfNode extends BaseNode @multiple: true if @condition instanceof Array @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert + # Add a new *else* clause to this **IfNode**, or push it down to the bottom + # of the chain recursively. push: (else_body) -> eb: else_body.unwrap() if @else_body then @else_body.push(eb) else @else_body: eb @@ -1085,12 +1108,14 @@ exports.IfNode: class IfNode extends BaseNode @tags.statement: true this - # Tag a chain of IfNodes with their switch condition for equality. + # Tag a chain of **IfNodes** with their object(s) to switch on for equality + # tests. `rewrite_switch` will perform the actual change at compile time. rewrite_condition: (expression) -> @switcher: expression this - # Rewrite a chain of IfNodes with their switch condition for equality. + # Rewrite a chain of **IfNodes** with their switch condition for equality. + # Ensure that the switch expression isn't evaluated more than once. rewrite_switch: (o) -> assigner: @switcher if not (@switcher.unwrap() instanceof LiteralNode) @@ -1105,7 +1130,7 @@ exports.IfNode: class IfNode extends BaseNode @else_body.rewrite_condition(@switcher) if @is_chain() this - # Rewrite a chain of IfNodes to add a default case as the final else. + # 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 @@ -1114,12 +1139,12 @@ exports.IfNode: class IfNode extends BaseNode @children.push @else_body: exprs this - # If the else_body is an IfNode itself, then we've got an if-else chain. + # 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. + # The **IfNode** only compiles into a statement if either of its bodies needs + # to be a statement. Otherwise a ternary is safe. is_statement: -> @statement ||= !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement())) @@ -1129,8 +1154,8 @@ exports.IfNode: class IfNode extends BaseNode 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 - # force sub-else bodies into statement form. + # Compile the **IfNode** as a regular *if-else* statement. Flattened chains + # force inner *else* bodies into statement form. compile_statement: (o) -> @rewrite_switch(o) if @switcher child: del o, 'chain_child' @@ -1150,7 +1175,7 @@ exports.IfNode: class IfNode extends BaseNode " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n$@tab}" "$if_part$else_part" - # Compile the IfNode into a ternary operator. + # Compile the IfNode as 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' @@ -1161,15 +1186,20 @@ exports.IfNode: class IfNode extends BaseNode # Tabs are two spaces for pretty printing. TAB: ' ' + +# Trim out all trailing whitespace, so that the generated code plays nice +# with Git. TRAILING_WHITESPACE: /\s+$/gm -# Keep the identifier regex in sync with the Lexer. -IDENTIFIER: /^[a-zA-Z$_](\w|\$)*$/ +# Keep this identifier regex in sync with the Lexer. +IDENTIFIER: /^[a-zA-Z$_](\w|\$)*$/ # Utility Functions # ----------------- -# Merge objects. +# Merge objects, returning a fresh copy with attributes from both sides. +# Used every time `compile` is called, to allow properties in the options hash +# to propagate down the tree without polluting other branches. merge: (options, overrides) -> fresh: {} (fresh[key]: val) for key, val of options @@ -1179,19 +1209,21 @@ merge: (options, overrides) -> # 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. +# Return a completely flattened version of an array. Handy for getting a +# list of `children`. 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. +# Delete a key from an object, returning the value. Useful when a node is +# looking for a particular method in an options hash. del: (obj, key) -> val: obj[key] delete obj[key] val -# Quickie helper for a generated LiteralNode. +# Handy helper for a generating LiteralNode. literal: (name) -> new LiteralNode(name)