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:
parent
8eedfe4bc6
commit
ec570c46bf
20 changed files with 4473 additions and 1245 deletions
2
Rakefile
2
Rakefile
|
@ -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
20
lib/cake.js
20
lib/cake.js
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
172
lib/grammar.js
172
lib/grammar.js
|
@ -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,
|
||||
|
|
|
@ -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 || {};
|
||||
|
|
|
@ -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];
|
||||
|
|
165
lib/lexer.js
165
lib/lexer.js
|
@ -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': '||',
|
||||
|
|
355
lib/nodes.js
355
lib/nodes.js
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
391
lib/parser.js
391
lib/parser.js
File diff suppressed because one or more lines are too long
11
lib/repl.js
11
lib/repl.js
|
@ -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) {
|
||||
|
|
|
@ -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'];
|
||||
})();
|
||||
|
|
31
lib/scope.js
31
lib/scope.js
|
@ -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(', ');
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]*?[:=])/
|
||||
|
|
|
@ -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}))
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue