1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

Big commit. First draft of stripping comments from generated JS output. Issue #41

This commit is contained in:
Jeremy Ashkenas 2010-06-27 12:59:54 -04:00
parent 8eedfe4bc6
commit ec570c46bf
20 changed files with 4473 additions and 1245 deletions

View file

@ -35,7 +35,7 @@ desc "Build the single concatenated and minified script for the browser"
task :browser do
sources = %w(helpers.js rewriter.js lexer.js parser.js scope.js nodes.js coffee-script.js)
code = sources.map {|s| File.read('lib/' + s) }.join('')
code = YUI::JavaScriptCompressor.new.compress(code)
# code = YUI::JavaScriptCompressor.new.compress(code)
File.open('extras/coffee-script.js', 'w+') {|f| f.write(HEADER + code) }
end

File diff suppressed because one or more lines are too long

View file

@ -1,27 +1,16 @@
(function(){
var CoffeeScript, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks;
var __hasProp = Object.prototype.hasOwnProperty;
// `cake` is a simplified version of [Make](http://www.gnu.org/software/make/)
// ([Rake](http://rake.rubyforge.org/), [Jake](http://github.com/280north/jake))
// 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');
helpers = require('./helpers').helpers;
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.
helpers.extend(global, {
// Define a Cake task with a short name, an optional sentence description,
// and the function to run as the action itself.
task: function(name, description, action) {
var _a;
if (!(action)) {
@ -36,13 +25,9 @@
};
return tasks[name];
},
// Define an option that the Cakefile accepts. The parsed options hash,
// containing all of the command-line options passed, will be made available
// as the first argument to the action.
option: function(letter, flag, description) {
return switches.push([letter, flag, description]);
},
// Invoke another task in the current Cakefile.
invoke: function(name) {
if (!(tasks[name])) {
missingTask(name);
@ -50,9 +35,6 @@
return tasks[name].action(options);
}
});
// Run `cake`. Executes all of the tasks you pass, in order. Note that Node's
// asynchrony may cause tasks to execute in a different order than you'd expect.
// If no tasks are passed, print the help screen.
exports.run = function() {
return path.exists('Cakefile', function(exists) {
var _a, _b, _c, _d, arg, args;
@ -76,7 +58,6 @@
return _a;
});
};
// Display the list of Cake tasks in a format similar to `rake -T`
printTasks = function() {
var _a, _b, desc, i, name, spaces, task;
puts('');
@ -98,7 +79,6 @@
return puts(oparse.help());
}
};
// Print an error and exit when attempting to all an undefined task.
missingTask = function(task) {
puts(("No such task: \"" + task + "\""));
return process.exit(1);

View file

@ -1,12 +1,5 @@
(function(){
var Lexer, compile, helpers, lexer, parser, path, processScripts;
// CoffeeScript can be used both on the server, as a command-line compiler based
// on Node.js/V8, or to run CoffeeScripts directly in the browser. This module
// contains the main entry functions for tokenzing, parsing, and compiling source
// CoffeeScript into JavaScript.
// If included on a webpage, it will automatically sniff out, compile, and
// execute all scripts present in `text/coffeescript` tags.
// Set up dependencies correctly for both the server and the browser.
if ((typeof process !== "undefined" && process !== null)) {
path = require('path');
Lexer = require('./lexer').Lexer;
@ -22,12 +15,8 @@
parser = this.parser;
helpers = this.helpers;
}
// The current CoffeeScript version number.
exports.VERSION = '0.6.2';
// Instantiate a Lexer for our use here.
lexer = new Lexer();
// Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
// compiler.
exports.compile = (compile = function(code, options) {
options = options || {};
try {
@ -39,27 +28,18 @@
throw err;
}
});
// Tokenize a string of CoffeeScript code, and return the array of tokens.
exports.tokens = function(code) {
return 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 = function(code) {
return parser.parse(lexer.tokenize(code));
};
// Compile and execute a string of CoffeeScript (on the server), correctly
// setting `__filename`, `__dirname`, and relative `require()`.
exports.run = (function(code, options) {
var __dirname, __filename;
module.filename = (__filename = options.source);
__dirname = path.dirname(__filename);
return 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: function() {
var token;
@ -78,10 +58,6 @@
return "";
}
};
// 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 ((typeof document !== "undefined" && document !== null) && document.getElementsByTagName) {
processScripts = function() {
var _a, _b, _c, _d, tag;

View file

@ -1,11 +1,5 @@
(function(){
var BANNER, CoffeeScript, SWITCHES, _a, compileOptions, compileScript, compileScripts, compileStdio, exec, fs, lint, optionParser, options, optparse, parseOptions, path, printTokens, sources, spawn, usage, version, watch, writeJs;
// The `coffee` utility. Handles command-line compilation of CoffeeScript
// into various forms: saved into `.js` files or printed to stdout, piped to
// [JSLint](http://javascriptlint.com/) or recompiled every time the source is
// saved, printed as a token stream or as the syntax tree, or launch an
// interactive REPL.
// External dependencies.
fs = require('fs');
path = require('path');
optparse = require('./optparse');
@ -13,17 +7,11 @@
_a = require('child_process');
spawn = _a.spawn;
exec = _a.exec;
// The help banner that is printed when `coffee` is called without arguments.
BANNER = 'coffee compiles CoffeeScript source files into JavaScript.\n\nUsage:\n coffee path/to/script.coffee';
// The list of all the valid option flags that `coffee` knows how to handle.
SWITCHES = [['-c', '--compile', 'compile to JavaScript and save as .js files'], ['-i', '--interactive', 'run an interactive CoffeeScript REPL'], ['-o', '--output [DIR]', 'set the directory for compiled JavaScript'], ['-w', '--watch', 'watch scripts for changes, and recompile'], ['-p', '--print', 'print the compiled JavaScript to stdout'], ['-l', '--lint', 'pipe the compiled JavaScript through JSLint'], ['-s', '--stdio', 'listen for and compile scripts over stdio'], ['-e', '--eval', 'compile a string from the command line'], ['--no-wrap', 'compile without the top-level function wrapper'], ['-t', '--tokens', 'print the tokens that the lexer produces'], ['-n', '--nodes', 'print the parse tree that Jison produces'], ['-v', '--version', 'display CoffeeScript version'], ['-h', '--help', 'display this help message']];
// Top-level objects shared by all the functions.
options = {};
sources = [];
optionParser = null;
// Run `coffee` by parsing passed options and determining what action to take.
// Many flags cause us to divert before compiling anything. Flags passed after
// `--` will be passed verbatim to your script as arguments in `process.argv`
exports.run = function() {
var flags, separator;
parseOptions();
@ -54,9 +42,6 @@
process.ARGV = (process.argv = flags);
return compileScripts();
};
// Asynchronously read in each CoffeeScript in a list of source files and
// compile them. If a directory is passed, recursively compile all
// '.coffee' extension source files in it and all subdirectories.
compileScripts = function() {
var _b, _c, _d, _e;
_b = []; _d = sources;
@ -99,9 +84,6 @@
}
return _b;
};
// Compile a single source script, containing the given code, according to the
// requested options. If evaluating the script directly sets `__filename`,
// `__dirname` and `module.filename` to be correct relative to the script's path.
compileScript = function(source, code, base) {
var codeOpts, js, o;
o = options;
@ -130,8 +112,6 @@
return puts(err.message);
}
};
// Attach the appropriate listeners to compile scripts incoming over **stdin**,
// and write them back to **stdout**.
compileStdio = function() {
var code, stdin;
code = '';
@ -145,9 +125,6 @@
return compileScript('stdio', code);
});
};
// Watch a source CoffeeScript file using `fs.watchFile`, recompiling it every
// time the file is updated. May be used in combination with other options,
// such as `--lint` or `--print`.
watch = function(source, base) {
return fs.watchFile(source, {
persistent: true,
@ -161,9 +138,6 @@
});
});
};
// Write out a JavaScript source file with the compiled code. By default, files
// are written out in `cwd` as `.js` files with the same name, but the output
// directory can be customized with `--output`.
writeJs = function(source, js, base) {
var baseDir, compile, dir, filename, jsPath, srcDir;
filename = path.basename(source, path.extname(source)) + '.js';
@ -186,8 +160,6 @@
}
});
};
// Pipe compiled JS through JSLint (requires a working `jsl` command), printing
// any errors or warnings that arise.
lint = function(js) {
var jsl, printIt;
printIt = function(buffer) {
@ -199,7 +171,6 @@
jsl.stdin.write(js);
return jsl.stdin.end();
};
// Pretty-print a stream of tokens.
printTokens = function(tokens) {
var _b, _c, _d, _e, _f, strings, tag, token, value;
strings = (function() {
@ -217,8 +188,6 @@
})();
return puts(strings.join(' '));
};
// Use the [OptionParser module](optparse.html) to extract all options from
// `process.argv` that are specified in `SWITCHES`.
parseOptions = function() {
var o;
optionParser = new optparse.OptionParser(SWITCHES, BANNER);
@ -228,7 +197,6 @@
sources = options.arguments.slice(2, options.arguments.length);
return sources;
};
// The compile-time options to pass to the CoffeeScript compiler.
compileOptions = function(source) {
var o;
o = {
@ -237,12 +205,10 @@
o['no_wrap'] = options['no-wrap'];
return o;
};
// Print the `--help` usage message and exit.
usage = function() {
puts(optionParser.help());
return process.exit(0);
};
// Print the `--version` message and exit.
version = function() {
puts(("CoffeeScript version " + CoffeeScript.VERSION));
return process.exit(0);

View file

@ -1,33 +1,8 @@
(function(){
var Parser, _a, _b, _c, _d, _e, _f, _g, _h, alt, alternatives, grammar, name, o, operators, token, tokens, unwrap;
var __hasProp = Object.prototype.hasOwnProperty;
// The CoffeeScript parser is generated by [Jison](http://github.com/zaach/jison)
// from this grammar file. Jison is a bottom-up parser generator, similar in
// style to [Bison](http://www.gnu.org/software/bison), implemented in JavaScript.
// It can recognize [LALR(1), LR(0), SLR(1), and LR(1)](http://en.wikipedia.org/wiki/LR_grammar)
// type grammars. To create the Jison parser, we list the pattern to match
// on the left-hand side, and the action to take (usually the creation of syntax
// tree nodes) on the right. As the parser runs, it
// shifts tokens from our token stream, from left to right, and
// [attempts to match](http://en.wikipedia.org/wiki/Bottom-up_parsing)
// the token sequence against the rules below. When a match can be made, it
// reduces into the [nonterminal](http://en.wikipedia.org/wiki/Terminal_and_nonterminal_symbols)
// (the enclosing name at the top), and we proceed from there.
// If you run the `cake build:parser` command, Jison constructs a parse table
// from our rules and saves it into `lib/parser.js`.
// The only dependency is on the **Jison.Parser**.
Parser = require('jison').Parser;
// Jison DSL
// ---------
// Since we're going to be wrapped in a function by Jison in any case, if our
// action immediately returns a value, we can optimize by removing the function
// wrapper and just returning the value directly.
unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/;
// Our handy DSL for Jison grammar generation, thanks to
// [Tim Caswell](http://github.com/creationix). For every rule in the grammar,
// we pass the pattern-defining string, the action to run, and extra options,
// optionally. If no action is specified, we simply pass the value of the
// previous nonterminal.
o = function(patternString, action, options) {
var match;
if (!(action)) {
@ -36,19 +11,7 @@
action = (match = (action + '').match(unwrap)) ? match[1] : ("(" + action + "())");
return [patternString, ("$$ = " + action + ";"), options];
};
// Grammatical Rules
// -----------------
// In all of the rules that follow, you'll see the name of the nonterminal as
// the key to a list of alternative matches. With each match's action, the
// dollar-sign variables are provided by Jison as references to the value of
// their numeric position, so in this rule:
// "Expression UNLESS Expression"
// `$1` would be the value of the first `Expression`, `$2` would be the token
// for the `UNLESS` terminal, and `$3` would be the value of the second
// `Expression`.
grammar = {
// The **Root** is the top-level node in the syntax tree. Since we parse bottom-up,
// all parsing must end here.
Root: [
o("", function() {
return new Expressions();
@ -56,7 +19,6 @@
return new Expressions();
}), o("Body"), o("Block TERMINATOR")
],
// Any list of statements and expressions, seperated by line breaks or semicolons.
Body: [
o("Line", function() {
return Expressions.wrap([$1]);
@ -64,9 +26,7 @@
return $1.push($3);
}), o("Body TERMINATOR")
],
// Expressions and statements, which make up a line in a body.
Line: [o("Expression"), o("Statement")],
// Pure statements which cannot be expressions.
Statement: [
o("Return"), o("Throw"), o("BREAK", function() {
return new LiteralNode($1);
@ -74,31 +34,19 @@
return new LiteralNode($1);
})
],
// All the different types of expressions in our language. The basic unit of
// CoffeeScript is the **Expression** -- everything that can be an expression
// is one. Expressions serve as the building blocks of many other rules, making
// them somewhat circular.
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("While"), o("For"), o("Switch"), o("Extends"), o("Class"), o("Splat"), o("Existence"), o("Comment")],
// A an indented block of expressions. Note that the [Rewriter](rewriter.html)
// will convert some postfix forms into blocks for us, by adjusting the
// token stream.
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("While"), o("For"), o("Switch"), o("Extends"), o("Class"), o("Splat"), o("Existence")],
Block: [
o("INDENT Body OUTDENT", function() {
return $2;
}), o("INDENT OUTDENT", function() {
return new Expressions();
}), o("TERMINATOR Comment", function() {
return Expressions.wrap([$2]);
})
],
// A literal identifier, a variable name or property.
Identifier: [
o("IDENTIFIER", function() {
return new LiteralNode($1);
})
],
// Alphanumerics are separated from the other **Literal** matchers because
// they can also serve as keys in object literals.
AlphaNumeric: [
o("NUMBER", function() {
return new LiteralNode($1);
@ -106,8 +54,6 @@
return new LiteralNode($1);
})
],
// All of our immediate values. These can (in general), be passed straight
// through and printed to JavaScript.
Literal: [
o("AlphaNumeric"), o("JS", function() {
return new LiteralNode($1);
@ -127,14 +73,11 @@
return new LiteralNode(false);
})
],
// Assignment of a variable, property, or index to a value.
Assign: [
o("Assignable ASSIGN Expression", function() {
return new AssignNode($1, $3);
})
],
// Assignment when it happens within an object literal. The difference from
// the ordinary **Assign** is that these allow numbers and strings as keys.
AssignObj: [
o("Identifier", function() {
return new ValueNode($1);
@ -142,9 +85,8 @@
return new AssignNode(new ValueNode($1), $3, 'object');
}), o("AlphaNumeric ASSIGN Expression", function() {
return new AssignNode(new ValueNode($1), $3, 'object');
}), o("Comment")
})
],
// A return statement from a function body.
Return: [
o("RETURN Expression", function() {
return new ReturnNode($2);
@ -152,25 +94,11 @@
return new ReturnNode(new ValueNode(new LiteralNode('null')));
})
],
// A comment. Because CoffeeScript passes comments through to JavaScript, we
// have to parse comments like any other construct, and identify all of the
// positions in which they can occur in the grammar.
Comment: [
o("COMMENT", function() {
return new CommentNode($1);
}), o("HERECOMMENT", function() {
return new CommentNode($1, 'herecomment');
})
],
// [The existential operator](http://jashkenas.github.com/coffee-script/#existence).
Existence: [
o("Expression ?", function() {
return new ExistenceNode($1);
})
],
// The **Code** node is the function literal. It's defined by an indented block
// of **Expressions** preceded by a function arrow, with an optional parameter
// list.
Code: [
o("PARAM_START ParamList PARAM_END FuncGlyph Block", function() {
return new CodeNode($2, $5, $4);
@ -178,8 +106,6 @@
return new CodeNode([], $2, $1);
})
],
// CoffeeScript has two different symbols for functions. `->` is for ordinary
// functions, and `=>` is for functions bound to the current value of *this*.
FuncGlyph: [
o("->", function() {
return 'func';
@ -187,9 +113,7 @@
return 'boundfunc';
})
],
// An optional, trailing comma.
OptComma: [o(''), o(',')],
// The list of parameters that a function accepts can be of any length.
ParamList: [
o("", function() {
return [];
@ -199,8 +123,6 @@
return $1.concat([$3]);
})
],
// A single parameter in a function definition can be ordinary, or a splat
// that hoovers up the remaining arguments.
Param: [
o("PARAM", function() {
return new LiteralNode($1);
@ -208,13 +130,11 @@
return new SplatNode($1);
})
],
// A splat that occurs outside of a parameter list.
Splat: [
o("Expression . . .", function() {
return new SplatNode($1);
})
],
// Variables and properties that can be assigned to.
SimpleAssignable: [
o("Identifier", function() {
return new ValueNode($1);
@ -224,7 +144,6 @@
return new ValueNode($1, [$2]);
}), o("ThisProperty")
],
// Everything that can be assigned to.
Assignable: [
o("SimpleAssignable"), o("Array", function() {
return new ValueNode($1);
@ -232,8 +151,6 @@
return new ValueNode($1);
})
],
// The types of things that can be treated as values -- assigned to, invoked
// as functions, indexed into, named as a class, etc.
Value: [
o("Assignable"), o("Literal", function() {
return new ValueNode($1);
@ -245,8 +162,6 @@
return new ValueNode(new LiteralNode('null'));
})
],
// The general group of accessors into an object, by property, by prototype
// or by array index or slice.
Accessor: [
o("PROPERTY_ACCESS Identifier", function() {
return new AccessorNode($2);
@ -260,7 +175,6 @@
return new SliceNode($1);
})
],
// Indexing into an object or array using bracket notation.
Index: [
o("INDEX_START Expression INDEX_END", function() {
return new IndexNode($2);
@ -272,14 +186,11 @@
return $2;
})
],
// In CoffeeScript, an object literal is simply a list of assignments.
Object: [
o("{ AssignList OptComma }", function() {
return new ObjectNode($2);
})
],
// Assignment of properties within an object literal can be separated by
// comma, as in JavaScript, or simply by newline.
AssignList: [
o("", function() {
return [];
@ -293,8 +204,6 @@
return $1.concat($4);
})
],
// Class definitions have optional bodies of prototype property assignments,
// and optional references to the superclass.
Class: [
o("CLASS SimpleAssignable", function() {
return new ClassNode($2);
@ -306,7 +215,6 @@
return new ClassNode($2, $4, $6);
})
],
// Assignments that can happen directly inside a class declaration.
ClassAssign: [
o("AssignObj", function() {
return $1;
@ -314,7 +222,6 @@
return new AssignNode(new ValueNode($1), $3, 'this');
})
],
// A list of assignments to a class.
ClassBody: [
o("", function() {
return [];
@ -324,21 +231,16 @@
return $1.concat($3);
})
],
// The three flavors of function call: normal, object instantiation with `new`,
// and calling `super()`
Call: [
o("Invocation"), o("NEW Invocation", function() {
return $2.newInstance();
}), o("Super")
],
// Extending an object by setting its prototype chain to reference a parent
// object.
Extends: [
o("SimpleAssignable EXTENDS Value", function() {
return new ExtendsNode($1, $3);
})
],
// Ordinary function invocation, or a chained series of calls.
Invocation: [
o("Value Arguments", function() {
return new CallNode($1, $2);
@ -346,19 +248,16 @@
return new CallNode($1, $2);
})
],
// The list of arguments to a function call.
Arguments: [
o("CALL_START ArgList OptComma CALL_END", function() {
return $2;
})
],
// Calling super.
Super: [
o("SUPER CALL_START ArgList OptComma CALL_END", function() {
return new CallNode('super', $3);
})
],
// A reference to the *this* current object.
This: [
o("THIS", function() {
return new ValueNode(new LiteralNode('this'));
@ -366,13 +265,11 @@
return new ValueNode(new LiteralNode('this'));
})
],
// A reference to a property on *this*.
ThisProperty: [
o("@ Identifier", function() {
return new ValueNode(new LiteralNode('this'), [new AccessorNode($2)]);
})
],
// The CoffeeScript range literal.
Range: [
o("[ Expression . . Expression ]", function() {
return new RangeNode($2, $5);
@ -380,7 +277,6 @@
return new RangeNode($2, $6, true);
})
],
// The slice literal.
Slice: [
o("INDEX_START Expression . . Expression INDEX_END", function() {
return new RangeNode($2, $5);
@ -388,15 +284,11 @@
return new RangeNode($2, $6, true);
})
],
// The array literal.
Array: [
o("[ ArgList OptComma ]", function() {
return new ArrayNode($2);
})
],
// The **ArgList** is both the list of objects passed into a function call,
// as well as the contents of an array literal
// (i.e. comma-separated expressions). Newlines work as well.
ArgList: [
o("", function() {
return [];
@ -410,9 +302,6 @@
return $1.concat($4);
})
],
// Just simple, comma-separated, required arguments (no fancy syntax). We need
// this to be separate from the **ArgList** for use in **Switch** blocks, where
// having the newlines wouldn't make sense.
SimpleArgs: [
o("Expression"), o("SimpleArgs , Expression", function() {
if ($1 instanceof Array) {
@ -422,7 +311,6 @@
}
})
],
// The variants of *try/catch/finally* exception handling blocks.
Try: [
o("TRY Block Catch", function() {
return new TryNode($2, $3[0], $3[1]);
@ -432,28 +320,21 @@
return new TryNode($2, $3[0], $3[1], $5);
})
],
// A catch clause names its error and runs a block of code.
Catch: [
o("CATCH Identifier Block", function() {
return [$2, $3];
})
],
// Throw an exception object.
Throw: [
o("THROW Expression", function() {
return new ThrowNode($2);
})
],
// Parenthetical expressions. Note that the **Parenthetical** is a **Value**,
// not an **Expression**, so if you need to use an expression in a place
// where only values are accepted, wrapping it in parentheses will always do
// the trick.
Parenthetical: [
o("( Line )", function() {
return new ParentheticalNode($2);
})
],
// The condition portion of a while loop.
WhileSource: [
o("WHILE Expression", function() {
return new WhileNode($2);
@ -472,8 +353,6 @@
});
})
],
// The while loop can either be normal, with a block of expressions to execute,
// or postfix, with a single expression. There is no do..while.
While: [
o("WhileSource Block", function() {
return $1.addBody($2);
@ -492,9 +371,6 @@
return new WhileNode(new LiteralNode('true')).addBody(Expressions.wrap([$2]));
})
],
// Array, object, and range comprehensions, at the most generic level.
// Comprehensions can either be normal, with a block of expressions to execute,
// or postfix, with a single expression.
For: [
o("Statement FOR ForVariables ForSource", function() {
return new ForNode($1, $4, $3[0], $3[1]);
@ -504,8 +380,6 @@
return new ForNode($4, $3, $2[0], $2[1]);
})
],
// An array of all accepted values for a variable inside the loop. This
// enables support for pattern matching.
ForValue: [
o("Identifier"), o("Array", function() {
return new ValueNode($1);
@ -513,9 +387,6 @@
return new ValueNode($1);
})
],
// An array or range comprehension has variables for the current element and
// (optional) reference to the current index. Or, *key, value*, in the case
// of object comprehensions.
ForVariables: [
o("ForValue", function() {
return [$1];
@ -523,9 +394,6 @@
return [$1, $3];
})
],
// The source of a comprehension is an array or object with an optional guard
// clause. If it's an array comprehension, you can also choose to step through
// in fixed-size increments.
ForSource: [
o("IN Expression", function() {
return {
@ -566,8 +434,6 @@
};
})
],
// The CoffeeScript switch/when/else block replaces the JavaScript
// switch/case/default by compiling into an if-else chain.
Switch: [
o("SWITCH Expression INDENT Whens OUTDENT", function() {
return $4.switchesOver($2);
@ -579,14 +445,11 @@
return $3.addElse($5, true);
})
],
// The inner list of whens is left recursive. At code-generation time, the
// IfNode will rewrite them into a proper chain.
Whens: [
o("When"), o("Whens When", function() {
return $1.addElse($2);
})
],
// An individual **When** clause, with action.
When: [
o("LEADING_WHEN SimpleArgs Block", function() {
return new IfNode($2, $3, {
@ -596,14 +459,8 @@
return new IfNode($2, $3, {
statement: true
});
}), o("Comment TERMINATOR When", function() {
$3.comment = $1;
return $3;
})
],
// The most basic form of *if* is a condition and an action. The following
// if-related rules are broken up along these lines in order to avoid
// ambiguity.
IfBlock: [
o("IF Expression Block", function() {
return new IfNode($2, $3);
@ -617,8 +474,6 @@
return $1.addElse($3);
})
],
// The full complement of *if* expressions, including postfix one-liner
// *if* and *unless*.
If: [
o("IfBlock"), o("Statement IF Expression", function() {
return new IfNode($3, Expressions.wrap([$1]), {
@ -640,12 +495,6 @@
});
})
],
// Arithmetic and logical operators, working on one or more operands.
// Here they are grouped by order of precedence. The actual precedence rules
// are defined at the bottom of the page. It would be shorter if we could
// combine most of these rules into a single generic *Operand OpSymbol Operand*
// -type rule, but in order to make the precedence binding possible, separate
// rules are necessary.
Operation: [
o("! Expression", function() {
return new OpNode('!', $2);
@ -742,20 +591,7 @@
})
]
};
// Precedence
// ----------
// Operators at the top of this list have higher precedence than the ones lower
// down. Following these rules is what makes `2 + 3 * 4` parse as:
// 2 + (3 * 4)
// And not:
// (2 + 3) * 4
operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["left", '==', '!='], ["left", '&&', '||'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW'], ["right", 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'NEW', 'SUPER', 'CLASS'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE']];
// Wrapping Up
// -----------
// Finally, now what we have our **grammar** and our **operators**, we can create
// our **Jison.Parser**. We do this by processing all of our rules, recording all
// terminals (every symbol which does not appear as the name of a rule above)
// as "tokens".
tokens = [];
_a = grammar;
for (name in _a) { if (__hasProp.call(_a, name)) {
@ -781,10 +617,6 @@
return _b;
})();
}}
// Initialize the **Parser** with our list of terminal **tokens**, our **grammar**
// rules, and the name of the root. Reverse the operators because Jison orders
// precedence from low to high, and we have it high to low
// (as in [Yacc](http://dinosaur.compilertools.net/yacc/index.html)).
exports.parser = new Parser({
tokens: tokens.join(' '),
bnf: grammar,

View file

@ -1,15 +1,10 @@
(function(){
var balancedString, compact, count, del, extend, flatten, helpers, include, indexOf, merge, starts;
var __hasProp = Object.prototype.hasOwnProperty;
// This file contains the common helper functions that we'd like to share among
// the **Lexer**, **Rewriter**, and the **Nodes**. Merge objects, flatten
// arrays, count characters, that sort of thing.
// Set up exported variables for both **Node.js** and the browser.
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
helpers = (exports.helpers = {});
// Cross-browser indexOf, so that IE can join the party.
helpers.indexOf = (indexOf = function(array, item, from) {
var _a, _b, index, other;
if (array.indexOf) {
@ -24,15 +19,12 @@
}
return -1;
});
// Does a list include a value?
helpers.include = (include = function(list, value) {
return indexOf(list, value) >= 0;
});
// Peek at the beginning of a given string to see if it matches a sequence.
helpers.starts = (starts = function(string, literal, start) {
return string.substring(start, (start || 0) + literal.length) === literal;
});
// Trim out all falsy values from an array.
helpers.compact = (compact = function(array) {
var _a, _b, _c, _d, item;
_a = []; _c = array;
@ -42,7 +34,6 @@
}
return _a;
});
// Count the number of occurences of a character in a string.
helpers.count = (count = function(string, letter) {
var num, pos;
num = 0;
@ -53,9 +44,6 @@
}
return num;
});
// Merge objects, returning a fresh copy with attributes from both sides.
// Used every time `BaseNode#compile` is called, to allow properties in the
// options hash to propagate down the tree without polluting other branches.
helpers.merge = (merge = function(options, overrides) {
var _a, _b, fresh, key, val;
fresh = {};
@ -73,8 +61,6 @@
}
return fresh;
});
// Extend a source object with the properties of another object (shallow copy).
// We use this to simulate Node's deprecated `process.mixin`
helpers.extend = (extend = function(object, properties) {
var _a, _b, key, val;
_a = []; _b = properties;
@ -84,8 +70,6 @@
}}
return _a;
});
// Return a completely flattened version of an array. Handy for getting a
// list of `children` from the nodes.
helpers.flatten = (flatten = function(array) {
var _a, _b, _c, item, memo;
memo = [];
@ -96,18 +80,12 @@
}
return memo;
});
// Delete a key from an object, returning the value. Useful when a node is
// looking for a particular method in an options hash.
helpers.del = (del = function(obj, key) {
var val;
val = obj[key];
delete obj[key];
return val;
});
// Matches a balanced group such as a single or double-quoted string. Pass in
// a series of delimiters, all of which must be nested correctly within the
// contents of the string. This method allows us to have strings within
// interpolations within strings, ad infinitum.
helpers.balancedString = (balancedString = function(str, delimited, options) {
var _a, _b, _c, _d, close, i, levels, open, pair, slash;
options = options || {};

View file

@ -1,7 +1,6 @@
(function(){
var _a, key, val;
var __hasProp = Object.prototype.hasOwnProperty;
// Loader for CoffeeScript as a Node.js library.
_a = require('./coffee-script');
for (key in _a) { if (__hasProp.call(_a, key)) {
val = _a[key];

View file

@ -1,13 +1,6 @@
(function(){
var ASSIGNED, ASSIGNMENT, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NEXT_CHARACTER, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, balancedString, compact, count, helpers, include, starts;
var ASSIGNED, ASSIGNMENT, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NEXT_CHARACTER, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, balancedString, compact, count, helpers, include, starts;
var __slice = Array.prototype.slice;
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
// matches against the beginning of the source code. When a match is found,
// a token is produced, we consume the match, and start again. Tokens are in the
// form:
// [tag, value, lineNumber]
// Which is a format that can be fed directly into [Jison](http://github.com/zaach/jison).
// Set up the Lexer for both Node.js and the browser, depending on where we are.
if ((typeof process !== "undefined" && process !== null)) {
_a = require('./rewriter');
Rewriter = _a.Rewriter;
@ -18,48 +11,25 @@
Rewriter = this.Rewriter;
helpers = this.helpers;
}
// Import the helpers we need.
_c = helpers;
include = _c.include;
count = _c.count;
starts = _c.starts;
compact = _c.compact;
balancedString = _c.balancedString;
// The Lexer Class
// ---------------
// The Lexer class reads a stream of CoffeeScript and divvys it up into tagged
// tokens. Some potential ambiguity in the grammar has been avoided by
// pushing some extra smarts into the Lexer.
exports.Lexer = (function() {
Lexer = function() { };
// **tokenize** is the Lexer's main method. Scan by attempting to match tokens
// one at a time, using a regular expression anchored at the start of the
// remaining code, or a custom recursive token-matching method
// (for interpolations). When the next token has been recorded, we move forward
// within the code past the token, and begin again.
// Each tokenizing method is responsible for incrementing `@i` by the number of
// characters it has consumed. `@i` can be thought of as our finger on the page
// of source.
// Before returning the token stream, run it through the [Rewriter](rewriter.html)
// unless explicitly asked not to.
Lexer.prototype.tokenize = function(code, options) {
var o;
code = code.replace(/(\r|\s+$)/g, '');
o = options || {};
this.code = code;
// The remainder of the source code.
this.i = 0;
// Current character position we're parsing.
this.line = o.line || 0;
// The current line.
this.indent = 0;
// The current indentation level.
this.outdebt = 0;
// The under-outdentation of the last outdent.
this.indents = [];
// The stack of all current indentation levels.
this.tokens = [];
// Stream of parsed tokens in the form ['TYPE', value, line]
while (this.i < this.code.length) {
this.chunk = this.code.slice(this.i);
this.extractNextToken();
@ -70,9 +40,6 @@
}
return (new Rewriter()).rewrite(this.tokens);
};
// At every position, run through this list of attempted matches,
// short-circuiting if any of them succeed. Their order determines precedence:
// `@literalToken` is the fallback catch-all.
Lexer.prototype.extractNextToken = function() {
if (this.identifierToken()) {
return null;
@ -103,14 +70,6 @@
}
return this.literalToken();
};
// Tokenizers
// ----------
// Matches identifying literals: variables, keywords, method names, etc.
// Check to ensure that JavaScript reserved words aren't being used as
// identifiers. Because CoffeeScript reserves a handful of keywords that are
// allowed in JavaScript, we're careful not to tag them as keywords when
// referenced as property names here, so you can still do `jQuery.is()` even
// though `is` means `===` otherwise.
Lexer.prototype.identifierToken = function() {
var close_index, forcedIdentifier, id, tag;
if (!(id = this.match(IDENTIFIER, 1))) {
@ -154,8 +113,6 @@
}
return true;
};
// Matches numbers, including decimals, hex, and exponential notation.
// Be careful not to interfere with ranges-in-progress.
Lexer.prototype.numberToken = function() {
var number;
if (!(number = this.match(NUMBER, 1))) {
@ -168,8 +125,6 @@
this.token('NUMBER', number);
return true;
};
// Matches strings, including multi-line strings. Ensures that quotation marks
// are balanced within the string's contents, and within nested interpolations.
Lexer.prototype.stringToken = function() {
var string;
if (!(starts(this.chunk, '"') || starts(this.chunk, "'"))) {
@ -183,8 +138,6 @@
this.i += string.length;
return true;
};
// Matches heredocs, adjusting indentation to the correct level, as heredocs
// preserve whitespace, but ignore indentation to the left.
Lexer.prototype.heredocToken = function() {
var doc, match, quote;
if (!(match = this.chunk.match(HEREDOC))) {
@ -199,34 +152,15 @@
this.i += match[1].length;
return true;
};
// Matches and conumes comments. We pass through comments into JavaScript,
// so they're treated as real tokens, like any other part of the language.
Lexer.prototype.commentToken = function() {
var comment, i, lines, match;
var match;
if (!(match = this.chunk.match(COMMENT))) {
return false;
}
if (match[3]) {
comment = this.sanitizeHeredoc(match[3], {
herecomment: true
});
this.token('HERECOMMENT', comment.split(MULTILINER));
this.token('TERMINATOR', '\n');
} else {
lines = compact(match[1].replace(COMMENT_CLEANER, '').split(MULTILINER));
i = this.tokens.length - 1;
if (this.unfinished()) {
while (this.tokens[i] && !include(LINE_BREAK, this.tokens[i][0])) {
i -= 1;
}
}
this.tokens.splice(i + 1, 0, ['COMMENT', lines, this.line], ['TERMINATOR', '\n', this.line]);
}
this.line += count(match[1], "\n");
this.i += match[1].length;
return true;
};
// Matches JavaScript interpolated directly into the source via backticks.
Lexer.prototype.jsToken = function() {
var script;
if (!(starts(this.chunk, '`'))) {
@ -239,10 +173,6 @@
this.i += script.length;
return true;
};
// Matches regular expression literals. Lexing regular expressions is difficult
// to distinguish from division, so we borrow some basic heuristics from
// JavaScript and Ruby, borrow slash balancing from `@balancedToken`, and
// borrow interpolation from `@interpolateString`.
Lexer.prototype.regexToken = function() {
var end, flags, regex, str;
if (!(this.chunk.match(REGEX_START))) {
@ -274,22 +204,12 @@
this.i += regex.length;
return true;
};
// Matches a token in which which the passed delimiter pairs must be correctly
// balanced (ie. strings, JS literals).
Lexer.prototype.balancedToken = function() {
var delimited;
var _d = arguments.length, _e = _d >= 1;
delimited = __slice.call(arguments, 0, _d - 0);
return balancedString(this.chunk, delimited);
};
// Matches newlines, indents, and outdents, and determines which is which.
// If we can detect that the current line is continued onto the the next line,
// then the newline is suppressed:
// elements
// .each( ... )
// .map( ... )
// Keeps track of the level of indentation, because a single outdent token
// can close multiple indents, so we need to know how far in we happen to be.
Lexer.prototype.lineToken = function() {
var diff, indent, nextCharacter, noNewlines, prev, size;
if (!(indent = this.match(MULTI_DENT, 1))) {
@ -299,7 +219,7 @@
this.i += indent.length;
prev = this.prev(2);
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
nextCharacter = this.chunk.match(NEXT_CHARACTER)[1];
nextCharacter = this.match(NEXT_CHARACTER, 1);
noNewlines = nextCharacter === '.' || nextCharacter === ',' || this.unfinished();
if (size === this.indent) {
if (noNewlines) {
@ -319,8 +239,6 @@
this.indent = size;
return true;
};
// Record an outdent token or multiple tokens, if we happen to be moving back
// inwards past several recorded indents.
Lexer.prototype.outdentToken = function(moveOut, noNewlines) {
var lastIndent;
if (moveOut > -this.outdebt) {
@ -340,8 +258,6 @@
}
return true;
};
// Matches and consumes non-meaningful whitespace. Tag the previous token
// as being "spaced", because there are some cases where it makes a difference.
Lexer.prototype.whitespaceToken = function() {
var prev, space;
if (!(space = this.match(WHITESPACE, 1))) {
@ -354,26 +270,18 @@
this.i += space.length;
return true;
};
// Generate a newline token. Consecutive newlines get merged together.
Lexer.prototype.newlineToken = function(newlines) {
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Use a `\` at a line-ending to suppress the newline.
// The slash is removed here once its job is done.
Lexer.prototype.suppressNewlines = function() {
if (this.value() === "\\") {
this.tokens.pop();
}
return true;
};
// We treat all other single characters as a token. Eg.: `( ) , . !`
// Multi-character operators are also literal tokens, so that Jison can assign
// the proper order of operations. There are some symbols that we tag specially
// here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish
// parentheses that indicate a method call from regular parentheses, and so on.
Lexer.prototype.literalToken = function() {
var match, prevSpaced, space, tag, value;
match = this.chunk.match(OPERATOR);
@ -412,11 +320,6 @@
this.token(tag, value);
return true;
};
// Token Manipulators
// ------------------
// As we consume a new `IDENTIFIER`, look at the previous token to determine
// if it's a special kind of accessor. Return `true` if any type of accessor
// is the previous token.
Lexer.prototype.tagAccessor = function() {
var accessor, prev;
if ((!(prev = this.prev())) || (prev && prev.spaced)) {
@ -442,8 +345,6 @@
return false;
}
};
// Sanitize a heredoc or herecomment by escaping internal double quotes and
// erasing all external indentation on the left-hand side.
Lexer.prototype.sanitizeHeredoc = function(doc, options) {
var _d, attempt, indent, match;
while (match = HEREDOC_INDENT.exec(doc)) {
@ -452,22 +353,14 @@
indent = attempt;
}
}
doc = doc.replace(new RegExp("^" + indent, 'gm'), '');
if (options.herecomment) {
return doc;
}
return doc.replace(MULTILINER, "\\n").replace(new RegExp(options.quote, 'g'), ("\\" + options.quote));
return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(new RegExp(options.quote, 'g'), ("\\" + options.quote));
};
// Tag a half assignment.
Lexer.prototype.tagHalfAssignment = function(tag) {
var last;
last = this.tokens.pop();
this.tokens.push([("" + tag + "="), ("" + tag + "="), last[2]]);
return true;
};
// A source of ambiguity in our grammar used to be parameter lists in function
// definitions versus argument lists in function calls. Walk backwards, tagging
// parameters specially in order to make things easier for the parser.
Lexer.prototype.tagParameters = function() {
var _d, i, tok;
if (this.tag() !== ')') {
@ -491,28 +384,15 @@
}
return true;
};
// Close up all remaining open blocks at the end of the file.
Lexer.prototype.closeIndentation = function() {
return this.outdentToken(this.indent);
};
// The error for when you try to use a forbidden word in JavaScript as
// an identifier.
Lexer.prototype.identifierError = function(word) {
throw new Error(("SyntaxError: Reserved word \"" + word + "\" on line " + (this.line + 1)));
};
// The error for when you try to assign to a reserved word in JavaScript,
// like "function" or "default".
Lexer.prototype.assignmentError = function() {
throw new Error(("SyntaxError: Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned"));
};
// Expand variables and expressions inside double-quoted strings using
// [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation)
// for substitution of bare variables as well as arbitrary expressions.
// "Hello $name."
// "Hello ${name.capitalize()}."
// If it encounters an interpolation, this method will recursively create a
// new Lexer, tokenize the interpolated contents, and merge them into the
// token stream.
Lexer.prototype.interpolateString = function(str, escapeQuotes) {
var _d, _e, _f, _g, _h, _i, _j, escaped, expr, group, i, idx, inner, interp, interpolated, lexer, match, nested, pi, quote, tag, tok, token, tokens, value;
if (str.length < 3 || !starts(str, '"')) {
@ -598,13 +478,9 @@
return tokens;
}
};
// Helpers
// -------
// Add a token to the results, taking note of the line number.
Lexer.prototype.token = function(tag, value) {
return this.tokens.push([tag, value, this.line]);
};
// Peek at a tag in the current token stream.
Lexer.prototype.tag = function(index, newTag) {
var tok;
if (!(tok = this.prev(index))) {
@ -616,7 +492,6 @@
}
return tok[0];
};
// Peek at a value in the current token stream.
Lexer.prototype.value = function(index, val) {
var tok;
if (!(tok = this.prev(index))) {
@ -628,12 +503,9 @@
}
return tok[1];
};
// Peek at a previous token, entire.
Lexer.prototype.prev = function(index) {
return this.tokens[this.tokens.length - (index || 1)];
};
// Attempt to match a string against the current chunk, returning the indexed
// match if successful, and `false` otherwise.
Lexer.prototype.match = function(regex, index) {
var m;
if (!(m = this.chunk.match(regex))) {
@ -645,7 +517,6 @@
return false;
}
};
// Are we in the midst of an unfinished expression?
Lexer.prototype.unfinished = function() {
var prev;
prev = this.prev(2);
@ -653,64 +524,38 @@
};
return Lexer;
})();
// Constants
// ---------
// Keywords that CoffeeScript shares in common with JavaScript.
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends", "class", "this", "null"];
// CoffeeScript-only keywords, which we're more relaxed about allowing. They can't
// be used standalone, but you can reference them as an attached property.
COFFEE_ALIASES = ["and", "or", "is", "isnt", "not"];
COFFEE_KEYWORDS = COFFEE_ALIASES.concat(["then", "unless", "until", "loop", "yes", "no", "on", "off", "of", "by", "where", "when"]);
// The list of keywords that are reserved by JavaScript, but not used, or are
// used by CoffeeScript internally. We throw an error when these are encountered,
// to avoid having a JavaScript error at runtime.
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "const", "let", "enum", "export", "import", "native"];
// The superset of both JavaScript keywords and reserved words, none of which may
// be used as identifiers or properties.
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
// Token matching regexes.
IDENTIFIER = /^([a-zA-Z\$_](\w|\$)*)/;
NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[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*(\.\w+)*)/;
OPERATOR = /^([+\*&|\/\-%=<>:!?]+)([ \t]*)/;
WHITESPACE = /^([ \t]+)/;
COMMENT = /^((\n?[ \t]*)?#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|((\n?[ \t]*)?#[^\n]*)+)/;
COMMENT = /^([\n \t]*#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|([\n \t]*#[^\n]*)+)/;
CODE = /^((-|=)>)/;
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
LAST_DENTS = /\n([ \t]*)/g;
LAST_DENT = /\n([ \t]*)/;
ASSIGNMENT = /^[:=]$/;
// Regex-matching-regexes.
REGEX_START = /^\/[^\/ ]/;
REGEX_INTERPOLATION = /([^\\]\$[a-zA-Z_@]|[^\\]\$\{.*[^\\]\})/;
REGEX_END = /^(([imgy]{1,4})\b|\W|$)/;
REGEX_ESCAPE = /\\[^\$]/g;
// Token cleaning regexes.
JS_CLEANER = /(^`|`$)/g;
MULTILINER = /\n/g;
STRING_NEWLINES = /\n[ \t]*/g;
COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/mg;
NO_NEWLINE = /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/;
HEREDOC_INDENT = /(\n+([ \t]*)|^([ \t]+))/g;
ASSIGNED = /^([a-zA-Z\$_]\w*[ \t]*?[:=])/;
NEXT_CHARACTER = /^\s*(\S)/;
// Tokens which a regular expression will never immediately follow, but which
// a division operator might.
// See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
// Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX = ['NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE', ']'];
// Tokens which could legitimately be invoked or indexed. A opening
// parentheses or bracket following these tokens will be recorded as the start
// of a function invocation or indexing operation.
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', '::'];
// Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
// occurs at the start of a line. We disambiguate these from trailing whens to
// avoid an ambiguity in the grammar.
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
// Half-assignments...
HALF_ASSIGNMENTS = ['-', '+', '/', '*', '%', '||', '&&', '?'];
// Conversions from CoffeeScript operators into JavaScript ones.
CONVERSIONS = {
'and': '&&',
'or': '||',

View file

@ -1,5 +1,5 @@
(function(){
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, flatten, helpers, include, indexOf, literal, merge, starts, utility;
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, flatten, helpers, include, indexOf, literal, merge, starts, utility;
var __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
@ -7,12 +7,6 @@
child.prototype = new ctor();
child.prototype.constructor = child;
};
// `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 and the [helper](helpers.html) functions.
if ((typeof process !== "undefined" && process !== null)) {
Scope = require('./scope').Scope;
helpers = require('./helpers').helpers;
@ -21,7 +15,6 @@
helpers = this.helpers;
Scope = this.Scope;
}
// Import the helpers we plan to use.
_a = helpers;
compact = _a.compact;
flatten = _a.flatten;
@ -30,27 +23,8 @@
include = _a.include;
indexOf = _a.indexOf;
starts = _a.starts;
//### BaseNode
// The **BaseNode** is the abstract base class for all nodes in the syntax tree.
// Each subclass implements the `compileNode` method, which performs the
// code generation for that node. To compile a node to JavaScript,
// call `compile` on it, which wraps `compileNode` 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() { };
// 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 *pureStatement*, 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).
// If a Node is *topSensitive*, 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.
BaseNode.prototype.compile = function(o) {
var closure, top;
this.options = merge(o || {});
@ -62,23 +36,18 @@
}
}
top = this.topSensitive() ? this.options.top : del(this.options, 'top');
closure = this.isStatement() && !this.isPureStatement() && !top && !this.options.asStatement && !(this instanceof CommentNode) && !this.containsPureStatement();
closure = this.isStatement() && !this.isPureStatement() && !top && !this.options.asStatement && !this.containsPureStatement();
if (closure) {
return this.compileClosure(this.options);
} else {
return this.compileNode(this.options);
}
};
// Statements converted into expressions via closure-wrapping share a scope
// object with their parent closure, to preserve the expected lexical scope.
BaseNode.prototype.compileClosure = function(o) {
this.tab = o.indent;
o.sharedScope = 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,
// by assigning it to a temporary variable.
BaseNode.prototype.compileReference = function(o, options) {
var compiled, pair, reference;
pair = (function() {
@ -95,7 +64,6 @@
}
return [pair[0].compile(o), pair[1].compile(o)];
};
// Convenience method to grab the current indentation level, plus tabbing in.
BaseNode.prototype.idt = function(tabs) {
var idt, num;
idt = this.tab || '';
@ -105,16 +73,9 @@
}
return idt;
};
// Construct a node that returns the current node's result.
// Note that this is overridden for smarter behavior for
// many statement nodes (eg IfNode, ForNode)...
BaseNode.prototype.makeReturn = function() {
return new ReturnNode(this);
};
// Does this node, or any of its children, contain a node of a certain kind?
// Recursively traverses down the *children* of the nodes, yielding to a block
// and returning true when the block finds a match. `contains` does not cross
// scope boundaries.
BaseNode.prototype.contains = function(block) {
var contains;
contains = false;
@ -126,25 +87,19 @@
});
return contains;
};
// Is this node of a certain type, or does it contain the type?
BaseNode.prototype.containsType = function(type) {
return this instanceof type || this.contains(function(n) {
return n instanceof type;
});
};
// Convenience for the most common use of contains. Does the node contain
// a pure statement?
BaseNode.prototype.containsPureStatement = function() {
return this.isPureStatement() || this.contains(function(n) {
return n.isPureStatement();
});
};
// Perform an in-order traversal of the AST. Crosses scope boundaries.
BaseNode.prototype.traverse = function(block) {
return this.traverseChildren(true, block);
};
// `toString` representation of the node, for inspecting the parse tree.
// This is what `coffee --nodes` prints out.
BaseNode.prototype.toString = function(idt) {
var _b, _c, _d, _e, child;
idt = idt || '';
@ -193,8 +148,6 @@
}
});
};
// Default implementations of the common node properties and methods. Nodes
// will override these with custom logic, if needed.
BaseNode.prototype['class'] = 'BaseNode';
BaseNode.prototype.children = [];
BaseNode.prototype.unwrap = function() {
@ -211,10 +164,6 @@
};
return BaseNode;
})();
//### Expressions
// The expressions body is the list of expressions that forms the body of an
// indented block of code -- the implementation of a function, a clause in an
// `if`, `switch`, or `try`, and so on...
exports.Expressions = (function() {
Expressions = function(nodes) {
this.expressions = compact(flatten(nodes || []));
@ -226,18 +175,14 @@
Expressions.prototype.isStatement = function() {
return true;
};
// Tack an expression on to the end of this expression list.
Expressions.prototype.push = function(node) {
this.expressions.push(node);
return this;
};
// Add an expression at the beginning of this expression list.
Expressions.prototype.unshift = function(node) {
this.expressions.unshift(node);
return this;
};
// If this Expressions consists of just a single node, unwrap it by pulling
// it back out.
Expressions.prototype.unwrap = function() {
if (this.expressions.length === 1) {
return this.expressions[0];
@ -245,26 +190,19 @@
return this;
}
};
// Is this an empty block of code?
Expressions.prototype.empty = function() {
return this.expressions.length === 0;
};
// An Expressions node does not return its entire body, rather it
// ensures that the final expression is returned.
Expressions.prototype.makeReturn = function() {
var idx, last;
idx = this.expressions.length - 1;
last = this.expressions[idx];
if (last instanceof CommentNode) {
last = this.expressions[idx -= 1];
}
if (!last || last instanceof ReturnNode) {
return this;
}
this.expressions[idx] = last.makeReturn();
return this;
};
// An **Expressions** is the only node that can serve as the root.
Expressions.prototype.compile = function(o) {
o = o || {};
if (o.scope) {
@ -284,8 +222,6 @@
return _b;
}).call(this).join("\n");
};
// If we happen to be the top-level **Expressions**, wrap everything in
// a safety closure, unless requested not to.
Expressions.prototype.compileRoot = function(o) {
var code;
o.indent = (this.tab = o.noWrap ? '' : TAB);
@ -298,8 +234,6 @@
return "(function(){\n" + code + "\n})();\n";
}
};
// Compile the expressions body for the contents of a function, with
// declarations of all inner variables pushed up to the top.
Expressions.prototype.compileWithDeclarations = function(o) {
var code;
code = this.compileNode(o);
@ -311,9 +245,6 @@
}
return code;
};
// Compiles a single expression within the expressions body. If we need to
// return the result, and it's an expression, simply return it. If it's a
// statement, ask the statement to do so.
Expressions.prototype.compileExpression = function(node, o) {
var compiledNode;
this.tab = o.indent;
@ -328,18 +259,12 @@
};
return Expressions;
})();
// Wrap up the given nodes as an **Expressions**, unless it already happens
// to be one.
Expressions.wrap = function(nodes) {
if (nodes.length === 1 && nodes[0] instanceof Expressions) {
return nodes[0];
}
return new Expressions(nodes);
};
//### LiteralNode
// Literals are static values that can be passed through directly into
// JavaScript without translation, such as: strings, numbers,
// `true`, `false`, `null`...
exports.LiteralNode = (function() {
LiteralNode = function(value) {
this.value = value;
@ -347,8 +272,6 @@
};
__extends(LiteralNode, BaseNode);
LiteralNode.prototype['class'] = 'LiteralNode';
// Break and continue must be treated as pure statements -- they lose their
// meaning when wrapped in a closure.
LiteralNode.prototype.isStatement = function() {
return this.value === 'break' || this.value === 'continue';
};
@ -364,9 +287,6 @@
};
return LiteralNode;
})();
//### ReturnNode
// A `return` is a *pureStatement* -- wrapping it in a closure wouldn't
// make sense.
exports.ReturnNode = (function() {
ReturnNode = function(expression) {
this.expression = expression;
@ -401,9 +321,6 @@
};
return ReturnNode;
})();
//### ValueNode
// A value, variable or literal or parenthesized, indexed or dotted into,
// or vanilla.
exports.ValueNode = (function() {
ValueNode = function(base, properties) {
this.base = base;
@ -414,8 +331,6 @@
ValueNode.prototype.SOAK = " == undefined ? undefined : ";
ValueNode.prototype['class'] = 'ValueNode';
ValueNode.prototype.children = ['base', 'properties'];
// A **ValueNode** has a base and a list of property accesses.
// Add a property access to the list.
ValueNode.prototype.push = function(prop) {
this.properties.push(prop);
return this;
@ -423,7 +338,6 @@
ValueNode.prototype.hasProperties = function() {
return !!this.properties.length;
};
// Some boolean checks for the benefit of other nodes.
ValueNode.prototype.isArray = function() {
return this.base instanceof ArrayNode && !this.hasProperties();
};
@ -440,8 +354,6 @@
return this.base.makeReturn();
}
};
// The value can be unwrapped as its inner node, if there are no attached
// properties.
ValueNode.prototype.unwrap = function() {
if (this.properties.length) {
return this;
@ -449,14 +361,12 @@
return this.base;
}
};
// Values are considered to be statements if their base is a statement.
ValueNode.prototype.isStatement = function() {
return this.base.isStatement && this.base.isStatement() && !this.hasProperties();
};
ValueNode.prototype.isNumber = function() {
return this.base instanceof LiteralNode && this.base.value.match(NUMBER);
};
// Works out if the value is the start of a chain.
ValueNode.prototype.isStart = function(o) {
var node;
if (this === o.chainRoot && this.properties[0] instanceof AccessorNode) {
@ -468,10 +378,6 @@
}
return node === this;
};
// We compile a value to JavaScript by compiling and joining each property.
// Things get much more insteresting if the chain of properties has *soak*
// operators `?.` interspersed. Then we have to take care not to accidentally
// evaluate a anything twice when building the soak chain.
ValueNode.prototype.compileNode = function(o) {
var _b, _c, baseline, complete, i, only, op, part, prop, props, temp;
only = del(o, 'onlyFirst');
@ -511,37 +417,6 @@
};
return ValueNode;
})();
//### CommentNode
// CoffeeScript passes through comments as JavaScript comments at the
// same position.
exports.CommentNode = (function() {
CommentNode = function(lines, kind) {
this.lines = lines;
this.kind = kind;
return this;
};
__extends(CommentNode, BaseNode);
CommentNode.prototype['class'] = 'CommentNode';
CommentNode.prototype.isStatement = function() {
return true;
};
CommentNode.prototype.makeReturn = function() {
return this;
};
CommentNode.prototype.compileNode = function(o) {
var sep;
if (this.kind === 'herecomment') {
sep = '\n' + this.tab;
return "" + this.tab + "/*" + sep + (this.lines.join(sep)) + "\n" + this.tab + "*/";
} else {
return ("" + this.tab + "//") + this.lines.join(("\n" + this.tab + "//"));
}
};
return CommentNode;
})();
//### CallNode
// Node for a function invocation. Takes care of converting `super()` calls into
// calls against the prototype's function of the same name.
exports.CallNode = (function() {
CallNode = function(variable, args) {
this.isNew = false;
@ -556,7 +431,6 @@
__extends(CallNode, BaseNode);
CallNode.prototype['class'] = 'CallNode';
CallNode.prototype.children = ['variable', 'args'];
// Tag this invocation as creating a new instance.
CallNode.prototype.newInstance = function() {
this.isNew = true;
return this;
@ -568,7 +442,6 @@
return '';
}
};
// Grab the reference to the superclass' implementation of the current method.
CallNode.prototype.superReference = function(o) {
var meth, methname;
methname = o.scope.method.name;
@ -583,7 +456,6 @@
})();
return meth;
};
// Compile a vanilla function call.
CallNode.prototype.compileNode = function(o) {
var _b, _c, _d, _e, _f, _g, _h, arg, args, compilation;
if (!(o.chainRoot)) {
@ -611,13 +483,9 @@
return compilation;
}
};
// `super()` is converted into a call against the superclass's implementation
// of the current function.
CallNode.prototype.compileSuper = function(args, o) {
return "" + (this.superReference(o)) + ".call(this" + (args.length ? ', ' : '') + args + ")";
};
// If you call a function with a splat, it's converted into a JavaScript
// `.apply()` call to allow an array of arguments to be passed.
CallNode.prototype.compileSplat = function(o) {
var meth, obj, temp;
meth = this.variable ? this.variable.compile(o) : this.superReference(o);
@ -631,10 +499,6 @@
};
return CallNode;
})();
//### ExtendsNode
// Node to extend an object's prototype with an ancestor object.
// After `goog.inherits` from the
// [Closure Library](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.html).
exports.ExtendsNode = (function() {
ExtendsNode = function(child, parent) {
this.child = child;
@ -644,7 +508,6 @@
__extends(ExtendsNode, BaseNode);
ExtendsNode.prototype['class'] = 'ExtendsNode';
ExtendsNode.prototype.children = ['child', 'parent'];
// Hooks one constructor into another's prototype chain.
ExtendsNode.prototype.compileNode = function(o) {
var ref;
ref = new ValueNode(literal(utility('extends')));
@ -652,9 +515,6 @@
};
return ExtendsNode;
})();
//### AccessorNode
// A `.` accessor into a property of a value, or the `::` shorthand for
// an accessor into the object's prototype.
exports.AccessorNode = (function() {
AccessorNode = function(name, tag) {
this.name = name;
@ -674,8 +534,6 @@
};
return AccessorNode;
})();
//### IndexNode
// A `[ ... ]` indexed accessor into an array or object.
exports.IndexNode = (function() {
IndexNode = function(index) {
this.index = index;
@ -693,10 +551,6 @@
};
return IndexNode;
})();
//### RangeNode
// A range literal. Ranges can be used to extract portions (slices) of arrays,
// to specify a range for comprehensions, or as a value, to be expanded into the
// corresponding array of integers at runtime.
exports.RangeNode = (function() {
RangeNode = function(from, to, exclusive) {
this.from = from;
@ -707,8 +561,6 @@
__extends(RangeNode, BaseNode);
RangeNode.prototype['class'] = 'RangeNode';
RangeNode.prototype.children = ['from', 'to'];
// Compiles the range's source variables -- where it starts and where it ends.
// But only if they need to be cached to avoid double evaluation.
RangeNode.prototype.compileVariables = function(o) {
var _b, _c, parts;
_b = this.from.compileReference(o);
@ -730,8 +582,6 @@
return '';
}
};
// When compiled normally, the range returns the contents of the *for loop*
// needed to iterate over the values in the range. Used by comprehensions.
RangeNode.prototype.compileNode = function(o) {
var equals, idx, op, step, vars;
if (!(o.index)) {
@ -745,7 +595,6 @@
op = starts(step, '-') ? (">" + equals) : ("<" + equals);
return "" + vars + "; " + (idx) + " " + op + " " + (this.toVar.compile(o)) + "; " + idx + " += " + step;
};
// When used as a value, expand the range into the equivalent array.
RangeNode.prototype.compileArray = function(o) {
var body, clause, equals, from, idt, post, pre, to, vars;
idt = this.idt(1);
@ -763,10 +612,6 @@
};
return RangeNode;
})();
//### SliceNode
// 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 = (function() {
SliceNode = function(range) {
this.range = range;
@ -784,8 +629,6 @@
};
return SliceNode;
})();
//### ObjectNode
// An object literal, nothing fancy.
exports.ObjectNode = (function() {
ObjectNode = function(props) {
this.objects = (this.properties = props || []);
@ -794,41 +637,23 @@
__extends(ObjectNode, BaseNode);
ObjectNode.prototype['class'] = 'ObjectNode';
ObjectNode.prototype.children = ['properties'];
// 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.
ObjectNode.prototype.compileNode = function(o) {
var _b, _c, _d, _e, _f, _g, _h, i, indent, inner, join, lastNoncom, nonComments, prop, props;
var _b, _c, _d, i, inner, join, last, prop, props;
o.indent = this.idt(1);
nonComments = (function() {
_b = []; _d = this.properties;
for (_c = 0, _e = _d.length; _c < _e; _c++) {
prop = _d[_c];
!(prop instanceof CommentNode) ? _b.push(prop) : null;
}
return _b;
}).call(this);
lastNoncom = nonComments[nonComments.length - 1];
last = this.properties.length - 1;
props = (function() {
_f = []; _g = this.properties;
for (i = 0, _h = _g.length; i < _h; i++) {
prop = _g[i];
_f.push((function() {
join = ",\n";
if ((prop === lastNoncom) || (prop instanceof CommentNode)) {
join = "\n";
}
if (i === this.properties.length - 1) {
join = '';
}
indent = prop instanceof CommentNode ? '' : this.idt(1);
if (!(prop instanceof AssignNode || prop instanceof CommentNode)) {
_b = []; _c = this.properties;
for (i = 0, _d = _c.length; i < _d; i++) {
prop = _c[i];
_b.push((function() {
join = i === last ? '' : ',\n';
if (!(prop instanceof AssignNode)) {
prop = new AssignNode(prop, prop, 'object');
}
return indent + prop.compile(o) + join;
return this.idt(1) + prop.compile(o) + join;
}).call(this));
}
return _f;
return _b;
}).call(this);
props = props.join('');
inner = props ? '\n' + props + '\n' + this.idt() : '';
@ -836,8 +661,6 @@
};
return ObjectNode;
})();
//### ArrayNode
// An array literal.
exports.ArrayNode = (function() {
ArrayNode = function(objects) {
this.objects = objects || [];
@ -859,8 +682,6 @@
code = obj.compile(o);
if (obj instanceof SplatNode) {
return this.compileSplatLiteral(this.objects, o);
} else if (obj instanceof CommentNode) {
objects.push(("\n" + code + "\n" + o.indent));
} else if (i === this.objects.length - 1) {
objects.push(code);
} else {
@ -876,8 +697,6 @@
};
return ArrayNode;
})();
//### ClassNode
// The CoffeeScript class definition.
exports.ClassNode = (function() {
ClassNode = function(variable, parent, props) {
this.variable = variable;
@ -892,15 +711,10 @@
ClassNode.prototype.isStatement = function() {
return true;
};
// Initialize a **ClassNode** with its name, an optional superclass, and a
// list of prototype property assignments.
ClassNode.prototype.makeReturn = function() {
this.returns = true;
return this;
};
// 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.
ClassNode.prototype.compileNode = function(o) {
var _b, _c, _d, _e, access, applied, className, constScope, construct, constructor, extension, func, me, pname, prop, props, pvar, returns, val;
extension = this.parent && new ExtendsNode(this.variable, this.parent);
@ -959,9 +773,6 @@
};
return ClassNode;
})();
//### AssignNode
// 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 = (function() {
AssignNode = function(variable, value, context) {
this.variable = variable;
@ -970,7 +781,6 @@
return this;
};
__extends(AssignNode, BaseNode);
// Matchers for detecting prototype assignments.
AssignNode.prototype.PROTO_ASSIGN = /^(\S+)\.prototype/;
AssignNode.prototype.LEADING_DOT = /^\.(prototype\.)?/;
AssignNode.prototype['class'] = 'AssignNode';
@ -987,10 +797,6 @@
AssignNode.prototype.isStatement = function() {
return this.isValue() && (this.variable.isArray() || this.variable.isObject());
};
// Compile an assignment, delegating to `compilePatternMatch` or
// `compileSplice` 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.
AssignNode.prototype.compileNode = function(o) {
var last, match, name, proto, stmt, top, val;
top = del(o, 'top');
@ -1030,10 +836,6 @@
return "(" + 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 the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring)
// for details.
AssignNode.prototype.compilePatternMatch = function(o) {
var _b, _c, _d, accessClass, assigns, code, i, idx, isString, obj, oindex, olength, splat, val, valVar, value;
valVar = o.scope.freeVariable();
@ -1045,16 +847,13 @@
_b = this.variable.base.objects;
for (i = 0, _c = _b.length; i < _c; i++) {
obj = _b[i];
// A regular array pattern-match.
idx = i;
if (this.variable.isObject()) {
if (obj instanceof AssignNode) {
// A regular object pattern-match.
_d = [obj.value, obj.variable.base];
obj = _d[0];
idx = _d[1];
} else {
// A shorthand `{a, b, c}: val` pattern-match.
idx = obj;
}
}
@ -1077,8 +876,6 @@
code = assigns.join("\n");
return code;
};
// Compile the assignment from an array splice literal, using JavaScript's
// `Array#splice` method.
AssignNode.prototype.compileSplice = function(o) {
var from, l, name, plus, range, to, val;
name = this.variable.compile(merge(o, {
@ -1094,10 +891,6 @@
};
return AssignNode;
})();
//### 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 = (function() {
CodeNode = function(params, body, tag) {
this.params = params || [];
@ -1108,11 +901,6 @@
__extends(CodeNode, BaseNode);
CodeNode.prototype['class'] = 'CodeNode';
CodeNode.prototype.children = ['params', 'body'];
// 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.
CodeNode.prototype.compileNode = function(o) {
var _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, code, func, i, inner, param, params, sharedScope, splat, top;
sharedScope = del(o, 'sharedScope');
@ -1169,8 +957,6 @@
CodeNode.prototype.topSensitive = function() {
return true;
};
// Short-circuit traverseChildren method to prevent it from crossing scope boundaries
// unless crossScope is true
CodeNode.prototype.traverseChildren = function(crossScope, func) {
if (crossScope) {
return CodeNode.__superClass__.traverseChildren.call(this, crossScope, func);
@ -1191,9 +977,6 @@
};
return CodeNode;
})();
//### SplatNode
// A splat, either as a parameter to a function, an argument to a call,
// or as part of a destructuring assignment.
exports.SplatNode = (function() {
SplatNode = function(name) {
if (!(name.compile)) {
@ -1213,8 +996,6 @@
return this.name.compile(o);
}
};
// Compiling a parameter splat means recovering the parameters that succeed
// the splat in the parameter list, by slicing the arguments object.
SplatNode.prototype.compileParam = function(o) {
var _b, _c, idx, len, name, pos, trailing, variadic;
name = this.name.compile(o);
@ -1231,15 +1012,11 @@
}
return "" + name + " = " + (utility('slice')) + ".call(arguments, " + this.index + ", " + len + " - " + (this.trailings.length) + ")";
};
// A compiling a splat as a destructuring assignment means slicing arguments
// from the right-hand-side's corresponding array.
SplatNode.prototype.compileValue = function(o, name, index, trailings) {
var trail;
trail = trailings ? (", " + (name) + ".length - " + trailings) : '';
return "" + (utility('slice')) + ".call(" + name + ", " + index + trail + ")";
};
// Utility function that converts arbitrary number of elements, mixed with
// splats, to a proper array
SplatNode.compileMixedArray = function(list, o) {
var _b, _c, _d, arg, args, code, i, prev;
args = [];
@ -1267,10 +1044,6 @@
};
return SplatNode;
}).call(this);
//### 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 = (function() {
WhileNode = function(condition, opts) {
if (opts && opts.invert) {
@ -1300,9 +1073,6 @@
WhileNode.prototype.topSensitive = function() {
return 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.
WhileNode.prototype.compileNode = function(o) {
var cond, post, pre, rvar, set, top;
top = del(o, 'top') && !this.returns;
@ -1328,9 +1098,6 @@
};
return WhileNode;
})();
//### OpNode
// Simple Arithmetic and logical operations. Performs some conversion from
// CoffeeScript operations into their JavaScript equivalents.
exports.OpNode = (function() {
OpNode = function(operator, first, second, flip) {
this.first = first;
@ -1340,17 +1107,12 @@
return this;
};
__extends(OpNode, BaseNode);
// The map of conversions from CoffeeScript to JavaScript symbols.
OpNode.prototype.CONVERSIONS = {
'==': '===',
'!=': '!=='
};
// The list of operators for which we perform
// [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin).
OpNode.prototype.CHAINABLE = ['<', '>', '>=', '<=', '===', '!=='];
// Our assignment operators that have no JavaScript equivalent.
OpNode.prototype.ASSIGNMENT = ['||=', '&&=', '?='];
// Operators must come before their operands with a space.
OpNode.prototype.PREFIX_OPERATORS = ['typeof', 'delete'];
OpNode.prototype['class'] = 'OpNode';
OpNode.prototype.children = ['first', 'second'];
@ -1376,10 +1138,6 @@
}
return [this.first.compile(o), this.operator, this.second.compile(o)].join(' ');
};
// Mimic Python's chained comparisons when multiple comparison operators are
// used sequentially. For example:
// bin/coffee -e "puts 50 < 65 > 10"
// true
OpNode.prototype.compileChain = function(o) {
var _b, _c, first, second, shared;
shared = this.first.unwrap().second;
@ -1394,9 +1152,6 @@
shared = _c[2];
return "(" + first + ") && (" + shared + " " + this.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.
OpNode.prototype.compileAssignment = function(o) {
var _b, first, second;
_b = [this.first.compile(o), this.second.compile(o)];
@ -1410,8 +1165,6 @@
}
return "" + first + " = " + first + " " + (this.operator.substr(0, 2)) + " " + second;
};
// If this is an existence operator, we delegate to `ExistenceNode.compileTest`
// to give us the safe references for the variables.
OpNode.prototype.compileExistence = function(o) {
var _b, first, second, test;
_b = [this.first.compile(o), this.second.compile(o)];
@ -1420,7 +1173,6 @@
test = ExistenceNode.compileTest(o, this.first);
return "" + test + " ? " + first + " : " + second;
};
// Compile a unary **OpNode**.
OpNode.prototype.compileUnary = function(o) {
var parts, space;
space = indexOf(this.PREFIX_OPERATORS, this.operator) >= 0 ? ' ' : '';
@ -1432,7 +1184,6 @@
};
return OpNode;
})();
//### InNode
exports.InNode = (function() {
InNode = function(object, array) {
this.object = object;
@ -1489,8 +1240,6 @@
};
return InNode;
})();
//### TryNode
// A classic *try/catch/finally* block.
exports.TryNode = (function() {
TryNode = function(attempt, error, recovery, ensure) {
this.attempt = attempt;
@ -1514,8 +1263,6 @@
}
return this;
};
// Compilation is more or less as you would expect -- the *finally* clause
// is optional, the *catch* is not.
TryNode.prototype.compileNode = function(o) {
var attemptPart, catchPart, errorPart, finallyPart;
o.indent = this.idt(1);
@ -1528,8 +1275,6 @@
};
return TryNode;
})();
//### ThrowNode
// Simple node to throw an exception.
exports.ThrowNode = (function() {
ThrowNode = function(expression) {
this.expression = expression;
@ -1541,7 +1286,6 @@
ThrowNode.prototype.isStatement = function() {
return true;
};
// A **ThrowNode** is already a return, of sorts...
ThrowNode.prototype.makeReturn = function() {
return this;
};
@ -1550,10 +1294,6 @@
};
return ThrowNode;
})();
//### ExistenceNode
// Checks a variable for existence -- not *null* and not *undefined*. This is
// similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth
// table.
exports.ExistenceNode = (function() {
ExistenceNode = function(expression) {
this.expression = expression;
@ -1565,9 +1305,6 @@
ExistenceNode.prototype.compileNode = function(o) {
return ExistenceNode.compileTest(o, this.expression);
};
// The meat of the **ExistenceNode** is in this static `compileTest` method
// because other nodes like to check the existence of their variables as well.
// Be careful not to double-evaluate anything.
ExistenceNode.compileTest = function(o, variable) {
var _b, first, second;
_b = variable.compileReference(o);
@ -1577,11 +1314,6 @@
};
return ExistenceNode;
}).call(this);
//### ParentheticalNode
// An extra set of parentheses, specified explicitly in the source. At one time
// we tried to clean up the results by detecting and removing redundant
// parentheses, but no longer -- you can put in as many as you please.
// Parentheses are a good way to force any statement to become an expression.
exports.ParentheticalNode = (function() {
ParentheticalNode = function(expression) {
this.expression = expression;
@ -1614,13 +1346,6 @@
};
return ParentheticalNode;
})();
//### ForNode
// CoffeeScript's replacement for the *for* loop is our array and object
// comprehensions, that compile into *for* loops here. They also act as an
// expression, able to return the result of each filtered iteration.
// Unlike Python array comprehensions, they can be multi-line, and you can pass
// the current index of the loop as a second parameter. Unlike Ruby blocks,
// you can map and filter in a single pass.
exports.ForNode = (function() {
ForNode = function(body, source, name, index) {
var _b;
@ -1665,10 +1390,6 @@
}
return '';
};
// Welcome to the hairiest method in all of CoffeeScript. Handles the inner
// loop, filtering, stepping, and result saving for array, object, and range
// comprehensions. Some of the generated code can be shared in common, and
// some cannot.
ForNode.prototype.compileNode = function(o) {
var body, close, codeInBody, forPart, index, ivar, lvar, name, namePart, range, returnResult, rvar, scope, source, sourcePart, stepPart, svar, topLevel, varPart, vars;
topLevel = del(o, 'top') && !this.returns;
@ -1756,11 +1477,6 @@
};
return ForNode;
})();
//### IfNode
// *If/else* statements. Our *switch/when* will be compiled into this. Acts as an
// expression by pushing down requested returns to the last line of each clause.
// Single-expression **IfNodes** are compiled into ternary operators if possible,
// because ternaries are already proper expressions, and don't need conversion.
exports.IfNode = (function() {
IfNode = function(condition, body, tags) {
this.condition = condition;
@ -1786,14 +1502,10 @@
this.tags.statement = true;
return this;
};
// Tag a chain of **IfNodes** with their object(s) to switch on for equality
// tests. `rewriteSwitch` will perform the actual change at compile time.
IfNode.prototype.switchesOver = function(expression) {
this.switchSubject = expression;
return this;
};
// Rewrite a chain of **IfNodes** with their switch condition for equality.
// Ensure that the switch expression isn't evaluated more than once.
IfNode.prototype.rewriteSwitch = function(o) {
var _b, _c, _d, cond, i, variable;
this.assigner = this.switchSubject;
@ -1818,11 +1530,9 @@
if (this.isChain) {
this.elseBodyNode().switchesOver(this.switchSubject);
}
// prevent this rewrite from happening again
this.switchSubject = undefined;
return this;
};
// Rewrite a chain of **IfNodes** to add a default case as the final *else*.
IfNode.prototype.addElse = function(elseBody, statement) {
if (this.isChain) {
this.elseBodyNode().addElse(elseBody, statement);
@ -1832,10 +1542,8 @@
}
return this;
};
// The **IfNode** only compiles into a statement if either of its bodies needs
// to be a statement. Otherwise a ternary is safe.
IfNode.prototype.isStatement = function() {
return this.statement = this.statement || !!(this.comment || this.tags.statement || this.bodyNode().isStatement() || (this.elseBody && this.elseBodyNode().isStatement()));
return this.statement = this.statement || !!(this.tags.statement || this.bodyNode().isStatement() || (this.elseBody && this.elseBodyNode().isStatement()));
};
IfNode.prototype.compileCondition = function(o) {
var _b, _c, _d, _e, cond;
@ -1867,10 +1575,8 @@
return new Expressions([node]);
}
};
// Compile the **IfNode** as a regular *if-else* statement. Flattened chains
// force inner *else* bodies into statement form.
IfNode.prototype.compileStatement = function(o) {
var body, child, comDent, condO, elsePart, ifDent, ifPart, prefix;
var body, child, comDent, condO, elsePart, ifDent, ifPart;
if (this.switchSubject) {
this.rewriteSwitch(o);
}
@ -1880,9 +1586,8 @@
o.top = true;
ifDent = child ? '' : this.idt();
comDent = child ? this.idt() : '';
prefix = this.comment ? ("" + (this.comment.compile(condO)) + "\n" + comDent) : '';
body = this.body.compile(o);
ifPart = ("" + prefix + (ifDent) + "if (" + (this.compileCondition(condO)) + ") {\n" + body + "\n" + this.tab + "}");
ifPart = ("" + (ifDent) + "if (" + (this.compileCondition(condO)) + ") {\n" + body + "\n" + this.tab + "}");
if (!(this.elseBody)) {
return ifPart;
}
@ -1892,7 +1597,6 @@
})) : (" else {\n" + (this.elseBody.compile(o)) + "\n" + this.tab + "}");
return "" + ifPart + elsePart;
};
// Compile the IfNode as a ternary operator.
IfNode.prototype.compileTernary = function(o) {
var elsePart, ifPart;
ifPart = this.condition.compile(o) + ' ? ' + this.bodyNode().compile(o);
@ -1901,13 +1605,6 @@
};
return IfNode;
})();
// Faux-Nodes
// ----------
//### PushNode
// Faux-nodes are never created by the grammar, but are used during code
// generation to generate other combinations of nodes. The **PushNode** creates
// the tree for `array.push(value)`, which is helpful for recording the result
// arrays from comprehensions.
PushNode = (exports.PushNode = {
wrap: function(array, expressions) {
var expr;
@ -1918,12 +1615,7 @@
return Expressions.wrap([new CallNode(new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr])]);
}
});
//### ClosureNode
// A faux-node used to wrap an expressions body in a closure.
ClosureNode = (exports.ClosureNode = {
// Wrap the expressions body, unless it contains a pure statement,
// in which case, no dice. If the body mentions `this` or `arguments`,
// then make sure that the closure wrapper preserves the original values.
wrap: function(expressions, statement) {
var args, call, func, mentionsArgs, mentionsThis, meth;
if (expressions.containsPureStatement()) {
@ -1953,36 +1645,19 @@
}
}
});
// Utility Functions
// -----------------
UTILITIES = {
// Correctly set up a prototype chain for inheritance, including a reference
// to the superclass for `super()` calls. See:
// [goog.inherits](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.source.html#line1206).
__extends: "function(child, parent) {\n var ctor = function(){ };\n ctor.prototype = parent.prototype;\n child.__superClass__ = parent.prototype;\n child.prototype = new ctor();\n child.prototype.constructor = child;\n }",
// Shortcuts to speed up the lookup time for native functions.
__hasProp: 'Object.prototype.hasOwnProperty',
__slice: 'Array.prototype.slice'
};
// Constants
// ---------
// Tabs are two spaces for pretty printing.
TAB = ' ';
// Trim out all trailing whitespace, so that the generated code plays nice
// with Git.
TRAILING_WHITESPACE = /[ \t]+$/gm;
// Keep these identifier regexes in sync with the Lexer.
IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/;
NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i;
// Is a literal value a string?
IS_STRING = /^['"]/;
// Utility Functions
// -----------------
// Handy helper for a generating LiteralNode.
literal = function(name) {
return new LiteralNode(name);
};
// Helper for ensuring that utility functions are assigned at the top level.
utility = function(name) {
var ref;
ref = ("__" + name);

View file

@ -1,23 +1,11 @@
(function(){
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments;
// A simple **OptionParser** class to parse option flags from the command-line.
// Use it like so:
// parser: new OptionParser switches, helpBanner
// options: parser.parse process.argv
exports.OptionParser = (function() {
OptionParser = function(rules, banner) {
this.banner = banner;
this.rules = buildRules(rules);
return this;
};
// Initialize with a list of valid options, in the form:
// [short-flag, long-flag, description]
// Along with an an optional banner for the usage help.
// Parse the list of arguments, populating an `options` object with all of the
// specified options, and returning it. `options.arguments` will be an array
// containing the remaning non-option arguments. This is a simpler API than
// many option parsers that allow you to attach callback actions for every
// flag. Instead, you're responsible for interpreting the options object.
OptionParser.prototype.parse = function(args) {
var _a, _b, _c, arg, isOption, matchedRule, options, rule;
options = {
@ -45,8 +33,6 @@
}
return options;
};
// Return the help text for this **OptionParser**, listing and describing all
// of the valid options, for `--help` and such.
OptionParser.prototype.help = function() {
var _a, _b, _c, _d, i, letPart, lines, rule, spaces;
lines = ['Available options:'];
@ -71,15 +57,10 @@
};
return OptionParser;
})();
// Helpers
// -------
// Regex matchers for option flags.
LONG_FLAG = /^(--\w[\w\-]+)/;
SHORT_FLAG = /^(-\w)/;
MULTI_FLAG = /^-(\w{2,})/;
OPTIONAL = /\[(.+)\]/;
// Build and return the list of option rules. If the optional *short-flag* is
// unspecified, leave it out by padding with `null`.
buildRules = function(rules) {
var _a, _b, _c, _d, tuple;
_a = []; _c = rules;
@ -94,8 +75,6 @@
}
return _a;
};
// Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
// description of what the option does.
buildRule = function(shortFlag, longFlag, description) {
var match;
match = longFlag.match(OPTIONAL);
@ -108,8 +87,6 @@
hasArgument: !!(match && match[1])
};
};
// Normalize arguments by expanding merged flags into multiple flags. This allows
// you to have `-wl` be the same as `--watch --lint`.
normalizeArguments = function(args) {
var _a, _b, _c, _d, _e, _f, arg, l, match, result;
args = args.slice(0);

File diff suppressed because one or more lines are too long

View file

@ -1,24 +1,14 @@
(function(){
var CoffeeScript, helpers, readline, repl, run, stdio;
// A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript
// and evaluates it. Good for simple tests, or poking around the **Node.js** API.
// Using it looks like this:
// 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');
helpers = require('./helpers').helpers;
readline = require('readline');
// Start by opening up **stdio**.
stdio = process.openStdin();
// Quick alias for quitting the REPL.
helpers.extend(global, {
quit: function() {
return 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 = function(buffer) {
var val;
try {
@ -35,7 +25,6 @@
}
return repl.prompt();
};
// Create the REPL by listening to **stdin**.
repl = readline.createInterface(stdio);
repl.setPrompt('coffee> ');
stdio.addListener('data', function(buffer) {

View file

@ -1,13 +1,6 @@
(function(){
var BALANCED_PAIRS, COMMENTS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, helpers, include, pair;
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, helpers, include, pair;
var __hasProp = Object.prototype.hasOwnProperty;
// The CoffeeScript language has a good deal of optional syntax, implicit syntax,
// and shorthand syntax. This can greatly complicate a grammar and bloat
// the resulting parse table. Instead of making the parser handle it all, we take
// a series of passes over the token stream, using this **Rewriter** to convert
// shorthand into the unambiguous long form, add implicit indentation and
// parentheses, balance incorrect nestings, and generally clean things up.
// Set up exported variables for both Node.js and the browser.
if ((typeof process !== "undefined" && process !== null)) {
_a = require('./helpers');
helpers = _a.helpers;
@ -15,21 +8,12 @@
this.exports = this;
helpers = this.helpers;
}
// Import the helpers we need.
_b = helpers;
include = _b.include;
// The **Rewriter** class is used by the [Lexer](lexer.html), directly against
// its internal array of tokens.
exports.Rewriter = (function() {
Rewriter = function() { };
// Rewrite the token stream in multiple passes, one logical filter at
// a time. This could certainly be changed into a single pass through the
// stream, with a big ol' efficient switch, but it's much nicer to work with
// like this. The order of these passes matters -- indentation must be
// corrected before implicit parentheses can be wrapped around blocks of code.
Rewriter.prototype.rewrite = function(tokens) {
this.tokens = tokens;
this.adjustComments();
this.removeLeadingNewlines();
this.removeMidExpressionNewlines();
this.closeOpenCallsAndIndexes();
@ -39,11 +23,6 @@
this.rewriteClosingParens();
return this.tokens;
};
// Rewrite the token stream, looking one token ahead and behind.
// Allow the return value of the block to tell us how many tokens to move
// forwards (or backwards) in the stream, to make sure we don't miss anything
// as tokens are inserted and removed, and the stream changes length under
// our feet.
Rewriter.prototype.scanTokens = function(block) {
var i, move;
i = 0;
@ -56,36 +35,6 @@
}
return true;
};
// Massage newlines and indentations so that comments don't have to be
// correctly indented, or appear on a line of their own.
Rewriter.prototype.adjustComments = function() {
return this.scanTokens((function(__this) {
var __func = function(prev, token, post, i) {
var _c, _d, after, before;
if (!(include(COMMENTS, token[0]))) {
return 1;
}
_c = [this.tokens[i - 2], this.tokens[i + 2]];
before = _c[0];
after = _c[1];
if (after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
before && before[0] === 'OUTDENT' && post && (prev[0] === post[0]) && (post[0] === 'TERMINATOR') ? this.tokens.splice(i - 2, 1) : this.tokens.splice(i, 0, after);
} else if (prev && !('TERMINATOR' === (_d = prev[0]) || 'INDENT' === _d || 'OUTDENT' === _d)) {
post && post[0] === 'TERMINATOR' && after && after[0] === 'OUTDENT' ? this.tokens.splice.apply(this.tokens, [i, 0].concat(this.tokens.splice(i + 2, 2))) : this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
return 2;
} else if (before && before[0] === 'OUTDENT' && prev && prev[0] === 'TERMINATOR' && post && post[0] === 'TERMINATOR' && after && after[0] === 'ELSE') {
this.tokens.splice(i + 1, 0, this.tokens.splice(i - 2, 1)[0]);
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
};
// Leading newlines would introduce an ambiguity in the grammar, so we
// dispatch them here.
Rewriter.prototype.removeLeadingNewlines = function() {
var _c;
_c = [];
@ -94,8 +43,6 @@
}
return _c;
};
// Some blocks occur in the middle of expressions -- when we're expecting
// this, remove their trailing newlines.
Rewriter.prototype.removeMidExpressionNewlines = function() {
return this.scanTokens((function(__this) {
var __func = function(prev, token, post, i) {
@ -110,9 +57,6 @@
});
})(this));
};
// The lexer has tagged the opening parenthesis of a method call, and the
// opening bracket of an indexing operation. Match them with their paired
// close.
Rewriter.prototype.closeOpenCallsAndIndexes = function() {
var brackets, parens;
parens = [0];
@ -150,9 +94,6 @@
});
})(this));
};
// Methods may be optionally called without parentheses, for simple cases.
// Insert the implicit parentheses here, so that the parser doesn't have to
// deal with them.
Rewriter.prototype.addImplicitParentheses = function() {
var closeCalls, stack;
stack = [0];
@ -225,10 +166,6 @@
});
})(this));
};
// Because our grammar is LALR(1), it can't handle some single-line
// expressions that lack ending delimiters. The **Rewriter** adds the implicit
// blocks, so it doesn't need to. ')' can close a single-line block,
// but we need to make sure it's balanced.
Rewriter.prototype.addImplicitIndentation = function() {
return this.scanTokens((function(__this) {
var __func = function(prev, token, post, i) {
@ -271,8 +208,6 @@
});
})(this));
};
// Ensure that all listed pairs of tokens are correctly balanced throughout
// the course of the token stream.
Rewriter.prototype.ensureBalance = function(pairs) {
var _c, _d, key, levels, line, open, openLine, unclosed, value;
levels = {};
@ -320,19 +255,6 @@
throw new Error(("unclosed " + open + " on line " + line));
}
};
// We'd like to support syntax like this:
// el.click((event) ->
// el.hide())
// In order to accomplish this, move outdents that follow closing parens
// inwards, safely. The steps to accomplish this are:
// 1. Check that all paired tokens are balanced and in order.
// 2. Rewrite the stream with a stack: if you see an `EXPRESSION_START`, add it
// to the stack. If you see an `EXPRESSION_END`, pop the stack and replace
// it with the inverse of what we've just popped.
// 3. Keep track of "debt" for tokens that we manufacture, to make sure we end
// up balanced in the end.
// 4. Be careful not to alter array or parentheses delimiters with overzealous
// rewriting.
Rewriter.prototype.rewriteClosingParens = function() {
var _c, debt, key, stack, val;
stack = [];
@ -383,12 +305,7 @@
};
return Rewriter;
})();
// Constants
// ---------
// List of the token pairs that must be balanced.
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']];
// The inverse mappings of `BALANCED_PAIRS` we're trying to fix up, so we can
// look things up from either end.
INVERSES = {};
_d = BALANCED_PAIRS;
for (_c = 0, _e = _d.length; _c < _e; _c++) {
@ -396,7 +313,6 @@
INVERSES[pair[0]] = pair[1];
INVERSES[pair[1]] = pair[0];
}
// The tokens that signal the start of a balanced pair.
EXPRESSION_START = (function() {
_f = []; _h = BALANCED_PAIRS;
for (_g = 0, _i = _h.length; _g < _i; _g++) {
@ -405,7 +321,6 @@
}
return _f;
})();
// The tokens that signal the end of a balanced pair.
EXPRESSION_END = (function() {
_j = []; _l = BALANCED_PAIRS;
for (_k = 0, _m = _l.length; _k < _m; _k++) {
@ -414,20 +329,11 @@
}
return _j;
})();
// Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END);
// Tokens that, if followed by an `IMPLICIT_CALL`, indicate a function invocation.
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@'];
// If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'THIS', 'NULL', '@', '->', '=>', '[', '(', '{'];
// Tokens indicating that the implicit call must enclose a block of expressions.
IMPLICIT_BLOCK = ['->', '=>', '{', '[', ','];
// Tokens that always mark the end of an implicit call for single-liners.
IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR', 'INDENT'].concat(EXPRESSION_END);
// Single-line flavors of block expressions that have unclosed endings.
// The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
// Comment flavors.
COMMENTS = ['COMMENT', 'HERECOMMENT'];
})();

View file

@ -1,13 +1,6 @@
(function(){
var Scope;
var __hasProp = Object.prototype.hasOwnProperty;
// The **Scope** class regulates lexical scoping within CoffeeScript. As you
// generate code, you create a tree of scopes in the same shape as the nested
// function bodies. Each scope knows about the variables declared within it,
// and has a reference to its parent enclosing scope. In this way, we know which
// variables are new and need to be declared with `var`, and which are shared
// with the outside.
// Set up exported variables for both **Node.js** and the browser.
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
@ -27,14 +20,7 @@
}
return this;
};
// The top-level **Scope** object.
Scope.root = null;
// Initialize a scope with its parent, for lookups up the chain,
// as well as a reference to the **Expressions** node is belongs to, which is
// where it should declare its variables, and a reference to the function that
// it wraps.
// Look up a variable name in lexical scope, and declare it if it does not
// already exist.
Scope.prototype.find = function(name) {
if (this.check(name)) {
return true;
@ -42,7 +28,6 @@
this.variables[name] = 'var';
return false;
};
// Test variables and return true the first time fn(v, k) returns true
Scope.prototype.any = function(fn) {
var _a, k, v;
_a = this.variables;
@ -54,21 +39,16 @@
}}
return false;
};
// Reserve a variable name as originating from a function parameter for this
// scope. No `var` required for internal references.
Scope.prototype.parameter = function(name) {
this.variables[name] = 'param';
return this.variables[name];
};
// Just check to see if a variable has already been declared, without reserving.
Scope.prototype.check = function(name) {
if (this.variables.hasOwnProperty(name)) {
return true;
}
return !!(this.parent && this.parent.check(name));
};
// If we need to store an intermediate result, find an available name for a
// compiler-generated variable. `_a`, `_b`, and so on...
Scope.prototype.freeVariable = function() {
var ordinal;
while (this.check(this.tempVar)) {
@ -78,8 +58,6 @@
this.variables[this.tempVar] = 'var';
return this.tempVar;
};
// Ensure that an assignment is made at the top of this scope
// (or at the top-level scope, if requested).
Scope.prototype.assign = function(name, value) {
this.variables[name] = {
value: value,
@ -87,21 +65,16 @@
};
return this.variables[name];
};
// Does this scope reference any variables that need to be declared in the
// given function body?
Scope.prototype.hasDeclarations = function(body) {
return body === this.expressions && this.any(function(k, val) {
return val === 'var';
});
};
// Does this scope reference any assignments that need to be declared at the
// top of the given function body?
Scope.prototype.hasAssignments = function(body) {
return body === this.expressions && this.any(function(k, val) {
return val.assigned;
});
};
// Return the list of variables first declared in this scope.
Scope.prototype.declaredVariables = function() {
var _a, _b, key, val;
return (function() {
@ -113,8 +86,6 @@
return _a;
}).call(this).sort();
};
// Return the list of assignments that are supposed to be made at the top
// of this scope.
Scope.prototype.assignedVariables = function() {
var _a, _b, key, val;
_a = []; _b = this.variables;
@ -124,11 +95,9 @@
}}
return _a;
};
// Compile the JavaScript for all of the variable declarations in this scope.
Scope.prototype.compiledDeclarations = function() {
return this.declaredVariables().join(', ');
};
// Compile the JavaScript for all of the variable assignments in this scope.
Scope.prototype.compiledAssignments = function() {
return this.assignedVariables().join(', ');
};

View file

@ -99,7 +99,6 @@ grammar: {
o "Class"
o "Splat"
o "Existence"
o "Comment"
]
# A an indented block of expressions. Note that the [Rewriter](rewriter.html)
@ -108,7 +107,6 @@ grammar: {
Block: [
o "INDENT Body OUTDENT", -> $2
o "INDENT OUTDENT", -> new Expressions()
o "TERMINATOR Comment", -> Expressions.wrap [$2]
]
# A literal identifier, a variable name or property.
@ -149,7 +147,6 @@ grammar: {
o "AlphaNumeric"
o "Identifier ASSIGN Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "AlphaNumeric ASSIGN Expression", -> new AssignNode new ValueNode($1), $3, 'object'
o "Comment"
]
# A return statement from a function body.
@ -158,14 +155,6 @@ grammar: {
o "RETURN", -> new ReturnNode new ValueNode new LiteralNode 'null'
]
# A comment. Because CoffeeScript passes comments through to JavaScript, we
# have to parse comments like any other construct, and identify all of the
# positions in which they can occur in the grammar.
Comment: [
o "COMMENT", -> new CommentNode $1
o "HERECOMMENT", -> new CommentNode $1, 'herecomment'
]
# [The existential operator](http://jashkenas.github.com/coffee-script/#existence).
Existence: [
o "Expression ?", -> new ExistenceNode $1
@ -475,7 +464,6 @@ grammar: {
When: [
o "LEADING_WHEN SimpleArgs Block", -> new IfNode $2, $3, {statement: true}
o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode $2, $3, {statement: true}
o "Comment TERMINATOR When", -> $3.comment: $1; $3
]
# The most basic form of *if* is a condition and an action. The following

View file

@ -136,20 +136,9 @@ exports.Lexer: class Lexer
@i: + match[1].length
true
# Matches and conumes comments. We pass through comments into JavaScript,
# so they're treated as real tokens, like any other part of the language.
# Matches and conumes comments.
commentToken: ->
return false unless match: @chunk.match(COMMENT)
if match[3]
comment: @sanitizeHeredoc match[3], {herecomment: true}
@token 'HERECOMMENT', comment.split MULTILINER
@token 'TERMINATOR', '\n'
else
lines: compact match[1].replace(COMMENT_CLEANER, '').split MULTILINER
i: @tokens.length - 1
if @unfinished()
i: - 1 while @tokens[i] and not include LINE_BREAK, @tokens[i][0]
@tokens.splice(i + 1, 0, ['COMMENT', lines, @line], ['TERMINATOR', '\n', @line])
@line: + count match[1], "\n"
@i: + match[1].length
true
@ -204,7 +193,7 @@ exports.Lexer: class Lexer
@i : + indent.length
prev: @prev(2)
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
nextCharacter: @chunk.match(NEXT_CHARACTER)[1]
nextCharacter: @match NEXT_CHARACTER, 1
noNewlines: nextCharacter is '.' or nextCharacter is ',' or @unfinished()
if size is @indent
return @suppressNewlines() if noNewlines
@ -303,15 +292,14 @@ exports.Lexer: class Lexer
prev[0] is '@'
if accessor then 'accessor' else false
# Sanitize a heredoc or herecomment by escaping internal double quotes and
# Sanitize a heredoc by escaping internal double quotes and
# erasing all external indentation on the left-hand side.
sanitizeHeredoc: (doc, options) ->
while match: HEREDOC_INDENT.exec doc
attempt: if match[2]? then match[2] else match[3]
indent: attempt if not indent or attempt.length < indent.length
doc: doc.replace(new RegExp("^" +indent, 'gm'), '')
return doc if options.herecomment
doc.replace(MULTILINER, "\\n")
doc.replace(new RegExp("^" +indent, 'gm'), '')
.replace(MULTILINER, "\\n")
.replace(new RegExp(options.quote, 'g'), "\\$options.quote")
# Tag a half assignment.
@ -487,7 +475,7 @@ HEREDOC : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\
INTERPOLATION : /^\$([a-zA-Z_@]\w*(\.\w+)*)/
OPERATOR : /^([+\*&|\/\-%=<>:!?]+)([ \t]*)/
WHITESPACE : /^([ \t]+)/
COMMENT : /^((\n?[ \t]*)?#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|((\n?[ \t]*)?#[^\n]*)+)/
COMMENT : /^([\n \t]*#{3}(?!#)[ \t]*\n+([\s\S]*?)[ \t]*\n+[ \t]*#{3}|([\n \t]*#[^\n]*)+)/
CODE : /^((-|=)>)/
MULTI_DENT : /^((\n([ \t]*))+)(\.)?/
LAST_DENTS : /\n([ \t]*)/g
@ -504,7 +492,6 @@ REGEX_ESCAPE : /\\[^\$]/g
JS_CLEANER : /(^`|`$)/g
MULTILINER : /\n/g
STRING_NEWLINES : /\n[ \t]*/g
COMMENT_CLEANER : /(^[ \t]*#|\n[ \t]*$)/mg
NO_NEWLINE : /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
HEREDOC_INDENT : /(\n+([ \t]*)|^([ \t]+))/g
ASSIGNED : /^([a-zA-Z\$_]\w*[ \t]*?[:=])/

View file

@ -47,7 +47,7 @@ exports.BaseNode: class BaseNode
del @options, 'chainRoot' unless this instanceof AccessorNode or this instanceof IndexNode
top: if @topSensitive() then @options.top else del @options, 'top'
closure: @isStatement() and not @isPureStatement() and not top and
not @options.asStatement and not (this instanceof CommentNode) and
not @options.asStatement and
not @containsPureStatement()
if closure then @compileClosure(@options) else @compileNode(@options)
@ -179,7 +179,6 @@ exports.Expressions: class Expressions extends BaseNode
makeReturn: ->
idx: @expressions.length - 1
last: @expressions[idx]
last: @expressions[idx: - 1] if last instanceof CommentNode
return this if not last or last instanceof ReturnNode
@expressions[idx]: last.makeReturn()
this
@ -362,29 +361,6 @@ exports.ValueNode: class ValueNode extends BaseNode
if op and @wrapped then "($complete)" else complete
#### CommentNode
# CoffeeScript passes through comments as JavaScript comments at the
# same position.
exports.CommentNode: class CommentNode extends BaseNode
class: 'CommentNode'
isStatement: -> yes
constructor: (lines, kind) ->
@lines: lines
@kind: kind
makeReturn: ->
this
compileNode: (o) ->
if @kind is 'herecomment'
sep: '\n' + @tab
"$@tab/*$sep${ @lines.join(sep) }\n$@tab*/"
else
"$@tab//" + @lines.join("\n$@tab//")
#### CallNode
# Node for a function invocation. Takes care of converting `super()` calls into
@ -582,20 +558,13 @@ exports.ObjectNode: class ObjectNode extends BaseNode
constructor: (props) ->
@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.
compileNode: (o) ->
o.indent: @idt 1
nonComments: prop for prop in @properties when not (prop instanceof CommentNode)
lastNoncom: nonComments[nonComments.length - 1]
last: @properties.length - 1
props: for prop, i in @properties
join: ",\n"
join: "\n" if (prop is lastNoncom) or (prop instanceof CommentNode)
join: '' if i is @properties.length - 1
indent: if prop instanceof CommentNode then '' else @idt 1
prop: new AssignNode prop, prop, 'object' unless prop instanceof AssignNode or prop instanceof CommentNode
indent + prop.compile(o) + join
join: if i is last then '' else ',\n'
prop: new AssignNode prop, prop, 'object' unless prop instanceof AssignNode
@idt(1) + prop.compile(o) + join
props: props.join('')
inner: if props then '\n' + props + '\n' + @idt() else ''
"{$inner}"
@ -620,8 +589,6 @@ exports.ArrayNode: class ArrayNode extends BaseNode
code: obj.compile(o)
if obj instanceof SplatNode
return @compileSplatLiteral @objects, o
else if obj instanceof CommentNode
objects.push "\n$code\n$o.indent"
else if i is @objects.length - 1
objects.push code
else
@ -1336,7 +1303,7 @@ exports.IfNode: class IfNode extends BaseNode
# The **IfNode** only compiles into a statement if either of its bodies needs
# to be a statement. Otherwise a ternary is safe.
isStatement: ->
@statement: or !!(@comment or @tags.statement or @bodyNode().isStatement() or (@elseBody and @elseBodyNode().isStatement()))
@statement: or !!(@tags.statement or @bodyNode().isStatement() or (@elseBody and @elseBodyNode().isStatement()))
compileCondition: (o) ->
(cond.compile(o) for cond in flatten([@condition])).join(' || ')
@ -1362,9 +1329,8 @@ exports.IfNode: class IfNode extends BaseNode
o.top: true
ifDent: if child then '' else @idt()
comDent: if child then @idt() else ''
prefix: if @comment then "${ @comment.compile(condO) }\n$comDent" else ''
body: @body.compile(o)
ifPart: "$prefix${ifDent}if (${ @compileCondition(condO) }) {\n$body\n$@tab}"
ifPart: "${ifDent}if (${ @compileCondition(condO) }) {\n$body\n$@tab}"
return ifPart unless @elseBody
elsePart: if @isChain
' else ' + @elseBodyNode().compile(merge(o, {indent: @idt(), chainChild: true}))

View file

@ -26,7 +26,6 @@ exports.Rewriter: class Rewriter
# corrected before implicit parentheses can be wrapped around blocks of code.
rewrite: (tokens) ->
@tokens: tokens
@adjustComments()
@removeLeadingNewlines()
@removeMidExpressionNewlines()
@closeOpenCallsAndIndexes()
@ -49,29 +48,6 @@ exports.Rewriter: class Rewriter
i: + move
true
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on a line of their own.
adjustComments: ->
@scanTokens (prev, token, post, i) =>
return 1 unless include COMMENTS, token[0]
[before, after]: [@tokens[i - 2], @tokens[i + 2]]
if after and after[0] is 'INDENT'
@tokens.splice i + 2, 1
if before and before[0] is 'OUTDENT' and post and prev[0] is post[0] is 'TERMINATOR'
@tokens.splice i - 2, 1
else
@tokens.splice i, 0, after
else if prev and prev[0] not in ['TERMINATOR', 'INDENT', 'OUTDENT']
if post and post[0] is 'TERMINATOR' and after and after[0] is 'OUTDENT'
@tokens.splice(i, 0, @tokens.splice(i + 2, 2)...)
else
@tokens.splice i, 0, ['TERMINATOR', "\n", prev[2]]
return 2
else if before and before[0] is 'OUTDENT' and prev and prev[0] is 'TERMINATOR' and
post and post[0] is 'TERMINATOR' and after and after[0] is 'ELSE'
@tokens.splice i + 1, 0, @tokens.splice(i - 2, 1)[0]
return 1
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
removeLeadingNewlines: ->
@ -297,6 +273,3 @@ IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR',
# The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
# Comment flavors.
COMMENTS: ['COMMENT', 'HERECOMMENT']

View file

@ -26,15 +26,6 @@ else
ok result
# If statement with a comment-only clause.
result: if false
# comment
else
27
ok result is 27
# Testing unless.
result: unless true
10