From 094b198d5d53156caebf0e7e127058fc7d3c1912 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 7 Mar 2010 16:41:06 -0500 Subject: [PATCH] first little bit of commenting the nodes.coffee -- with some slight refactors --- documentation/docs/nodes.html | 198 ++++++++++++++------------- lib/nodes.js | 249 ++++++++++++++++++---------------- src/nodes.coffee | 207 +++++++++++++++------------- test/test_expressions.coffee | 2 +- 4 files changed, 351 insertions(+), 305 deletions(-) diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html index fb709ccc..40fc487b 100644 --- a/documentation/docs/nodes.html +++ b/documentation/docs/nodes.html @@ -1,109 +1,108 @@ - nodes.coffee

nodes.coffee

#
if process?
+      nodes.coffee           
push: (arg)->@args.push(arg)@children.push(arg) - thiscode: arg.compileocode: ifarginstanceofSplatNodethencodeelse"[$code]"ifiis0thencodeelse".concat($code)" - "$@prefix$meth.apply($obj, ${ args.join('') })" '''constructor: (child,parent)-> - @children: [@child: child,@parent: parent]intro: "($@from_var <= $@to_var ? $idx"compare: "$intro <$equals $@to_var : $idx >$equals $@to_var)"incr: "$intro += $step : $idx -= $step)" - "$vars; $compare; $incr"return@compile_assignment(o)if@ASSIGNMENT.indexOf(@operator)>=0return@compile_unary(o)if@is_unary()return@compile_existence(o)if@operatoris'?' - [@first.compile(o),@operator,@second.compile(o)].join' 'space: if@PREFIX_OPERATORS.indexOf(@operator)>=0then' 'else''parts: [@operator,space,@first.compile(o)]parts: parts.reverse()if@flip - parts.join('')finally_part: (@ensureor'')and' finally {\n'+@ensure.compile(merge(o,{returns: null}))+"\n${@idt()}}""${@idt()}try {\n$attempt_part\n${@idt()}}$catch_part$finally_part" -statementTryNodecompile_node: (o)->"${@idt()}throw ${@expression.compile(o)};" -statementThrowNode,trueifvariableinstanceofCallNodeor(variableinstanceofValueNodeandvariable.has_properties())[first,second]:variable.compile_reference(o)[first,second]:[first.compile(o),second.compile(o)] - "(typeof $first !== \"undefined\" && $second !== null)"returncodeif@is_statement()l: code.lengthcode: code.substr(o,l-1)ifcode.substr(l-1,1)is';' - "($code)"close: if@objectthen'}}\n'else'}\n'"$set_result${source_part}for ($for_part) {\n$var_part$body\n${@idt()}$close${@idt()}$return_result" -statementForNode

nodes.coffee

#

nodes.coffee contains all of the node classes for the syntax tree. Most +nodes are created as the result of actions in the grammar, +but some are created by other nodes as a method of code generation. To convert +the syntax tree into a string of JavaScript code, call compile() on the root.

#

Set up for both Node.js and the browser, by +including the Scope class.

if process?
   process.mixin require 'scope'
 else
-  this.exports: this
#

Some helper functions

#

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|\$)*$/
#

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) ->
-  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) ->
-  val: obj[key]
-  delete obj[key]
-  val
#

Quickie helper for a generated LiteralNode.

literal: (name) ->
-  new LiteralNode(name)
#

Mark a node as a statement, or a statement only.

statement: (klass, only) ->
+  this.exports: this
#

Helper function that marks a node as a JavaScript statement, or as a +pure_statement. Statements must be wrapped in a closure when used as an +expression, and nodes tagged as pure_statement cannot be closure-wrapped +without losing their meaning.

statement: (klass, only) ->
   klass::is_statement: -> true
-  (klass::is_statement_only: -> true) if only
#

The abstract base class for all CoffeeScript nodes. -All nodes are implement a "compile_node" method, which performs the -code generation for that node. To compile a node, call the "compile" -method, which wraps "compile_node" in some extra smarts, to know when the -generated code should be wrapped up in a closure. An options hash is passed -and cloned throughout, containing messages from higher in the AST, -information about the current scope, and indentation level.

exports.BaseNode: class BaseNode
#

This is extremely important -- we convert JS statements into expressions -by wrapping them in a closure, only if it's possible, and we're not at + (klass::is_pure_statement: -> true) if only

#

The BaseNode is the abstract base class for all nodes in the syntax tree. +Each subclass implements the compile_node method, which performs the +code generation for that node. To compile a node to JavaScript, +call compile on it, which wraps compile_node in some generic extra smarts, +to know when the generated code needs to be wrapped up in a closure. +An options hash is passed and cloned throughout, containing information about +the environment from higher in the tree (such as if a returned value is +being requested by the surrounding function), information about the current +scope, and indentation level.

exports.BaseNode: class BaseNode
#

Common logic for determining whether to wrap this node in a closure before +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.

  compile: (o) ->
+already been asked to return the result (because statements know how to
+return results).

  compile: (o) ->
     @options: merge o or {}
     @indent:  o.indent
     del @options, 'operation' unless @operation_sensitive()
     top:      if @top_sensitive() then @options.top else del @options, 'top'
-    closure:  @is_statement() and not @is_statement_only() and not top and
+    closure:  @is_statement() and not @is_pure_statement() and not top and
               not @options.returns and not (this instanceof CommentNode) and
-              not @contains (node) -> node.is_statement_only()
-    if closure then @compile_closure(@options) else @compile_node(@options)
#

Statements converted into expressions share scope with their parent -closure, to preserve JavaScript-style lexical scope.

  compile_closure: (o) ->
+              not @contains (node) -> node.is_pure_statement()
+    if closure then @compile_closure(@options) else @compile_node(@options)
#

Statements converted into expressions via closure-wrapping share a scope +object with their parent closure, to preserve the expected lexical scope.

  compile_closure: (o) ->
     @indent: o.indent
     o.shared_scope: o.scope
-    ClosureNode.wrap(this).compile(o)
#

If the code generation wishes to use the result of a complex expression -in multiple places, ensure that the expression is only ever evaluated once.

  compile_reference: (o) ->
+    ClosureNode.wrap(this).compile(o)
#

If the code generation wishes to use the result of a complex expression +in multiple places, ensure that the expression is only ever evaluated once, +by assigning it to a temporary variable.

  compile_reference: (o) ->
     reference: literal(o.scope.free_variable())
     compiled:  new AssignNode(reference, this)
-    [compiled, reference]
#

Quick short method for the current indentation level, plus tabbing in.

  idt: (tabs) ->
+    [compiled, reference]
#

Quick short method for the current indentation level, plus tabbing in.

  idt: (tabs) ->
     idt: (@indent || '')
     idt += TAB for i in [0...(tabs or 0)]
-    idt
#

Does this node, or any of its children, contain a node of a certain kind?

  contains: (block) ->
+    idt
#

Does this node, or any of its children, contain a node of a certain kind?

  contains: (block) ->
     for node in @children
       return true if block(node)
       return true if node.contains and node.contains block
-    false
#

Perform an in-order traversal of the AST.

  traverse: (block) ->
+    false
#

Perform an in-order traversal of the AST.

  traverse: (block) ->
     for node in @children
       block node
-      node.traverse block if node.traverse
#

toString representation of the node, for inspecting the parse tree.

  toString: (idt) ->
+      node.traverse block if node.traverse
#

toString representation of the node, for inspecting the parse tree.

  toString: (idt) ->
     idt ||= ''
-    '\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
#

Default implementations of the common node methods.

  unwrap:               -> this
+    '\n' + idt + @type + (child.toString(idt + TAB) for child in @children).join('')
#

Default implementations of the common node methods.

  unwrap:               -> this
   children:             []
   is_statement:         -> false
-  is_statement_only:    -> false
+  is_pure_statement:    -> false
   top_sensitive:        -> false
-  operation_sensitive:  -> false
#

A collection of nodes, each one representing an expression.

exports.Expressions: class Expressions extends BaseNode
+  operation_sensitive:  -> false
#

A collection of nodes, each one representing an expression.

exports.Expressions: class Expressions extends BaseNode
   type: 'Expressions'
 
   constructor: (nodes) ->
-    @children: @expressions: compact flatten nodes or []
#

Tack an expression on to the end of this expression list.

  push: (node) ->
+    @children: @expressions: compact flatten nodes or []
#

Tack an expression on to the end of this expression list.

  push: (node) ->
     @expressions.push(node)
-    this
#

Tack an expression on to the beginning of this expression list.

  unshift: (node) ->
+    this
#

Tack an expression on to the beginning of this expression list.

  unshift: (node) ->
     @expressions.unshift(node)
-    this
#

If this Expressions consists of a single node, pull it back out.

  unwrap: ->
-    if @expressions.length is 1 then @expressions[0] else this
#

Is this an empty block of code?

  empty: ->
-    @expressions.length is 0
#

Is the node last in this block of expressions?

  is_last: (node) ->
+    this
#

If this Expressions consists of a single node, pull it back out.

  unwrap: ->
+    if @expressions.length is 1 then @expressions[0] else this
#

Is this an empty block of code?

  empty: ->
+    @expressions.length is 0
#

Is the node last in this block of expressions?

  is_last: (node) ->
     l: @expressions.length
     last_index: if @expressions[l - 1] instanceof CommentNode then 2 else 1
     node is @expressions[l - last_index]
 
   compile: (o) ->
     o ||= {}
-    if o.scope then super(o) else @compile_root(o)
#

Compile each expression in the Expressions body.

  compile_node: (o) ->
-    (@compile_expression(node, merge(o)) for node in @expressions).join("\n")
#

If this is the top-level Expressions, wrap everything in a safety closure.

  compile_root: (o) ->
+    if o.scope then super(o) else @compile_root(o)
#

Compile each expression in the Expressions body.

  compile_node: (o) ->
+    (@compile_expression(node, merge(o)) for node in @expressions).join("\n")
#

If this is the top-level Expressions, wrap everything in a safety closure.

  compile_root: (o) ->
     o.indent: @indent: indent: if o.no_wrap then '' else TAB
     o.scope: new Scope(null, this, null)
     code: if o.globals then @compile_node(o) else @compile_with_declarations(o)
     code: code.replace(TRAILING_WHITESPACE, '')
-    if o.no_wrap then code else "(function(){\n$code\n})();\n"
#

Compile the expressions body, with declarations of all inner variables + if o.no_wrap then code else "(function(){\n$code\n})();\n"

#

Compile the expressions body, with declarations of all inner variables pushed up to the top.

  compile_with_declarations: (o) ->
     code: @compile_node(o)
     args: @contains (node) -> node instanceof ValueNode and node.is_arguments()
     code: "${@idt()}arguments = Array.prototype.slice.call(arguments, 0);\n$code" if args
     code: "${@idt()}var ${o.scope.compiled_assignments()};\n$code"  if o.scope.has_assignments(this)
     code: "${@idt()}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this)
-    code
#

Compiles a single expression within the expressions body.

  compile_expression: (node, o) ->
+    code
#

Compiles a single expression within the expressions body.

  compile_expression: (node, o) ->
     @indent: o.indent
-    stmt:    node.is_statement()
#

We need to return the result if this is the last node in the expressions body.

    returns: del(o, 'returns') and @is_last(node) and not node.is_statement_only()
#

Return the regular compile of the node, unless we need to return the result.

    return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns
#

If it's a statement, the node knows how to return itself.

    return node.compile(merge(o, {returns: true})) if node.is_statement()
#

Otherwise, we can just return the value of the expression.

    return "${@idt()}return ${node.compile(o)};"
#

Wrap up a node as an Expressions, unless it already is one.

Expressions.wrap: (nodes) ->
+    stmt:    node.is_statement()
#

We need to return the result if this is the last node in the expressions body.

    returns: del(o, 'returns') and @is_last(node) and not node.is_pure_statement()
#

Return the regular compile of the node, unless we need to return the result.

    return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns
#

If it's a statement, the node knows how to return itself.

    return node.compile(merge(o, {returns: true})) if node.is_statement()
#

Otherwise, we can just return the value of the expression.

    return "${@idt()}return ${node.compile(o)};"
#

Wrap up a node as an Expressions, unless it already is one.

Expressions.wrap: (nodes) ->
   return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
   new Expressions(nodes)
 
-statement Expressions
#

Literals are static values that can be passed through directly into +statement Expressions

#

Literals are static values that can be passed through directly into JavaScript without translation, eg.: strings, numbers, true, false, null...

exports.LiteralNode: class LiteralNode extends BaseNode
   type: 'Literal'
 
   constructor: (value) ->
-    @value: value
#

Break and continue must be treated as statements -- they lose their meaning + @value: value

#

Break and continue must be treated as statements -- they lose their meaning when wrapped in a closure.

  is_statement: ->
     @value is 'break' or @value is 'continue'
 
-  is_statement_only: LiteralNode::is_statement
+  is_pure_statement: LiteralNode::is_statement
 
   compile_node: (o) ->
     idt: if @is_statement() then @idt() else ''
@@ -111,7 +110,7 @@ when wrapped in a closure.

"$idt$@value$end" toString: (idt) -> - " \"$@value\""
#

Return an expression, or wrap it in a closure and return it.

exports.ReturnNode: class ReturnNode extends BaseNode
+    " \"$@value\""
#

Return an expression, or wrap it in a closure and return it.

exports.ReturnNode: class ReturnNode extends BaseNode
   type: 'Return'
 
   constructor: (expression) ->
@@ -121,7 +120,7 @@ when wrapped in a closure.

return @expression.compile(merge(o, {returns: true})) if @expression.is_statement() "${@idt()}return ${@expression.compile(o)};" -statement ReturnNode, true
#

A value, indexed or dotted into, or vanilla.

exports.ValueNode: class ValueNode extends BaseNode
+statement ReturnNode, true
#

A value, indexed or dotted into, or vanilla.

exports.ValueNode: class ValueNode extends BaseNode
   type: 'Value'
 
   SOAK: " == undefined ? undefined : "
@@ -153,7 +152,7 @@ when wrapped in a closure.

@base.value is 'arguments' unwrap: -> - if @properties.length then this else @base
#

Values are statements if their base is a statement.

  is_statement: ->
+    if @properties.length then this else @base
#

Values are statements if their base is a statement.

  is_statement: ->
     @base.is_statement and @base.is_statement() and not @has_properties()
 
   compile_node: (o) ->
@@ -180,7 +179,7 @@ when wrapped in a closure.

complete += part @last: part - if op and soaked then "($complete)" else complete
#

Pass through CoffeeScript comments into JavaScript comments at the + if op and soaked then "($complete)" else complete

#

Pass through CoffeeScript comments into JavaScript comments at the same position.

exports.CommentNode: class CommentNode extends BaseNode
   type: 'Comment'
 
@@ -191,7 +190,7 @@ same position.

compile_node: (o) -> "${@idt()}//" + @lines.join("\n${@idt()}//") -statement CommentNode
#

Node for a function invocation. Takes care of converting super() calls into +statement CommentNode

#

Node for a function invocation. Takes care of converting super() calls into calls against the prototype's function of the same name.

exports.CallNode: class CallNode extends BaseNode
   type: 'Call'
 
@@ -206,17 +205,17 @@ calls against the prototype's function of the same name.

#

Compile a vanilla function call.

  compile_node: (o) ->
+    this
#

Compile a vanilla function call.

  compile_node: (o) ->
     return @compile_splat(o) if @args[@args.length - 1] instanceof SplatNode
     args: (arg.compile(o) for arg in @args).join(', ')
     return @compile_super(args, o) if @variable is 'super'
-    "$@prefix${@variable.compile(o)}($args)"
#

Compile a call against the superclass's implementation of the current function.

  compile_super: (args, o) ->
+    "$@prefix${@variable.compile(o)}($args)"
#

Compile a call against the superclass's implementation of the current function.

  compile_super: (args, o) ->
     methname: o.scope.method.name
     meth: if o.scope.method.proto
       "${o.scope.method.proto}.__superClass__.$methname"
     else
       "$methname.__superClass__.constructor"
-    "$meth.call(this${ if args.length then ', ' else '' }$args)"
#

Compile a function call being passed variable arguments.

  compile_splat: (o) ->
+    "$meth.call(this${ if args.length then ', ' else '' }$args)"
#

Compile a function call being passed variable arguments.

  compile_splat: (o) ->
     meth: @variable.compile o
     obj:  @variable.source or 'this'
     if obj.match(/\(/)
@@ -227,7 +226,7 @@ calls against the prototype's function of the same name.

#

Node to extend an object's prototype with an ancestor object. + "$@prefix$meth.apply($obj, ${ args.join('') })"

#

Node to extend an object's prototype with an ancestor object. After goog.inherits from the Closure Library.

exports.ExtendsNode: class ExtendsNode extends BaseNode
   type: 'Extends'
 
@@ -242,11 +241,11 @@ After goog.inherits from the Closure Library.

#

Hooking one constructor into another's prototype chain.

  compile_node: (o) ->
+    @children:  [@child: child, @parent: parent]
#

Hooking one constructor into another's prototype chain.

  compile_node: (o) ->
     o.scope.assign('__extends', @code, true)
     ref:  new ValueNode literal('__extends')
     call: new CallNode ref, [@child, @parent]
-    call.compile(o)
#

A dotted accessor into a part of a value, or the :: shorthand for + call.compile(o)

#

A dotted accessor into a part of a value, or the :: shorthand for an accessor into the object's prototype.

exports.AccessorNode: class AccessorNode extends BaseNode
   type: 'Accessor'
 
@@ -257,7 +256,7 @@ an accessor into the object's prototype.

this compile_node: (o) -> - '.' + (if @prototype then 'prototype.' else '') + @name.compile(o)
#

An indexed accessor into a part of an array or object.

exports.IndexNode: class IndexNode extends BaseNode
+    '.' + (if @prototype then 'prototype.' else '') + @name.compile(o)
#

An indexed accessor into a part of an array or object.

exports.IndexNode: class IndexNode extends BaseNode
   type: 'Index'
 
   constructor: (index, tag) ->
@@ -266,7 +265,7 @@ an accessor into the object's prototype.

compile_node: (o) -> idx: @index.compile o - "[$idx]"
#

A range literal. Ranges can be used to extract portions (slices) of arrays, + "[$idx]"

#

A range literal. Ranges can be used to extract portions (slices) of arrays, or to specify a range for list comprehensions.

exports.RangeNode: class RangeNode extends BaseNode
   type: 'Range'
 
@@ -290,13 +289,13 @@ or to specify a range for list comprehensions.

#

Expand the range into the equivalent array, if it's not being used as + "$vars; $compare; $incr"

#

Expand the range into the equivalent array, if it's not being used as part of a comprehension, slice, or splice. TODO: This generates pretty ugly code ... shrink it.

  compile_array: (o) ->
     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 + (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) is the index of the beginning.

exports.SliceNode: class SliceNode extends BaseNode
   type: 'Slice'
@@ -309,11 +308,11 @@ 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)"
#

An object literal.

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) ->
     o.indent: @idt(1)
@@ -327,7 +326,7 @@ 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
+    "{$inner}"
#

A class literal, including optional superclass and constructor.

exports.ClassNode: class ClassNode extends BaseNode
   type: 'Class'
 
   constructor: (variable, parent, props) ->
@@ -366,7 +365,7 @@ 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
+statement ClassNode
#

An array literal.

exports.ArrayNode: class ArrayNode extends BaseNode
   type: 'Array'
 
   constructor: (objects) ->
@@ -384,24 +383,24 @@ commas affixed to comments. TODO: Extract this and add it to ArrayNode.

"$code, " objects: objects.join('') ending: if objects.indexOf('\n') >= 0 then "\n${@idt()}]" else ']' - "[$objects$ending"
#

A faux-node that is never created by the grammar, but is used during + "[$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: {
 
   wrap: (array, expressions) ->
     expr: expressions.unwrap()
-    return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
+    return expressions if expr.is_pure_statement() or expr.contains (n) -> n.is_pure_statement()
     Expressions.wrap([new CallNode(
       new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr]
     )])
 
-}
#

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

ClosureNode: exports.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
+}
#

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/
@@ -439,7 +438,7 @@ code generation to generate a quick "array.push(value)" tree of nodes.

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

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

#

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

#

A function definition. The only node that creates a new Scope. + "$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
   type: 'Code'
 
@@ -514,7 +513,7 @@ A CodeNode does not have any children -- they're within the new scope.

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"

#

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'
 
@@ -531,7 +530,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'
 
@@ -563,7 +562,7 @@ it, all other loops can be manufactured.

@body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter "$pre {\n${ @body.compile(o) }\n${@idt()}}$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'
 
@@ -599,7 +598,7 @@ CoffeeScript operations into their JavaScript equivalents.

#

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
@@ -621,7 +620,7 @@ CoffeeScript operations into their JavaScript equivalents.

#

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) ->
@@ -638,7 +637,7 @@ CoffeeScript operations into their JavaScript equivalents.

#

Throw an exception.

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

Throw an exception.

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

#

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) ->
@@ -661,7 +660,7 @@ CoffeeScript operations into their JavaScript equivalents.

#

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) ->
@@ -675,7 +674,7 @@ CoffeeScript operations into their JavaScript equivalents.

#

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
@@ -742,7 +741,7 @@ the current index of the loop as a second parameter.

#

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
@@ -764,9 +763,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())
@@ -778,14 +777,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()))
 
@@ -793,7 +792,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'
@@ -811,9 +810,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${@idt()}}" - "$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"
+    "$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) ->
+  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) ->
+  val: obj[key]
+  delete obj[key]
+  val
#

Quickie helper for a generated LiteralNode.

literal: (name) ->
+  new LiteralNode(name)
 
 
\ No newline at end of file diff --git a/lib/nodes.js b/lib/nodes.js index 9e259c84..9a10e585 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1,127 +1,83 @@ (function(){ var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, literal, merge, statement; - var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + var __extends = function(child, parent) { var ctor = function(){ }; ctor.prototype = parent.prototype; child.__superClass__ = parent.prototype; child.prototype = new ctor(); child.prototype.constructor = child; - }; + }, __hasProp = Object.prototype.hasOwnProperty; + // `nodes.coffee` contains all of the node classes for the syntax tree. Most + // nodes are created as the result of actions in the [grammar](grammar.html), + // but some are created by other nodes as a method of code generation. To convert + // the syntax tree into a string of JavaScript code, call `compile()` on the root. + // Set up for both **Node.js** and the browser, by + // including the [Scope](scope.html) class. (typeof process !== "undefined" && process !== null) ? process.mixin(require('scope')) : (this.exports = this); - // Some helper functions - // 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|\$)*$/; - // Merge objects. - merge = function merge(options, overrides) { - var _a, _b, fresh, key, val; - fresh = {}; - _a = options; - for (key in _a) { if (__hasProp.call(_a, key)) { - val = _a[key]; - ((fresh[key] = val)); - }} - if (overrides) { - _b = overrides; - for (key in _b) { if (__hasProp.call(_b, key)) { - val = _b[key]; - ((fresh[key] = val)); - }} - } - return fresh; - }; - // Trim out all falsy values from an array. - compact = function compact(array) { - var _a, _b, _c, _d, item; - _a = []; _b = array; - for (_c = 0, _d = _b.length; _c < _d; _c++) { - item = _b[_c]; - if (item) { - _a.push(item); - } - } - return _a; - }; - // Return a completely flattened version of an array. - flatten = function flatten(array) { - var _a, _b, _c, item, memo; - memo = []; - _a = array; - for (_b = 0, _c = _a.length; _b < _c; _b++) { - item = _a[_b]; - item instanceof Array ? (memo = memo.concat(item)) : memo.push(item); - } - return memo; - }; - // Delete a key from an object, returning the value. - del = function del(obj, key) { - var val; - val = obj[key]; - delete obj[key]; - return val; - }; - // Quickie helper for a generated LiteralNode. - literal = function literal(name) { - return new LiteralNode(name); - }; - // Mark a node as a statement, or a statement only. + // Helper function that marks a node as a JavaScript *statement*, or as a + // *pure_statement*. Statements must be wrapped in a closure when used as an + // expression, and nodes tagged as *pure_statement* cannot be closure-wrapped + // without losing their meaning. statement = function statement(klass, only) { klass.prototype.is_statement = function is_statement() { return true; }; if (only) { - return ((klass.prototype.is_statement_only = function is_statement_only() { + return ((klass.prototype.is_pure_statement = function is_pure_statement() { return true; })); } }; - // The abstract base class for all CoffeeScript nodes. - // All nodes are implement a "compile_node" method, which performs the - // code generation for that node. To compile a node, call the "compile" - // method, which wraps "compile_node" in some extra smarts, to know when the - // generated code should be wrapped up in a closure. An options hash is passed - // and cloned throughout, containing messages from higher in the AST, - // information about the current scope, and indentation level. + // The **BaseNode** is the abstract base class for all nodes in the syntax tree. + // Each subclass implements the `compile_node` method, which performs the + // code generation for that node. To compile a node to JavaScript, + // call `compile` on it, which wraps `compile_node` in some generic extra smarts, + // to know when the generated code needs to be wrapped up in a closure. + // An options hash is passed and cloned throughout, containing information about + // the environment from higher in the tree (such as if a returned value is + // being requested by the surrounding function), information about the current + // scope, and indentation level. exports.BaseNode = (function() { BaseNode = function BaseNode() { }; - // This is extremely important -- we convert JS statements into expressions - // by wrapping them in a closure, only if it's possible, and we're not at + // Common logic for determining whether to wrap this node in a closure before + // 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. + // already been asked to return the result (because statements know how to + // return results). BaseNode.prototype.compile = function compile(o) { var closure, top; this.options = merge(o || {}); - this.indent = o.indent; + this.tab = o.indent; if (!(this.operation_sensitive())) { del(this.options, 'operation'); } top = this.top_sensitive() ? this.options.top : del(this.options, 'top'); - closure = this.is_statement() && !this.is_statement_only() && !top && !this.options.returns && !(this instanceof CommentNode) && !this.contains(function(node) { - return node.is_statement_only(); + closure = this.is_statement() && !this.is_pure_statement() && !top && !this.options.returns && !(this instanceof CommentNode) && !this.contains(function(node) { + return node.is_pure_statement(); }); return closure ? this.compile_closure(this.options) : this.compile_node(this.options); }; - // Statements converted into expressions share scope with their parent - // closure, to preserve JavaScript-style lexical scope. + // Statements converted into expressions via closure-wrapping share a scope + // object with their parent closure, to preserve the expected lexical scope. BaseNode.prototype.compile_closure = function compile_closure(o) { - this.indent = o.indent; + this.tab = o.indent; o.shared_scope = o.scope; return ClosureNode.wrap(this).compile(o); }; // If the code generation wishes to use the result of a complex expression - // in multiple places, ensure that the expression is only ever evaluated once. + // in multiple places, ensure that the expression is only ever evaluated once, + // by assigning it to a temporary variable. BaseNode.prototype.compile_reference = function compile_reference(o) { var compiled, reference; reference = literal(o.scope.free_variable()); compiled = new AssignNode(reference, this); return [compiled, reference]; }; - // Quick short method for the current indentation level, plus tabbing in. + // Convenience method to grab the current indentation level, plus tabbing in. BaseNode.prototype.idt = function idt(tabs) { var _a, _b, _c, _d, i, idt; - idt = (this.indent || ''); + idt = this.tab || ''; _c = 0; _d = (tabs || 0); for (_b = 0, i = _c; (_c <= _d ? i < _d : i > _d); (_c <= _d ? i += 1 : i -= 1), _b++) { idt += TAB; @@ -179,7 +135,7 @@ BaseNode.prototype.is_statement = function is_statement() { return false; }; - BaseNode.prototype.is_statement_only = function is_statement_only() { + BaseNode.prototype.is_pure_statement = function is_pure_statement() { return false; }; BaseNode.prototype.top_sensitive = function top_sensitive() { @@ -241,8 +197,8 @@ }; // If this is the top-level Expressions, wrap everything in a safety closure. Expressions.prototype.compile_root = function compile_root(o) { - var code, indent; - o.indent = (this.indent = (indent = o.no_wrap ? '' : TAB)); + var code; + o.indent = (this.tab = o.no_wrap ? '' : TAB); o.scope = new Scope(null, this, null); code = o.globals ? this.compile_node(o) : this.compile_with_declarations(o); code = code.replace(TRAILING_WHITESPACE, ''); @@ -257,23 +213,23 @@ return node instanceof ValueNode && node.is_arguments(); }); if (args) { - code = (this.idt()) + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code; + code = (this.tab) + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code; } if (o.scope.has_assignments(this)) { - code = (this.idt()) + "var " + (o.scope.compiled_assignments()) + ";\n" + code; + code = (this.tab) + "var " + (o.scope.compiled_assignments()) + ";\n" + code; } if (o.scope.has_declarations(this)) { - code = (this.idt()) + "var " + (o.scope.compiled_declarations()) + ";\n" + code; + code = (this.tab) + "var " + (o.scope.compiled_declarations()) + ";\n" + code; } return code; }; // Compiles a single expression within the expressions body. Expressions.prototype.compile_expression = function compile_expression(node, o) { var returns, stmt; - this.indent = o.indent; + this.tab = o.indent; stmt = node.is_statement(); // We need to return the result if this is the last node in the expressions body. - returns = del(o, 'returns') && this.is_last(node) && !node.is_statement_only(); + returns = del(o, 'returns') && this.is_last(node) && !node.is_pure_statement(); // Return the regular compile of the node, unless we need to return the result. if (!(returns)) { return (stmt ? '' : this.idt()) + node.compile(merge(o, { @@ -287,7 +243,7 @@ })); } // Otherwise, we can just return the value of the expression. - return (this.idt()) + "return " + (node.compile(o)) + ";"; + return (this.tab) + "return " + (node.compile(o)) + ";"; }; return Expressions; }).call(this); @@ -313,7 +269,7 @@ LiteralNode.prototype.is_statement = function is_statement() { return this.value === 'break' || this.value === 'continue'; }; - LiteralNode.prototype.is_statement_only = LiteralNode.prototype.is_statement; + LiteralNode.prototype.is_pure_statement = LiteralNode.prototype.is_statement; LiteralNode.prototype.compile_node = function compile_node(o) { var end, idt; idt = this.is_statement() ? this.idt() : ''; @@ -339,7 +295,7 @@ returns: true })); } - return (this.idt()) + "return " + (this.expression.compile(o)) + ";"; + return (this.tab) + "return " + (this.expression.compile(o)) + ";"; }; return ReturnNode; }).call(this); @@ -428,7 +384,7 @@ __extends(CommentNode, BaseNode); CommentNode.prototype.type = 'Comment'; CommentNode.prototype.compile_node = function compile_node(o) { - return (this.idt()) + "//" + this.lines.join("\n" + (this.idt()) + "//"); + return this.tab + "//" + this.lines.join("\n" + this.tab + "//"); }; return CommentNode; }).call(this); @@ -569,14 +525,14 @@ RangeNode.prototype.type = 'Range'; RangeNode.prototype.compile_variables = function compile_variables(o) { var _a, _b, from, to; - this.indent = o.indent; + this.tab = o.indent; _a = [o.scope.free_variable(), o.scope.free_variable()]; this.from_var = _a[0]; this.to_var = _a[1]; _b = [this.from.compile(o), this.to.compile(o)]; from = _b[0]; to = _b[1]; - return this.from_var + " = " + from + "; " + this.to_var + " = " + to + ";\n" + (this.idt()); + return this.from_var + " = " + from + "; " + this.to_var + " = " + to + ";\n" + this.tab; }; RangeNode.prototype.compile_node = function compile_node(o) { var compare, equals, idx, incr, intro, step, vars; @@ -753,7 +709,7 @@ return _a; }).call(this); objects = objects.join(''); - ending = objects.indexOf('\n') >= 0 ? "\n" + (this.idt()) + "]" : ']'; + ending = objects.indexOf('\n') >= 0 ? "\n" + this.tab + "]" : ']'; return "[" + objects + ending; }; return ArrayNode; @@ -764,8 +720,8 @@ wrap: function wrap(array, expressions) { var expr; expr = expressions.unwrap(); - if (expr.is_statement_only() || expr.contains(function(n) { - return n.is_statement_only(); + if (expr.is_pure_statement() || expr.contains(function(n) { + return n.is_pure_statement(); })) { return expressions; } @@ -832,13 +788,13 @@ } val = name + " = " + val; if (stmt) { - return (this.idt()) + val + ";"; + return this.tab + val + ";"; } if (!top || o.returns) { val = "(" + val + ")"; } if (o.returns) { - val = (this.idt()) + "return " + val; + val = (this.tab) + "return " + val; } return val; }; @@ -849,7 +805,7 @@ var _a, _b, _c, access_class, assigns, code, i, idx, obj, val, val_var, value; val_var = o.scope.free_variable(); value = this.value.is_statement() ? ClosureNode.wrap(this.value) : this.value; - assigns = [(this.idt()) + val_var + " = " + (value.compile(o)) + ";"]; + assigns = [this.tab + val_var + " = " + (value.compile(o)) + ";"]; o.top = true; o.as_statement = true; _a = this.variable.base.objects; @@ -874,7 +830,7 @@ } code = assigns.join("\n"); if (o.returns) { - code += "\n" + (this.idt()) + "return " + (this.variable.compile(o)) + ";"; + code += "\n" + (this.tab) + "return " + (this.variable.compile(o)) + ";"; } return code; }; @@ -942,7 +898,7 @@ return func; } inner = "(function" + name_part + "() {\n" + (this.idt(2)) + "return __func.apply(__this, arguments);\n" + (this.idt(1)) + "});"; - return "(function(__this) {\n" + (this.idt(1)) + "var __func = " + func + ";\n" + (this.idt(1)) + "return " + inner + "\n" + (this.idt()) + "})(this)"; + return "(function(__this) {\n" + (this.idt(1)) + "var __func = " + func + ";\n" + (this.idt(1)) + "return " + inner + "\n" + this.tab + "})(this)"; }; CodeNode.prototype.top_sensitive = function top_sensitive() { return true; @@ -1029,20 +985,20 @@ set = ''; if (!top) { rvar = o.scope.free_variable(); - set = (this.idt()) + rvar + " = [];\n"; + set = this.tab + rvar + " = [];\n"; if (this.body) { this.body = PushNode.wrap(rvar, this.body); } } - post = returns ? "\n" + (this.idt()) + "return " + rvar + ";" : ''; - pre = set + (this.idt()) + "while (" + cond + ")"; + post = returns ? "\n" + (this.tab) + "return " + rvar + ";" : ''; + pre = set + (this.tab) + "while (" + cond + ")"; if (!this.body) { return pre + " null;" + post; } if (this.filter) { this.body = Expressions.wrap([new IfNode(this.filter, this.body)]); } - return pre + " {\n" + (this.body.compile(o)) + "\n" + (this.idt()) + "}" + post; + return pre + " {\n" + (this.body.compile(o)) + "\n" + this.tab + "}" + post; }; return WhileNode; }).call(this); @@ -1157,11 +1113,11 @@ o.top = true; attempt_part = this.attempt.compile(o); error_part = this.error ? " (" + (this.error.compile(o)) + ") " : ' '; - catch_part = ((this.recovery || '') && ' catch') + error_part + "{\n" + (this.recovery.compile(o)) + "\n" + (this.idt()) + "}"; + catch_part = ((this.recovery || '') && ' catch') + error_part + "{\n" + (this.recovery.compile(o)) + "\n" + this.tab + "}"; finally_part = (this.ensure || '') && ' finally {\n' + this.ensure.compile(merge(o, { returns: null - })) + "\n" + (this.idt()) + "}"; - return (this.idt()) + "try {\n" + attempt_part + "\n" + (this.idt()) + "}" + catch_part + finally_part; + })) + "\n" + this.tab + "}"; + return (this.tab) + "try {\n" + attempt_part + "\n" + this.tab + "}" + catch_part + finally_part; }; return TryNode; }).call(this); @@ -1175,7 +1131,7 @@ __extends(ThrowNode, BaseNode); ThrowNode.prototype.type = 'Throw'; ThrowNode.prototype.compile_node = function compile_node(o) { - return (this.idt()) + "throw " + (this.expression.compile(o)) + ";"; + return (this.tab) + "throw " + (this.expression.compile(o)) + ";"; }; return ThrowNode; }).call(this); @@ -1288,7 +1244,7 @@ for_part = index_var + " = 0, " + for_part + ", " + index_var + "++"; } else { index_var = null; - source_part = svar + " = " + (this.source.compile(o)) + ";\n" + (this.idt()); + source_part = svar + " = " + (this.source.compile(o)) + ";\n" + this.tab; if (name) { var_part = body_dent + name + " = " + svar + "[" + ivar + "];\n"; } @@ -1324,7 +1280,7 @@ for_part = ivar + " in " + svar + ") { if (__hasProp.call(" + svar + ", " + ivar + ")"; } if (!(top_level)) { - return_result = "\n" + (this.idt()) + return_result + ";"; + return_result = "\n" + this.tab + return_result + ";"; } body = body.compile(merge(o, { indent: body_dent, @@ -1332,7 +1288,7 @@ })); vars = range ? name : name + ", " + ivar; close = this.object ? '}}\n' : '}\n'; - return set_result + (source_part) + "for (" + for_part + ") {\n" + var_part + body + "\n" + (this.idt()) + close + (this.idt()) + return_result; + return set_result + (source_part) + "for (" + for_part + ") {\n" + var_part + body + "\n" + this.tab + close + this.tab + return_result; }; return ForNode; }).call(this); @@ -1450,14 +1406,14 @@ com_dent = child ? this.idt() : ''; prefix = this.comment ? (this.comment.compile(cond_o)) + "\n" + com_dent : ''; body = Expressions.wrap([this.body]).compile(o); - if_part = prefix + (if_dent) + "if (" + (this.compile_condition(cond_o)) + ") {\n" + body + "\n" + (this.idt()) + "}"; + if_part = prefix + (if_dent) + "if (" + (this.compile_condition(cond_o)) + ") {\n" + body + "\n" + this.tab + "}"; if (!(this.else_body)) { return if_part; } else_part = this.is_chain() ? ' else ' + this.else_body.compile(merge(o, { indent: this.idt(), chain_child: true - })) : " else {\n" + (Expressions.wrap([this.else_body]).compile(o)) + "\n" + (this.idt()) + "}"; + })) : " else {\n" + (Expressions.wrap([this.else_body]).compile(o)) + "\n" + this.tab + "}"; return if_part + else_part; }; // Compile the IfNode into a ternary operator. @@ -1469,4 +1425,65 @@ }; return IfNode; }).call(this); + // 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 = function merge(options, overrides) { + var _a, _b, fresh, key, val; + fresh = {}; + _a = options; + for (key in _a) { if (__hasProp.call(_a, key)) { + val = _a[key]; + ((fresh[key] = val)); + }} + if (overrides) { + _b = overrides; + for (key in _b) { if (__hasProp.call(_b, key)) { + val = _b[key]; + ((fresh[key] = val)); + }} + } + return fresh; + }; + // Trim out all falsy values from an array. + compact = function compact(array) { + var _a, _b, _c, _d, item; + _a = []; _b = array; + for (_c = 0, _d = _b.length; _c < _d; _c++) { + item = _b[_c]; + if (item) { + _a.push(item); + } + } + return _a; + }; + // Return a completely flattened version of an array. + flatten = function flatten(array) { + var _a, _b, _c, item, memo; + memo = []; + _a = array; + for (_b = 0, _c = _a.length; _b < _c; _b++) { + item = _a[_b]; + item instanceof Array ? (memo = memo.concat(item)) : memo.push(item); + } + return memo; + }; + // Delete a key from an object, returning the value. + del = function del(obj, key) { + var val; + val = obj[key]; + delete obj[key]; + return val; + }; + // Quickie helper for a generated LiteralNode. + literal = function literal(name) { + return new LiteralNode(name); + }; })(); diff --git a/src/nodes.coffee b/src/nodes.coffee index b6de32f5..e143e4d7 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1,91 +1,68 @@ +# `nodes.coffee` contains all of the node classes for the syntax tree. Most +# nodes are created as the result of actions in the [grammar](grammar.html), +# but some are created by other nodes as a method of code generation. To convert +# the syntax tree into a string of JavaScript code, call `compile()` on the root. + +# Set up for both **Node.js** and the browser, by +# including the [Scope](scope.html) class. if process? process.mixin require 'scope' else this.exports: this -# Some helper functions - -# 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|\$)*$/ - - -# 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) -> - 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) -> - val: obj[key] - delete obj[key] - val - -# Quickie helper for a generated LiteralNode. -literal: (name) -> - new LiteralNode(name) - -# Mark a node as a statement, or a statement only. +# Helper function that marks a node as a JavaScript *statement*, or as a +# *pure_statement*. Statements must be wrapped in a closure when used as an +# expression, and nodes tagged as *pure_statement* cannot be closure-wrapped +# without losing their meaning. statement: (klass, only) -> klass::is_statement: -> true - (klass::is_statement_only: -> true) if only + (klass::is_pure_statement: -> true) if only - -# The abstract base class for all CoffeeScript nodes. -# All nodes are implement a "compile_node" method, which performs the -# code generation for that node. To compile a node, call the "compile" -# method, which wraps "compile_node" in some extra smarts, to know when the -# generated code should be wrapped up in a closure. An options hash is passed -# and cloned throughout, containing messages from higher in the AST, -# information about the current scope, and indentation level. +# The **BaseNode** is the abstract base class for all nodes in the syntax tree. +# Each subclass implements the `compile_node` method, which performs the +# code generation for that node. To compile a node to JavaScript, +# call `compile` on it, which wraps `compile_node` in some generic extra smarts, +# to know when the generated code needs to be wrapped up in a closure. +# An options hash is passed and cloned throughout, containing information about +# the environment from higher in the tree (such as if a returned value is +# being requested by the surrounding function), information about the current +# scope, and indentation level. exports.BaseNode: class BaseNode - # This is extremely important -- we convert JS statements into expressions - # by wrapping them in a closure, only if it's possible, and we're not at + # Common logic for determining whether to wrap this node in a closure before + # 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. + # already been asked to return the result (because statements know how to + # return results). compile: (o) -> @options: merge o or {} - @indent: o.indent + @tab: o.indent del @options, 'operation' unless @operation_sensitive() top: if @top_sensitive() then @options.top else del @options, 'top' - closure: @is_statement() and not @is_statement_only() and not top and + closure: @is_statement() and not @is_pure_statement() and not top and not @options.returns and not (this instanceof CommentNode) and - not @contains (node) -> node.is_statement_only() + not @contains (node) -> node.is_pure_statement() if closure then @compile_closure(@options) else @compile_node(@options) - # Statements converted into expressions share scope with their parent - # closure, to preserve JavaScript-style lexical scope. + # Statements converted into expressions via closure-wrapping share a scope + # object with their parent closure, to preserve the expected lexical scope. compile_closure: (o) -> - @indent: o.indent + @tab: o.indent o.shared_scope: o.scope - ClosureNode.wrap(this).compile(o) + ClosureNode.wrap(this).compile o # If the code generation wishes to use the result of a complex expression - # in multiple places, ensure that the expression is only ever evaluated once. + # in multiple places, ensure that the expression is only ever evaluated once, + # by assigning it to a temporary variable. compile_reference: (o) -> - reference: literal(o.scope.free_variable()) - compiled: new AssignNode(reference, this) + reference: literal o.scope.free_variable() + compiled: new AssignNode reference, this [compiled, reference] - # Quick short method for the current indentation level, plus tabbing in. + # Convenience method to grab the current indentation level, plus tabbing in. idt: (tabs) -> - idt: (@indent || '') + idt: @tab or '' idt += TAB for i in [0...(tabs or 0)] idt @@ -111,7 +88,7 @@ exports.BaseNode: class BaseNode unwrap: -> this children: [] is_statement: -> false - is_statement_only: -> false + is_pure_statement: -> false top_sensitive: -> false operation_sensitive: -> false @@ -157,7 +134,7 @@ exports.Expressions: class Expressions extends BaseNode # If this is the top-level Expressions, wrap everything in a safety closure. compile_root: (o) -> - o.indent: @indent: indent: if o.no_wrap then '' else TAB + o.indent: @tab: if o.no_wrap then '' else TAB o.scope: new Scope(null, this, null) code: if o.globals then @compile_node(o) else @compile_with_declarations(o) code: code.replace(TRAILING_WHITESPACE, '') @@ -168,23 +145,23 @@ exports.Expressions: class Expressions extends BaseNode compile_with_declarations: (o) -> code: @compile_node(o) args: @contains (node) -> node instanceof ValueNode and node.is_arguments() - code: "${@idt()}arguments = Array.prototype.slice.call(arguments, 0);\n$code" if args - code: "${@idt()}var ${o.scope.compiled_assignments()};\n$code" if o.scope.has_assignments(this) - code: "${@idt()}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this) + code: "${@tab}arguments = Array.prototype.slice.call(arguments, 0);\n$code" if args + code: "${@tab}var ${o.scope.compiled_assignments()};\n$code" if o.scope.has_assignments(this) + code: "${@tab}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this) code # Compiles a single expression within the expressions body. compile_expression: (node, o) -> - @indent: o.indent + @tab: o.indent stmt: node.is_statement() # We need to return the result if this is the last node in the expressions body. - returns: del(o, 'returns') and @is_last(node) and not node.is_statement_only() + returns: del(o, 'returns') and @is_last(node) and not node.is_pure_statement() # Return the regular compile of the node, unless we need to return the result. return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns # If it's a statement, the node knows how to return itself. return node.compile(merge(o, {returns: true})) if node.is_statement() # Otherwise, we can just return the value of the expression. - return "${@idt()}return ${node.compile(o)};" + return "${@tab}return ${node.compile(o)};" # Wrap up a node as an Expressions, unless it already is one. Expressions.wrap: (nodes) -> @@ -207,7 +184,7 @@ exports.LiteralNode: class LiteralNode extends BaseNode is_statement: -> @value is 'break' or @value is 'continue' - is_statement_only: LiteralNode::is_statement + is_pure_statement: LiteralNode::is_statement compile_node: (o) -> idt: if @is_statement() then @idt() else '' @@ -227,7 +204,7 @@ exports.ReturnNode: class ReturnNode extends BaseNode compile_node: (o) -> return @expression.compile(merge(o, {returns: true})) if @expression.is_statement() - "${@idt()}return ${@expression.compile(o)};" + "${@tab}return ${@expression.compile(o)};" statement ReturnNode, true @@ -308,7 +285,7 @@ exports.CommentNode: class CommentNode extends BaseNode this compile_node: (o) -> - "${@idt()}//" + @lines.join("\n${@idt()}//") + "$@tab//" + @lines.join("\n$@tab//") statement CommentNode @@ -426,10 +403,10 @@ exports.RangeNode: class RangeNode extends BaseNode @exclusive: !!exclusive compile_variables: (o) -> - @indent: o.indent + @tab: o.indent [@from_var, @to_var]: [o.scope.free_variable(), o.scope.free_variable()] [from, to]: [@from.compile(o), @to.compile(o)] - "$@from_var = $from; $@to_var = $to;\n${@idt()}" + "$@from_var = $from; $@to_var = $to;\n$@tab" compile_node: (o) -> return @compile_array(o) unless o.index @@ -556,7 +533,7 @@ exports.ArrayNode: class ArrayNode extends BaseNode else "$code, " objects: objects.join('') - ending: if objects.indexOf('\n') >= 0 then "\n${@idt()}]" else ']' + ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']' "[$objects$ending" @@ -566,7 +543,7 @@ PushNode: exports.PushNode: { wrap: (array, expressions) -> expr: expressions.unwrap() - return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only() + return expressions if expr.is_pure_statement() or expr.contains (n) -> n.is_pure_statement() Expressions.wrap([new CallNode( new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr] )]) @@ -621,9 +598,9 @@ exports.AssignNode: class AssignNode extends BaseNode return "$name: $val" if @context is 'object' o.scope.find name unless @is_value() and @variable.has_properties() val: "$name = $val" - return "${@idt()}$val;" if stmt + return "$@tab$val;" if stmt val: "($val)" if not top or o.returns - val: "${@idt()}return $val" if o.returns + val: "${@tab}return $val" if o.returns val # Implementation of recursive pattern matching, when assigning array or @@ -632,7 +609,7 @@ exports.AssignNode: class AssignNode extends BaseNode compile_pattern_match: (o) -> val_var: o.scope.free_variable() value: if @value.is_statement() then ClosureNode.wrap(@value) else @value - assigns: ["${@idt()}$val_var = ${ value.compile(o) };"] + assigns: ["$@tab$val_var = ${ value.compile(o) };"] o.top: true o.as_statement: true for obj, i in @variable.base.objects @@ -646,7 +623,7 @@ exports.AssignNode: class AssignNode extends BaseNode val: new ValueNode(literal(val_var), [new access_class(idx)]) assigns.push(new AssignNode(obj, val).compile(o)) code: assigns.join("\n") - code += "\n${@idt()}return ${ @variable.compile(o) };" if o.returns + code += "\n${@tab}return ${ @variable.compile(o) };" if o.returns code compile_splice: (o) -> @@ -691,7 +668,7 @@ exports.CodeNode: class CodeNode extends BaseNode func: "($func)" if top and not @bound return func unless @bound inner: "(function$name_part() {\n${@idt(2)}return __func.apply(__this, arguments);\n${@idt(1)}});" - "(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n${@idt()}})(this)" + "(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n$@tab})(this)" top_sensitive: -> true @@ -755,13 +732,13 @@ exports.WhileNode: class WhileNode extends BaseNode set: '' if not top rvar: o.scope.free_variable() - set: "${@idt()}$rvar = [];\n" + set: "$@tab$rvar = [];\n" @body: PushNode.wrap(rvar, @body) if @body - post: if returns then "\n${@idt()}return $rvar;" else '' - pre: "$set${@idt()}while ($cond)" + post: if returns then "\n${@tab}return $rvar;" else '' + pre: "$set${@tab}while ($cond)" return "$pre null;$post" if not @body @body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter - "$pre {\n${ @body.compile(o) }\n${@idt()}}$post" + "$pre {\n${ @body.compile(o) }\n$@tab}$post" statement WhileNode @@ -845,9 +822,9 @@ exports.TryNode: class TryNode extends BaseNode o.top: true attempt_part: @attempt.compile(o) error_part: if @error then " (${ @error.compile(o) }) " else ' ' - catch_part: "${ (@recovery or '') and ' catch' }$error_part{\n${ @recovery.compile(o) }\n${@idt()}}" - finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + "\n${@idt()}}" - "${@idt()}try {\n$attempt_part\n${@idt()}}$catch_part$finally_part" + catch_part: "${ (@recovery or '') and ' catch' }$error_part{\n${ @recovery.compile(o) }\n$@tab}" + 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 @@ -860,7 +837,7 @@ exports.ThrowNode: class ThrowNode extends BaseNode @children: [@expression: expression] compile_node: (o) -> - "${@idt()}throw ${@expression.compile(o)};" + "${@tab}throw ${@expression.compile(o)};" statement ThrowNode, true @@ -944,7 +921,7 @@ exports.ForNode: class ForNode extends BaseNode for_part: "$index_var = 0, $for_part, $index_var++" else index_var: null - source_part: "$svar = ${ @source.compile(o) };\n${@idt()}" + source_part: "$svar = ${ @source.compile(o) };\n$@tab" var_part: "$body_dent$name = $svar[$ivar];\n" if name if not @object lvar: scope.free_variable() @@ -963,11 +940,11 @@ exports.ForNode: class ForNode extends BaseNode if @object o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true) for_part: "$ivar in $svar) { if (__hasProp.call($svar, $ivar)" - return_result: "\n${@idt()}$return_result;" unless top_level + return_result: "\n$@tab$return_result;" unless top_level body: body.compile(merge(o, {indent: body_dent, top: true})) vars: if range then name else "$name, $ivar" close: if @object then '}}\n' else '}\n' - "$set_result${source_part}for ($for_part) {\n$var_part$body\n${@idt()}$close${@idt()}$return_result" + "$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$@tab$return_result" statement ForNode @@ -1054,12 +1031,12 @@ exports.IfNode: class IfNode extends BaseNode com_dent: if child then @idt() else '' prefix: if @comment then "${ @comment.compile(cond_o) }\n$com_dent" else '' body: Expressions.wrap([@body]).compile(o) - if_part: "$prefix${if_dent}if (${ @compile_condition(cond_o) }) {\n$body\n${@idt()}}" + if_part: "$prefix${if_dent}if (${ @compile_condition(cond_o) }) {\n$body\n$@tab}" return if_part unless @else_body else_part: if @is_chain() ' else ' + @else_body.compile(merge(o, {indent: @idt(), chain_child: true})) else - " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n${@idt()}}" + " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n$@tab}" "$if_part$else_part" # Compile the IfNode into a ternary operator. @@ -1067,3 +1044,43 @@ exports.IfNode: class IfNode extends BaseNode 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) -> + 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) -> + 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) -> + val: obj[key] + delete obj[key] + val + +# Quickie helper for a generated LiteralNode. +literal: (name) -> + new LiteralNode(name) diff --git a/test/test_expressions.coffee b/test/test_expressions.coffee index 891aa5f2..a25f95c7 100644 --- a/test/test_expressions.coffee +++ b/test/test_expressions.coffee @@ -1,4 +1,4 @@ -# Ensure that we don't wrap Nodes that are "statement_only" in a closure. +# Ensure that we don't wrap Nodes that are "pure_statement" in a closure. items: [1, 2, 3, "bacon", 4, 5]