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"
|
||||
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 = YUI::JavaScriptCompressor.new.compress(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(){
|
||||
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
|
||||
// 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
|
||||
|
@ -9,9 +9,9 @@
|
|||
// Set up the Lexer for both Node.js and the browser, depending on where we are.
|
||||
if ((typeof process !== "undefined" && process !== null)) {
|
||||
Rewriter = require('./rewriter').Rewriter;
|
||||
process.mixin(require('./helpers'));
|
||||
} else {
|
||||
this.exports = this;
|
||||
Rewriter = this.Rewriter;
|
||||
}
|
||||
// The Lexer Class
|
||||
// ---------------
|
||||
|
@ -203,7 +203,7 @@
|
|||
Lexer.prototype.balanced_token = function balanced_token() {
|
||||
var delimited;
|
||||
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,
|
||||
// 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() {
|
||||
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
|
||||
// [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.
|
||||
|
@ -484,7 +438,7 @@
|
|||
tokens.push(['IDENTIFIER', interp]);
|
||||
i += group.length - 1;
|
||||
pi = i + 1;
|
||||
} else if (((expr = this.balanced_string(str.substring(i), [['${', '}']])))) {
|
||||
} else if (((expr = balanced_string(str.substring(i), [['${', '}']])))) {
|
||||
if (pi < i) {
|
||||
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
|
||||
// avoid an ambiguity in the grammar.
|
||||
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(){
|
||||
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 ctor = function(){ };
|
||||
ctor.prototype = parent.prototype;
|
||||
child.__superClass__ = parent.prototype;
|
||||
child.prototype = new ctor();
|
||||
child.prototype.constructor = child;
|
||||
}, __hasProp = Object.prototype.hasOwnProperty;
|
||||
};
|
||||
// `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.
|
||||
(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
|
||||
// *pure_statement*. Statements must be wrapped in a closure when used as an
|
||||
// expression, and nodes tagged as *pure_statement* cannot be closure-wrapped
|
||||
|
@ -1562,58 +1567,6 @@ idt += TAB
|
|||
IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/;
|
||||
// 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.
|
||||
literal = function literal(name) {
|
||||
return new LiteralNode(name);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(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;
|
||||
// The CoffeeScript language has a decent amount of optional syntax,
|
||||
// implicit syntax, and shorthand syntax. These things can greatly complicate a
|
||||
|
@ -9,9 +9,7 @@
|
|||
// 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))) {
|
||||
this.exports = this;
|
||||
}
|
||||
(typeof process !== "undefined" && process !== null) ? process.mixin(require('./helpers')) : (this.exports = this);
|
||||
// The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
||||
// its internal array of tokens.
|
||||
exports.Rewriter = (function() {
|
||||
|
@ -374,10 +372,4 @@
|
|||
// 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'];
|
||||
// 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.
|
||||
if process?
|
||||
Rewriter: require('./rewriter').Rewriter
|
||||
process.mixin require './helpers'
|
||||
else
|
||||
this.exports: this
|
||||
Rewriter: this.Rewriter
|
||||
|
||||
# The Lexer Class
|
||||
# ---------------
|
||||
|
@ -146,7 +146,7 @@ exports.Lexer: class Lexer
|
|||
# Matches a token in which which the passed delimiter pairs must be correctly
|
||||
# balanced (ie. strings, JS literals).
|
||||
balanced_token: (delimited...) ->
|
||||
@balanced_string @chunk, delimited
|
||||
balanced_string @chunk, delimited
|
||||
|
||||
# Matches and conumes comments. We pass through comments into JavaScript,
|
||||
# so they're treated as real tokens, like any other part of the language.
|
||||
|
@ -304,36 +304,6 @@ exports.Lexer: class Lexer
|
|||
assignment_error: ->
|
||||
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
|
||||
# [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.
|
||||
|
@ -362,7 +332,7 @@ exports.Lexer: class Lexer
|
|||
tokens.push ['IDENTIFIER', interp]
|
||||
i += group.length - 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
|
||||
inner: expr.substring(2, expr.length - 1)
|
||||
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
|
||||
# avoid an ambiguity in the grammar.
|
||||
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.
|
||||
if process?
|
||||
process.mixin require 'scope'
|
||||
process.mixin require 'helpers'
|
||||
else
|
||||
this.exports: this
|
||||
|
||||
|
@ -1197,33 +1198,6 @@ IDENTIFIER: /^[a-zA-Z\$_](\w|\$)*$/
|
|||
# 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.
|
||||
literal: (name) ->
|
||||
new LiteralNode(name)
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
# generally clean things up.
|
||||
|
||||
# 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
|
||||
# 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.
|
||||
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
|
||||
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