Merge branch 'slice' of git://github.com/matehat/coffee-script

This commit is contained in:
Jeremy Ashkenas
2010-03-30 18:19:41 -04:00
15 changed files with 526 additions and 286 deletions

View File

@@ -1,6 +1,11 @@
(function(){
var CoffeeScript, fs, helpers, no_such_task, oparse, options, optparse, path, print_tasks, switches, tasks;
var __hasProp = Object.prototype.hasOwnProperty;
var __range = function(array, from, to, exclusive) {
return [
(from < 0 ? from + array.length : from || 0),
(to < 0 ? to + array.length : to || array.length) + (exclusive ? 0 : 1)
];
}, __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,
@@ -53,7 +58,7 @@
if (!(exists)) {
throw new Error("Cakefile not found in " + (process.cwd()));
}
args = process.argv.slice(2, process.argv.length);
args = process.argv.slice.apply(process.argv, __range(process.argv, 2, process.argv.length, true));
CoffeeScript.run(fs.readFileSync('Cakefile'), {
source: 'Cakefile'
});

View File

@@ -1,5 +1,11 @@
(function(){
var BANNER, CoffeeScript, SWITCHES, compile_options, compile_script, compile_scripts, compile_stdio, fs, lint, option_parser, options, optparse, parse_options, path, print_tokens, sources, usage, version, watch_scripts, write_js;
var __range = function(array, from, to, exclusive) {
return [
(from < 0 ? from + array.length : from || 0),
(to < 0 ? to + array.length : to || array.length) + (exclusive ? 0 : 1)
];
};
// 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
@@ -45,8 +51,8 @@
separator = sources.indexOf('--');
flags = [];
if (separator >= 0) {
flags = sources.slice((separator + 1), sources.length);
sources = sources.slice(0, separator);
flags = sources.slice.apply(sources, __range(sources, (separator + 1), sources.length, true));
sources = sources.slice.apply(sources, __range(sources, 0, separator, true));
}
process.ARGV = (process.argv = flags);
if (options.watch) {
@@ -202,7 +208,7 @@
o = (options = option_parser.parse(process.argv));
options.run = !(o.compile || o.print || o.lint);
options.print = !!(o.print || (o.eval || o.stdio && o.compile));
sources = options.arguments.slice(2, options.arguments.length);
sources = options.arguments.slice.apply(options.arguments, __range(options.arguments, 2, options.arguments.length, true));
return sources;
};
// The compile-time options to pass to the CoffeeScript compiler.

View File

@@ -340,10 +340,18 @@
})
],
// The slice literal.
Slice: [o("INDEX_START Expression . . Expression INDEX_END", function() {
Slice: [o("INDEX_START . . Expression INDEX_END", function() {
return new RangeNode(null, $4);
}), o("INDEX_START Expression . . INDEX_END", function() {
return new RangeNode($2, null);
}), o("INDEX_START Expression . . Expression INDEX_END", function() {
return new RangeNode($2, $5);
}), o("INDEX_START . . . Expression INDEX_END", function() {
return new RangeNode(null, $5, true);
}), o("INDEX_START Expression . . . Expression INDEX_END", function() {
return new RangeNode($2, $6, true);
}), o("INDEX_START . . . INDEX_END", function() {
return new RangeNode(null, null, true);
})
],
// The array literal.

View File

@@ -1,5 +1,6 @@
(function(){
var ACCESSORS, ASSIGNMENT, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_ESCAPE, REGEX_FLAGS, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, balanced_string, compact, count, helpers, include, starts;
var ACCESSORS, ASSIGNMENT, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_ESCAPE, REGEX_FLAGS, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, _d, balanced_string, compact, count, helpers, include, k, starts, u;
var __arraySlice = Array.prototype.slice, __hasProp = Object.prototype.hasOwnProperty;
// 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
@@ -236,7 +237,7 @@
// balanced (ie. strings, JS literals).
Lexer.prototype.balanced_token = function balanced_token() {
var delimited;
delimited = Array.prototype.slice.call(arguments, 0, arguments.length - 0);
delimited = __arraySlice.call(arguments, 0, arguments.length - 0);
return balanced_string(this.chunk, delimited);
};
// Matches and conumes comments. We pass through comments into JavaScript,
@@ -609,7 +610,13 @@
// 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", "debugger", "enum", "export", "import", "native", "__extends", "__hasProp"];
RESERVED = ["case", "default", "do", "function", "var", "void", "with", "const", "let", "debugger", "enum", "export", "import", "native"].concat((function() {
_c = []; _d = (u = require('./utilities')).utilities.functions;
for (k in _d) { if (__hasProp.call(_d, k)) {
_c.push(u.utilities.key(k));
}}
return _c;
}).call(this));
// 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);

View File

@@ -1,12 +1,24 @@
(function(){
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, CurryNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, helpers, literal, merge, statement;
var __extends = function(child, parent) {
var __extend = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype;
child.prototype = new ctor();
child.prototype.constructor = child;
};
}, __range = function(array, from, to, exclusive) {
return [
(from < 0 ? from + array.length : from || 0),
(to < 0 ? to + array.length : to || array.length) + (exclusive ? 0 : 1)
];
}, __bind = function(func, obj, args) {
obj = obj || {};
return (typeof args !== 'undefined' && args !== null) ? function() {
return func.apply(obj, args.concat(__arraySlice.call(arguments, 0)));
} : function() {
return func.apply(obj, arguments);
};
}, __arraySlice = Array.prototype.slice;
// `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
@@ -189,7 +201,7 @@
this.children = (this.expressions = compact(flatten(nodes || [])));
return this;
};
__extends(Expressions, BaseNode);
__extend(Expressions, BaseNode);
Expressions.prototype.type = 'Expressions';
// Tack an expression on to the end of this expression list.
Expressions.prototype.push = function push(node) {
@@ -275,7 +287,7 @@
var code;
code = this.compile_node(o);
if (o.scope.has_assignments(this)) {
code = "" + (this.tab) + "var " + (o.scope.compiled_assignments()) + ";\n" + code;
code = "" + (this.tab) + "var " + (o.scope.compiled_assignments(this.tab)) + ";\n" + code;
}
if (o.scope.has_declarations(this)) {
code = "" + (this.tab) + "var " + (o.scope.compiled_declarations()) + ";\n" + code;
@@ -317,7 +329,7 @@
this.value = value;
return this;
};
__extends(LiteralNode, BaseNode);
__extend(LiteralNode, BaseNode);
LiteralNode.prototype.type = 'Literal';
// Break and continue must be treated as pure statements -- they lose their
// meaning when wrapped in a closure.
@@ -344,7 +356,7 @@
this.children = [(this.expression = expression)];
return this;
};
__extends(ReturnNode, BaseNode);
__extend(ReturnNode, BaseNode);
ReturnNode.prototype.type = 'Return';
ReturnNode.prototype.top_sensitive = function top_sensitive() {
return true;
@@ -372,7 +384,7 @@
this.children = flatten([(this.base = base), (this.properties = (properties || []))]);
return this;
};
__extends(ValueNode, BaseNode);
__extend(ValueNode, BaseNode);
ValueNode.prototype.type = 'Value';
ValueNode.prototype.SOAK = " == undefined ? undefined : ";
// A **ValueNode** has a base and a list of property accesses.
@@ -420,11 +432,13 @@
// operators `?.` interspersed. Then we have to take care not to accidentally
// evaluate a anything twice when building the soak chain.
ValueNode.prototype.compile_node = function compile_node(o) {
var _a, _b, _c, baseline, complete, only, op, part, prop, props, soaked, temp;
var _a, _b, _c, baseline, complete, only, op, part, prop, props, replace, soaked, splice, temp;
soaked = false;
only = del(o, 'only_first');
op = del(o, 'operation');
props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties;
splice = del(o, 'splice');
replace = del(o, 'replace');
props = only ? this.properties.slice.apply(this.properties, __range(this.properties, 0, this.properties.length - 1, true)) : this.properties;
baseline = this.base.compile(o);
if (this.base instanceof ObjectNode && this.has_properties()) {
baseline = "(" + baseline + ")";
@@ -442,6 +456,17 @@
} else {
complete = complete + this.SOAK + (baseline += prop.compile(o));
}
} else if (prop instanceof SliceNode) {
o.array = complete;
if (splice) {
o.replace = replace;
part = prop.compile_splice(o);
} else {
part = prop.compile_slice(o);
}
baseline = part;
complete = part;
this.last = part;
} else {
part = prop.compile(o);
baseline += part;
@@ -466,7 +491,7 @@
this;
return this;
};
__extends(CommentNode, BaseNode);
__extend(CommentNode, BaseNode);
CommentNode.prototype.type = 'Comment';
CommentNode.prototype.make_return = function make_return() {
return this;
@@ -486,14 +511,10 @@
this.is_super = variable === 'super';
this.variable = this.is_super ? null : variable;
this.children = compact(flatten([this.variable, (this.args = (args || []))]));
this.compile_splat_arguments = (function(func, obj, args) {
return function() {
return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)));
};
}(SplatNode.compile_mixed_array, this, [this.args]));
this.compile_splat_arguments = __bind(SplatNode.compile_mixed_array, this, [this.args]);
return this;
};
__extends(CallNode, BaseNode);
__extend(CallNode, BaseNode);
CallNode.prototype.type = 'Call';
// Tag this invocation as creating a new instance.
CallNode.prototype.new_instance = function new_instance() {
@@ -560,16 +581,11 @@
exports.CurryNode = (function() {
CurryNode = function CurryNode(meth, args) {
this.children = flatten([(this.meth = meth), (this.context = args[0]), (this.args = (args.slice(1) || []))]);
this.compile_splat_arguments = (function(func, obj, args) {
return function() {
return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)));
};
}(SplatNode.compile_mixed_array, this, [this.args]));
this.compile_splat_arguments = __bind(SplatNode.compile_mixed_array, this, [this.args]);
return this;
};
__extends(CurryNode, CallNode);
__extend(CurryNode, CallNode);
CurryNode.prototype.type = 'Curry';
CurryNode.prototype.body = 'func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)))';
CurryNode.prototype.arguments = function arguments(o) {
var _a, _b, _c, arg;
_b = this.args;
@@ -582,11 +598,9 @@
return (new ArrayNode(this.args)).compile(o);
};
CurryNode.prototype.compile_node = function compile_node(o) {
var body, curried, curry;
body = Expressions.wrap([literal(this.body)]);
curried = new CodeNode([], body);
curry = new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried]));
return (new ParentheticalNode(new CallNode(curry, [this.meth, this.context, literal(this.arguments(o))]))).compile(o);
var ref;
ref = new ValueNode(literal(o.scope.utility('bind')));
return (new CallNode(ref, [this.meth, this.context, literal(this.arguments(o))])).compile(o);
};
return CurryNode;
}).call(this);
@@ -599,16 +613,13 @@
this.children = [(this.child = child), (this.parent = parent)];
return this;
};
__extends(ExtendsNode, BaseNode);
__extend(ExtendsNode, BaseNode);
ExtendsNode.prototype.type = 'Extends';
ExtendsNode.prototype.code = '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 }';
// Hooks one constructor into another's prototype chain.
ExtendsNode.prototype.compile_node = function compile_node(o) {
var call, ref;
o.scope.assign('__extends', this.code, true);
ref = new ValueNode(literal('__extends'));
call = new CallNode(ref, [this.child, this.parent]);
return call.compile(o);
var ref;
ref = new ValueNode(literal(o.scope.utility('extend')));
return (new CallNode(ref, [this.child, this.parent])).compile(o);
};
return ExtendsNode;
}).call(this);
@@ -623,7 +634,7 @@
this;
return this;
};
__extends(AccessorNode, BaseNode);
__extend(AccessorNode, BaseNode);
AccessorNode.prototype.type = 'Accessor';
AccessorNode.prototype.compile_node = function compile_node(o) {
var proto_part;
@@ -640,7 +651,7 @@
this.soak_node = tag === 'soak';
return this;
};
__extends(IndexNode, BaseNode);
__extend(IndexNode, BaseNode);
IndexNode.prototype.type = 'Index';
IndexNode.prototype.compile_node = function compile_node(o) {
var idx;
@@ -659,7 +670,7 @@
this.exclusive = !!exclusive;
return this;
};
__extends(RangeNode, BaseNode);
__extend(RangeNode, BaseNode);
RangeNode.prototype.type = 'Range';
// Compiles the range's source variables -- where it starts and where it ends.
RangeNode.prototype.compile_variables = function compile_variables(o) {
@@ -715,7 +726,7 @@
this;
return this;
};
__extends(SliceNode, BaseNode);
__extend(SliceNode, BaseNode);
SliceNode.prototype.type = 'Slice';
SliceNode.prototype.compile_node = function compile_node(o) {
var from, plus_part, to;
@@ -724,6 +735,29 @@
plus_part = this.range.exclusive ? '' : ' + 1';
return ".slice(" + from + ", " + to + plus_part + ")";
};
SliceNode.prototype.compile_splice = function compile_splice(o) {
var _a, _b, args, array, call, exclusive, from, replace, rng, to, v;
array = del(o, 'array');
replace = del(o, 'replace');
from = (typeof (_a = this.range.from) !== "undefined" && _a !== null) ? this.range.from : literal('null');
to = (typeof (_b = this.range.to) !== "undefined" && _b !== null) ? this.range.to : literal('null');
exclusive = this.range.exclusive ? 'true' : 'false';
v = o.scope.free_variable();
rng = new CallNode(new ValueNode(literal(o.scope.utility('range'))), [literal(array), from, to, literal(exclusive)]);
args = literal("[(" + v + " = " + (rng.compile(o)) + ")[0], " + v + "[1] - " + v + "[0]].concat(" + (replace.compile(o)) + ")");
call = new CallNode(new ValueNode(literal(array), [literal('.splice.apply')]), [literal(array), args]);
return call.compile(o);
};
SliceNode.prototype.compile_slice = function compile_slice(o) {
var _a, _b, array, call, exclusive, from, rng, to;
array = del(o, 'array');
from = (typeof (_a = this.range.from) !== "undefined" && _a !== null) ? this.range.from : literal('null');
to = (typeof (_b = this.range.to) !== "undefined" && _b !== null) ? this.range.to : literal('null');
exclusive = this.range.exclusive ? 'true' : 'false';
rng = new CallNode(new ValueNode(literal(o.scope.utility('range'))), [literal(array), from, to, literal(exclusive)]);
call = new CallNode(new ValueNode(literal(array), [literal('.slice.apply')]), [literal(array), rng]);
return call.compile(o);
};
return SliceNode;
}).call(this);
//### ObjectNode
@@ -733,7 +767,7 @@
this.children = (this.objects = (this.properties = props || []));
return this;
};
__extends(ObjectNode, BaseNode);
__extend(ObjectNode, BaseNode);
ObjectNode.prototype.type = 'Object';
// All the mucking about with commas is to make sure that CommentNodes and
// AssignNodes get interleaved correctly, with no trailing commas or
@@ -780,14 +814,10 @@
exports.ArrayNode = (function() {
ArrayNode = function ArrayNode(objects) {
this.children = (this.objects = objects || []);
this.compile_splat_literal = (function(func, obj, args) {
return function() {
return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)));
};
}(SplatNode.compile_mixed_array, this, [this.objects]));
this.compile_splat_literal = __bind(SplatNode.compile_mixed_array, this, [this.objects]);
return this;
};
__extends(ArrayNode, BaseNode);
__extend(ArrayNode, BaseNode);
ArrayNode.prototype.type = 'Array';
ArrayNode.prototype.compile_node = function compile_node(o) {
var _a, _b, code, ending, i, obj, objects;
@@ -798,7 +828,7 @@
obj = _a[i];
code = obj.compile(o);
if (obj instanceof SplatNode) {
return this.compile_splat_literal(this.objects, o);
return this.compile_splat_literal(o);
} else if (obj instanceof CommentNode) {
objects.push("\n" + code + "\n" + o.indent);
} else if (i === this.objects.length - 1) {
@@ -821,7 +851,7 @@
this.returns = false;
return this;
};
__extends(ClassNode, BaseNode);
__extend(ClassNode, BaseNode);
ClassNode.prototype.type = 'Class';
// Initialize a **ClassNode** with its name, an optional superclass, and a
// list of prototype property assignments.
@@ -881,7 +911,7 @@
this.context = context;
return this;
};
__extends(AssignNode, BaseNode);
__extend(AssignNode, BaseNode);
AssignNode.prototype.type = 'Assign';
// Matchers for detecting prototype assignments.
AssignNode.prototype.PROTO_ASSIGN = /^(\S+)\.prototype/;
@@ -980,17 +1010,11 @@
// Compile the assignment from an array splice literal, using JavaScript's
// `Array#splice` method.
AssignNode.prototype.compile_splice = function compile_splice(o) {
var from, l, name, plus, range, to, val;
name = this.variable.compile(merge(o, {
only_first: true
return this.variable.compile(merge(o, {
only_first: true,
splice: true,
replace: this.value
}));
l = this.variable.properties.length;
range = this.variable.properties[l - 1].range;
plus = range.exclusive ? '' : ' + 1';
from = range.from.compile(o);
to = range.to.compile(o) + ' - ' + from + plus;
val = this.value.compile(o);
return "" + (name) + ".splice.apply(" + name + ", [" + from + ", " + to + "].concat(" + val + "))";
};
return AssignNode;
}).call(this);
@@ -1005,7 +1029,7 @@
this.bound = tag === 'boundfunc';
return this;
};
__extends(CodeNode, BaseNode);
__extend(CodeNode, BaseNode);
CodeNode.prototype.type = 'Code';
// Compilation creates a new scope unless explicitly asked to share with the
// outer scope. Handles splat parameters in the parameter list by peeking at
@@ -1013,7 +1037,7 @@
// arrow, generates a wrapper that saves the current value of `this` through
// a closure.
CodeNode.prototype.compile_node = function compile_node(o) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, code, func, i, inner, name_part, param, params, shared_scope, splat, top;
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, code, func, i, name_part, param, params, ref, shared_scope, splat, top;
shared_scope = del(o, 'shared_scope');
top = del(o, 'top');
o.scope = shared_scope || new Scope(o.scope, this.body, this);
@@ -1062,8 +1086,8 @@
if (!(this.bound)) {
return func;
}
inner = "(function" + name_part + "() {\n" + (this.idt(2)) + "return __func.apply(__this, arguments);\n" + (this.idt(1)) + "});";
return "(function(__this) {\n" + (this.idt(1)) + "var __func = " + func + ";\n" + (this.idt(1)) + "return " + inner + "\n" + this.tab + "})(this)";
ref = new ValueNode(literal(o.scope.utility('bind')));
return (new CallNode(ref, [literal(func), literal('this')])).compile(o);
};
CodeNode.prototype.top_sensitive = function top_sensitive() {
return true;
@@ -1110,7 +1134,7 @@
this.children = [(this.name = name)];
return this;
};
__extends(SplatNode, BaseNode);
__extend(SplatNode, BaseNode);
SplatNode.prototype.type = 'Splat';
SplatNode.prototype.compile_node = function compile_node(o) {
var _a;
@@ -1133,15 +1157,15 @@
o.scope.assign(trailing.compile(o), "arguments[arguments.length - " + this.trailings.length + " + " + i + "]");
i += 1;
}
return "" + name + " = Array.prototype.slice.call(arguments, " + this.index + ", arguments.length - " + (this.trailings.length) + ")";
return "" + name + " = " + (o.scope.utility('arraySlice')) + ".call(arguments, " + this.index + ", arguments.length - " + (this.trailings.length) + ")";
};
// A compiling a splat as a destructuring assignment means slicing arguments
// from the right-hand-side's corresponding array.
SplatNode.prototype.compile_value = function compile_value(o, name, index, trailings) {
if ((typeof trailings !== "undefined" && trailings !== null)) {
return "Array.prototype.slice.call(" + name + ", " + index + ", " + (name) + ".length - " + trailings + ")";
return "" + (o.scope.utility('arraySlice')) + ".call(" + name + ", " + index + ", " + (name) + ".length - " + trailings + ")";
} else {
return "Array.prototype.slice.call(" + name + ", " + index + ")";
return "" + (o.scope.utility('arraySlice')) + ".call(" + name + ", " + index + ")";
}
};
// Utility function that converts arbitrary number of elements, mixed with
@@ -1183,7 +1207,7 @@
this.filter = opts && opts.filter;
return this;
};
__extends(WhileNode, BaseNode);
__extend(WhileNode, BaseNode);
WhileNode.prototype.type = 'While';
WhileNode.prototype.add_body = function add_body(body) {
this.children.push((this.body = body));
@@ -1239,7 +1263,7 @@
this.flip = !!flip;
return this;
};
__extends(OpNode, BaseNode);
__extend(OpNode, BaseNode);
OpNode.prototype.type = 'Op';
// The map of conversions from CoffeeScript to JavaScript symbols.
OpNode.prototype.CONVERSIONS = {
@@ -1340,7 +1364,7 @@
this;
return this;
};
__extends(TryNode, BaseNode);
__extend(TryNode, BaseNode);
TryNode.prototype.type = 'Try';
TryNode.prototype.make_return = function make_return() {
if (this.attempt) {
@@ -1373,7 +1397,7 @@
this.children = [(this.expression = expression)];
return this;
};
__extends(ThrowNode, BaseNode);
__extend(ThrowNode, BaseNode);
ThrowNode.prototype.type = 'Throw';
// A **ThrowNode** is already a return, of sorts...
ThrowNode.prototype.make_return = function make_return() {
@@ -1394,7 +1418,7 @@
this.children = [(this.expression = expression)];
return this;
};
__extends(ExistenceNode, BaseNode);
__extend(ExistenceNode, BaseNode);
ExistenceNode.prototype.type = 'Existence';
ExistenceNode.prototype.compile_node = function compile_node(o) {
return ExistenceNode.compile_test(o, this.expression);
@@ -1429,7 +1453,7 @@
this.children = [(this.expression = expression)];
return this;
};
__extends(ParentheticalNode, BaseNode);
__extend(ParentheticalNode, BaseNode);
ParentheticalNode.prototype.type = 'Paren';
ParentheticalNode.prototype.is_statement = function is_statement() {
return this.expression.is_statement();
@@ -1481,7 +1505,7 @@
this.returns = false;
return this;
};
__extends(ForNode, BaseNode);
__extend(ForNode, BaseNode);
ForNode.prototype.type = 'For';
ForNode.prototype.top_sensitive = function top_sensitive() {
return true;
@@ -1553,10 +1577,7 @@
body = PushNode.wrap(rvar, body);
}
this.filter ? (body = Expressions.wrap([new IfNode(this.filter, body)])) : null;
if (this.object) {
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true);
for_part = "" + ivar + " in " + svar + ") { if (__hasProp.call(" + svar + ", " + ivar + ")";
}
this.object ? (for_part = "" + ivar + " in " + svar + ") { if (" + (o.scope.utility('hasProp')) + ".call(" + svar + ", " + ivar + ")") : null;
body = body.compile(merge(o, {
indent: body_dent,
top: true
@@ -1588,7 +1609,7 @@
}
return this;
};
__extends(IfNode, BaseNode);
__extend(IfNode, BaseNode);
IfNode.prototype.type = 'If';
// Add a new *else* clause to this **IfNode**, or push it down to the bottom
// of the chain recursively.

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,13 @@
(function(){
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, helpers, include, pair;
var __hasProp = Object.prototype.hasOwnProperty;
var __bind = function(func, obj, args) {
obj = obj || {};
return (typeof args !== 'undefined' && args !== null) ? function() {
return func.apply(obj, args.concat(__arraySlice.call(arguments, 0)));
} : function() {
return func.apply(obj, arguments);
};
}, __arraySlice = Array.prototype.slice, __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
@@ -57,8 +64,7 @@
// Massage newlines and indentations so that comments don't have to be
// correctly indented, or appear on a line of their own.
Rewriter.prototype.adjust_comments = function adjust_comments() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
return this.scan_tokens(__bind(function(prev, token, post, i) {
var after;
if (!(token[0] === 'COMMENT')) {
return 1;
@@ -74,11 +80,7 @@
} else {
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
}, this));
};
// Leading newlines would introduce an ambiguity in the grammar, so we
// dispatch them here.
@@ -93,18 +95,13 @@
// Some blocks occur in the middle of expressions -- when we're expecting
// this, remove their trailing newlines.
Rewriter.prototype.remove_mid_expression_newlines = function remove_mid_expression_newlines() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
return this.scan_tokens(__bind(function(prev, token, post, i) {
if (!(post && include(EXPRESSION_CLOSE, post[0]) && token[0] === 'TERMINATOR')) {
return 1;
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
}, 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
@@ -113,8 +110,7 @@
var brackets, parens;
parens = [0];
brackets = [0];
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
return this.scan_tokens(__bind(function(prev, token, post, i) {
var _a;
if ((_a = token[0]) === 'CALL_START') {
parens.push(0);
@@ -140,11 +136,7 @@
}
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
}, this));
};
// Methods may be optionally called without parentheses, for simple cases.
// Insert the implicit parentheses here, so that the parser doesn't have to
@@ -154,8 +146,7 @@
stack = [0];
calls = 0;
parens = 0;
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
return this.scan_tokens(__bind(function(prev, token, post, i) {
var _a, _b, _c, idx, last, open, size, stack_pointer, tag, tmp;
tag = token[0];
if (tag === 'CALL_START') {
@@ -199,19 +190,14 @@
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
stack[stack.length - 1] += 1;
return 2;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
}, 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.add_implicit_indentation = function add_implicit_indentation() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
return this.scan_tokens(__bind(function(prev, token, post, i) {
var idx, insertion, outdent, parens, pre, starter, tok;
if (!(include(SINGLE_LINERS, token[0]) && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) {
return 1;
@@ -243,11 +229,7 @@
}
this.tokens.splice(i, 1);
return 0;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
}, this));
};
// Ensure that all listed pairs of tokens are correctly balanced throughout
// the course of the token stream.
@@ -255,8 +237,7 @@
var _a, _b, key, levels, line, open, open_line, unclosed, value;
levels = {};
open_line = {};
this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
this.scan_tokens(__bind(function(prev, token, post, i) {
var _a, _b, _c, _d, close, open, pair;
_b = pairs;
for (_a = 0, _c = _b.length; _a < _c; _a++) {
@@ -279,11 +260,7 @@
}
}
return 1;
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
}, this));
unclosed = (function() {
_a = []; _b = levels;
for (key in _b) { if (__hasProp.call(_b, key)) {
@@ -318,8 +295,7 @@
val = _a[key];
(debt[key] = 0);
}}
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
return this.scan_tokens(__bind(function(prev, token, post, i) {
var inv, match, mtag, tag;
tag = token[0];
inv = INVERSES[token[0]];
@@ -345,11 +321,7 @@
} else {
return 1;
}
};
return (function() {
return __func.apply(__this, arguments);
});
})(this));
}, this));
};
return Rewriter;
}).call(this);

View File

@@ -1,5 +1,5 @@
(function(){
var Scope;
var Scope, utilities;
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
@@ -11,6 +11,7 @@
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
utilities = (typeof process !== "undefined" && process !== null) ? require('./utilities').utilities : this.utilities;
exports.Scope = (function() {
Scope = function Scope(parent, expressions, method) {
var _a;
@@ -26,6 +27,14 @@
// 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.
// Find the top-most scope object, used for defined global variables
Scope.prototype.topmost = function topmost() {
if (this.parent) {
return this.parent.topmost();
} else {
return this;
}
};
// Look up a variable name in lexical scope, and declare it if it does not
// already exist.
Scope.prototype.find = function find(name) {
@@ -35,6 +44,18 @@
this.variables[name] = 'var';
return false;
};
// Test variables and return true the first time fn(v, k) returns true
Scope.prototype.any = function any(fn) {
var _a, k, v;
_a = this.variables;
for (v in _a) { if (__hasProp.call(_a, v)) {
k = _a[v];
if (fn(v, k)) {
return true;
}
}}
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 parameter(name) {
@@ -62,8 +83,8 @@
// Ensure that an assignment is made at the top of this scope
// (or at the top-level scope, if requested).
Scope.prototype.assign = function assign(name, value, top_level) {
if (top_level && this.parent) {
return this.parent.assign(name, value, top_level);
if (top_level) {
return this.topmost().assign(name, value);
}
this.variables[name] = {
value: value,
@@ -71,15 +92,52 @@
};
return this.variables[name];
};
// Ensure the CoffeeScript utility object is included in the top level
// then return a CallNode curried constructor bound to the utility function
Scope.prototype.utility = function utility(name) {
var _a, _b, _c, _d, dep;
if (this.parent) {
return this.topmost().utility(name);
}
if ((typeof (_d = utilities.functions[name]) !== "undefined" && _d !== null)) {
this.utilities = this.utilities || {};
this.utilities[name] = utilities.functions[name];
_b = (utilities.dependencies[name] || []);
for (_a = 0, _c = _b.length; _a < _c; _a++) {
dep = _b[_a];
!this.utilities[dep] ? this.utility(dep) : null;
}
}
return "" + (utilities.key(name));
};
// Formats an javascript object containing the utility methods required
// in the scope
Scope.prototype.included_utilities = function included_utilities(tab) {
var _a, _b, _c, key;
if ((typeof (_c = this.utilities) !== "undefined" && _c !== null)) {
_a = []; _b = this.utilities;
for (key in _b) { if (__hasProp.call(_b, key)) {
_a.push(utilities.format(key, tab));
}}
return _a;
} else {
return [];
}
};
// Does this scope reference any variables that need to be declared in the
// given function body?
Scope.prototype.has_declarations = function has_declarations(body) {
return body === this.expressions && this.declared_variables().length;
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.has_assignments = function has_assignments(body) {
return body === this.expressions && this.assigned_variables().length;
var _a;
return body === this.expressions && ((typeof (_a = this.utilities) !== "undefined" && _a !== null) || this.any(function(k, val) {
return val.assigned;
}));
};
// Return the list of variables first declared in this scope.
Scope.prototype.declared_variables = function declared_variables() {
@@ -109,8 +167,8 @@
return this.declared_variables().join(', ');
};
// Compile the JavaScript for all of the variable assignments in this scope.
Scope.prototype.compiled_assignments = function compiled_assignments() {
return this.assigned_variables().join(', ');
Scope.prototype.compiled_assignments = function compiled_assignments(tab) {
return this.assigned_variables().concat(this.included_utilities(tab)).join(', ');
};
return Scope;
}).call(this);

27
lib/utilities.js Normal file
View File

@@ -0,0 +1,27 @@
(function(){
var utilities;
if (!((typeof process !== "undefined" && process !== null))) {
this.exports = this;
}
exports.utilities = (function() {
utilities = function utilities() { };
utilities.key = function key(name) {
return "__" + name;
};
utilities.format = function format(key, tab) {
return "" + (utilities.key(key)) + " = " + (utilities.functions[key].replace(/\n/g, "\n" + tab) || 'undefined');
};
utilities.dependencies = {
bind: ['arraySlice'],
splice: ['range']
};
utilities.functions = {
extend: "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}",
bind: "function(func, obj, args) {\n obj = obj || {};\n return (typeof args !== 'undefined' && args !== null) ? function() {\n return func.apply(obj, args.concat(" + (utilities.key('arraySlice')) + ".call(arguments, 0)));\n } : function() {\n return func.apply(obj, arguments);\n };\n}",
range: "function(array, from, to, exclusive) {\n return [\n (from < 0 ? from + array.length : from || 0),\n (to < 0 ? to + array.length : to || array.length) + (exclusive ? 0 : 1)\n ];\n}",
hasProp: 'Object.prototype.hasOwnProperty',
arraySlice: 'Array.prototype.slice'
};
return utilities;
}).call(this);
})();

View File

@@ -328,8 +328,12 @@ grammar: {
# The slice literal.
Slice: [
o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode $2, $5
o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode $2, $6, true
o "INDEX_START . . Expression INDEX_END", -> new RangeNode null, $4
o "INDEX_START Expression . . INDEX_END", -> new RangeNode $2, null
o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode $2, $5
o "INDEX_START . . . Expression INDEX_END", -> new RangeNode null, $5, true
o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode $2, $6, true
o "INDEX_START . . . INDEX_END", -> new RangeNode null, null, true
]
# The array literal.

View File

@@ -464,7 +464,7 @@ KEYWORDS: JS_KEYWORDS.concat COFFEE_KEYWORDS
RESERVED: [
"case", "default", "do", "function", "var", "void", "with"
"const", "let", "debugger", "enum", "export", "import", "native",
"__extends", "__hasProp"
(u.utilities.key(k) for k of (u: require './utilities').utilities.functions)...
]
# The superset of both JavaScript keywords and reserved words, none of which may

View File

@@ -188,7 +188,7 @@ exports.Expressions: class Expressions extends BaseNode
# declarations of all inner variables pushed up to the top.
compile_with_declarations: (o) ->
code: @compile_node(o)
code: "${@tab}var ${o.scope.compiled_assignments()};\n$code" if o.scope.has_assignments(this)
code: "${@tab}var ${o.scope.compiled_assignments(@tab)};\n$code" if o.scope.has_assignments(this)
code: "${@tab}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this)
code
@@ -308,6 +308,8 @@ exports.ValueNode: class ValueNode extends BaseNode
soaked: false
only: del(o, 'only_first')
op: del(o, 'operation')
splice: del(o, 'splice')
replace: del(o, 'replace')
props: if only then @properties[0...@properties.length - 1] else @properties
baseline: @base.compile o
baseline: "($baseline)" if @base instanceof ObjectNode and @has_properties()
@@ -322,6 +324,16 @@ exports.ValueNode: class ValueNode extends BaseNode
complete: "($temp = $complete)$@SOAK" + (baseline: temp + prop.compile(o))
else
complete: complete + @SOAK + (baseline: + prop.compile(o))
else if prop instanceof SliceNode
o.array: complete
if splice
o.replace: replace
part: prop.compile_splice(o)
else
part: prop.compile_slice(o)
baseline = part
complete = part
@last: part
else
part: prop.compile(o)
baseline: + part
@@ -408,8 +420,6 @@ exports.CallNode: class CallNode extends BaseNode
exports.CurryNode: class CurryNode extends CallNode
type: 'Curry'
body: 'func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)))'
constructor: (meth, args) ->
@children: flatten [@meth: meth, @context: args[0], @args: (args.slice(1) or [])]
@compile_splat_arguments: SplatNode.compile_mixed_array <- @, @args
@@ -420,10 +430,8 @@ exports.CurryNode: class CurryNode extends CallNode
(new ArrayNode(@args)).compile o
compile_node: (o) ->
body: Expressions.wrap([literal @body])
curried: new CodeNode([], body)
curry: new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried]))
(new ParentheticalNode(new CallNode(curry, [@meth, @context, literal(@arguments(o))]))).compile o
ref: new ValueNode literal(o.scope.utility('bind'))
(new CallNode(ref, [@meth, @context, literal(@arguments(o))])).compile o
#### ExtendsNode
@@ -433,25 +441,13 @@ exports.CurryNode: class CurryNode extends CallNode
exports.ExtendsNode: class ExtendsNode extends BaseNode
type: 'Extends'
code: '''
function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype;
child.prototype = new ctor();
child.prototype.constructor = child;
}
'''
constructor: (child, parent) ->
@children: [@child: child, @parent: parent]
# Hooks one constructor into another's prototype chain.
compile_node: (o) ->
o.scope.assign('__extends', @code, true)
ref: new ValueNode literal('__extends')
call: new CallNode ref, [@child, @parent]
call.compile(o)
ref: new ValueNode literal(o.scope.utility('extend'))
(new CallNode ref, [@child, @parent]).compile o
#### AccessorNode
@@ -544,6 +540,27 @@ exports.SliceNode: class SliceNode extends BaseNode
plus_part: if @range.exclusive then '' else ' + 1'
".slice($from, $to$plus_part)"
compile_splice: (o) ->
array: del o, 'array'
replace: del o, 'replace'
from: if @range.from? then @range.from else literal('null')
to: if @range.to? then @range.to else literal('null')
exclusive: if @range.exclusive then 'true' else 'false'
v: o.scope.free_variable()
rng: new CallNode new ValueNode(literal(o.scope.utility('range'))), [literal(array), from, to, literal(exclusive)]
args: literal "[($v = ${rng.compile(o)})[0], $v[1] - $v[0]].concat(${replace.compile(o)})"
call: new CallNode new ValueNode(literal(array), [literal('.splice.apply')]), [literal(array), args]
call.compile(o)
compile_slice: (o) ->
array: del o, 'array'
from: if @range.from? then @range.from else literal('null')
to: if @range.to? then @range.to else literal('null')
exclusive: if @range.exclusive then 'true' else 'false'
rng: new CallNode new ValueNode(literal(o.scope.utility('range'))), [literal(array), from, to, literal(exclusive)]
call: new CallNode new ValueNode(literal(array), [literal('.slice.apply')]), [literal(array), rng]
call.compile(o)
#### ObjectNode
# An object literal, nothing fancy.
@@ -588,7 +605,7 @@ exports.ArrayNode: class ArrayNode extends BaseNode
for obj, i in @objects
code: obj.compile(o)
if obj instanceof SplatNode
return @compile_splat_literal(@objects, o)
return @compile_splat_literal o
else if obj instanceof CommentNode
objects.push "\n$code\n$o.indent"
else if i is @objects.length - 1
@@ -734,14 +751,7 @@ exports.AssignNode: class AssignNode extends BaseNode
# Compile the assignment from an array splice literal, using JavaScript's
# `Array#splice` method.
compile_splice: (o) ->
name: @variable.compile(merge(o, {only_first: true}))
l: @variable.properties.length
range: @variable.properties[l - 1].range
plus: if range.exclusive then '' else ' + 1'
from: range.from.compile(o)
to: range.to.compile(o) + ' - ' + from + plus
val: @value.compile(o)
"${name}.splice.apply($name, [$from, $to].concat($val))"
@variable.compile(merge(o, {only_first: true, splice: true, replace: @value}))
#### CodeNode
@@ -791,8 +801,8 @@ exports.CodeNode: class CodeNode extends BaseNode
func: "function${ if @bound then '' else name_part }(${ params.join(', ') }) {$code${@idt(if @bound then 1 else 0)}}"
func: "($func)" if top and not @bound
return func unless @bound
inner: "(function$name_part() {\n${@idt(2)}return __func.apply(__this, arguments);\n${@idt(1)}});"
"(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n$@tab})(this)"
ref: new ValueNode literal(o.scope.utility('bind'))
(new CallNode ref, [literal(func), literal('this')]).compile o
top_sensitive: ->
true
@@ -835,13 +845,13 @@ exports.SplatNode: class SplatNode extends BaseNode
for trailing in @trailings
o.scope.assign(trailing.compile(o), "arguments[arguments.length - $@trailings.length + $i]")
i: + 1
"$name = Array.prototype.slice.call(arguments, $@index, arguments.length - ${@trailings.length})"
"$name = ${o.scope.utility('arraySlice')}.call(arguments, $@index, arguments.length - ${@trailings.length})"
# A compiling a splat as a destructuring assignment means slicing arguments
# from the right-hand-side's corresponding array.
compile_value: (o, name, index, trailings) ->
if trailings? then "Array.prototype.slice.call($name, $index, ${name}.length - $trailings)" \
else "Array.prototype.slice.call($name, $index)"
if trailings? then "${o.scope.utility('arraySlice')}.call($name, $index, ${name}.length - $trailings)" \
else "${o.scope.utility('arraySlice')}.call($name, $index)"
# Utility function that converts arbitrary number of elements, mixed with
# splats, to a proper array
@@ -1160,8 +1170,7 @@ exports.ForNode: class ForNode extends BaseNode
if @filter
body: Expressions.wrap([new IfNode(@filter, body)])
if @object
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true)
for_part: "$ivar in $svar) { if (__hasProp.call($svar, $ivar)"
for_part: "$ivar in $svar) { if (${o.scope.utility('hasProp')}.call($svar, $ivar)"
body: body.compile(merge(o, {indent: body_dent, top: true}))
vars: if range then name else "$name, $ivar"
close: if @object then '}}\n' else '}\n'

View File

@@ -7,6 +7,7 @@
# Set up exported variables for both **Node.js** and the browser.
this.exports: this unless process?
utilities: if process? then require('./utilities').utilities else this.utilities
exports.Scope: class Scope
@@ -19,12 +20,22 @@ exports.Scope: class Scope
@variables: {}
@temp_var: if @parent then @parent.temp_var else '_a'
# Find the top-most scope object, used for defined global variables
topmost: ->
if @parent then @parent.topmost() else @
# Look up a variable name in lexical scope, and declare it if it does not
# already exist.
find: (name) ->
return true if @check name
@variables[name]: 'var'
false
# Test variables and return true the first time fn(v, k) returns true
any: (fn) ->
for v, k of @variables when fn(v, k)
return true
return false
# Reserve a variable name as originating from a function parameter for this
# scope. No `var` required for internal references.
@@ -48,18 +59,35 @@ exports.Scope: class Scope
# Ensure that an assignment is made at the top of this scope
# (or at the top-level scope, if requested).
assign: (name, value, top_level) ->
return @parent.assign(name, value, top_level) if top_level and @parent
return @topmost().assign(name, value) if top_level
@variables[name]: {value: value, assigned: true}
# Ensure the CoffeeScript utility object is included in the top level
# then return a CallNode curried constructor bound to the utility function
utility: (name) ->
return @topmost().utility(name) if @parent
if utilities.functions[name]?
@utilities: or {}
@utilities[name]: utilities.functions[name]
@utility(dep) for dep in (utilities.dependencies[name] or []) when not @utilities[dep]
"${utilities.key(name)}"
# Formats an javascript object containing the utility methods required
# in the scope
included_utilities: (tab) ->
if @utilities?
utilities.format(key, tab) for key of @utilities
else []
# Does this scope reference any variables that need to be declared in the
# given function body?
has_declarations: (body) ->
body is @expressions and @declared_variables().length
body is @expressions and @any (k, val) -> val is 'var'
# Does this scope reference any assignments that need to be declared at the
# top of the given function body?
has_assignments: (body) ->
body is @expressions and @assigned_variables().length
body is @expressions and (@utilities? or @any (k, val) -> val.assigned)
# Return the list of variables first declared in this scope.
declared_variables: ->
@@ -75,5 +103,5 @@ exports.Scope: class Scope
@declared_variables().join ', '
# Compile the JavaScript for all of the variable assignments in this scope.
compiled_assignments: ->
@assigned_variables().join ', '
compiled_assignments: (tab) ->
[@assigned_variables()..., @included_utilities(tab)...].join ', '

45
src/utilities.coffee Normal file
View File

@@ -0,0 +1,45 @@
this.exports: this unless process?
exports.utilities: class utilities
@key: (name) ->
"__$name"
@format: (key, tab) ->
"${utilities.key(key)} = ${utilities.functions[key].replace(/\n/g, "\n$tab") or 'undefined'}"
@dependencies: {
bind: ['arraySlice']
splice: ['range']
}
@functions: {
extend: """
function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype;
child.prototype = new ctor();
child.prototype.constructor = child;
}
"""
bind: """
function(func, obj, args) {
obj = obj || {};
return (typeof args !== 'undefined' && args !== null) ? function() {
return func.apply(obj, args.concat(${utilities.key('arraySlice')}.call(arguments, 0)));
} : function() {
return func.apply(obj, arguments);
};
}
"""
range: """
function(array, from, to, exclusive) {
return [
(from < 0 ? from + array.length : from || 0),
(to < 0 ? to + array.length : to || array.length) + (exclusive ? 0 : 1)
];
}
"""
hasProp: 'Object.prototype.hasOwnProperty'
arraySlice: 'Array.prototype.slice'
}

View File

@@ -13,4 +13,46 @@ ok countdown is "10 9 8 7 6 5 4 3 2 1"
array: [(1+5)..1+9]
ok array.join(' ') is "6 7 8 9 10"
ok array.join(' ') is "6 7 8 9 10"
hello: "Hello World"
ok hello[...] is "Hello World"
ok hello[1..] is "ello World"
ok hello[6..] is "World"
ok hello[6..-1] is "World"
ok hello[6...-1] is "Worl"
ok hello[1...1] is ""
ok hello[1..1] is "e"
ok hello[1...5] is "ello"
ok hello[0..4] is "Hello"
ok hello[...-1] is "Hello Worl"
ok hello[...-6] is "Hello"
ok hello[..1000] is "Hello World"
# http://wiki.ecmascript.org/doku.php?id=proposals:slice_syntax
s: "hello, world"
ok s[3...5] is 'lo'
ok s[10..] is 'ld'
a: [0, 1, 2, 3, 4, 5, 6, 7]
deepEqual a[2...6], [2, 3, 4, 5]
deepEqual a[-6...-2], [2, 3, 4, 5]
deepEqual a[...2], [0, 1]
# http://techearth.net/python/index.php5?title=Python:Basics:Slices
alphabet: 'abcdefghij'
ok alphabet[1...3] is 'bc'
ok alphabet[...3] is 'abc'
ok alphabet[1..] is 'bcdefghij'
ok alphabet[...] is 'abcdefghij'
ok alphabet[-1..] is 'j'
ok alphabet[...-1] is 'abcdefghi'
lstFruit: ['apple', 'banana', 'cherry', 'date']
deepEqual lstFruit[1...3], ['banana', 'cherry']
lstFruit2: lstFruit[...]
deepEqual lstFruit2, lstFruit
lstFruit[1]: 'blueberry'
deepEqual lstFruit2, ['apple', 'banana', 'cherry', 'date']