diff --git a/Cakefile b/Cakefile index f9dc0549..a11b83cd 100644 --- a/Cakefile +++ b/Cakefile @@ -41,10 +41,6 @@ task 'build:ultraviolet', 'build and install the Ultraviolet syntax highlighter' exec 'sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax' -task 'build:underscore', 'rebuild the Underscore.coffee documentation page', -> - exec 'uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html' - - task 'build:browser', 'rebuild the merged script for inclusion in the browser', -> exec 'rake browser', (err) -> throw err if err @@ -59,6 +55,10 @@ task 'doc:source', 'rebuild the internal documentation', -> throw err if err +task 'doc:underscore', 'rebuild the Underscore.coffee documentation page', -> + exec 'uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html' + + task 'test', 'run the CoffeeScript language test suite', -> process.mixin require 'assert' test_count: 0 diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html index 6c8e2bb6..e316e6cb 100644 --- a/documentation/docs/nodes.html +++ b/documentation/docs/nodes.html @@ -23,7 +23,11 @@ compiling it, or to compile directly. We need to wrap if this node is a statement, and it's not a pure_statement, and we're not at the top level of a block (which would be unnecessary), and we haven't already been asked to return the result (because statements know how to -return results).
compile: (o) ->
+return results).
+
+If a Node is top_sensitive, that means that it needs to compile differently
+depending on whether it's being used as part of a larger expression, or is a
+top-level statement within the function body.
compile: (o) ->
@options: merge o or {}
@tab: o.indent
del @options, 'operation' unless @operation_sensitive()
@@ -305,8 +309,8 @@ it instead of wrapping nodes. An array slice literal. Unlike JavaScript's Array#slice, the second parameter -specifies the index of the end of the slice (just like the first parameter) + (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
An array slice literal. Unlike JavaScript's Array#slice, the second parameter
+specifies the index of the end of the slice, just as the first parameter
is the index of the beginning.
exports.SliceNode: class SliceNode extends BaseNode
type: 'Slice'
@@ -318,13 +322,15 @@ is the index of the beginning. An object literal.
exports.ObjectNode: class ObjectNode extends BaseNode
+ ".slice($from, $to$plus_part)"An object literal, nothing fancy.
exports.ObjectNode: class ObjectNode extends BaseNode
type: 'Object'
constructor: (props) ->
- @children: @objects: @properties: props or []All the mucking about with commas is to make sure that CommentNodes and + @children: @objects: @properties: props or []
All the mucking about with commas is to make sure that CommentNodes and AssignNodes get interleaved correctly, with no trailing commas or -commas affixed to comments. TODO: Extract this and add it to ArrayNode.
compile_node: (o) ->
+commas affixed to comments.
+
+TODO: Extract this and add it to ArrayNode.
compile_node: (o) ->
o.indent: @idt(1)
non_comments: prop for prop in @properties when not (prop instanceof CommentNode)
last_noncom: non_comments[non_comments.length - 1]
@@ -336,13 +342,30 @@ commas affixed to comments. TODO: Extract this and add it to ArrayNode.
indent + prop.compile(o) + join
props: props.join('')
inner: if props then '\n' + props + '\n' + @idt() else ''
- "{$inner}"A class literal, including optional superclass and constructor.
exports.ClassNode: class ClassNode extends BaseNode
- type: 'Class'
+ "{$inner}"An array literal.
exports.ArrayNode: class ArrayNode extends BaseNode
+ type: 'Array'
- constructor: (variable, parent, props) ->
- @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
+ constructor: (objects) ->
+ @children: @objects: objects or []
compile_node: (o) ->
+ o.indent: @idt(1)
+ objects: for obj, i in @objects
+ code: obj.compile(o)
+ if obj instanceof CommentNode
+ "\n$code\n${o.indent}"
+ else if i is @objects.length - 1
+ code
+ else
+ "$code, "
+ objects: objects.join('')
+ ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']'
+ "[$objects$ending"The CoffeeScript class definition.
exports.ClassNode: class ClassNode extends BaseNode
+ type: 'Class'Initialize a ClassNode with its name, an optional superclass, and a +list of prototype property assignments.
constructor: (variable, parent, props) ->
+ @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]Instead of generating the JavaScript string directly, we build up the +equivalent syntax tree and compile that, in pieces. You can see the +constructor, property assignments, and inheritance getting built out below.
compile_node: (o) ->
extension: @parent and new ExtendsNode(@variable, @parent)
constructor: null
props: new Expressions()
@@ -375,26 +398,9 @@ commas affixed to comments. TODO: Extract this and add it to ArrayNode.
returns: if ret then '\n' + @idt() + 'return ' + @variable.compile(o) + ';' else ''
"$construct$extension$props$returns"
-statement ClassNodeAn array literal.
exports.ArrayNode: class ArrayNode extends BaseNode
- type: 'Array'
-
- constructor: (objects) ->
- @children: @objects: objects or []
-
- compile_node: (o) ->
- o.indent: @idt(1)
- objects: for obj, i in @objects
- code: obj.compile(o)
- if obj instanceof CommentNode
- "\n$code\n${o.indent}"
- else if i is @objects.length - 1
- code
- else
- "$code, "
- objects: objects.join('')
- ending: if objects.indexOf('\n') >= 0 then "\n$@tab]" else ']'
- "[$objects$ending"A faux-node that is never created by the grammar, but is used during -code generation to generate a quick "array.push(value)" tree of nodes.
PushNode: exports.PushNode: {
+statement ClassNodeA faux-node that is never created by the grammar, but is used during
+code generation to generate a quick array.push(value) tree of nodes.
+Helpful for recording the result arrays from comprehensions.
PushNode: exports.PushNode: {
wrap: (array, expressions) ->
expr: expressions.unwrap()
@@ -403,17 +409,16 @@ code generation to generate a quick "array.push(value)" tree of nodes.
new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr]
)])
-}A faux-node used to wrap an expressions body in a closure.
ClosureNode: exports.ClosureNode: {
+}A faux-node used to wrap an expressions body in a closure.
ClosureNode: exports.ClosureNode: {
wrap: (expressions, statement) ->
func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
call: new CallNode(new ValueNode(func, [new AccessorNode(literal('call'))]), [literal('this')])
if statement then Expressions.wrap([call]) else call
-}Setting the value of a local variable, or the value of an object property.
exports.AssignNode: class AssignNode extends BaseNode
- type: 'Assign'
-
- PROTO_ASSIGN: /^(\S+)\.prototype/
+}The AssignNode is used to assign a local variable to value, or to set the +property of an object -- including within object literals.
exports.AssignNode: class AssignNode extends BaseNode
+ type: 'Assign'Matchers for detecting prototype assignments.
PROTO_ASSIGN: /^(\S+)\.prototype/
LEADING_DOT: /^\.(prototype\.)?/
constructor: (variable, value, context) ->
@@ -427,9 +432,10 @@ code generation to generate a quick "array.push(value)" tree of nodes.
@variable instanceof ValueNode
is_statement: ->
- @is_value() and (@variable.is_array() or @variable.is_object())
-
- compile_node: (o) ->
+ @is_value() and (@variable.is_array() or @variable.is_object())Compile an assignment, delegating to compile_pattern_match or
+compile_splice if appropriate. Keep track of the name of the base object
+we've been assigned to, for correct internal references. If the variable
+has not been seen yet within the current scope, declare it.
compile_node: (o) ->
top: del o, 'top'
return @compile_pattern_match(o) if @is_statement()
return @compile_splice(o) if @is_value() and @variable.is_splice()
@@ -448,9 +454,10 @@ code generation to generate a quick "array.push(value)" tree of nodes.
return "$@tab$val;" if stmt
val: "($val)" if not top or o.returns
val: "${@tab}return $val" if o.returns
- valImplementation of recursive pattern matching, when assigning array or + val
Brief implementation of recursive pattern matching, when assigning array or object literals to a value. Peeks at their properties to assign inner names. -See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring
compile_pattern_match: (o) ->
+See the ECMAScript Harmony Wiki
+for details. compile_pattern_match: (o) ->
val_var: o.scope.free_variable()
value: if @value.is_statement() then ClosureNode.wrap(@value) else @value
assigns: ["$@tab$val_var = ${ value.compile(o) };"]
@@ -468,9 +475,8 @@ See: http
assigns.push(new AssignNode(obj, val).compile(o))
code: assigns.join("\n")
code += "\n${@tab}return ${ @variable.compile(o) };" if o.returns
- code
-
- compile_splice: (o) ->
+ codeCompile the assignment from an array splice literal, using JavaScript's
+Array#splice method.
compile_splice: (o) ->
name: @variable.compile(merge(o, {only_first: true}))
l: @variable.properties.length
range: @variable.properties[l - 1].range
@@ -478,16 +484,19 @@ See: http
from: range.from.compile(o)
to: range.to.compile(o) + ' - ' + from + plus
val: @value.compile(o)
- "$name.splice.apply($name, [$from, $to].concat($val))"A function definition. The only node that creates a new Scope. -A CodeNode does not have any children -- they're within the new scope.
exports.CodeNode: class CodeNode extends BaseNode
+ "$name.splice.apply($name, [$from, $to].concat($val))"A function definition. This is the only node that creates a new Scope. +When for the purposes of walking the contents of a function body, the CodeNode +has no children -- they're within the inner scope.
exports.CodeNode: class CodeNode extends BaseNode
type: 'Code'
constructor: (params, body, tag) ->
@params: params or []
@body: body or new Expressions()
- @bound: tag is 'boundfunc'
-
- compile_node: (o) ->
+ @bound: tag is 'boundfunc'Compilation creates a new scope unless explicitly asked to share with the
+outer scope. Handles splat parameters in the parameter list by peeking at
+the JavaScript arguments objects. If the function is bound with the =>
+arrow, generates a wrapper that saves the current value of this through
+a closure.
compile_node: (o) ->
shared_scope: del o, 'shared_scope'
top: del o, 'top'
o.scope: shared_scope or new Scope(o.scope, @body, this)
@@ -511,19 +520,16 @@ A CodeNode does not have any children -- they're within the new scope.
"(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n$@tab})(this)"
top_sensitive: ->
- true
-
- real_children: ->
- flatten [@params, @body.expressions]
-
- traverse: (block) ->
+ trueWhen traversing (for printing or inspecting), return the real children of +the function -- the parameters and body of expressions.
real_children: ->
+ flatten [@params, @body.expressions]Custom traverse implementation that uses the real_children.
traverse: (block) ->
block this
block(child) for child in @real_children()
toString: (idt) ->
idt ||= ''
children: (child.toString(idt + TAB) for child in @real_children()).join('')
- "\n$idt$children"A splat, either as a parameter to a function, an argument to a call, + "\n$idt$children"
A splat, either as a parameter to a function, an argument to a call, or in a destructuring assignment.
exports.SplatNode: class SplatNode extends BaseNode
type: 'Splat'
@@ -540,7 +546,7 @@ or in a destructuring assignment. A while loop, the only sort of low-level loop exposed by CoffeeScript. From + "Array.prototype.slice.call($name, $index)"
A while loop, the only sort of low-level loop exposed by CoffeeScript. From it, all other loops can be manufactured.
exports.WhileNode: class WhileNode extends BaseNode
type: 'While'
@@ -572,7 +578,7 @@ it, all other loops can be manufactured. Simple Arithmetic and logical operations. Performs some conversion from +statement WhileNode
Simple Arithmetic and logical operations. Performs some conversion from CoffeeScript operations into their JavaScript equivalents.
exports.OpNode: class OpNode extends BaseNode
type: 'Op'
@@ -608,7 +614,7 @@ CoffeeScript operations into their JavaScript equivalents. Mimic Python's chained comparisons. See: + [@first.compile(o), @operator, @second.compile(o)].join ' '
Mimic Python's chained comparisons. See: http://docs.python.org/reference/expressions.html#notin
compile_chain: (o) ->
shared: @first.unwrap().second
[@first.second, shared]: shared.compile_reference(o) if shared instanceof CallNode
@@ -630,7 +636,7 @@ CoffeeScript operations into their JavaScript equivalents. A try/catch/finally block.
exports.TryNode: class TryNode extends BaseNode
+ parts.join('')A try/catch/finally block.
exports.TryNode: class TryNode extends BaseNode
type: 'Try'
constructor: (attempt, error, recovery, ensure) ->
@@ -647,7 +653,7 @@ CoffeeScript operations into their JavaScript equivalents. Throw an exception.
exports.ThrowNode: class ThrowNode extends BaseNode
+statement TryNodeThrow an exception.
exports.ThrowNode: class ThrowNode extends BaseNode
type: 'Throw'
constructor: (expression) ->
@@ -656,7 +662,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, trueCheck an expression for existence (meaning not null or undefined).
exports.ExistenceNode: class ExistenceNode extends BaseNode
type: 'Existence'
constructor: (expression) ->
@@ -670,7 +676,7 @@ CoffeeScript operations into their JavaScript equivalents. An extra set of parentheses, specified explicitly in the source.
exports.ParentheticalNode: class ParentheticalNode extends BaseNode
+ "(typeof $first !== \"undefined\" && $second !== null)"An extra set of parentheses, specified explicitly in the source.
exports.ParentheticalNode: class ParentheticalNode extends BaseNode
type: 'Paren'
constructor: (expression) ->
@@ -684,7 +690,7 @@ CoffeeScript operations into their JavaScript equivalents. The replacement for the for loop is an array comprehension (that compiles) + "($code)"
The replacement for the for loop is an array comprehension (that compiles) into a for loop. Also acts as an expression, able to return the result of the comprehenion. Unlike Python array comprehensions, it's able to pass the current index of the loop as a second parameter.
exports.ForNode: class ForNode extends BaseNode
@@ -751,7 +757,7 @@ the current index of the loop as a second parameter. If/else statements. Switch/whens get compiled into these. Acts as an +statement ForNode
If/else statements. Switch/whens get compiled into these. Acts as an expression by pushing down requested returns to the expression bodies. Single-expression IfNodes are compiled into ternary operators if possible, because ternaries are first-class returnable assignable expressions.
exports.IfNode: class IfNode extends BaseNode
@@ -773,9 +779,9 @@ because ternaries are first-class returnable assignable expressions.
force_statement: ->
@tags.statement: true
- thisTag a chain of IfNodes with their switch condition for equality.
rewrite_condition: (expression) ->
+ thisTag a chain of IfNodes with their switch condition for equality.
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.
rewrite_switch: (o) ->
assigner: @switcher
if not (@switcher.unwrap() instanceof LiteralNode)
variable: literal(o.scope.free_variable())
@@ -787,14 +793,14 @@ because ternaries are first-class returnable assignable expressions.
else
new OpNode('is', assigner, @condition)
@else_body.rewrite_condition(@switcher) if @is_chain()
- 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 + 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 IfNodeThe IfNode only compiles into a statement if either of the bodies needs to be a statement.
is_statement: ->
@statement ||= !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement()))
@@ -802,7 +808,7 @@ to be a statement. Compile the IfNode as a regular if-else statement. Flattened chains + if @is_statement() then @compile_statement(o) else @compile_ternary(o)
Compile the IfNode as a regular if-else statement. Flattened chains force sub-else bodies into statement form.
compile_statement: (o) ->
@rewrite_switch(o) if @switcher
child: del o, 'chain_child'
@@ -820,22 +826,22 @@ force sub-else bodies into statement form. 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"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: ' '
+TRAILING_WHITESPACE: /\s+$/gmKeep 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
- 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.
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.
del: (obj, key) ->
val: obj[key]
delete obj[key]
- valQuickie helper for a generated LiteralNode.
literal: (name) ->
+ valQuickie helper for a generated LiteralNode.
literal: (name) ->
new LiteralNode(name)