mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
big refactor -- pulled all helper functions into helpers.coffee to facilitate sharing.
This commit is contained in:
parent
4932d25540
commit
b297510d2b
9 changed files with 225 additions and 240 deletions
2
Rakefile
2
Rakefile
|
@ -23,7 +23,7 @@ end
|
||||||
|
|
||||||
desc "Build the single concatenated and minified script for the browser"
|
desc "Build the single concatenated and minified script for the browser"
|
||||||
task :browser do
|
task :browser do
|
||||||
sources = %w(rewriter.js lexer.js parser.js scope.js nodes.js coffee-script.js)
|
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 = 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(code) }
|
File.open('extras/coffee-script.js', 'w+') {|f| f.write(code) }
|
||||||
|
|
125
lib/helpers.js
Normal file
125
lib/helpers.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
(function(){
|
||||||
|
var __hasProp = Object.prototype.hasOwnProperty;
|
||||||
|
// Set up exported variables for both Node.js and the browser.
|
||||||
|
if (!((typeof process !== "undefined" && process !== null))) {
|
||||||
|
this.exports = this;
|
||||||
|
}
|
||||||
|
// Does a list include a value?
|
||||||
|
exports.include = function include(list, value) {
|
||||||
|
return list.indexOf(value) >= 0;
|
||||||
|
};
|
||||||
|
// Peek at the beginning of a given string to see if it matches a sequence.
|
||||||
|
exports.starts = function starts(string, literal, start) {
|
||||||
|
return string.substring(start, (start || 0) + literal.length) === literal;
|
||||||
|
};
|
||||||
|
// Trim out all falsy values from an array.
|
||||||
|
exports.compact = function compact(array) {
|
||||||
|
var _a, _b, _c, _d, item;
|
||||||
|
_a = []; _b = array;
|
||||||
|
for (_c = 0, _d = _b.length; _c < _d; _c++) {
|
||||||
|
item = _b[_c];
|
||||||
|
if (item) {
|
||||||
|
_a.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _a;
|
||||||
|
};
|
||||||
|
// Count the number of occurences of a character in a string.
|
||||||
|
exports.count = function count(string, letter) {
|
||||||
|
var num, pos;
|
||||||
|
num = 0;
|
||||||
|
pos = string.indexOf(letter);
|
||||||
|
while (pos !== -1) {
|
||||||
|
num += 1;
|
||||||
|
pos = string.indexOf(letter, pos + 1);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
};
|
||||||
|
// Merge objects, returning a fresh copy with attributes from both sides.
|
||||||
|
// Used every time `compile` is called, to allow properties in the options hash
|
||||||
|
// to propagate down the tree without polluting other branches.
|
||||||
|
exports.merge = function merge(options, overrides) {
|
||||||
|
var _a, _b, fresh, key, val;
|
||||||
|
fresh = {};
|
||||||
|
_a = options;
|
||||||
|
for (key in _a) { if (__hasProp.call(_a, key)) {
|
||||||
|
val = _a[key];
|
||||||
|
((fresh[key] = val));
|
||||||
|
}}
|
||||||
|
if (overrides) {
|
||||||
|
_b = overrides;
|
||||||
|
for (key in _b) { if (__hasProp.call(_b, key)) {
|
||||||
|
val = _b[key];
|
||||||
|
((fresh[key] = val));
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
return fresh;
|
||||||
|
};
|
||||||
|
// Return a completely flattened version of an array. Handy for getting a
|
||||||
|
// list of `children`.
|
||||||
|
exports.flatten = function flatten(array) {
|
||||||
|
var _a, _b, _c, item, memo;
|
||||||
|
memo = [];
|
||||||
|
_a = array;
|
||||||
|
for (_b = 0, _c = _a.length; _b < _c; _b++) {
|
||||||
|
item = _a[_b];
|
||||||
|
item instanceof Array ? (memo = memo.concat(item)) : memo.push(item);
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
exports.del = function del(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 etc...
|
||||||
|
exports.balanced_string = function balanced_string(str, delimited, options) {
|
||||||
|
var _a, _b, _c, _d, close, i, levels, open, pair, slash;
|
||||||
|
options = options || {};
|
||||||
|
slash = delimited[0][0] === '/';
|
||||||
|
levels = [];
|
||||||
|
i = 0;
|
||||||
|
while (i < str.length) {
|
||||||
|
if (levels.length && exports.starts(str, '\\', i)) {
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
_a = delimited;
|
||||||
|
for (_b = 0, _c = _a.length; _b < _c; _b++) {
|
||||||
|
pair = _a[_b];
|
||||||
|
_d = pair;
|
||||||
|
open = _d[0];
|
||||||
|
close = _d[1];
|
||||||
|
if (levels.length && exports.starts(str, close, i) && levels[levels.length - 1] === pair) {
|
||||||
|
levels.pop();
|
||||||
|
i += close.length - 1;
|
||||||
|
if (!(levels.length)) {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (exports.starts(str, open, i)) {
|
||||||
|
levels.push(pair);
|
||||||
|
i += open.length - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!levels.length || slash && exports.starts(str, '\n', i)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (levels.length) {
|
||||||
|
if (slash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new Error("SyntaxError: Unterminated " + (levels.pop()[0]) + " starting on line " + (this.line + 1));
|
||||||
|
}
|
||||||
|
return !i ? false : str.substring(0, i);
|
||||||
|
};
|
||||||
|
})();
|
87
lib/lexer.js
87
lib/lexer.js
|
@ -1,5 +1,5 @@
|
||||||
(function(){
|
(function(){
|
||||||
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_ESCAPE, REGEX_FLAGS, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, compact, count, include, starts;
|
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_ESCAPE, REGEX_FLAGS, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE;
|
||||||
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
|
// 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,
|
// 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
|
// a token is produced, we consume the match, and start again. Tokens are in the
|
||||||
|
@ -9,9 +9,9 @@
|
||||||
// Set up the Lexer for both Node.js and the browser, depending on where we are.
|
// Set up the Lexer for both Node.js and the browser, depending on where we are.
|
||||||
if ((typeof process !== "undefined" && process !== null)) {
|
if ((typeof process !== "undefined" && process !== null)) {
|
||||||
Rewriter = require('./rewriter').Rewriter;
|
Rewriter = require('./rewriter').Rewriter;
|
||||||
|
process.mixin(require('./helpers'));
|
||||||
} else {
|
} else {
|
||||||
this.exports = this;
|
this.exports = this;
|
||||||
Rewriter = this.Rewriter;
|
|
||||||
}
|
}
|
||||||
// The Lexer Class
|
// The Lexer Class
|
||||||
// ---------------
|
// ---------------
|
||||||
|
@ -203,7 +203,7 @@
|
||||||
Lexer.prototype.balanced_token = function balanced_token() {
|
Lexer.prototype.balanced_token = function balanced_token() {
|
||||||
var delimited;
|
var delimited;
|
||||||
delimited = Array.prototype.slice.call(arguments, 0);
|
delimited = Array.prototype.slice.call(arguments, 0);
|
||||||
return this.balanced_string(this.chunk, delimited);
|
return balanced_string(this.chunk, delimited);
|
||||||
};
|
};
|
||||||
// Matches and conumes comments. We pass through comments into JavaScript,
|
// Matches and conumes comments. We pass through comments into JavaScript,
|
||||||
// so they're treated as real tokens, like any other part of the language.
|
// so they're treated as real tokens, like any other part of the language.
|
||||||
|
@ -403,52 +403,6 @@
|
||||||
Lexer.prototype.assignment_error = function assignment_error() {
|
Lexer.prototype.assignment_error = function assignment_error() {
|
||||||
throw new Error("SyntaxError: Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned");
|
throw new Error("SyntaxError: Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned");
|
||||||
};
|
};
|
||||||
// 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 etc...
|
|
||||||
Lexer.prototype.balanced_string = function balanced_string(str, delimited) {
|
|
||||||
var _a, _b, _c, _d, close, i, levels, open, pair, slash;
|
|
||||||
slash = delimited[0][0] === '/';
|
|
||||||
levels = [];
|
|
||||||
i = 0;
|
|
||||||
while (i < str.length) {
|
|
||||||
if (levels.length && starts(str, '\\', i)) {
|
|
||||||
i += 1;
|
|
||||||
} else {
|
|
||||||
_a = delimited;
|
|
||||||
for (_b = 0, _c = _a.length; _b < _c; _b++) {
|
|
||||||
pair = _a[_b];
|
|
||||||
_d = pair;
|
|
||||||
open = _d[0];
|
|
||||||
close = _d[1];
|
|
||||||
if (levels.length && starts(str, close, i) && levels[levels.length - 1] === pair) {
|
|
||||||
levels.pop();
|
|
||||||
i += close.length - 1;
|
|
||||||
if (!(levels.length)) {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else if (starts(str, open, i)) {
|
|
||||||
levels.push(pair);
|
|
||||||
i += open.length - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!levels.length || slash && starts(str, '\n', i)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
if (levels.length) {
|
|
||||||
if (slash) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw new Error("SyntaxError: Unterminated " + (levels.pop()[0]) + " starting on line " + (this.line + 1));
|
|
||||||
}
|
|
||||||
return !i ? false : str.substring(0, i);
|
|
||||||
};
|
|
||||||
// Expand variables and expressions inside double-quoted strings using
|
// Expand variables and expressions inside double-quoted strings using
|
||||||
// [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation)
|
// [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.
|
// for substitution of bare variables as well as arbitrary expressions.
|
||||||
|
@ -484,7 +438,7 @@
|
||||||
tokens.push(['IDENTIFIER', interp]);
|
tokens.push(['IDENTIFIER', interp]);
|
||||||
i += group.length - 1;
|
i += group.length - 1;
|
||||||
pi = i + 1;
|
pi = i + 1;
|
||||||
} else if (((expr = this.balanced_string(str.substring(i), [['${', '}']])))) {
|
} else if (((expr = balanced_string(str.substring(i), [['${', '}']])))) {
|
||||||
if (pi < i) {
|
if (pi < i) {
|
||||||
tokens.push(['STRING', '' + quote + (str.substring(pi, i)) + quote]);
|
tokens.push(['STRING', '' + quote + (str.substring(pi, i)) + quote]);
|
||||||
}
|
}
|
||||||
|
@ -632,37 +586,4 @@
|
||||||
// occurs at the start of a line. We disambiguate these from trailing whens to
|
// occurs at the start of a line. We disambiguate these from trailing whens to
|
||||||
// avoid an ambiguity in the grammar.
|
// avoid an ambiguity in the grammar.
|
||||||
BEFORE_WHEN = ['INDENT', 'OUTDENT', 'TERMINATOR'];
|
BEFORE_WHEN = ['INDENT', 'OUTDENT', 'TERMINATOR'];
|
||||||
// Utility Functions
|
|
||||||
// -----------------
|
|
||||||
// Does a list include a value?
|
|
||||||
include = function include(list, value) {
|
|
||||||
return list.indexOf(value) >= 0;
|
|
||||||
};
|
|
||||||
// Peek at the beginning of a given string to see if it matches a sequence.
|
|
||||||
starts = function starts(string, literal, start) {
|
|
||||||
return string.substring(start, (start || 0) + literal.length) === literal;
|
|
||||||
};
|
|
||||||
// Trim out all falsy values from an array.
|
|
||||||
compact = function compact(array) {
|
|
||||||
var _a, _b, _c, _d, item;
|
|
||||||
_a = []; _b = array;
|
|
||||||
for (_c = 0, _d = _b.length; _c < _d; _c++) {
|
|
||||||
item = _b[_c];
|
|
||||||
if (item) {
|
|
||||||
_a.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _a;
|
|
||||||
};
|
|
||||||
// Count the number of occurences of a character in a string.
|
|
||||||
count = function count(string, letter) {
|
|
||||||
var num, pos;
|
|
||||||
num = 0;
|
|
||||||
pos = string.indexOf(letter);
|
|
||||||
while (pos !== -1) {
|
|
||||||
num += 1;
|
|
||||||
pos = string.indexOf(letter, pos + 1);
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
63
lib/nodes.js
63
lib/nodes.js
|
@ -1,19 +1,24 @@
|
||||||
(function(){
|
(function(){
|
||||||
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, literal, merge, statement;
|
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, literal, statement;
|
||||||
var __extends = function(child, parent) {
|
var __extends = function(child, parent) {
|
||||||
var ctor = function(){ };
|
var ctor = function(){ };
|
||||||
ctor.prototype = parent.prototype;
|
ctor.prototype = parent.prototype;
|
||||||
child.__superClass__ = parent.prototype;
|
child.__superClass__ = parent.prototype;
|
||||||
child.prototype = new ctor();
|
child.prototype = new ctor();
|
||||||
child.prototype.constructor = child;
|
child.prototype.constructor = child;
|
||||||
}, __hasProp = Object.prototype.hasOwnProperty;
|
};
|
||||||
// `nodes.coffee` contains all of the node classes for the syntax tree. Most
|
// `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),
|
// 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
|
// 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.
|
// the syntax tree into a string of JavaScript code, call `compile()` on the root.
|
||||||
// Set up for both **Node.js** and the browser, by
|
// Set up for both **Node.js** and the browser, by
|
||||||
// including the [Scope](scope.html) class.
|
// including the [Scope](scope.html) class.
|
||||||
(typeof process !== "undefined" && process !== null) ? process.mixin(require('scope')) : (this.exports = this);
|
if ((typeof process !== "undefined" && process !== null)) {
|
||||||
|
process.mixin(require('scope'));
|
||||||
|
process.mixin(require('helpers'));
|
||||||
|
} else {
|
||||||
|
this.exports = this;
|
||||||
|
}
|
||||||
// Helper function that marks a node as a JavaScript *statement*, or as a
|
// Helper function that marks a node as a JavaScript *statement*, or as a
|
||||||
// *pure_statement*. Statements must be wrapped in a closure when used as an
|
// *pure_statement*. Statements must be wrapped in a closure when used as an
|
||||||
// expression, and nodes tagged as *pure_statement* cannot be closure-wrapped
|
// expression, and nodes tagged as *pure_statement* cannot be closure-wrapped
|
||||||
|
@ -1562,58 +1567,6 @@ idt += TAB
|
||||||
IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/;
|
IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/;
|
||||||
// Utility Functions
|
// Utility Functions
|
||||||
// -----------------
|
// -----------------
|
||||||
// Merge objects, returning a fresh copy with attributes from both sides.
|
|
||||||
// Used every time `compile` is called, to allow properties in the options hash
|
|
||||||
// to propagate down the tree without polluting other branches.
|
|
||||||
merge = function merge(options, overrides) {
|
|
||||||
var _a, _b, fresh, key, val;
|
|
||||||
fresh = {};
|
|
||||||
_a = options;
|
|
||||||
for (key in _a) { if (__hasProp.call(_a, key)) {
|
|
||||||
val = _a[key];
|
|
||||||
((fresh[key] = val));
|
|
||||||
}}
|
|
||||||
if (overrides) {
|
|
||||||
_b = overrides;
|
|
||||||
for (key in _b) { if (__hasProp.call(_b, key)) {
|
|
||||||
val = _b[key];
|
|
||||||
((fresh[key] = val));
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
return fresh;
|
|
||||||
};
|
|
||||||
// Trim out all falsy values from an array.
|
|
||||||
compact = function compact(array) {
|
|
||||||
var _a, _b, _c, _d, item;
|
|
||||||
_a = []; _b = array;
|
|
||||||
for (_c = 0, _d = _b.length; _c < _d; _c++) {
|
|
||||||
item = _b[_c];
|
|
||||||
if (item) {
|
|
||||||
_a.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _a;
|
|
||||||
};
|
|
||||||
// Return a completely flattened version of an array. Handy for getting a
|
|
||||||
// list of `children`.
|
|
||||||
flatten = function flatten(array) {
|
|
||||||
var _a, _b, _c, item, memo;
|
|
||||||
memo = [];
|
|
||||||
_a = array;
|
|
||||||
for (_b = 0, _c = _a.length; _b < _c; _b++) {
|
|
||||||
item = _a[_b];
|
|
||||||
item instanceof Array ? (memo = memo.concat(item)) : memo.push(item);
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
del = function del(obj, key) {
|
|
||||||
var val;
|
|
||||||
val = obj[key];
|
|
||||||
delete obj[key];
|
|
||||||
return val;
|
|
||||||
};
|
|
||||||
// Handy helper for a generating LiteralNode.
|
// Handy helper for a generating LiteralNode.
|
||||||
literal = function literal(name) {
|
literal = function literal(name) {
|
||||||
return new LiteralNode(name);
|
return new LiteralNode(name);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
(function(){
|
(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, 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, pair;
|
||||||
var __hasProp = Object.prototype.hasOwnProperty;
|
var __hasProp = Object.prototype.hasOwnProperty;
|
||||||
// The CoffeeScript language has a decent amount of optional syntax,
|
// The CoffeeScript language has a decent amount of optional syntax,
|
||||||
// implicit syntax, and shorthand syntax. These things can greatly complicate a
|
// implicit syntax, and shorthand syntax. These things can greatly complicate a
|
||||||
|
@ -9,9 +9,7 @@
|
||||||
// add implicit indentation and parentheses, balance incorrect nestings, and
|
// add implicit indentation and parentheses, balance incorrect nestings, and
|
||||||
// generally clean things up.
|
// generally clean things up.
|
||||||
// Set up exported variables for both Node.js and the browser.
|
// Set up exported variables for both Node.js and the browser.
|
||||||
if (!((typeof process !== "undefined" && process !== null))) {
|
(typeof process !== "undefined" && process !== null) ? process.mixin(require('./helpers')) : (this.exports = this);
|
||||||
this.exports = this;
|
|
||||||
}
|
|
||||||
// The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
// The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
||||||
// its internal array of tokens.
|
// its internal array of tokens.
|
||||||
exports.Rewriter = (function() {
|
exports.Rewriter = (function() {
|
||||||
|
@ -374,10 +372,4 @@
|
||||||
// The grammar can't disambiguate them, so we insert the implicit indentation.
|
// The grammar can't disambiguate them, so we insert the implicit indentation.
|
||||||
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
||||||
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
|
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
|
||||||
// Utility Functions
|
|
||||||
// -----------------
|
|
||||||
// Does a list include a value?
|
|
||||||
include = function include(list, value) {
|
|
||||||
return list.indexOf(value) >= 0;
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
77
src/helpers.coffee
Normal file
77
src/helpers.coffee
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# Set up exported variables for both Node.js and the browser.
|
||||||
|
this.exports: this unless process?
|
||||||
|
|
||||||
|
# Does a list include a value?
|
||||||
|
exports.include: (list, value) ->
|
||||||
|
list.indexOf(value) >= 0
|
||||||
|
|
||||||
|
# Peek at the beginning of a given string to see if it matches a sequence.
|
||||||
|
exports.starts: (string, literal, start) ->
|
||||||
|
string.substring(start, (start or 0) + literal.length) is literal
|
||||||
|
|
||||||
|
# Trim out all falsy values from an array.
|
||||||
|
exports.compact: (array) -> item for item in array when item
|
||||||
|
|
||||||
|
# Count the number of occurences of a character in a string.
|
||||||
|
exports.count: (string, letter) ->
|
||||||
|
num: 0
|
||||||
|
pos: string.indexOf(letter)
|
||||||
|
while pos isnt -1
|
||||||
|
num += 1
|
||||||
|
pos: string.indexOf(letter, pos + 1)
|
||||||
|
num
|
||||||
|
|
||||||
|
# Merge objects, returning a fresh copy with attributes from both sides.
|
||||||
|
# Used every time `compile` is called, to allow properties in the options hash
|
||||||
|
# to propagate down the tree without polluting other branches.
|
||||||
|
exports.merge: (options, overrides) ->
|
||||||
|
fresh: {}
|
||||||
|
(fresh[key]: val) for key, val of options
|
||||||
|
(fresh[key]: val) for key, val of overrides if overrides
|
||||||
|
fresh
|
||||||
|
|
||||||
|
# Return a completely flattened version of an array. Handy for getting a
|
||||||
|
# list of `children`.
|
||||||
|
exports.flatten: (array) ->
|
||||||
|
memo: []
|
||||||
|
for item in array
|
||||||
|
if item instanceof Array then memo: memo.concat(item) else memo.push(item)
|
||||||
|
memo
|
||||||
|
|
||||||
|
# Delete a key from an object, returning the value. Useful when a node is
|
||||||
|
# looking for a particular method in an options hash.
|
||||||
|
exports.del: (obj, key) ->
|
||||||
|
val: obj[key]
|
||||||
|
delete obj[key]
|
||||||
|
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 etc...
|
||||||
|
exports.balanced_string: (str, delimited, options) ->
|
||||||
|
options ||= {}
|
||||||
|
slash: delimited[0][0] is '/'
|
||||||
|
levels: []
|
||||||
|
i: 0
|
||||||
|
while i < str.length
|
||||||
|
if levels.length and exports.starts str, '\\', i
|
||||||
|
i += 1
|
||||||
|
else
|
||||||
|
for pair in delimited
|
||||||
|
[open, close]: pair
|
||||||
|
if levels.length and exports.starts(str, close, i) and levels[levels.length - 1] is pair
|
||||||
|
levels.pop()
|
||||||
|
i += close.length - 1
|
||||||
|
i += 1 unless levels.length
|
||||||
|
break
|
||||||
|
else if exports.starts str, open, i
|
||||||
|
levels.push(pair)
|
||||||
|
i += open.length - 1
|
||||||
|
break
|
||||||
|
break if not levels.length or slash and exports.starts str, '\n', i
|
||||||
|
i += 1
|
||||||
|
if levels.length
|
||||||
|
return false if slash
|
||||||
|
throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}"
|
||||||
|
if not i then false else str.substring(0, i)
|
|
@ -10,9 +10,9 @@
|
||||||
# Set up the Lexer for both Node.js and the browser, depending on where we are.
|
# Set up the Lexer for both Node.js and the browser, depending on where we are.
|
||||||
if process?
|
if process?
|
||||||
Rewriter: require('./rewriter').Rewriter
|
Rewriter: require('./rewriter').Rewriter
|
||||||
|
process.mixin require './helpers'
|
||||||
else
|
else
|
||||||
this.exports: this
|
this.exports: this
|
||||||
Rewriter: this.Rewriter
|
|
||||||
|
|
||||||
# The Lexer Class
|
# The Lexer Class
|
||||||
# ---------------
|
# ---------------
|
||||||
|
@ -146,7 +146,7 @@ exports.Lexer: class Lexer
|
||||||
# Matches a token in which which the passed delimiter pairs must be correctly
|
# Matches a token in which which the passed delimiter pairs must be correctly
|
||||||
# balanced (ie. strings, JS literals).
|
# balanced (ie. strings, JS literals).
|
||||||
balanced_token: (delimited...) ->
|
balanced_token: (delimited...) ->
|
||||||
@balanced_string @chunk, delimited
|
balanced_string @chunk, delimited
|
||||||
|
|
||||||
# Matches and conumes comments. We pass through comments into JavaScript,
|
# Matches and conumes comments. We pass through comments into JavaScript,
|
||||||
# so they're treated as real tokens, like any other part of the language.
|
# so they're treated as real tokens, like any other part of the language.
|
||||||
|
@ -304,36 +304,6 @@ exports.Lexer: class Lexer
|
||||||
assignment_error: ->
|
assignment_error: ->
|
||||||
throw new Error "SyntaxError: Reserved word \"${@value()}\" on line ${@line + 1} can't be assigned"
|
throw new Error "SyntaxError: Reserved word \"${@value()}\" on line ${@line + 1} can't be assigned"
|
||||||
|
|
||||||
# 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 etc...
|
|
||||||
balanced_string: (str, delimited) ->
|
|
||||||
slash: delimited[0][0] is '/'
|
|
||||||
levels: []
|
|
||||||
i: 0
|
|
||||||
while i < str.length
|
|
||||||
if levels.length and starts str, '\\', i
|
|
||||||
i += 1
|
|
||||||
else
|
|
||||||
for pair in delimited
|
|
||||||
[open, close]: pair
|
|
||||||
if levels.length and starts(str, close, i) and levels[levels.length - 1] is pair
|
|
||||||
levels.pop()
|
|
||||||
i += close.length - 1
|
|
||||||
i += 1 unless levels.length
|
|
||||||
break
|
|
||||||
else if starts str, open, i
|
|
||||||
levels.push(pair)
|
|
||||||
i += open.length - 1
|
|
||||||
break
|
|
||||||
break if not levels.length or slash and starts str, '\n', i
|
|
||||||
i += 1
|
|
||||||
if levels.length
|
|
||||||
return false if slash
|
|
||||||
throw new Error "SyntaxError: Unterminated ${levels.pop()[0]} starting on line ${@line + 1}"
|
|
||||||
if not i then false else str.substring(0, i)
|
|
||||||
|
|
||||||
# Expand variables and expressions inside double-quoted strings using
|
# Expand variables and expressions inside double-quoted strings using
|
||||||
# [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation)
|
# [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.
|
# for substitution of bare variables as well as arbitrary expressions.
|
||||||
|
@ -362,7 +332,7 @@ exports.Lexer: class Lexer
|
||||||
tokens.push ['IDENTIFIER', interp]
|
tokens.push ['IDENTIFIER', interp]
|
||||||
i += group.length - 1
|
i += group.length - 1
|
||||||
pi: i + 1
|
pi: i + 1
|
||||||
else if (expr: @balanced_string str.substring(i), [['${', '}']])
|
else if (expr: balanced_string str.substring(i), [['${', '}']])
|
||||||
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
|
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i
|
||||||
inner: expr.substring(2, expr.length - 1)
|
inner: expr.substring(2, expr.length - 1)
|
||||||
if inner.length
|
if inner.length
|
||||||
|
@ -509,26 +479,3 @@ ACCESSORS: ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@']
|
||||||
# occurs at the start of a line. We disambiguate these from trailing whens to
|
# occurs at the start of a line. We disambiguate these from trailing whens to
|
||||||
# avoid an ambiguity in the grammar.
|
# avoid an ambiguity in the grammar.
|
||||||
BEFORE_WHEN: ['INDENT', 'OUTDENT', 'TERMINATOR']
|
BEFORE_WHEN: ['INDENT', 'OUTDENT', 'TERMINATOR']
|
||||||
|
|
||||||
# Utility Functions
|
|
||||||
# -----------------
|
|
||||||
|
|
||||||
# Does a list include a value?
|
|
||||||
include: (list, value) ->
|
|
||||||
list.indexOf(value) >= 0
|
|
||||||
|
|
||||||
# Peek at the beginning of a given string to see if it matches a sequence.
|
|
||||||
starts: (string, literal, start) ->
|
|
||||||
string.substring(start, (start or 0) + literal.length) is literal
|
|
||||||
|
|
||||||
# Trim out all falsy values from an array.
|
|
||||||
compact: (array) -> item for item in array when item
|
|
||||||
|
|
||||||
# Count the number of occurences of a character in a string.
|
|
||||||
count: (string, letter) ->
|
|
||||||
num: 0
|
|
||||||
pos: string.indexOf(letter)
|
|
||||||
while pos isnt -1
|
|
||||||
num += 1
|
|
||||||
pos: string.indexOf(letter, pos + 1)
|
|
||||||
num
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
# including the [Scope](scope.html) class.
|
# including the [Scope](scope.html) class.
|
||||||
if process?
|
if process?
|
||||||
process.mixin require 'scope'
|
process.mixin require 'scope'
|
||||||
|
process.mixin require 'helpers'
|
||||||
else
|
else
|
||||||
this.exports: this
|
this.exports: this
|
||||||
|
|
||||||
|
@ -1197,33 +1198,6 @@ IDENTIFIER: /^[a-zA-Z\$_](\w|\$)*$/
|
||||||
# Utility Functions
|
# Utility Functions
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|
||||||
# Merge objects, returning a fresh copy with attributes from both sides.
|
|
||||||
# Used every time `compile` is called, to allow properties in the options hash
|
|
||||||
# to propagate down the tree without polluting other branches.
|
|
||||||
merge: (options, overrides) ->
|
|
||||||
fresh: {}
|
|
||||||
(fresh[key]: val) for key, val of options
|
|
||||||
(fresh[key]: val) for key, val of overrides if overrides
|
|
||||||
fresh
|
|
||||||
|
|
||||||
# Trim out all falsy values from an array.
|
|
||||||
compact: (array) -> item for item in array when item
|
|
||||||
|
|
||||||
# Return a completely flattened version of an array. Handy for getting a
|
|
||||||
# list of `children`.
|
|
||||||
flatten: (array) ->
|
|
||||||
memo: []
|
|
||||||
for item in array
|
|
||||||
if item instanceof Array then memo: memo.concat(item) else memo.push(item)
|
|
||||||
memo
|
|
||||||
|
|
||||||
# Delete a key from an object, returning the value. Useful when a node is
|
|
||||||
# looking for a particular method in an options hash.
|
|
||||||
del: (obj, key) ->
|
|
||||||
val: obj[key]
|
|
||||||
delete obj[key]
|
|
||||||
val
|
|
||||||
|
|
||||||
# Handy helper for a generating LiteralNode.
|
# Handy helper for a generating LiteralNode.
|
||||||
literal: (name) ->
|
literal: (name) ->
|
||||||
new LiteralNode(name)
|
new LiteralNode(name)
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
# generally clean things up.
|
# generally clean things up.
|
||||||
|
|
||||||
# Set up exported variables for both Node.js and the browser.
|
# Set up exported variables for both Node.js and the browser.
|
||||||
this.exports: this unless process?
|
if process?
|
||||||
|
process.mixin require './helpers'
|
||||||
|
else
|
||||||
|
this.exports: this
|
||||||
|
|
||||||
# The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
# The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
||||||
# its internal array of tokens.
|
# its internal array of tokens.
|
||||||
|
@ -253,10 +256,3 @@ IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'INDENT', 'OUTDEN
|
||||||
# The grammar can't disambiguate them, so we insert the implicit indentation.
|
# The grammar can't disambiguate them, so we insert the implicit indentation.
|
||||||
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
|
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
|
||||||
SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
|
SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
|
||||||
|
|
||||||
# Utility Functions
|
|
||||||
# -----------------
|
|
||||||
|
|
||||||
# Does a list include a value?
|
|
||||||
include: (list, value) ->
|
|
||||||
list.indexOf(value) >= 0
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue