more underscore examples raised a slight bug with a lexing ambiguity between leading whens (in switches), and trailing whens (in comprehensions) -- made two different tokens to distinguish them

This commit is contained in:
Jeremy Ashkenas 2010-01-03 22:25:38 -05:00
parent 32cd15f038
commit 672dd70bdb
4 changed files with 125 additions and 164 deletions

View File

@ -47,7 +47,7 @@ _.each: obj, iterator, context =>
try try
return obj.forEach(iterator, context) if obj.forEach return obj.forEach(iterator, context) if obj.forEach
if _.isArray(obj) or _.isArguments(obj) if _.isArray(obj) or _.isArguments(obj)
return iterator.call(context, item, i, obj) for item, i in obj return iterator.call(context, item, i, obj) for item, i in obj
iterator.call(context, obj[key], key, obj) for key in _.keys(obj) iterator.call(context, obj[key], key, obj) for key in _.keys(obj)
catch e catch e
throw e if e isnt breaker throw e if e isnt breaker
@ -58,34 +58,33 @@ _.each: obj, iterator, context =>
_.map: obj, iterator, context => _.map: obj, iterator, context =>
return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
results: [] results: []
mapper: value, index, list => results.push(iterator.call(context, value, index, list)) _.each(obj) value, index, list =>
_.each(obj, mapper) results.push(iterator.call(context, value, index, list))
results results
# Reduce builds up a single result from a list of values. Also known as # 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. # inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
_.reduce: obj, memo, iterator, context => _.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 (obj and _.isFunction(obj.reduce))
reducer: value, index, list => memo: iterator.call(context, memo, value, index, list) _.each(obj) value, index, list =>
_.each(obj, reducer) memo: iterator.call(context, memo, value, index, list)
memo memo
# The right-associative version of reduce, also known as foldr. Uses # The right-associative version of reduce, also known as foldr. Uses
# JavaScript 1.8's version of reduceRight, if available. # JavaScript 1.8's version of reduceRight, if available.
_.reduceRight: obj, memo, iterator, context => _.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 (obj and _.isFunction(obj.reduceRight))
reversed: _.clone(_.toArray(obj)).reverse() _.each(_.clone(_.toArray(obj)).reverse()) value, index =>
reverser: value, index => memo: iterator.call(context, memo, value, index, obj) memo: iterator.call(context, memo, value, index, obj)
_.each(reversed, reverser)
memo memo
# Return the first value which passes a truth test. # Return the first value which passes a truth test.
_.detect: obj, iterator, context => _.detect: obj, iterator, context =>
result: null result: null
_.each(obj, (value, index, list => _.each(obj) value, index, list =>
if iterator.call(context, value, index, list) if iterator.call(context, value, index, list)
result: value result: value
_.breakLoop())) _.breakLoop()
result result
# Return all the elements that pass a truth test. Use JavaScript 1.6's # Return all the elements that pass a truth test. Use JavaScript 1.6's
@ -187,72 +186,59 @@ _.max: obj, iterator, context =>
# } # }
# return low; # return low;
# }; # };
#
# # Convert anything iterable into a real, live array. # Convert anything iterable into a real, live array.
# _.toArray = function(iterable) { _.toArray: iterable =>
# if (!iterable) return []; return [] if (!iterable)
# if (iterable.toArray) return iterable.toArray(); return iterable.toArray() if (iterable.toArray)
# if (_.isArray(iterable)) return iterable; return iterable if (_.isArray(iterable))
# if (_.isArguments(iterable)) return slice.call(iterable); return slice.call(iterable) if (_.isArguments(iterable))
# return _.map(iterable, function(val){ return val; }); _.values(iterable)
# };
# # Return the number of elements in an object.
# # Return the number of elements in an object. _.size: obj => _.toArray(obj).length
# _.size = function(obj) {
# return _.toArray(obj).length; # -------------------------- Array Functions: ------------------------------
# };
# # Get the first element of an array. Passing "n" will return the first N
# /*-------------------------- Array Functions: ------------------------------*/ # values in the array. Aliased as "head". The "guard" check allows it to work
# # with _.map.
# # Get the first element of an array. Passing "n" will return the first N _.first: array, n, guard =>
# # values in the array. Aliased as "head". The "guard" check allows it to work if n and not guard then slice.call(array, 0, n) else array[0]
# # with _.map.
# _.first = function(array, n, guard) { # Returns everything but the first entry of the array. Aliased as "tail".
# return n && !guard ? slice.call(array, 0, n) : array[0]; # Especially useful on the arguments object. Passing an "index" will return
# }; # the rest of the values in the array from that index onward. The "guard"
# # check allows it to work with _.map.
# # Returns everything but the first entry of the array. Aliased as "tail". _.rest: array, index, guard =>
# # Especially useful on the arguments object. Passing an "index" will return slice.call(array, if _.isUndefined(index) or guard then 1 else index)
# # the rest of the values in the array from that index onward. The "guard"
# //check allows it to work with _.map. # Get the last element of an array.
# _.rest = function(array, index, guard) { _.last: array => array[array.length - 1]
# return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
# }; # Trim out all falsy values from an array.
# _.compact: array => el for el in array when el
# # Get the last element of an array.
# _.last = function(array) { # Return a completely flattened version of an array.
# return array[array.length - 1]; _.flatten: array =>
# }; _.reduce(array, []) memo, value =>
# return memo.concat(_.flatten(value)) if _.isArray(value)
# # Trim out all falsy values from an array. memo.push(value)
# _.compact = function(array) { memo
# return _.select(array, function(value){ return !!value; });
# }; # Return a version of the array that does not contain the specified value(s).
# _.without: array =>
# # Return a completely flattened version of an array. values: _.rest(arguments)
# _.flatten = function(array) { _.select(array, (value => not _.include(values, value)))
# return _.reduce(array, [], function(memo, value) {
# if (_.isArray(value)) return memo.concat(_.flatten(value)); # Produce a duplicate-free version of the array. If the array has already
# memo.push(value); # been sorted, you have the option of using a faster algorithm.
# return memo; _.uniq: array, isSorted =>
# }); _.reduce(array, []) memo, el, i =>
# }; if (i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el)))
# memo.push(el)
# # Return a version of the array that does not contain the specified value(s). memo
# _.without = function(array) {
# var values = _.rest(arguments);
# return _.select(array, function(value){ return !_.include(values, value); });
# };
#
# # 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 = function(array, isSorted) {
# return _.reduce(array, [], function(memo, el, i) {
# if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
# return memo;
# });
# };
#
# # Produce an array that contains every item shared between all the # # Produce an array that contains every item shared between all the
# # passed-in arrays. # # passed-in arrays.
# _.intersect = function(array) { # _.intersect = function(array) {
@ -316,21 +302,18 @@ _.bind: func, obj =>
args: _.rest(arguments, 2) args: _.rest(arguments, 2)
=> func.apply(obj or root, args.concat(_.toArray(arguments))) => func.apply(obj or root, args.concat(_.toArray(arguments)))
# # Bind all of an object's methods to that object. Useful for ensuring that # Bind all of an object's methods to that object. Useful for ensuring that
# # all callbacks defined on an object belong to it. # all callbacks defined on an object belong to it.
# _.bindAll = function(obj) { _.bindAll: obj =>
# var funcs = _.rest(arguments); funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
# if (funcs.length == 0) funcs = _.functions(obj); _.each(funcs, (f => obj[f]: _.bind(obj[f], obj)))
# _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); obj
# return obj;
# }; # Delays a function for the given number of milliseconds, and then calls
# # it with the arguments supplied.
# # Delays a function for the given number of milliseconds, and then calls _.delay: func, wait =>
# # it with the arguments supplied. args: _.rest(arguments, 2)
# _.delay = function(func, wait) { setTimeout((=> func.apply(func, args)), wait)
# var args = _.rest(arguments, 2);
# return setTimeout(function(){ return func.apply(func, args); }, wait);
# };
# Defers a function, scheduling it to run after the current call stack has # Defers a function, scheduling it to run after the current call stack has
# cleared. # cleared.
@ -352,37 +335,30 @@ _.compose: =>
args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0] args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0]
args[0] args[0]
# /* ------------------------- Object Functions: ---------------------------- */ # ------------------------- Object Functions: ----------------------------
#
# # Retrieve the names of an object's properties. # Retrieve the names of an object's properties.
# _.keys = function(obj) { _.keys: obj =>
# if(_.isArray(obj)) return _.range(0, obj.length); return _.range(0, obj.length) if _.isArray(obj)
# var keys = []; key for val, key in obj
# for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key);
# return keys; # Retrieve the values of an object's properties.
# }; _.values: obj =>
# _.map(obj, _.identity)
# # Retrieve the values of an object's properties.
# _.values = function(obj) { # Return a sorted list of the function names available in Underscore.
# return _.map(obj, _.identity); _.functions: obj =>
# }; _.select(_.keys(obj), key => _.isFunction(obj[key])).sort()
#
# # Return a sorted list of the function names available in Underscore. # Extend a given object with all of the properties in a source object.
# _.functions = function(obj) { _.extend: destination, source =>
# return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); destination[key]: val for val, key in source
# }; destination
#
# # Extend a given object with all of the properties in a source object. # Create a (shallow-cloned) duplicate of an object.
# _.extend = function(destination, source) { _.clone: obj =>
# for (var property in source) destination[property] = source[property]; return obj.slice(0) if _.isArray(ob)
# return destination; _.extend({}, obj)
# };
#
# # Create a (shallow-cloned) duplicate of an object.
# _.clone = function(obj) {
# if (_.isArray(obj)) return obj.slice(0);
# return _.extend({}, obj);
# };
# Perform a deep comparison to check if two objects are equal. # Perform a deep comparison to check if two objects are equal.
_.isEqual: a, b => _.isEqual: a, b =>
@ -444,16 +420,6 @@ _.tap: obj, interceptor =>
interceptor(obj) interceptor(obj)
obj obj
# # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString
# # functions based on their toString identifiers.
# var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String'];
# for (var i=0, l=types.length; i<l; i++) {
# (function() {
# var identifier = '[object ' + types[i] + ']';
# _['is' + types[i]] = function(obj) { return toString.call(obj) == identifier; };
# })();
# }
# -------------------------- Utility Functions: -------------------------- # -------------------------- Utility Functions: --------------------------
# Run Underscore.js in noConflict mode, returning the '_' variable to its # Run Underscore.js in noConflict mode, returning the '_' variable to its
@ -507,11 +473,11 @@ _.tail: _.rest
_.methods: _.functions _.methods: _.functions
# /*------------------------ Setup the OOP Wrapper: --------------------------*/ # /*------------------------ Setup the OOP Wrapper: --------------------------*/
#
# # Helper function to continue chaining intermediate results. # Helper function to continue chaining intermediate results.
# var result = function(obj, chain) { result: obj, chain =>
# return chain ? _(obj).chain() : obj; if chain then _(obj).chain() else obj
# };
# #
# # Add all of the Underscore functions to the wrapper object. # # Add all of the Underscore functions to the wrapper object.
# _.each(_.functions(_), function(name) { # _.each(_.functions(_), function(name) {
@ -530,24 +496,17 @@ _.methods: _.functions
# return result(this._wrapped, this._chain); # return result(this._wrapped, this._chain);
# }; # };
# }); # });
#
# # Add all accessor Array functions to the wrapper. # Add all accessor Array functions to the wrapper.
# _.each(['concat', 'join', 'slice'], function(name) { _.each(['concat', 'join', 'slice']) name =>
# var method = Array.prototype[name]; method: Array.prototype[name]
# wrapper.prototype[name] = function() { wrapper.prototype[name]: =>
# return result(method.apply(this._wrapped, arguments), this._chain); result(method.apply(this._wrapped, arguments), this._chain)
# };
# }); # Start chaining a wrapped Underscore object.
# wrapper.prototype.chain: =>
# # Start chaining a wrapped Underscore object. this._chain: true
# wrapper.prototype.chain = function() { this
# this._chain = true;
# return this; # Extracts the result from a wrapped and chained object.
# }; wrapper.prototype.value: => this._wrapped
#
# # Extracts the result from a wrapped and chained object.
# wrapper.prototype.value = function() {
# return this._wrapped;
# };
#
#

View File

@ -8,8 +8,8 @@ token IDENTIFIER PROPERTY_ACCESS
token CODE PARAM PARAM_SPLAT NEW RETURN token CODE PARAM PARAM_SPLAT NEW RETURN
token TRY CATCH FINALLY THROW token TRY CATCH FINALLY THROW
token BREAK CONTINUE token BREAK CONTINUE
token FOR IN BY WHILE token FOR IN BY WHEN WHILE
token SWITCH WHEN token SWITCH LEADING_WHEN
token DELETE INSTANCEOF TYPEOF token DELETE INSTANCEOF TYPEOF
token SUPER EXTENDS token SUPER EXTENDS
token NEWLINE token NEWLINE
@ -32,7 +32,7 @@ prechigh
left '.' left '.'
right INDENT right INDENT
left OUTDENT left OUTDENT
right WHEN IN BY right WHEN LEADING_WHEN IN BY
right THROW FOR NEW SUPER right THROW FOR NEW SUPER
left EXTENDS left EXTENDS
left ASSIGN '||=' '&&=' left ASSIGN '||=' '&&='
@ -367,8 +367,9 @@ rule
# An individual when. # An individual when.
When: When:
WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| WHEN Expression Block Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } | LEADING_WHEN Expression Block
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| Comment | Comment
; ;

View File

@ -85,6 +85,7 @@ module CoffeeScript
# Keywords are special identifiers tagged with their own name, # Keywords are special identifiers tagged with their own name,
# 'if' will result in an [:IF, "if"] token. # 'if' will result in an [:IF, "if"] token.
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag)
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.') @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.')
token(tag, identifier) token(tag, identifier)
@i += identifier.length @i += identifier.length

View File

@ -27,7 +27,7 @@ module CoffeeScript
# Single-line flavors of block expressions that have unclosed endings. # Single-line flavors of block expressions that have unclosed endings.
# The grammar can't disambiguate them, so we insert the implicit indentation. # The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN]
SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :WHEN] SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN]
# Rewrite the token stream in multiple passes, one logical filter at # Rewrite the token stream in multiple passes, one logical filter at
# a time. This could certainly be changed into a single pass through the # a time. This could certainly be changed into a single pass through the