mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-01-14 01:07:55 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44398d044f | ||
|
|
3feb874b1e | ||
|
|
f7183e6918 | ||
|
|
707cd2d734 | ||
|
|
c11c3ed2f2 | ||
|
|
5fd0972b5d | ||
|
|
70cb195e6f | ||
|
|
c219adffd5 | ||
|
|
cd6dd5abfd | ||
|
|
29ece0e6ba | ||
|
|
45bad556ab | ||
|
|
30cf63ec92 | ||
|
|
969c2e528d | ||
|
|
bb2bf7ce57 | ||
|
|
2969e156c7 | ||
|
|
b08995cbcc | ||
|
|
47f71f9193 | ||
|
|
fec2eaef7e | ||
|
|
56eb474bf3 |
4
Cakefile
4
Cakefile
@@ -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', ->
|
||||
|
||||
8
bin/cake
8
bin/cake
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
15
index.html
15
index.html
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
297
lib/lexer.js
297
lib/lexer.js
@@ -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;
|
||||
};
|
||||
})();
|
||||
|
||||
51
lib/nodes.js
51
lib/nodes.js
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"description": "Unfancy JavaScript",
|
||||
"keywords": ["javascript", "language"],
|
||||
"author": "Jeremy Ashkenas",
|
||||
"version": "0.5.3"
|
||||
"version": "0.5.4"
|
||||
}
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
|
||||
252
src/lexer.coffee
252
src/lexer.coffee
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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
|
||||
|
||||
@@ -23,3 +23,13 @@ ok func()
|
||||
func
|
||||
func
|
||||
# Line3
|
||||
|
||||
obj: {
|
||||
# comment
|
||||
# comment
|
||||
# comment
|
||||
one: 1
|
||||
# comment
|
||||
two: 2
|
||||
# comment
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
@@ -1,4 +1,4 @@
|
||||
a: [(x) -> x, (x) -> x * x]
|
||||
a: [((x) -> x), ((x) -> x * x)]
|
||||
|
||||
ok a.length is 2
|
||||
|
||||
|
||||
Reference in New Issue
Block a user