From b0aec3cbe2131c12bbea68d4e63354ce947133c1 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas
Throw an exception.
exports.ThrowNode: class ThrowNode extends BaseNode
+statement TryNodeSimple 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. Check an expression for existence (meaning not null or undefined).
exports.ExistenceNode: class ExistenceNode extends BaseNode
+statement ThrowNodeChecks 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)"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. 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)"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. 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. 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 ForNodeIf/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.invertAdd 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
- thisTag a chain of IfNodes with their switch condition for equality.
rewrite_condition: (expression) ->
+ thisTag 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
- thisRewrite a chain of IfNodes with their switch condition for equality.
rewrite_switch: (o) ->
+ thisRewrite 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()
- thisRewrite a chain of IfNodes to add a default case as the final else.
add_else: (exprs, statement) ->
+ thisRewrite 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
- thisIf the else_body is an IfNode itself, then we've got an if-else chain.
is_chain: ->
- @chain ||= @else_body and @else_body instanceof IfNodeThe IfNode only compiles into a statement if either of the bodies needs -to be a statement.
is_statement: ->
+ thisIf the else_body is an IfNode itself, then we've got an if-else chain.
is_chain: ->
+ @chain ||= @else_body and @else_body instanceof IfNodeThe 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. 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"Tabs are two spaces for pretty printing.
TAB: ' '
-TRAILING_WHITESPACE: /\s+$/gmKeep the identifier regex in sync with the Lexer.
IDENTIFIER: /^[a-zA-Z$_](\w|\$)*$/Merge objects.
merge: (options, overrides) ->
+ "$if_part : $else_part"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+$/gmKeep this identifier regex in sync with the Lexer.
IDENTIFIER: /^[a-zA-Z$_](\w|\$)*$/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
- freshTrim out all falsy values from an array.
compact: (array) -> item for item in array when itemReturn a completely flattened version of an array.
flatten: (array) ->
+ freshTrim out all falsy values from an array.
compact: (array) -> item for item in array when itemReturn 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)
- memoDelete a key from an object, returning the value.
del: (obj, key) ->
+ memoDelete 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]
- valQuickie helper for a generated LiteralNode.
literal: (name) ->
+ valHandy helper for a generating LiteralNode.
literal: (name) ->
new LiteralNode(name)