From 532464f7ae3995ffbdabf47239136456d31aafd2 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 7 Mar 2010 22:47:34 -0500 Subject: [PATCH] waypoint -- docs halfway down through the OpNode --- documentation/docs/cake.html | 10 +-- documentation/docs/coffee-script.html | 14 +++- documentation/docs/command.html | 22 +++---- documentation/docs/lexer.html | 2 +- documentation/docs/nodes.html | 94 +++++++++++++-------------- documentation/docs/repl.html | 6 +- src/nodes.coffee | 28 ++++++-- 7 files changed, 100 insertions(+), 76 deletions(-) diff --git a/documentation/docs/cake.html b/documentation/docs/cake.html index 1c909799..a48a30f6 100644 --- a/documentation/docs/cake.html +++ b/documentation/docs/cake.html @@ -4,10 +4,10 @@ for CoffeeScript. You define tasks with names and descriptions in a Cakefile, and can call them from the command line, or invoke them from other tasks.

Running cake with no arguments will print out a list of all the tasks in the -current directory's Cakefile.

#

External dependencies.

fs:       require 'fs'
-path:     require 'path'
-coffee:   require 'coffee-script'
-optparse: require 'optparse'
#

Keep track of the list of defined tasks, the accepted options, and so on.

tasks: {}
+current directory's Cakefile.

#

External dependencies.

fs:           require 'fs'
+path:         require 'path'
+optparse:     require 'optparse'
+CoffeeScript: require 'coffee-script'
#

Keep track of the list of defined tasks, the accepted options, and so on.

tasks: {}
 options: {}
 switches: []
 oparse: null
#

Mixin the top-level Cake functions for Cakefiles to use directly.

process.mixin {
#

Define a Cake task with a short name, a sentence description, @@ -25,7 +25,7 @@ If no tasks are passed, print the help screen.

path.exists 'Cakefile', (exists) -> throw new Error("Cakefile not found in ${process.cwd()}") unless exists args: process.argv[2...process.argv.length] - eval coffee.compile fs.readFileSync 'Cakefile' + CoffeeScript.run fs.readFileSync('Cakefile'), {source: 'Cakefile'} oparse: new optparse.OptionParser switches return print_tasks() unless args.length options: oparse.parse(args) diff --git a/documentation/docs/coffee-script.html b/documentation/docs/coffee-script.html index 369c44c4..17421b4b 100644 --- a/documentation/docs/coffee-script.html +++ b/documentation/docs/coffee-script.html @@ -14,11 +14,19 @@ execute all scripts present in text/coffeescript tags.

parser: exports.parser this.exports: this.CoffeeScript: {}
#

The current CoffeeScript version number.

exports.VERSION: '0.5.4'
#

Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison compiler.

exports.compile: (code, options) ->
-  (parser.parse lexer.tokenize code).compile options
#

Tokenize a string of CoffeeScript code, and return the array of tokens.

exports.tokens: (code) ->
+  try
+    (parser.parse lexer.tokenize code).compile options
+  catch err
+    err.message: "In ${options.source}, ${err.message}" if options.source
+    throw err
#

Tokenize a string of CoffeeScript code, and return the array of tokens.

exports.tokens: (code) ->
   lexer.tokenize code
#

Tokenize and parse a string of CoffeeScript code, and return the AST. You can then compile it by calling .compile() on the root, or traverse it by using .traverse() with a callback.

exports.nodes: (code) ->
-  parser.parse lexer.tokenize code
#

The real Lexer produces a generic stream of tokens. This object provides a + parser.parse lexer.tokenize code

#

Compile and execute a string of CoffeeScript (on the server), correctly +setting __filename, __dirname, and relative require().

exports.run: (code, options) ->
+  module.filename: __filename: options.source
+  __dirname: path.dirname __filename
+  eval exports.compile code, options
#

The real Lexer produces a generic stream of tokens. This object provides a thin wrapper around it, compatible with the Jison API. We can then pass it directly as a "Jison lexer".

parser.lexer: {
   lex: ->
@@ -32,7 +40,7 @@ directly as a "Jison lexer".

@pos: 0 upcomingInput: -> "" showPosition: -> @pos -}
#

Activate CoffeeScript in the browser by having it compile and evaluate +}

#

Activate CoffeeScript in the browser by having it compile and evaluate all script tags with a content-type of text/coffeescript. This happens on page load. Unfortunately, the text contents of remote scripts cannot be accessed from the browser, so only inline script tags will work.

if document? and document.getElementsByTagName
diff --git a/documentation/docs/command.html b/documentation/docs/command.html
index 4f657a7d..e4bcd721 100644
--- a/documentation/docs/command.html
+++ b/documentation/docs/command.html
@@ -34,7 +34,7 @@ Many flags cause us to divert before compiling anything. Flags passed after
   return version()                            if options.version
   return require 'repl'                       if options.interactive
   return compile_stdio()                      if options.stdio
-  return compile_script 'unknown', sources[0] if options.eval
+  return compile_script 'console', sources[0] if options.eval
   return usage()                              unless sources.length
   separator: sources.indexOf '--'
   flags: []
@@ -54,19 +54,16 @@ requested options. Both compile_scripts and watch_scripts share this method
 in common. If evaluating the script directly sets __filename, __dirname
 and module.filename to be correct relative to the script's path.

compile_script: (source, code) ->
   o: options
+  code_opts: compile_options source
   try
     if      o.tokens            then print_tokens CoffeeScript.tokens code
     else if o.nodes             then puts CoffeeScript.nodes(code).toString()
+    else if o.run               then CoffeeScript.run code, code_opts
     else
-      js: CoffeeScript.compile code, compile_options()
+      js: CoffeeScript.compile code, code_opts
       if      o.compile         then write_js source, js
       else if o.lint            then lint js
       else if o.print or o.eval then print js
-      else
-        __filename:       source
-        __dirname:        path.dirname source
-        module.filename:  source
-        eval js
   catch err
     if o.watch                  then puts err.message else throw err
#

Attach the appropriate listeners to compile scripts incoming over stdin, and write them back to stdout.

compile_stdio: ->
@@ -75,7 +72,7 @@ and write them back to stdout.

process.stdio.addListener 'data', (string) -> code += string if string process.stdio.addListener 'close', -> - process.stdio.write CoffeeScript.compile code, compile_options()
#

Watch a list of source CoffeeScript files using fs.watchFile, recompiling + process.stdio.write CoffeeScript.compile code, compile_options('stdio')

#

Watch a list of source CoffeeScript files using fs.watchFile, recompiling them every time the files are updated. May be used in combination with other options, such as --lint or --print.

watch_scripts: ->
   watch: (source) ->
@@ -103,9 +100,12 @@ any errors or warnings that arise.

puts strings.join(' ')
#

Use the OptionParser module to extract all options from process.argv that are specified in SWITCHES.

parse_options: ->
   option_parser: new optparse.OptionParser SWITCHES, BANNER
-  options: option_parser.parse(process.argv)
-  sources: options.arguments[2...options.arguments.length]
#

The compile-time options to pass to the CoffeeScript compiler.

compile_options: ->
-  if options['no-wrap'] then {no_wrap: true} else {}
#

Print the --help usage message and exit.

usage: ->
+  o: options:    option_parser.parse(process.argv)
+  options.run:   not (o.compile or o.print or o.lint or o.eval)
+  sources:       options.arguments[2...options.arguments.length]
#

The compile-time options to pass to the CoffeeScript compiler.

compile_options: (source) ->
+  o: {source: source}
+  o['no-wrap']: options['no-wrap']
+  o
#

Print the --help usage message and exit.

usage: ->
   puts option_parser.help()
   process.exit 0
#

Print the --version message and exit.

version: ->
   puts "CoffeeScript version ${CoffeeScript.VERSION}"
diff --git a/documentation/docs/lexer.html b/documentation/docs/lexer.html
index 2fec663c..044c5b7b 100644
--- a/documentation/docs/lexer.html
+++ b/documentation/docs/lexer.html
@@ -317,7 +317,7 @@ to avoid having a JavaScript error at runtime.

be used as identifiers or properties.

JS_FORBIDDEN: JS_KEYWORDS.concat RESERVED
#

Token matching regexes.

IDENTIFIER    : /^([a-zA-Z$_](\w|\$)*)/
 NUMBER        : /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
 HEREDOC       : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/
-INTERPOLATION : /^\$([a-zA-Z_@]\w*)/
+INTERPOLATION : /^\$([a-zA-Z_@]\w*(\.\w+)*)/
 OPERATOR      : /^([+\*&|\/\-%=<>:!?]+)/
 WHITESPACE    : /^([ \t]+)/
 COMMENT       : /^(((\n?[ \t]*)?#[^\n]*)+)/
diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html
index e316e6cb..7c5441f3 100644
--- a/documentation/docs/nodes.html
+++ b/documentation/docs/nodes.html
@@ -228,8 +228,8 @@ of the current function.

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)"
#

If you call a function with a splat, it's converted into a JavaScript + "${methname}.__superClass__.constructor" + "${meth}.call(this${ if args.length then ', ' else '' }$args)"

#

If you call a function with a splat, it's converted into a JavaScript .apply() call to allow the variable-length arguments.

  compile_splat: (o) ->
     meth: @variable.compile o
     obj:  @variable.source or 'this'
@@ -241,7 +241,7 @@ of the current function.

code: arg.compile o code: if arg instanceof SplatNode then code else "[$code]" if i is 0 then code else ".concat($code)" - "$@prefix$meth.apply($obj, ${ args.join('') })"
#

ExtendsNode

#

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

#

ExtendsNode

#

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'
@@ -353,7 +353,7 @@ commas affixed to comments.

objects: for obj, i in @objects code: obj.compile(o) if obj instanceof CommentNode - "\n$code\n${o.indent}" + "\n$code\n$o.indent" else if i is @objects.length - 1 code else @@ -484,7 +484,7 @@ for details.

< from: range.from.compile(o) to: range.to.compile(o) + ' - ' + from + plus val: @value.compile(o) - "$name.splice.apply($name, [$from, $to].concat($val))"
#

CodeNode

#

A function definition. This is the only node that creates a new Scope. + "${name}.splice.apply($name, [$from, $to].concat($val))"

#

CodeNode

#

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

exports.CodeNode: class CodeNode extends BaseNode
   type: 'Code'
@@ -530,7 +530,7 @@ the function -- the parameters and body of expressions.

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

SplatNode

#

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

exports.SplatNode: class SplatNode extends BaseNode
+or as part of a destructuring assignment.

exports.SplatNode: class SplatNode extends BaseNode
   type: 'Splat'
 
   constructor: (name) ->
@@ -538,16 +538,15 @@ or in a destructuring assignment.

@children: [@name: name] compile_node: (o) -> - if @index? then @compile_param(o) else @name.compile(o) - - compile_param: (o) -> + if @index? then @compile_param(o) else @name.compile(o)
#

Compiling a parameter splat means recovering the parameters that succeed +the splat in the parameter list, by slicing the arguments object.

  compile_param: (o) ->
     name: @name.compile(o)
     o.scope.find name
-    "$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 -it, all other loops can be manufactured.

exports.WhileNode: class WhileNode extends BaseNode
+    "$name = Array.prototype.slice.call(arguments, $@index)"
#

A compiling a splat as a destructuring assignment means slicing arguments +from the right-hand-side's corresponding array.

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

WhileNode

#

A while loop, the only sort of low-level loop exposed by CoffeeScript. From +it, all other loops can be manufactured. Useful in cases where you need more +flexibility or more speed than a comprehension can provide.

exports.WhileNode: class WhileNode extends BaseNode
   type: 'While'
 
   constructor: (condition, opts) ->
@@ -559,9 +558,9 @@ it, all other loops can be manufactured.

this top_sensitive: -> - true - - compile_node: (o) -> + true
#

The main difference from a JavaScript while is that the CoffeeScript +while can be used as a part of a larger expression -- while loops may +return an array containing the computed result of each iteration.

  compile_node: (o) ->
     returns:    del(o, 'returns')
     top:        del(o, 'top') and not returns
     o.indent:   @idt(1)
@@ -578,11 +577,9 @@ it, all other loops can be manufactured.

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

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

#

OpNode

#

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

exports.OpNode: class OpNode extends BaseNode
-  type: 'Op'
-
-  CONVERSIONS: {
+  type: 'Op'
#

The map of conversions from CoffeeScript to JavaScript symbols.

  CONVERSIONS: {
     '==':   '==='
     '!=':   '!=='
     'and':  '&&'
@@ -590,11 +587,8 @@ CoffeeScript operations into their JavaScript equivalents.

'is': '===' 'isnt': '!==' 'not': '!' - } - - CHAINABLE: ['<', '>', '>=', '<=', '===', '!=='] - ASSIGNMENT: ['||=', '&&=', '?='] - PREFIX_OPERATORS: ['typeof', 'delete'] + }
#

The list of operators for which we perform +Python-style comparison chaining.

  CHAINABLE:        ['<', '>', '>=', '<=', '===', '!==']
#

Our assignment operators that have no JavaScript equivalent.

  ASSIGNMENT:       ['||=', '&&=', '?=']
#

Operators must come before their operands with a space.

  PREFIX_OPERATORS: ['typeof', 'delete']
 
   constructor: (operator, first, second, flip) ->
     @type += ' ' + operator
@@ -614,14 +608,14 @@ CoffeeScript operations into their JavaScript equivalents.

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

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

  compile_chain: (o) ->
+    [@first.compile(o), @operator, @second.compile(o)].join ' '
#

Mimic Python's chained comparisons when multiple comparison operators are +used sequentially. For example: 50 < 65 > 10

  compile_chain: (o) ->
     shared: @first.unwrap().second
     [@first.second, shared]: shared.compile_reference(o) if shared instanceof CallNode
     [first, second, shared]: [@first.compile(o), @second.compile(o), shared.compile(o)]
-    "($first) && ($shared $@operator $second)"
-
-  compile_assignment: (o) ->
+    "($first) && ($shared $@operator $second)"
#

When compiling a conditional assignment, take care to ensure that the +operands are only evaluated once, even though we have to reference them +more than once.

  compile_assignment: (o) ->
     [first, second]: [@first.compile(o), @second.compile(o)]
     o.scope.find(first) if first.match(IDENTIFIER)
     return "$first = ${ ExistenceNode.compile_test(o, @first) } ? $first : $second" if @operator is '?='
@@ -636,7 +630,7 @@ CoffeeScript operations into their JavaScript equivalents.

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

A try/catch/finally block.

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

A try/catch/finally block.

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

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

Throw an exception.

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

Throw an exception.

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

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

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

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

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

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

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

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

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

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

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

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

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

#

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

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

if not @object lvar: scope.free_variable() step_part: if @step then "$ivar += ${ @step.compile(o) }" else "$ivar++" - for_part: "$ivar = 0, $lvar = $svar.length; $ivar < $lvar; $step_part" + for_part: "$ivar = 0, $lvar = ${svar}.length; $ivar < $lvar; $step_part" set_result: if rvar then @idt() + rvar + ' = []; ' else @idt() return_result: rvar or '' body: ClosureNode.wrap(body, true) if top_level and @contains (n) -> n instanceof CodeNode @@ -757,7 +751,7 @@ the current index of the loop as a second parameter.

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

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

#

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

exports.IfNode: class IfNode extends BaseNode
@@ -779,9 +773,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())
@@ -793,14 +787,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()))
 
@@ -808,7 +802,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'
@@ -826,22 +820,22 @@ force sub-else bodies into statement form.

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

Compile the IfNode into a ternary operator.

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

Compile the IfNode into a ternary operator.

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

Constants

#

Tabs are two spaces for pretty printing.

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

Keep the identifier regex in sync with the Lexer.

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

Utility Functions

#

Merge objects.

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

Constants

#

Tabs are two spaces for pretty printing.

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

Keep the identifier regex in sync with the Lexer.

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

Utility Functions

#

Merge objects.

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

Trim out all falsy values from an array.

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

Return a completely flattened version of an array.

flatten: (array) ->
+  fresh
#

Trim out all falsy values from an array.

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

Return a completely flattened version of an array.

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

Delete a key from an object, returning the value.

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

Delete a key from an object, returning the value.

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

Quickie helper for a generated LiteralNode.

literal: (name) ->
+  val
#

Quickie helper for a generated LiteralNode.

literal: (name) ->
   new LiteralNode(name)
 
 
\ No newline at end of file diff --git a/documentation/docs/repl.html b/documentation/docs/repl.html index 16d69f1b..73fff170 100644 --- a/documentation/docs/repl.html +++ b/documentation/docs/repl.html @@ -3,11 +3,13 @@ and evaluates it. Good for simple tests, or poking around the Node.js
coffee> puts "$num bottles of beer" for num in [99..1]
-
#

Require the coffee-script module to get access to the compiler.

CoffeeScript: require 'coffee-script'
#

Our prompt.

prompt: 'coffee> '
#

Quick alias for quitting the REPL.

quit: -> process.exit(0)
#

The main REPL function. run is called every time a line of code is entered. +

#

Require the coffee-script module to get access to the compiler.

CoffeeScript: require 'coffee-script'
#

Our prompt.

prompt: 'coffee> '
#

Quick alias for quitting the REPL.

process.mixin {
+  quit: -> process.exit(0)
+}
#

The main REPL function. run is called every time a line of code is entered. Attempt to evaluate the command. If there's an exception, print it out instead of exiting.

run: (code) ->
   try
-    val: eval CoffeeScript.compile code, {no_wrap: true, globals: true}
+    val: CoffeeScript.run code, {no_wrap: true, globals: true, source: 'repl'}
     p val if val isnt undefined
   catch err
     puts err.stack or err.toString()
diff --git a/src/nodes.coffee b/src/nodes.coffee
index fc78fed5..d539e447 100644
--- a/src/nodes.coffee
+++ b/src/nodes.coffee
@@ -768,7 +768,7 @@ exports.CodeNode: class CodeNode extends BaseNode
 #### SplatNode
 
 # A splat, either as a parameter to a function, an argument to a call,
-# or in a destructuring assignment.
+# or as part of a destructuring assignment.
 exports.SplatNode: class SplatNode extends BaseNode
   type: 'Splat'
 
@@ -779,17 +779,23 @@ exports.SplatNode: class SplatNode extends BaseNode
   compile_node: (o) ->
     if @index? then @compile_param(o) else @name.compile(o)
 
+  # Compiling a parameter splat means recovering the parameters that succeed
+  # the splat in the parameter list, by slicing the arguments object.
   compile_param: (o) ->
     name: @name.compile(o)
     o.scope.find name
     "$name = Array.prototype.slice.call(arguments, $@index)"
 
+  # A compiling a splat as a destructuring assignment means slicing arguments
+  # from the right-hand-side's corresponding array.
   compile_value: (o, name, index) ->
     "Array.prototype.slice.call($name, $index)"
 
+#### WhileNode
 
 # A while loop, the only sort of low-level loop exposed by CoffeeScript. From
-# it, all other loops can be manufactured.
+# it, all other loops can be manufactured. Useful in cases where you need more
+# flexibility or more speed than a comprehension can provide.
 exports.WhileNode: class WhileNode extends BaseNode
   type: 'While'
 
@@ -804,6 +810,9 @@ exports.WhileNode: class WhileNode extends BaseNode
   top_sensitive: ->
     true
 
+  # The main difference from a JavaScript *while* is that the CoffeeScript
+  # *while* can be used as a part of a larger expression -- while loops may
+  # return an array containing the computed result of each iteration.
   compile_node: (o) ->
     returns:    del(o, 'returns')
     top:        del(o, 'top') and not returns
@@ -823,12 +832,14 @@ exports.WhileNode: class WhileNode extends BaseNode
 
 statement WhileNode
 
+#### OpNode
 
 # Simple Arithmetic and logical operations. Performs some conversion from
 # CoffeeScript operations into their JavaScript equivalents.
 exports.OpNode: class OpNode extends BaseNode
   type: 'Op'
 
+  # The map of conversions from CoffeeScript to JavaScript symbols.
   CONVERSIONS: {
     '==':   '==='
     '!=':   '!=='
@@ -839,8 +850,14 @@ exports.OpNode: class OpNode extends BaseNode
     'not':  '!'
   }
 
+  # The list of operators for which we perform
+  # [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin).
   CHAINABLE:        ['<', '>', '>=', '<=', '===', '!==']
+
+  # Our assignment operators that have no JavaScript equivalent.
   ASSIGNMENT:       ['||=', '&&=', '?=']
+
+  # Operators must come before their operands with a space.
   PREFIX_OPERATORS: ['typeof', 'delete']
 
   constructor: (operator, first, second, flip) ->
@@ -863,14 +880,17 @@ exports.OpNode: class OpNode extends BaseNode
     return @compile_existence(o)  if @operator is '?'
     [@first.compile(o), @operator, @second.compile(o)].join ' '
 
-  # Mimic Python's chained comparisons. See:
-  # http://docs.python.org/reference/expressions.html#notin
+  # Mimic Python's chained comparisons when multiple comparison operators are
+  # used sequentially. For example: `50 < 65 > 10`
   compile_chain: (o) ->
     shared: @first.unwrap().second
     [@first.second, shared]: shared.compile_reference(o) if shared instanceof CallNode
     [first, second, shared]: [@first.compile(o), @second.compile(o), shared.compile(o)]
     "($first) && ($shared $@operator $second)"
 
+  # When compiling a conditional assignment, take care to ensure that the
+  # operands are only evaluated once, even though we have to reference them
+  # more than once.
   compile_assignment: (o) ->
     [first, second]: [@first.compile(o), @second.compile(o)]
     o.scope.find(first) if first.match(IDENTIFIER)