Compare commits

..

19 Commits
0.5.3 ... 0.5.4

Author SHA1 Message Date
Jeremy Ashkenas
44398d044f Updating docs for CoffeeScript 0.5.4. Tag it and bag it. 2010-03-03 23:01:53 -05:00
Jeremy Ashkenas
3feb874b1e Merge branch 'path_fix' of git://github.com/cehoffman/coffee-script 2010-03-03 20:17:58 -05:00
Chris Hoffman
f7183e6918 Use new node api for resolving a chain of symlinks 2010-03-03 16:34:17 -06:00
Samuel Reis
707cd2d734 updated command_line.js 2010-03-03 14:32:28 +01:00
Samuel Reis
c11c3ed2f2 fix: process.watchFile has moved to fs.watchFile 2010-03-03 14:31:30 +01:00
Jeremy Ashkenas
5fd0972b5d improvement to comment handling that should ensure that they have no effect on indentation 2010-03-02 19:23:21 -05:00
Jeremy Ashkenas
70cb195e6f rebuilding extras/coffee-script.js 2010-03-02 16:52:55 -05:00
Jeremy Ashkenas
c219adffd5 removing special rule from rewriter for naked functions in arrays 2010-03-02 00:43:01 -05:00
Jeremy Ashkenas
cd6dd5abfd couple more tweaks to lexer.coffee 2010-02-28 21:39:07 -05:00
Jeremy Ashkenas
29ece0e6ba better commenting the coffeescript lexer as the first trial for docco 2010-02-28 20:44:33 -05:00
Jeremy Ashkenas
45bad556ab defining __filename and __dirname correctly as local variables for eval'd scripts 2010-02-28 19:56:00 -05:00
Jeremy Ashkenas
30cf63ec92 updating Underscore.coffee to Underscore.js version 0.6.0 2010-02-28 15:12:18 -05:00
Jeremy Ashkenas
969c2e528d a number of refactors to the Lexer. It should be a good bit clearer to read now. 2010-02-28 13:34:52 -05:00
Jeremy Ashkenas
bb2bf7ce57 allowing chaining of property accesses by indentation level (really nice for Node and jQuery work) ticket #221 2010-02-28 12:49:37 -05:00
Jeremy Ashkenas
2969e156c7 fixing require paths in the Cakefile so that build:jison will work, even if you don't have it installed. 2010-02-28 10:37:12 -05:00
Jeremy Ashkenas
b08995cbcc fixing heredocs with multiple double quotes (broken regex from the Ruby translation), with tests. 2010-02-28 10:29:30 -05:00
Jeremy Ashkenas
47f71f9193 updating docs to say 'Node.js greater than 0.1.30' 2010-02-28 10:11:01 -05:00
Jeremy Ashkenas
fec2eaef7e removing the (now-unused) inherits helper, and adding a helper for creating LiteralNodes 2010-02-28 00:22:06 -05:00
Jeremy Ashkenas
56eb474bf3 If you don't specify a constructor, one will be provided for you by the state. 2010-02-28 00:13:17 -05:00
26 changed files with 1210 additions and 946 deletions

View File

@@ -29,6 +29,7 @@ task 'build', 'build the CoffeeScript language from source', ->
task 'build:parser', 'rebuild the Jison parser (run build first)', ->
require.paths.unshift 'vendor/jison/lib'
parser: require('grammar').parser
js: parser.generate()
parser_path: 'lib/parser.js'
@@ -45,7 +46,8 @@ task 'build:underscore', 'rebuild the Underscore.coffee documentation page', ->
task 'build:browser', 'rebuild the merged script for inclusion in the browser', ->
exec 'rake browser'
exec 'rake browser', (err) ->
throw err if err
task 'doc', 'watch and continually rebuild the documentation', ->

View File

@@ -3,13 +3,7 @@
process.mixin(require('sys'));
var path = require('path');
var fs = require('fs');
var lib = null;
if (fs.lstatSync(__filename).isSymbolicLink()) {
lib = path.join(path.dirname(fs.readlinkSync(__filename)), '../lib');
} else {
lib = path.join(__dirname, '../lib');
}
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require.paths.unshift(lib);
require('cake').run();

View File

@@ -3,13 +3,7 @@
process.mixin(require('sys'));
var path = require('path');
var fs = require('fs');
var lib = null;
if (fs.lstatSync(__filename).isSymbolicLink()) {
lib = path.join(path.dirname(fs.readlinkSync(__filename)), '../lib');
} else {
lib = path.join(__dirname, '../lib');
}
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require.paths.unshift(lib);
require('command_line').run();

View File

@@ -111,7 +111,7 @@ alert reverse '!tpircseeffoC'</textarea></div>
<p>
<b>Latest Version:</b>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>
</p>
<h2>
@@ -153,10 +153,12 @@ alert reverse '!tpircseeffoC'</textarea></div>
<p>
To install, first make sure you have a working version of
<a href="http://nodejs.org/">Node.js</a>, 0.1.30 or higher. Then clone the CoffeeScript
<a href="http://nodejs.org/">Node.js</a> greater than version 0.1.30 (Node
moves quickly, using the latest master is your best bet).
Then clone the CoffeeScript
<a href="http://github.com/jashkenas/coffee-script">source repository</a>
from GitHub, or download the latest
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>.
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>.
To install the CoffeeScript compiler system-wide
under <tt>/usr/local</tt>, open the directory and run:
</p>
@@ -795,6 +797,13 @@ coffee --print app/scripts/*.coffee > concatenation.js</pre>
Change Log
</h2>
<p>
<b class="header" style="margin-top: 20px;">0.5.4</b>
Bugfix that corrects the Node.js global constants <tt>__filename</tt> and
<tt>__dirname</tt>. Tweaks for more flexible parsing of nested function
literals and improperly-indented comments. Updates for the latest Node.js API.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.3</b>
CoffeeScript now has a syntax for defining classes. Many of the core

File diff suppressed because it is too large Load Diff

View File

@@ -18,36 +18,55 @@
previousUnderscore: root._
# If Underscore is called as a function, it returns a wrapped object that
# can be used OO-style. This wrapper holds altered versions of all the
# underscore functions. Wrapped objects may be chained.
wrapper: (obj) ->
this._wrapped: obj
this
# Establish the object that gets thrown to break out of a loop iteration.
breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
# Create a safe reference to the Underscore object forreference below.
_: root._: (obj) -> new wrapper(obj)
# Quick regexp-escaping function, because JS doesn't have RegExp.escape().
escapeRegExp: (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1')
# Save bytes in the minified (but not gzipped) version:
ArrayProto: Array.prototype
ObjProto: Object.prototype
#Create quick reference variables for speed access to core prototypes.
slice: ArrayProto.slice
unshift: ArrayProto.unshift
toString: ObjProto.toString
hasOwnProperty: ObjProto.hasOwnProperty
propertyIsEnumerable: ObjProto.propertyIsEnumerable
# All ECMA5 native implementations we hope to use are declared here.
nativeForEach: ArrayProto.forEach
nativeMap: ArrayProto.map
nativeReduce: ArrayProto.reduce
nativeReduceRight: ArrayProto.reduceRight
nativeFilter: ArrayProto.filter
nativeEvery: ArrayProto.every
nativeSome: ArrayProto.some
nativeIndexOf: ArrayProto.indexOf
nativeLastIndexOf: ArrayProto.lastIndexOf
nativeIsArray: Array.isArray
nativeKeys: Object.keys
# Create a safe reference to the Underscore object for use below.
_: (obj) -> new wrapper(obj)
# Export the Underscore object for CommonJS.
if typeof(exports) != 'undefined' then exports._: _
# Create quick reference variables for speed access to core prototypes.
slice: Array::slice
unshift: Array::unshift
toString: Object::toString
hasOwnProperty: Object::hasOwnProperty
propertyIsEnumerable: Object::propertyIsEnumerable
# Export Underscore to global scope.
root._: _
# Current version.
_.VERSION: '0.5.8'
_.VERSION: '0.6.0'
# ------------------------ Collection Functions: ---------------------------
@@ -55,12 +74,13 @@
# The cornerstone, an each implementation.
# Handles objects implementing forEach, arrays, and raw objects.
_.each: (obj, iterator, context) ->
index: 0
try
return obj.forEach(iterator, context) if obj.forEach
if _.isNumber(obj.length)
return iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
iterator.call(context, val, key, obj) for key, val of obj
if nativeForEach and obj.forEach is nativeForEach
obj.forEach iterator, context
else if _.isNumber obj.length
iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
else
iterator.call(context, val, key, obj) for key, val of obj
catch e
throw e if e isnt breaker
obj
@@ -69,28 +89,28 @@
# Return the results of applying the iterator to each element. Use JavaScript
# 1.6's version of map, if possible.
_.map: (obj, iterator, context) ->
return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
return obj.map(iterator, context) if nativeMap and obj.map is nativeMap
results: []
_.each obj, (value, index, list) ->
results.push(iterator.call(context, value, index, list))
results.push iterator.call context, value, index, list
results
# Reduce builds up a single result from a list of values. Also known as
# inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
_.reduce: (obj, memo, iterator, context) ->
return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce))
return obj.reduce(_.bind(iterator, context), memo) if nativeReduce and obj.reduce is nativeReduce
_.each obj, (value, index, list) ->
memo: iterator.call(context, memo, value, index, list)
memo: iterator.call context, memo, value, index, list
memo
# The right-associative version of reduce, also known as foldr. Uses
# JavaScript 1.8's version of reduceRight, if available.
_.reduceRight: (obj, memo, iterator, context) ->
return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight))
return obj.reduceRight(_.bind(iterator, context), memo) if nativeReduceRight and obj.reduceRight is nativeReduceRight
_.each _.clone(_.toArray(obj)).reverse(), (value, index) ->
memo: iterator.call(context, memo, value, index, obj)
memo: iterator.call context, memo, value, index, obj
memo
@@ -98,7 +118,7 @@
_.detect: (obj, iterator, context) ->
result: null
_.each obj, (value, index, list) ->
if iterator.call(context, value, index, list)
if iterator.call context, value, index, list
result: value
_.breakLoop()
result
@@ -106,11 +126,11 @@
# Return all the elements that pass a truth test. Use JavaScript 1.6's
# filter(), if it exists.
_.select: (obj, iterator, context) ->
if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context)
_.filter: (obj, iterator, context) ->
return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter
results: []
_.each obj, (value, index, list) ->
results.push(value) if iterator.call(context, value, index, list)
results.push value if iterator.call context, value, index, list
results
@@ -118,15 +138,15 @@
_.reject: (obj, iterator, context) ->
results: []
_.each obj, (value, index, list) ->
results.push(value) if not iterator.call(context, value, index, list)
results.push value if not iterator.call context, value, index, list
results
# Determine whether all of the elements match a truth test. Delegate to
# JavaScript 1.6's every(), if it is present.
_.all: (obj, iterator, context) ->
_.every: (obj, iterator, context) ->
iterator ||= _.identity
return obj.every(iterator, context) if obj and _.isFunction(obj.every)
return obj.every iterator, context if nativeEvery and obj.every is nativeEvery
result: true
_.each obj, (value, index, list) ->
_.breakLoop() unless (result: result and iterator.call(context, value, index, list))
@@ -135,9 +155,9 @@
# Determine if at least one element in the object matches a truth test. Use
# JavaScript 1.6's some(), if it exists.
_.any: (obj, iterator, context) ->
_.some: (obj, iterator, context) ->
iterator ||= _.identity
return obj.some(iterator, context) if obj and _.isFunction(obj.some)
return obj.some iterator, context if nativeSome and obj.some is nativeSome
result: false
_.each obj, (value, index, list) ->
_.breakLoop() if (result: iterator.call(context, value, index, list))
@@ -147,7 +167,7 @@
# Determine if a given value is included in the array or object,
# based on '==='.
_.include: (obj, target) ->
return _.indexOf(obj, target) isnt -1 if obj and _.isFunction(obj.indexOf)
return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf
for key, val of obj
return true if val is target
false
@@ -155,13 +175,13 @@
# Invoke a method with arguments on every item in a collection.
_.invoke: (obj, method) ->
args: _.rest(arguments, 2)
args: _.rest arguments, 2
(if method then val[method] else val).apply(val, args) for val in obj
# Convenience version of a common use case of map: fetching a property.
_.pluck: (obj, key) ->
_.map(obj, ((val) -> val[key]))
_.map(obj, (val) -> val[key])
# Return the maximum item or (item-based computation).
@@ -184,7 +204,7 @@
result.value
# Sort the object's values by a criteria produced by an iterator.
# Sort the object's values by a criterion produced by an iterator.
_.sortBy: (obj, iterator, context) ->
_.pluck(((_.map obj, (value, index, list) ->
{value: value, criteria: iterator.call(context, value, index, list)}
@@ -198,7 +218,8 @@
# be inserted so as to maintain order. Uses binary search.
_.sortedIndex: (array, obj, iterator) ->
iterator ||= _.identity
low: 0; high: array.length
low: 0
high: array.length
while low < high
mid: (low + high) >> 1
if iterator(array[mid]) < iterator(obj) then low: mid + 1 else high: mid
@@ -246,30 +267,30 @@
# Return a completely flattened version of an array.
_.flatten: (array) ->
_.reduce array, [], (memo, value) ->
return memo.concat(_.flatten(value)) if _.isArray(value)
memo.push(value)
return memo.concat(_.flatten(value)) if _.isArray value
memo.push value
memo
# Return a version of the array that does not contain the specified value(s).
_.without: (array) ->
values: _.rest(arguments)
val for val in _.toArray(array) when not _.include(values, val)
values: _.rest arguments
val for val in _.toArray(array) when not _.include values, val
# Produce a duplicate-free version of the array. If the array has already
# been sorted, you have the option of using a faster algorithm.
_.uniq: (array, isSorted) ->
memo: []
for el, i in _.toArray(array)
memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
for el, i in _.toArray array
memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
memo
# Produce an array that contains every item shared between all the
# passed-in arrays.
_.intersect: (array) ->
rest: _.rest(arguments)
rest: _.rest arguments
_.select _.uniq(array), (item) ->
_.all rest, (other) ->
_.indexOf(other, item) >= 0
@@ -278,10 +299,10 @@
# Zip together multiple lists into a single array -- elements that share
# an index go together.
_.zip: ->
length: _.max(_.pluck(arguments, 'length'))
results: new Array(length)
length: _.max _.pluck arguments, 'length'
results: new Array length
for i in [0...length]
results[i]: _.pluck(arguments, String(i))
results[i]: _.pluck arguments, String i
results
@@ -289,7 +310,7 @@
# we need this function. Return the position of the first occurence of an
# item in an array, or -1 if the item is not included in the array.
_.indexOf: (array, item) ->
return array.indexOf(item) if array.indexOf
return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf
i: 0; l: array.length
while l - i
if array[i] is item then return i else i++
@@ -299,7 +320,7 @@
# Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
# if possible.
_.lastIndexOf: (array, item) ->
return array.lastIndexOf(item) if array.lastIndexOf
return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf
i: array.length
while i
if array[i] is item then return i else i--
@@ -317,7 +338,7 @@
step: a[2] or 1
len: Math.ceil((stop - start) / step)
return [] if len <= 0
range: new Array(len)
range: new Array len
idx: 0
while true
return range if (if step > 0 then i - stop else stop - i) >= 0
@@ -331,36 +352,36 @@
# Create a function bound to a given object (assigning 'this', and arguments,
# optionally). Binding with arguments is also known as 'curry'.
_.bind: (func, obj) ->
args: _.rest(arguments, 2)
-> func.apply(obj or root, args.concat(arguments))
args: _.rest arguments, 2
-> func.apply obj or root, args.concat arguments
# Bind all of an object's methods to that object. Useful for ensuring that
# all callbacks defined on an object belong to it.
_.bindAll: (obj) ->
funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
_.each(funcs, (f) -> obj[f]: _.bind(obj[f], obj))
_.each funcs, (f) -> obj[f]: _.bind obj[f], obj
obj
# Delays a function for the given number of milliseconds, and then calls
# it with the arguments supplied.
_.delay: (func, wait) ->
args: _.rest(arguments, 2)
args: _.rest arguments, 2
setTimeout((-> func.apply(func, args)), wait)
# Defers a function, scheduling it to run after the current call stack has
# cleared.
_.defer: (func) ->
_.delay.apply(_, [func, 1].concat(_.rest(arguments)))
_.delay.apply _, [func, 1].concat _.rest arguments
# Returns the first function passed as an argument to the second,
# allowing you to adjust arguments, run code before and after, and
# conditionally execute the original function.
_.wrap: (func, wrapper) ->
-> wrapper.apply(wrapper, [func].concat(arguments))
-> wrapper.apply wrapper, [func].concat arguments
# Returns a function that is the composition of a list of functions, each
@@ -377,38 +398,37 @@
# ------------------------- Object Functions: ----------------------------
# Retrieve the names of an object's properties.
_.keys: (obj) ->
return _.range(0, obj.length) if _.isArray(obj)
_.keys: nativeKeys or (obj) ->
return _.range 0, obj.length if _.isArray(obj)
key for key, val of obj
# Retrieve the values of an object's properties.
_.values: (obj) ->
_.map(obj, _.identity)
_.map obj, _.identity
# Return a sorted list of the function names available in Underscore.
_.functions: (obj) ->
_.select(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
_.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort()
# Extend a given object with all of the properties in a source object.
_.extend: (destination, source) ->
for key, val of source
destination[key]: val
(destination[key]: val) for key, val of source
destination
# Create a (shallow-cloned) duplicate of an object.
_.clone: (obj) ->
return obj.slice(0) if _.isArray(obj)
_.extend({}, obj)
return obj.slice 0 if _.isArray obj
_.extend {}, obj
# Invokes interceptor with the obj, and then returns obj.
# The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
_.tap: (obj, interceptor) ->
interceptor(obj)
interceptor obj
obj
@@ -444,12 +464,15 @@
# Different object sizes?
return false if aKeys.length isnt bKeys.length
# Recursive comparison of contents.
# for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
return true
(return false) for key, val of a when !_.isEqual(val, b[key])
true
# Is a given array or object empty?
_.isEmpty: (obj) -> _.keys(obj).length is 0
_.isEmpty: (obj) ->
return obj.length is 0 if _.isArray obj
(return false) for key of obj when hasOwnProperty.call(obj, key)
true
# Is a given value a DOM element?
@@ -457,7 +480,7 @@
# Is a given value an array?
_.isArray: (obj) -> !!(obj and obj.concat and obj.unshift)
_.isArray: nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift)
# Is a given variable an arguments object?
@@ -477,6 +500,10 @@
_.isNumber: (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]'
# Is a given value a boolean?
_.isBoolean: (obj) -> obj is true or obj is false
# Is a given value a Date?
_.isDate: (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
@@ -511,10 +538,22 @@
_.identity: (value) -> value
# Run a function n times.
_.times: (n, iterator, context) ->
iterator.call(context, i) for i in [0...n]
# Break out of the middle of an iteration.
_.breakLoop: -> throw breaker
# Add your own custom functions to the Underscore object, ensuring that
# they're correctly added to the OOP wrapper as well.
_.mixin: (obj) ->
for name in _.functions(obj)
addToWrapper name, _[name]: obj[name]
# Generate a unique integer id (unique within the entire client session).
# Useful for temporary DOM ids.
idCounter: 0
@@ -536,11 +575,12 @@
# Single-quote fix from Rick Strahl's version.
_.template: (str, data) ->
c: _.templateSettings
endMatch: new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g")
fn: new Function 'obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj){p.push(\'' +
str.replace(/[\r\t\n]/g, " ")
.replace(new RegExp("'(?=[^"+c.end[0]+"]*"+c.end+")","g"),"\t")
.replace(endMatch,"\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(c.interpolate, "',$1,'")
@@ -555,9 +595,9 @@
_.forEach: _.each
_.foldl: _.inject: _.reduce
_.foldr: _.reduceRight
_.filter: _.select
_.every: _.all
_.some: _.any
_.select: _.filter
_.all: _.every
_.any: _.some
_.head: _.first
_.tail: _.rest
_.methods: _.functions
@@ -565,17 +605,29 @@
# ------------------------ Setup the OOP Wrapper: --------------------------
# If Underscore is called as a function, it returns a wrapped object that
# can be used OO-style. This wrapper holds altered versions of all the
# underscore functions. Wrapped objects may be chained.
wrapper: (obj) ->
this._wrapped: obj
this
# Helper function to continue chaining intermediate results.
result: (obj, chain) ->
if chain then _(obj).chain() else obj
# Add all of the Underscore functions to the wrapper object.
_.each _.functions(_), (name) ->
method: _[name]
# A method to easily add functions to the OOP wrapper.
addToWrapper: (name, func) ->
wrapper.prototype[name]: ->
unshift.call(arguments, this._wrapped)
result(method.apply(_, arguments), this._chain)
args: _.toArray arguments
unshift.call args, this._wrapped
result func.apply(_, args), this._chain
# Add all of the Underscore functions to the wrapper object.
_.mixin _
# Add all mutator Array functions to the wrapper.

File diff suppressed because one or more lines are too long

View File

@@ -97,7 +97,7 @@ alert reverse '!tpircseeffoC'</textarea></div>
<p>
<b>Latest Version:</b>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>
<a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>
</p>
<h2>
@@ -250,10 +250,12 @@ cubed_list = (function() {
<p>
To install, first make sure you have a working version of
<a href="http://nodejs.org/">Node.js</a>, 0.1.30 or higher. Then clone the CoffeeScript
<a href="http://nodejs.org/">Node.js</a> greater than version 0.1.30 (Node
moves quickly, using the latest master is your best bet).
Then clone the CoffeeScript
<a href="http://github.com/jashkenas/coffee-script">source repository</a>
from GitHub, or download the latest
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.3">0.5.3</a>.
release: <a href="http://github.com/jashkenas/coffee-script/tarball/0.5.4">0.5.4</a>.
To install the CoffeeScript compiler system-wide
under <tt>/usr/local</tt>, open the directory and run:
</p>
@@ -1676,6 +1678,13 @@ task(<span class="String"><span class="String">'</span>test<span class="String">
Change Log
</h2>
<p>
<b class="header" style="margin-top: 20px;">0.5.4</b>
Bugfix that corrects the Node.js global constants <tt>__filename</tt> and
<tt>__dirname</tt>. Tweaks for more flexible parsing of nested function
literals and improperly-indented comments. Updates for the latest Node.js API.
</p>
<p>
<b class="header" style="margin-top: 20px;">0.5.3</b>
CoffeeScript now has a syntax for defining classes. Many of the core

View File

@@ -32,7 +32,7 @@
return this.pos;
}
};
exports.VERSION = '0.5.3';
exports.VERSION = '0.5.4';
// Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile = function compile(code, options) {
return (parser.parse(lexer.tokenize(code))).compile(options);

View File

@@ -78,7 +78,7 @@
// Compile a single source script, containing the given code, according to the
// requested options. Both compile_scripts and watch_scripts share this method.
compile_script = function compile_script(source, code) {
var js, o;
var __dirname, __filename, js, o;
o = options;
try {
if (o.tokens) {
@@ -94,6 +94,8 @@
} else if (o.print || o.eval) {
return print(js);
} else {
__filename = source;
__dirname = path.dirname(source);
return eval(js);
}
}
@@ -124,7 +126,7 @@
watch_scripts = function watch_scripts() {
var _a, _b, _c, _d, source, watch;
watch = function watch(source) {
return process.watchFile(source, {
return fs.watchFile(source, {
persistent: true,
interval: 500
}, function(curr, prev) {

View File

@@ -1,24 +1,36 @@
(function(){
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE;
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RESERVED, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, compact, count, include;
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
// matches against the beginning of the source code. When a match is found,
// a token is produced, we consume the match, and start again. Tokens are in the
// form:
// [tag, value, line_number]
// Which is a format that can be fed directly into [Jison](http://github.com/zaach/jison).
// Set up the Lexer for both Node.js and the browser, depending on where we are.
if ((typeof process !== "undefined" && process !== null)) {
Rewriter = require('./rewriter').Rewriter;
} else {
this.exports = this;
Rewriter = this.Rewriter;
}
// Constants ============================================================
// Keywords that CoffeScript shares in common with JS.
// Constants
// ---------
// Keywords that CoffeeScript shares in common with JavaScript.
JS_KEYWORDS = ["if", "else", "true", "false", "new", "return", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", "delete", "instanceof", "typeof", "switch", "super", "extends", "class"];
// CoffeeScript-only keywords -- which we're more relaxed about allowing.
// CoffeeScript-only keywords, which we're more relaxed about allowing. They can't
// be used standalone, but you can reference them as an attached property.
COFFEE_KEYWORDS = ["then", "unless", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "of", "by", "where", "when"];
// The list of keywords passed verbatim to the parser.
// The combined list of keywords is the superset that gets passed verbatim to
// the parser.
KEYWORDS = JS_KEYWORDS.concat(COFFEE_KEYWORDS);
// The list of keywords that are reserved by JavaScript, but not used, or are
// used by CoffeeScript internally. Using these will throw an error.
// 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"];
// JavaScript keywords and reserved words together, excluding CoffeeScript ones.
// The superset of both JavaScript keywords and reserved words, none of which may
// be used as identifiers or properties.
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
// Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
// Token matching regexes.
IDENTIFIER = /^([a-zA-Z$_](\w|\$)*)/;
NUMBER = /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i;
STRING = /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/;
@@ -45,32 +57,38 @@
// See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
// Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX = ['NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE'];
// Tokens which could legitimately be invoked or indexed.
// Tokens which could legitimately be invoked or indexed. A opening
// parentheses or bracket following these tokens will be recorded as the start
// of a function invocation or indexing operation.
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@'];
// Tokens that indicate an access -- keywords immediately following will be
// treated as identifiers.
ACCESSORS = ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@'];
// Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
// Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
// occurs at the start of a line. We disambiguate these from trailing whens to
// avoid an ambiguity in the grammar.
BEFORE_WHEN = ['INDENT', 'OUTDENT', 'TERMINATOR'];
// The lexer reads a stream of CoffeeScript and divvys it up into tagged
// The Lexer Class
// ---------------
// The Lexer class reads a stream of CoffeeScript and divvys it up into tagged
// tokens. A minor bit of the ambiguity in the grammar has been avoided by
// pushing some extra smarts into the Lexer.
exports.Lexer = (function() {
Lexer = function Lexer() { };
// Scan by attempting to match tokens one character at a time. Slow and steady.
// Scan by attempting to match tokens one at a time. Slow and steady.
Lexer.prototype.tokenize = function tokenize(code) {
this.code = code;
// Cleanup code by remove extra line breaks, TODO: chomp
// The remainder of the source code.
this.i = 0;
// Current character position we're parsing
this.line = 1;
// Current character position we're parsing.
this.line = 0;
// The current line.
this.indent = 0;
// The current indent level.
this.indents = [];
// The stack of all indent levels we are currently within.
this.tokens = [];
// Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
// Collection of all parsed tokens in the form ['TOKEN_TYPE', value, line]
while (this.i < this.code.length) {
this.chunk = this.code.slice(this.i);
this.extract_next_token();
@@ -99,10 +117,10 @@
if (this.regex_token()) {
return null;
}
if (this.indent_token()) {
if (this.comment_token()) {
return null;
}
if (this.comment_token()) {
if (this.line_token()) {
return null;
}
if (this.whitespace_token()) {
@@ -110,32 +128,23 @@
}
return this.literal_token();
};
// Tokenizers ==========================================================
// Tokenizers
// ----------
// Matches identifying literals: variables, keywords, method names, etc.
Lexer.prototype.identifier_token = function identifier_token() {
var id, tag;
if (!((id = this.match(IDENTIFIER, 1)))) {
return false;
}
if (this.value() === '::') {
this.tag(1, 'PROTOTYPE_ACCESS');
}
if (this.value() === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
this.tokens.splice(-2, 1);
} else {
this.tag(1, 'PROPERTY_ACCESS');
}
}
this.name_access_type();
tag = 'IDENTIFIER';
if (KEYWORDS.indexOf(id) >= 0 && !((ACCESSORS.indexOf(this.tag()) >= 0) && !this.prev().spaced)) {
if (include(KEYWORDS, id) && !(include(ACCESSORS, this.tag(0)) && !this.prev().spaced)) {
tag = id.toUpperCase();
}
if (RESERVED.indexOf(id) >= 0) {
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + this.line);
if (include(RESERVED, id)) {
this.identifier_error(id);
}
if (tag === 'WHEN' && BEFORE_WHEN.indexOf(this.tag()) >= 0) {
if (tag === 'WHEN' && include(BEFORE_WHEN, this.tag())) {
tag = 'LEADING_WHEN';
}
this.token(tag, id);
@@ -160,21 +169,19 @@
}
escaped = string.replace(STRING_NEWLINES, " \\\n");
this.token('STRING', escaped);
this.line += this.count(string, "\n");
this.line += count(string, "\n");
this.i += string.length;
return true;
};
// Matches heredocs, adjusting indentation to the correct level.
Lexer.prototype.heredoc_token = function heredoc_token() {
var doc, indent, match;
var doc, match;
if (!((match = this.chunk.match(HEREDOC)))) {
return false;
}
doc = match[2] || match[4];
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
doc = doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace('"', '\\"');
doc = this.sanitize_heredoc(match[2] || match[4]);
this.token('STRING', '"' + doc + '"');
this.line += this.count(match[1], "\n");
this.line += count(match[1], "\n");
this.i += match[1].length;
return true;
};
@@ -194,7 +201,7 @@
if (!((regex = this.match(REGEX, 1)))) {
return false;
}
if (NOT_REGEX.indexOf(this.tag()) >= 0) {
if (include(NOT_REGEX, this.tag())) {
return false;
}
this.token('REGEX', regex);
@@ -203,59 +210,63 @@
};
// Matches and conumes comments.
Lexer.prototype.comment_token = function comment_token() {
var comment;
var comment, lines;
if (!((comment = this.match(COMMENT, 1)))) {
return false;
}
this.line += (comment.match(MULTILINER) || []).length;
this.token('COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER));
lines = comment.replace(COMMENT_CLEANER, '').split(MULTILINER);
this.token('COMMENT', compact(lines));
this.token('TERMINATOR', "\n");
this.i += comment.length;
return true;
};
// Record tokens for indentation differing from the previous line.
Lexer.prototype.indent_token = function indent_token() {
// Matches newlines, indents, and outdents, and determines which is which.
Lexer.prototype.line_token = function line_token() {
var diff, indent, next_character, no_newlines, prev, size;
if (!((indent = this.match(MULTI_DENT, 1)))) {
return false;
}
this.line += indent.match(MULTILINER).length;
this.i += indent.length;
next_character = this.chunk.match(MULTI_DENT)[4];
prev = this.prev(2);
no_newlines = next_character === '.' || (this.value() && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE));
if (no_newlines) {
return this.suppress_newlines(indent);
}
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
next_character = this.chunk.match(MULTI_DENT)[4];
no_newlines = next_character === '.' || (this.value() && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE));
if (size === this.indent) {
if (no_newlines) {
return this.suppress_newlines(indent);
}
return this.newline_token(indent);
}
if (size > this.indent) {
} else if (size > this.indent) {
if (no_newlines) {
return this.suppress_newlines(indent);
}
diff = size - this.indent;
this.token('INDENT', diff);
this.indents.push(diff);
} else {
this.outdent_token(this.indent - size);
this.outdent_token(this.indent - size, no_newlines);
}
this.indent = size;
return true;
};
// Record an oudent token or tokens, if we're moving back inwards past
// multiple recorded indents.
Lexer.prototype.outdent_token = function outdent_token(move_out) {
// Record an outdent token or tokens, if we happen to be moving back inwards
// past multiple recorded indents.
Lexer.prototype.outdent_token = function outdent_token(move_out, no_newlines) {
var last_indent;
while (move_out > 0 && this.indents.length) {
last_indent = this.indents.pop();
this.token('OUTDENT', last_indent);
move_out -= last_indent;
}
if (!(this.tag() === 'TERMINATOR')) {
if (!(this.tag() === 'TERMINATOR' || no_newlines)) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Matches and consumes non-meaningful whitespace.
// Matches and consumes non-meaningful whitespace. Tag the previous token
// as being "spaced", because there are some cases where it makes a difference.
Lexer.prototype.whitespace_token = function whitespace_token() {
var prev, space;
if (!((space = this.match(WHITESPACE, 1)))) {
@@ -268,23 +279,23 @@
this.i += space.length;
return true;
};
// Multiple newlines get merged together.
// Use a trailing \ to escape newlines.
// Generate a newline token. Multiple newlines get merged together.
Lexer.prototype.newline_token = function newline_token(newlines) {
if (!(this.tag() === 'TERMINATOR')) {
this.token('TERMINATOR', "\n");
}
return true;
};
// Tokens to explicitly escape newlines are removed once their job is done.
// Use a `\` at a line-ending to suppress the newline.
// The slash is removed here once its job is done.
Lexer.prototype.suppress_newlines = function suppress_newlines(newlines) {
if (this.value() === "\\") {
this.tokens.pop();
}
return true;
};
// We treat all other single characters as a token. Eg.: ( ) , . !
// Multi-character operators are also literal tokens, so that Racc can assign
// We treat all other single characters as a token. Eg.: `( ) , . !`
// Multi-character operators are also literal tokens, so that Jison can assign
// the proper order of operations.
Lexer.prototype.literal_token = function literal_token() {
var match, not_spaced, tag, value;
@@ -298,8 +309,8 @@
tag = value;
if (value.match(ASSIGNMENT)) {
tag = 'ASSIGN';
if (JS_FORBIDDEN.indexOf(this.value()) >= 0) {
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
if (include(JS_FORBIDDEN, this.value)) {
this.assignment_error();
}
} else if (value === ';') {
tag = 'TERMINATOR';
@@ -310,7 +321,7 @@
} else if (value === ']' && this.soaked_index) {
tag = 'SOAKED_INDEX_END';
this.soaked_index = false;
} else if (CALLABLE.indexOf(this.tag()) >= 0 && not_spaced) {
} else if (include(CALLABLE, this.tag()) && not_spaced) {
if (value === '(') {
tag = 'CALL_START';
}
@@ -322,56 +333,29 @@
this.i += value.length;
return true;
};
// Helpers =============================================================
// Add a token to the results, taking note of the line number.
Lexer.prototype.token = function token(tag, value) {
return this.tokens.push([tag, value, this.line]);
// Token Manipulators
// ------------------
// As we consume a new `IDENTIFIER`, look at the previous token to determine
// if it's a special kind of accessor.
Lexer.prototype.name_access_type = function name_access_type() {
if (this.value() === '::') {
this.tag(1, 'PROTOTYPE_ACCESS');
}
if (this.value() === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
return this.tokens.splice(-2, 1);
} else {
return this.tag(1, 'PROPERTY_ACCESS');
}
}
};
// Look at a tag in the current token stream.
Lexer.prototype.tag = function tag(index, tag) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof tag !== "undefined" && tag !== null)) {
return (tok[0] = tag);
}
return tok[0];
};
// Look at a value in the current token stream.
Lexer.prototype.value = function value(index, val) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof val !== "undefined" && val !== null)) {
return (tok[1] = val);
}
return tok[1];
};
// Look at a previous token.
Lexer.prototype.prev = function prev(index) {
return this.tokens[this.tokens.length - (index || 1)];
};
// Count the occurences of a character in a string.
Lexer.prototype.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;
};
// Attempt to match a string against the current chunk, returning the indexed
// match.
Lexer.prototype.match = function match(regex, index) {
var m;
if (!((m = this.chunk.match(regex)))) {
return false;
}
return m ? m[index] : false;
// Sanitize a heredoc by escaping double quotes and erasing all external
// indentation on the left-hand side.
Lexer.prototype.sanitize_heredoc = function sanitize_heredoc(doc) {
var indent;
indent = (doc.match(HEREDOC_INDENT) || ['']).sort()[0];
return doc.replace(new RegExp("^" + indent, 'gm'), '').replace(MULTILINER, "\\n").replace(/"/g, '\\"');
};
// A source of ambiguity in our grammar was parameter lists in function
// definitions (as opposed to argument lists in function calls). Tag
@@ -399,11 +383,90 @@
}
return true;
};
// Close up all remaining open blocks. IF the first token is an indent,
// axe it.
// Close up all remaining open blocks at the end of the file.
Lexer.prototype.close_indentation = function close_indentation() {
return this.outdent_token(this.indent);
};
// Error for when you try to use a forbidden word in JavaScript as
// an identifier.
Lexer.prototype.identifier_error = function identifier_error(word) {
throw new Error('SyntaxError: Reserved word "' + word + '" on line ' + this.line);
};
// Error for when you try to assign to a reserved word in JavaScript,
// like "function" or "default".
Lexer.prototype.assignment_error = function assignment_error() {
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
};
// Helpers
// -------
// Add a token to the results, taking note of the line number.
Lexer.prototype.token = function token(tag, value) {
return this.tokens.push([tag, value, this.line]);
};
// Peek at a tag in the current token stream.
Lexer.prototype.tag = function tag(index, tag) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof tag !== "undefined" && tag !== null)) {
return (tok[0] = tag);
}
return tok[0];
};
// Peek at a value in the current token stream.
Lexer.prototype.value = function value(index, val) {
var tok;
if (!((tok = this.prev(index)))) {
return null;
}
if ((typeof val !== "undefined" && val !== null)) {
return (tok[1] = val);
}
return tok[1];
};
// Peek at a previous token, entire.
Lexer.prototype.prev = function prev(index) {
return this.tokens[this.tokens.length - (index || 1)];
};
// Attempt to match a string against the current chunk, returning the indexed
// match if successful, and `false` otherwise.
Lexer.prototype.match = function match(regex, index) {
var m;
if (!((m = this.chunk.match(regex)))) {
return false;
}
return m ? m[index] : false;
};
return Lexer;
}).call(this);
// Utility Functions
// -----------------
// Does a list include a value?
include = function include(list, value) {
return list.indexOf(value) >= 0;
};
// 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;
};
})();

View File

@@ -1,5 +1,5 @@
(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, inherit, 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, compact, del, flatten, literal, merge, statement;
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
@@ -62,17 +62,9 @@
delete obj[key];
return val;
};
// Quickie inheritance convenience wrapper to reduce typing.
inherit = function inherit(parent, props) {
var _a, klass, name, prop;
klass = del(props, 'constructor');
__extends(klass, parent);
_a = props;
for (name in _a) { if (__hasProp.call(_a, name)) {
prop = _a[name];
((klass.prototype[name] = prop));
}}
return klass;
// Quickie helper for a generated LiteralNode.
literal = function literal(name) {
return new LiteralNode(name);
};
// Mark a node as a statement, or a statement only.
statement = function statement(klass, only) {
@@ -122,7 +114,7 @@
// in multiple places, ensure that the expression is only ever evaluated once.
BaseNode.prototype.compile_reference = function compile_reference(o) {
var compiled, reference;
reference = new LiteralNode(o.scope.free_variable());
reference = literal(o.scope.free_variable());
compiled = new AssignNode(reference, this);
return [compiled, reference];
};
@@ -527,7 +519,7 @@
ExtendsNode.prototype.compile_node = function compile_node(o) {
var call, ref;
o.scope.assign('__extends', this.code, true);
ref = new ValueNode(new LiteralNode('__extends'));
ref = new ValueNode(literal('__extends'));
call = new CallNode(ref, [this.child, this.parent]);
return call.compile(o);
};
@@ -601,10 +593,10 @@
RangeNode.prototype.compile_array = function compile_array(o) {
var arr, body, name;
name = o.scope.free_variable();
body = Expressions.wrap([new LiteralNode(name)]);
body = Expressions.wrap([literal(name)]);
arr = Expressions.wrap([new ForNode(body, {
source: (new ValueNode(this))
}, new LiteralNode(name))
}, literal(name))
]);
return (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o);
};
@@ -688,7 +680,7 @@
__extends(ClassNode, BaseNode);
ClassNode.prototype.type = 'Class';
ClassNode.prototype.compile_node = function compile_node(o) {
var _a, _b, _c, construct, extension, func, prop, props, ret, returns, val;
var _a, _b, _c, applied, construct, extension, func, prop, props, ret, returns, val;
extension = this.parent && new ExtendsNode(this.variable, this.parent);
constructor = null;
props = new Expressions();
@@ -699,7 +691,7 @@
prop = _a[_b];
if (prop.variable && prop.variable.base.value === 'constructor') {
func = prop.value;
func.body.push(new ReturnNode(new LiteralNode('this')));
func.body.push(new ReturnNode(literal('this')));
constructor = new AssignNode(this.variable, func);
} else {
if (prop.variable) {
@@ -709,8 +701,13 @@
props.push(prop);
}
}
if (!(constructor)) {
constructor = new AssignNode(this.variable, new CodeNode());
if (!constructor) {
if (this.parent) {
applied = new ValueNode(this.parent, [new AccessorNode(literal('apply'))]);
constructor = new AssignNode(this.variable, new CodeNode([], new Expressions([new CallNode(applied, [literal('this'), literal('arguments')])])));
} else {
constructor = new AssignNode(this.variable, new CodeNode());
}
}
construct = this.idt() + constructor.compile(o) + ';\n';
props = props.empty() ? '' : props.compile(o) + '\n';
@@ -766,7 +763,7 @@
})) {
return expressions;
}
return Expressions.wrap([new CallNode(new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr])]);
return Expressions.wrap([new CallNode(new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr])]);
}
});
// A faux-node used to wrap an expressions body in a closure.
@@ -774,7 +771,7 @@
wrap: function wrap(expressions, statement) {
var call, func;
func = new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])));
call = new CallNode(new ValueNode(func, [new AccessorNode(new LiteralNode('call'))]), [new LiteralNode('this')]);
call = new CallNode(new ValueNode(func, [new AccessorNode(literal('call'))]), [literal('this')]);
return statement ? Expressions.wrap([call]) : call;
}
});
@@ -859,12 +856,12 @@
}
access_class = this.variable.is_array() ? IndexNode : AccessorNode;
if (obj instanceof SplatNode) {
val = new LiteralNode(obj.compile_value(o, val_var, this.variable.base.objects.indexOf(obj)));
val = literal(obj.compile_value(o, val_var, this.variable.base.objects.indexOf(obj)));
} else {
if (!(typeof idx === 'object')) {
idx = new LiteralNode(idx);
idx = literal(idx);
}
val = new ValueNode(new LiteralNode(val_var), [new access_class(idx)]);
val = new ValueNode(literal(val_var), [new access_class(idx)]);
}
assigns.push(new AssignNode(obj, val).compile(o));
}
@@ -974,7 +971,7 @@
exports.SplatNode = (function() {
SplatNode = function SplatNode(name) {
if (!(name.compile)) {
name = new LiteralNode(name);
name = literal(name);
}
this.children = [(this.name = name)];
return this;
@@ -1362,7 +1359,7 @@
var _a, _b, _c, assigner, cond, i, variable;
assigner = this.switcher;
if (!(this.switcher.unwrap() instanceof LiteralNode)) {
variable = new LiteralNode(o.scope.free_variable());
variable = literal(o.scope.free_variable());
assigner = new AssignNode(variable, this.switcher);
this.switcher = variable;
}

View File

@@ -85,19 +85,14 @@
Rewriter.prototype.adjust_comments = function adjust_comments() {
return this.scan_tokens((function(__this) {
var __func = function(prev, token, post, i) {
var after, before;
var after;
if (!(token[0] === 'COMMENT')) {
return 1;
}
before = this.tokens[i - 2];
after = this.tokens[i + 2];
if (before && after && ((before[0] === 'INDENT' && after[0] === 'OUTDENT') || (before[0] === 'OUTDENT' && after[0] === 'INDENT')) && before[1] === after[1]) {
if (after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
this.tokens.splice(i - 2, 1);
return 0;
} else if (prev && prev[0] === 'TERMINATOR' && after && after[0] === 'INDENT') {
this.tokens.splice(i + 2, 1);
this.tokens[i - 1] = after;
this.tokens.splice(i, 0, after);
return 1;
} else if (prev && prev[0] !== 'TERMINATOR' && prev[0] !== 'INDENT' && prev[0] !== 'OUTDENT') {
this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
@@ -252,7 +247,7 @@
idx += 1;
tok = this.tokens[idx];
pre = this.tokens[idx - 1];
if ((!tok || (SINGLE_CLOSERS.indexOf(tok[0]) >= 0 && tok[1] !== ';') || (pre[0] === ',' && tok[0] === 'PARAM_START') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
if ((!tok || (SINGLE_CLOSERS.indexOf(tok[0]) >= 0 && tok[1] !== ';') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
insertion = pre[0] === "," ? idx - 1 : idx;
this.tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]]);
break;
@@ -325,14 +320,12 @@
// el.hide())
// In order to accomplish this, move outdents that follow closing parens
// inwards, safely. The steps to accomplish this are:
//
// 1. Check that all paired tokens are balanced and in order.
// 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
// to the stack. If you see an ')' or OUTDENT, pop the stack and replace
// it with the inverse of what we've just popped.
// 3. Keep track of "debt" for tokens that we fake, to make sure we end
// up balanced in the end.
//
Rewriter.prototype.rewrite_closing_parens = function rewrite_closing_parens() {
var _l, debt, key, stack, val;
stack = [];

View File

@@ -7,7 +7,6 @@
// Scope objects form a tree corresponding to the shape of the function
// definitions present in the script. They provide lexical scope, to determine
// whether a variable has been seen before or if it needs to be declared.
//
// Initialize a scope with its parent, for lookups up the chain,
// as well as the Expressions body where it should declare its variables,
// and the function that it wraps.

View File

@@ -3,5 +3,5 @@
"description": "Unfancy JavaScript",
"keywords": ["javascript", "language"],
"author": "Jeremy Ashkenas",
"version": "0.5.3"
"version": "0.5.4"
}

View File

@@ -24,7 +24,7 @@ parser.lexer: {
showPosition: -> @pos
}
exports.VERSION: '0.5.3'
exports.VERSION: '0.5.4'
# Compile CoffeeScript to JavaScript, using the Coffee/Jison compiler.
exports.compile: (code, options) ->

View File

@@ -80,7 +80,10 @@ compile_script: (source, code) ->
if o.compile then write_js source, js
else if o.lint then lint js
else if o.print or o.eval then print js
else eval js
else
__filename: source
__dirname: path.dirname source
eval js
catch err
if o.watch then puts err.message else throw err
@@ -97,7 +100,7 @@ compile_stdio: ->
# files are updated.
watch_scripts: ->
watch: (source) ->
process.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
return if curr.mtime.getTime() is prev.mtime.getTime()
fs.readFile source, (err, code) -> compile_script(source, code)
watch(source) for source in sources

View File

@@ -1,12 +1,23 @@
# The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
# matches against the beginning of the source code. When a match is found,
# a token is produced, we consume the match, and start again. Tokens are in the
# form:
#
# [tag, value, line_number]
#
# Which is a format that can be fed directly into [Jison](http://github.com/zaach/jison).
# Set up the Lexer for both Node.js and the browser, depending on where we are.
if process?
Rewriter: require('./rewriter').Rewriter
else
this.exports: this
Rewriter: this.Rewriter
# Constants ============================================================
# Constants
# ---------
# Keywords that CoffeScript shares in common with JS.
# Keywords that CoffeeScript shares in common with JavaScript.
JS_KEYWORDS: [
"if", "else",
"true", "false",
@@ -18,7 +29,8 @@ JS_KEYWORDS: [
"switch", "super", "extends", "class"
]
# CoffeeScript-only keywords -- which we're more relaxed about allowing.
# CoffeeScript-only keywords, which we're more relaxed about allowing. They can't
# be used standalone, but you can reference them as an attached property.
COFFEE_KEYWORDS: [
"then", "unless",
"yes", "no", "on", "off",
@@ -26,21 +38,24 @@ COFFEE_KEYWORDS: [
"of", "by", "where", "when"
]
# The list of keywords passed verbatim to the parser.
# The combined list of keywords is the superset that gets passed verbatim to
# the parser.
KEYWORDS: JS_KEYWORDS.concat COFFEE_KEYWORDS
# The list of keywords that are reserved by JavaScript, but not used, or are
# used by CoffeeScript internally. Using these will throw an error.
# 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"
]
# JavaScript keywords and reserved words together, excluding CoffeeScript ones.
# The superset of both JavaScript keywords and reserved words, none of which may
# be used as identifiers or properties.
JS_FORBIDDEN: JS_KEYWORDS.concat RESERVED
# Token matching regexes. (keep the IDENTIFIER regex in sync with AssignNode.)
# Token matching regexes.
IDENTIFIER : /^([a-zA-Z$_](\w|\$)*)/
NUMBER : /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
STRING : /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/
@@ -66,35 +81,44 @@ HEREDOC_INDENT : /^[ \t]+/mg
# Tokens which a regular expression will never immediately follow, but which
# a division operator might.
#
# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
#
# Our list is shorter, due to sans-parentheses method calls.
NOT_REGEX: [
'NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE'
]
# Tokens which could legitimately be invoked or indexed.
# Tokens which could legitimately be invoked or indexed. A opening
# parentheses or bracket following these tokens will be recorded as the start
# of a function invocation or indexing operation.
CALLABLE: ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@']
# Tokens that indicate an access -- keywords immediately following will be
# treated as identifiers.
ACCESSORS: ['PROPERTY_ACCESS', 'PROTOTYPE_ACCESS', 'SOAK_ACCESS', '@']
# Tokens that, when immediately preceding a 'WHEN', indicate that its leading.
# Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
# occurs at the start of a line. We disambiguate these from trailing whens to
# avoid an ambiguity in the grammar.
BEFORE_WHEN: ['INDENT', 'OUTDENT', 'TERMINATOR']
# The lexer reads a stream of CoffeeScript and divvys it up into tagged
# The Lexer Class
# ---------------
# The Lexer class reads a stream of CoffeeScript and divvys it up into tagged
# tokens. A minor bit of the ambiguity in the grammar has been avoided by
# pushing some extra smarts into the Lexer.
exports.Lexer: class Lexer
# Scan by attempting to match tokens one character at a time. Slow and steady.
# Scan by attempting to match tokens one at a time. Slow and steady.
tokenize: (code) ->
@code : code # Cleanup code by remove extra line breaks, TODO: chomp
@i : 0 # Current character position we're parsing
@line : 1 # The current line.
@indent : 0 # The current indent level.
@indents : [] # The stack of all indent levels we are currently within.
@tokens : [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
@code : code # The remainder of the source code.
@i : 0 # Current character position we're parsing.
@line : 0 # The current line.
@indent : 0 # The current indent level.
@indents : [] # The stack of all indent levels we are currently within.
@tokens : [] # Collection of all parsed tokens in the form ['TOKEN_TYPE', value, line]
while @i < @code.length
@chunk: @code.slice(@i)
@extract_next_token()
@@ -110,28 +134,23 @@ exports.Lexer: class Lexer
return if @string_token()
return if @js_token()
return if @regex_token()
return if @indent_token()
return if @comment_token()
return if @line_token()
return if @whitespace_token()
return @literal_token()
# Tokenizers ==========================================================
# Tokenizers
# ----------
# Matches identifying literals: variables, keywords, method names, etc.
identifier_token: ->
return false unless id: @match IDENTIFIER, 1
@tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
if @value() is '.' and not (@value(2) is '.')
if @tag(2) is '?'
@tag(1, 'SOAK_ACCESS')
@tokens.splice(-2, 1)
else
@tag(1, 'PROPERTY_ACCESS')
@name_access_type()
tag: 'IDENTIFIER'
tag: id.toUpperCase() if KEYWORDS.indexOf(id) >= 0 and
not ((ACCESSORS.indexOf(@tag()) >= 0) and not @prev().spaced)
throw new Error('SyntaxError: Reserved word "' + id + '" on line ' + @line) if RESERVED.indexOf(id) >= 0
tag: 'LEADING_WHEN' if tag is 'WHEN' and BEFORE_WHEN.indexOf(@tag()) >= 0
tag: id.toUpperCase() if include(KEYWORDS, id) and
not (include(ACCESSORS, @tag(0)) and not @prev().spaced)
@identifier_error id if include RESERVED, id
tag: 'LEADING_WHEN' if tag is 'WHEN' and include BEFORE_WHEN, @tag()
@token(tag, id)
@i += id.length
true
@@ -148,20 +167,16 @@ exports.Lexer: class Lexer
return false unless string: @match STRING, 1
escaped: string.replace STRING_NEWLINES, " \\\n"
@token 'STRING', escaped
@line += @count string, "\n"
@line += count string, "\n"
@i += string.length
true
# Matches heredocs, adjusting indentation to the correct level.
heredoc_token: ->
return false unless match = @chunk.match(HEREDOC)
doc: match[2] or match[4]
indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
doc: doc.replace(new RegExp("^" + indent, 'gm'), '')
.replace(MULTILINER, "\\n")
.replace('"', '\\"')
doc: @sanitize_heredoc match[2] or match[4]
@token 'STRING', '"' + doc + '"'
@line += @count match[1], "\n"
@line += count match[1], "\n"
@i += match[1].length
true
@@ -175,7 +190,7 @@ exports.Lexer: class Lexer
# Matches regular expression literals.
regex_token: ->
return false unless regex: @match REGEX, 1
return false if NOT_REGEX.indexOf(@tag()) >= 0
return false if include NOT_REGEX, @tag()
@token 'REGEX', regex
@i += regex.length
true
@@ -184,42 +199,47 @@ exports.Lexer: class Lexer
comment_token: ->
return false unless comment: @match COMMENT, 1
@line += (comment.match(MULTILINER) or []).length
@token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
lines: comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
@token 'COMMENT', compact lines
@token 'TERMINATOR', "\n"
@i += comment.length
true
# Record tokens for indentation differing from the previous line.
indent_token: ->
# Matches newlines, indents, and outdents, and determines which is which.
line_token: ->
return false unless indent: @match MULTI_DENT, 1
@line += indent.match(MULTILINER).length
@i += indent.length
next_character: @chunk.match(MULTI_DENT)[4]
prev: @prev(2)
no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and prev and (prev[0] isnt '.') and not @value().match(CODE))
return @suppress_newlines(indent) if no_newlines
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
return @newline_token(indent) if size is @indent
if size > @indent
next_character: @chunk.match(MULTI_DENT)[4]
no_newlines: next_character is '.' or (@value() and @value().match(NO_NEWLINE) and
prev and (prev[0] isnt '.') and not @value().match(CODE))
if size is @indent
return @suppress_newlines(indent) if no_newlines
return @newline_token(indent)
else if size > @indent
return @suppress_newlines(indent) if no_newlines
diff: size - @indent
@token 'INDENT', diff
@indents.push diff
else
@outdent_token @indent - size
@outdent_token @indent - size, no_newlines
@indent: size
true
# Record an oudent token or tokens, if we're moving back inwards past
# multiple recorded indents.
outdent_token: (move_out) ->
# Record an outdent token or tokens, if we happen to be moving back inwards
# past multiple recorded indents.
outdent_token: (move_out, no_newlines) ->
while move_out > 0 and @indents.length
last_indent: @indents.pop()
@token 'OUTDENT', last_indent
move_out -= last_indent
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR' or no_newlines
true
# Matches and consumes non-meaningful whitespace.
# Matches and consumes non-meaningful whitespace. Tag the previous token
# as being "spaced", because there are some cases where it makes a difference.
whitespace_token: ->
return false unless space: @match WHITESPACE, 1
prev: @prev()
@@ -227,19 +247,19 @@ exports.Lexer: class Lexer
@i += space.length
true
# Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
# Generate a newline token. Multiple newlines get merged together.
newline_token: (newlines) ->
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
true
# Tokens to explicitly escape newlines are removed once their job is done.
# Use a `\` at a line-ending to suppress the newline.
# The slash is removed here once its job is done.
suppress_newlines: (newlines) ->
@tokens.pop() if @value() is "\\"
true
# We treat all other single characters as a token. Eg.: ( ) , . !
# Multi-character operators are also literal tokens, so that Racc can assign
# We treat all other single characters as a token. Eg.: `( ) , . !`
# Multi-character operators are also literal tokens, so that Jison can assign
# the proper order of operations.
literal_token: ->
match: @chunk.match(OPERATOR)
@@ -250,7 +270,7 @@ exports.Lexer: class Lexer
tag: value
if value.match(ASSIGNMENT)
tag: 'ASSIGN'
throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
@assignment_error() if include JS_FORBIDDEN, @value
else if value is ';'
tag: 'TERMINATOR'
else if value is '[' and @tag() is '?' and not_spaced
@@ -260,49 +280,34 @@ exports.Lexer: class Lexer
else if value is ']' and @soaked_index
tag: 'SOAKED_INDEX_END'
@soaked_index: false
else if CALLABLE.indexOf(@tag()) >= 0 and not_spaced
else if include(CALLABLE, @tag()) and not_spaced
tag: 'CALL_START' if value is '('
tag: 'INDEX_START' if value is '['
@token tag, value
@i += value.length
true
# Helpers =============================================================
# Token Manipulators
# ------------------
# Add a token to the results, taking note of the line number.
token: (tag, value) ->
@tokens.push([tag, value, @line])
# As we consume a new `IDENTIFIER`, look at the previous token to determine
# if it's a special kind of accessor.
name_access_type: ->
@tag(1, 'PROTOTYPE_ACCESS') if @value() is '::'
if @value() is '.' and not (@value(2) is '.')
if @tag(2) is '?'
@tag(1, 'SOAK_ACCESS')
@tokens.splice(-2, 1)
else
@tag 1, 'PROPERTY_ACCESS'
# Look at a tag in the current token stream.
tag: (index, tag) ->
return unless tok: @prev(index)
return tok[0]: tag if tag?
tok[0]
# Look at a value in the current token stream.
value: (index, val) ->
return unless tok: @prev(index)
return tok[1]: val if val?
tok[1]
# Look at a previous token.
prev: (index) ->
@tokens[@tokens.length - (index or 1)]
# Count the 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
# Attempt to match a string against the current chunk, returning the indexed
# match.
match: (regex, index) ->
return false unless m: @chunk.match(regex)
if m then m[index] else false
# Sanitize a heredoc by escaping double quotes and erasing all external
# indentation on the left-hand side.
sanitize_heredoc: (doc) ->
indent: (doc.match(HEREDOC_INDENT) or ['']).sort()[0]
doc.replace(new RegExp("^" + indent, 'gm'), '')
.replace(MULTILINER, "\\n")
.replace(/"/g, '\\"')
# A source of ambiguity in our grammar was parameter lists in function
# definitions (as opposed to argument lists in function calls). Tag
@@ -321,7 +326,64 @@ exports.Lexer: class Lexer
when '(' then return tok[0]: 'PARAM_START'
true
# Close up all remaining open blocks. IF the first token is an indent,
# axe it.
# Close up all remaining open blocks at the end of the file.
close_indentation: ->
@outdent_token(@indent)
# Error for when you try to use a forbidden word in JavaScript as
# an identifier.
identifier_error: (word) ->
throw new Error 'SyntaxError: Reserved word "' + word + '" on line ' + @line
# Error for when you try to assign to a reserved word in JavaScript,
# like "function" or "default".
assignment_error: ->
throw new Error 'SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned'
# Helpers
# -------
# Add a token to the results, taking note of the line number.
token: (tag, value) ->
@tokens.push([tag, value, @line])
# Peek at a tag in the current token stream.
tag: (index, tag) ->
return unless tok: @prev(index)
return tok[0]: tag if tag?
tok[0]
# Peek at a value in the current token stream.
value: (index, val) ->
return unless tok: @prev(index)
return tok[1]: val if val?
tok[1]
# Peek at a previous token, entire.
prev: (index) ->
@tokens[@tokens.length - (index or 1)]
# Attempt to match a string against the current chunk, returning the indexed
# match if successful, and `false` otherwise.
match: (regex, index) ->
return false unless m: @chunk.match(regex)
if m then m[index] else false
# Utility Functions
# -----------------
# Does a list include a value?
include: (list, value) ->
list.indexOf(value) >= 0
# 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

View File

@@ -36,12 +36,9 @@ del: (obj, key) ->
delete obj[key]
val
# Quickie inheritance convenience wrapper to reduce typing.
inherit: (parent, props) ->
klass: del(props, 'constructor')
klass extends parent
(klass.prototype[name]: prop) for name, prop of props
klass
# Quickie helper for a generated LiteralNode.
literal: (name) ->
new LiteralNode(name)
# Mark a node as a statement, or a statement only.
statement: (klass, only) ->
@@ -82,7 +79,7 @@ exports.BaseNode: class BaseNode
# If the code generation wishes to use the result of a complex expression
# in multiple places, ensure that the expression is only ever evaluated once.
compile_reference: (o) ->
reference: new LiteralNode(o.scope.free_variable())
reference: literal(o.scope.free_variable())
compiled: new AssignNode(reference, this)
[compiled, reference]
@@ -387,7 +384,7 @@ exports.ExtendsNode: class ExtendsNode extends BaseNode
# Hooking one constructor into another's prototype chain.
compile_node: (o) ->
o.scope.assign('__extends', @code, true)
ref: new ValueNode new LiteralNode '__extends'
ref: new ValueNode literal('__extends')
call: new CallNode ref, [@child, @parent]
call.compile(o)
@@ -451,8 +448,8 @@ exports.RangeNode: class RangeNode extends BaseNode
# TODO: This generates pretty ugly code ... shrink it.
compile_array: (o) ->
name: o.scope.free_variable()
body: Expressions.wrap([new LiteralNode(name)])
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, new LiteralNode(name))])
body: Expressions.wrap([literal(name)])
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))])
(new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
@@ -515,7 +512,7 @@ exports.ClassNode: class ClassNode extends BaseNode
for prop in @properties
if prop.variable and prop.variable.base.value is 'constructor'
func: prop.value
func.body.push(new ReturnNode(new LiteralNode('this')))
func.body.push(new ReturnNode(literal('this')))
constructor: new AssignNode(@variable, func)
else
if prop.variable
@@ -523,7 +520,14 @@ exports.ClassNode: class ClassNode extends BaseNode
prop: new AssignNode(val, prop.value)
props.push prop
constructor: new AssignNode(@variable, new CodeNode()) unless constructor
if not constructor
if @parent
applied: new ValueNode(@parent, [new AccessorNode(literal('apply'))])
constructor: new AssignNode(@variable, new CodeNode([], new Expressions([
new CallNode(applied, [literal('this'), literal('arguments')])
])))
else
constructor: new AssignNode(@variable, new CodeNode())
construct: @idt() + constructor.compile(o) + ';\n'
props: if props.empty() then '' else props.compile(o) + '\n'
@@ -564,7 +568,7 @@ PushNode: exports.PushNode: {
expr: expressions.unwrap()
return expressions if expr.is_statement_only() or expr.contains (n) -> n.is_statement_only()
Expressions.wrap([new CallNode(
new ValueNode(new LiteralNode(array), [new AccessorNode(new LiteralNode('push'))]), [expr]
new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr]
)])
}
@@ -575,7 +579,7 @@ ClosureNode: exports.ClosureNode: {
wrap: (expressions, statement) ->
func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
call: new CallNode(new ValueNode(func, [new AccessorNode(new LiteralNode('call'))]), [new LiteralNode('this')])
call: new CallNode(new ValueNode(func, [new AccessorNode(literal('call'))]), [literal('this')])
if statement then Expressions.wrap([call]) else call
}
@@ -635,10 +639,10 @@ exports.AssignNode: class AssignNode extends BaseNode
[obj, idx]: [obj.value, obj.variable.base] if @variable.is_object()
access_class: if @variable.is_array() then IndexNode else AccessorNode
if obj instanceof SplatNode
val: new LiteralNode(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
val: literal(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
else
idx: new LiteralNode(idx) unless typeof idx is 'object'
val: new ValueNode(new LiteralNode(val_var), [new access_class(idx)])
idx: literal(idx) unless typeof idx is 'object'
val: new ValueNode(literal(val_var), [new access_class(idx)])
assigns.push(new AssignNode(obj, val).compile(o))
code: assigns.join("\n")
code += '\n' + @idt() + 'return ' + @variable.compile(o) + ';' if o.returns
@@ -708,7 +712,7 @@ exports.SplatNode: class SplatNode extends BaseNode
type: 'Splat'
constructor: (name) ->
name: new LiteralNode(name) unless name.compile
name: literal(name) unless name.compile
@children: [@name: name]
compile_node: (o) ->
@@ -994,7 +998,7 @@ exports.IfNode: class IfNode extends BaseNode
rewrite_switch: (o) ->
assigner: @switcher
if not (@switcher.unwrap() instanceof LiteralNode)
variable: new LiteralNode(o.scope.free_variable())
variable: literal(o.scope.free_variable())
assigner: new AssignNode(variable, @switcher)
@switcher: variable
@condition: if @multiple

View File

@@ -72,18 +72,10 @@ exports.Rewriter: class Rewriter
adjust_comments: ->
@scan_tokens (prev, token, post, i) =>
return 1 unless token[0] is 'COMMENT'
before: @tokens[i - 2]
after: @tokens[i + 2]
if before and after and
((before[0] is 'INDENT' and after[0] is 'OUTDENT') or
(before[0] is 'OUTDENT' and after[0] is 'INDENT')) and
before[1] is after[1]
if after and after[0] is 'INDENT'
@tokens.splice(i + 2, 1)
@tokens.splice(i - 2, 1)
return 0
else if prev and prev[0] is 'TERMINATOR' and after and after[0] is 'INDENT'
@tokens.splice(i + 2, 1)
@tokens[i - 1]: after
@tokens.splice(i, 0, after)
return 1
else if prev and prev[0] isnt 'TERMINATOR' and prev[0] isnt 'INDENT' and prev[0] isnt 'OUTDENT'
@tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]])
@@ -180,7 +172,6 @@ exports.Rewriter: class Rewriter
pre: @tokens[idx - 1]
if (not tok or
(SINGLE_CLOSERS.indexOf(tok[0]) >= 0 and tok[1] isnt ';') or
(pre[0] is ',' and tok[0] is 'PARAM_START') or
(tok[0] is ')' && parens is 0)) and
not (starter is 'ELSE' and tok[0] is 'ELSE')
insertion: if pre[0] is "," then idx - 1 else idx

View File

@@ -1,4 +1,24 @@
results: [1, 2, 3].map (x) ->
x * x
ok results.join(' ') is '1 4 9', 'basic block syntax'
ok results.join(' ') is '1 4 9', 'basic block syntax'
# Chained blocks, with proper indentation levels:
results: []
counter: {
tick: (func) ->
results.push func()
this
}
counter
.tick ->
3
.tick ->
2
.tick ->
1
ok results.join(' ') is '3 2 1'

View File

@@ -35,4 +35,12 @@ class SubClass extends SuperClass
constructor: ->
super 'sub'
ok (new SubClass()).prop is 'top-super-sub'
ok (new SubClass()).prop is 'top-super-sub'
class OneClass
constructor: (name) -> @name: name
class TwoClass extends OneClass
ok (new TwoClass('three')).name is 'three'

View File

@@ -14,6 +14,11 @@ ok y.x.name is 'x'
() ->
# Multiple nested function declarations mixed with implicit calls should not
# cause a syntax error.
(one) -> (two) -> three four, (five) -> six seven, eight, (nine) ->
obj: {
name: "Fred"
@@ -70,7 +75,7 @@ ok result is 10
# And even with strange things like this:
funcs: [(x) -> x, (x) -> x * x]
funcs: [((x) -> x), ((x) -> x * x)]
result: funcs[1] 5
ok result is 25

View File

@@ -23,3 +23,13 @@ ok func()
func
func
# Line3
obj: {
# comment
# comment
# comment
one: 1
# comment
two: 2
# comment
}

View File

@@ -36,6 +36,7 @@ a: '''
ok a is " a\n b\nc"
a: '''
a
@@ -44,3 +45,8 @@ b c
'''
ok a is "a\n\n\nb c"
a: '''more"than"one"quote'''
ok a is 'more"than"one"quote'

View File

@@ -1,4 +1,4 @@
a: [(x) -> x, (x) -> x * x]
a: [((x) -> x), ((x) -> x * x)]
ok a.length is 2