Skip to content
Browse files

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
  • Loading branch information...
1 parent 32cd15f commit 672dd70bdb9666486256e0ef01d41213fcfb4bf9 @jashkenas committed
Showing with 125 additions and 164 deletions.
  1. +117 −158 examples/underscore.coffee
  2. +6 −5 lib/coffee_script/grammar.y
  3. +1 −0 lib/coffee_script/lexer.rb
  4. +1 −1 lib/coffee_script/rewriter.rb
View
275 examples/underscore.coffee
@@ -47,7 +47,7 @@ _.each: obj, iterator, context =>
try
return obj.forEach(iterator, context) if obj.forEach
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)
catch e
throw e if e isnt breaker
@@ -58,34 +58,33 @@ _.each: obj, iterator, context =>
_.map: obj, iterator, context =>
return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
results: []
- mapper: value, index, list => results.push(iterator.call(context, value, index, list))
- _.each(obj, mapper)
+ _.each(obj) 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))
- reducer: value, index, list => memo: iterator.call(context, memo, value, index, list)
- _.each(obj, reducer)
+ _.each(obj) 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))
- reversed: _.clone(_.toArray(obj)).reverse()
- reverser: value, index => memo: iterator.call(context, memo, value, index, obj)
- _.each(reversed, reverser)
+ _.each(_.clone(_.toArray(obj)).reverse()) value, index =>
+ memo: iterator.call(context, memo, value, index, obj)
memo
# Return the first value which passes a truth test.
_.detect: obj, iterator, context =>
result: null
- _.each(obj, (value, index, list =>
+ _.each(obj) value, index, list =>
if iterator.call(context, value, index, list)
result: value
- _.breakLoop()))
+ _.breakLoop()
result
# Return all the elements that pass a truth test. Use JavaScript 1.6's
@@ -187,72 +186,59 @@ _.max: obj, iterator, context =>
# }
# return low;
# };
-#
-# # Convert anything iterable into a real, live array.
-# _.toArray = function(iterable) {
-# if (!iterable) return [];
-# if (iterable.toArray) return iterable.toArray();
-# if (_.isArray(iterable)) return iterable;
-# if (_.isArguments(iterable)) return slice.call(iterable);
-# return _.map(iterable, function(val){ return val; });
-# };
-#
-# # Return the number of elements in an object.
-# _.size = function(obj) {
-# return _.toArray(obj).length;
-# };
-#
-# /*-------------------------- Array Functions: ------------------------------*/
-#
-# # Get the first element of an array. Passing "n" will return the first N
-# # values in the array. Aliased as "head". The "guard" check allows it to work
-# # with _.map.
-# _.first = function(array, n, guard) {
-# return n && !guard ? slice.call(array, 0, n) : array[0];
-# };
-#
-# # Returns everything but the first entry of the array. Aliased as "tail".
-# # 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.
-# _.rest = function(array, index, guard) {
-# return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
-# };
-#
-# # Get the last element of an array.
-# _.last = function(array) {
-# return array[array.length - 1];
-# };
-#
-# # Trim out all falsy values from an array.
-# _.compact = function(array) {
-# return _.select(array, function(value){ return !!value; });
-# };
-#
-# # Return a completely flattened version of an array.
-# _.flatten = function(array) {
-# return _.reduce(array, [], function(memo, value) {
-# if (_.isArray(value)) return memo.concat(_.flatten(value));
-# memo.push(value);
-# return memo;
-# });
-# };
-#
-# # Return a version of the array that does not contain the specified value(s).
-# _.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;
-# });
-# };
-#
+
+# Convert anything iterable into a real, live array.
+_.toArray: iterable =>
+ return [] if (!iterable)
+ return iterable.toArray() if (iterable.toArray)
+ return iterable if (_.isArray(iterable))
+ return slice.call(iterable) if (_.isArguments(iterable))
+ _.values(iterable)
+
+# Return the number of elements in an object.
+_.size: obj => _.toArray(obj).length
+
+# -------------------------- Array Functions: ------------------------------
+
+# Get the first element of an array. Passing "n" will return the first N
+# values in the array. Aliased as "head". The "guard" check allows it to work
+# with _.map.
+_.first: array, n, guard =>
+ if n and not guard then slice.call(array, 0, n) else array[0]
+
+# Returns everything but the first entry of the array. Aliased as "tail".
+# 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.
+_.rest: array, index, guard =>
+ slice.call(array, if _.isUndefined(index) or guard then 1 else index)
+
+# Get the last element of an array.
+_.last: array => array[array.length - 1]
+
+# Trim out all falsy values from an array.
+_.compact: array => el for el in array when el
+
+# 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)
+ memo
+
+# Return a version of the array that does not contain the specified value(s).
+_.without: array =>
+ values: _.rest(arguments)
+ _.select(array, (value => not _.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: 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)
+ memo
+
# # Produce an array that contains every item shared between all the
# # passed-in arrays.
# _.intersect = function(array) {
@@ -316,21 +302,18 @@ _.bind: func, obj =>
args: _.rest(arguments, 2)
=> func.apply(obj or root, args.concat(_.toArray(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 = function(obj) {
-# var funcs = _.rest(arguments);
-# if (funcs.length == 0) funcs = _.functions(obj);
-# _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
-# return obj;
-# };
-#
-# # Delays a function for the given number of milliseconds, and then calls
-# # it with the arguments supplied.
-# _.delay = function(func, wait) {
-# var args = _.rest(arguments, 2);
-# return setTimeout(function(){ return func.apply(func, args); }, wait);
-# };
+# 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)))
+ 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)
+ setTimeout((=> func.apply(func, args)), wait)
# Defers a function, scheduling it to run after the current call stack has
# cleared.
@@ -352,37 +335,30 @@ _.compose: =>
args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0]
args[0]
-# /* ------------------------- Object Functions: ---------------------------- */
-#
-# # Retrieve the names of an object's properties.
-# _.keys = function(obj) {
-# if(_.isArray(obj)) return _.range(0, obj.length);
-# var keys = [];
-# for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key);
-# return keys;
-# };
-#
-# # Retrieve the values of an object's properties.
-# _.values = function(obj) {
-# return _.map(obj, _.identity);
-# };
-#
-# # Return a sorted list of the function names available in Underscore.
-# _.functions = function(obj) {
-# return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
-# };
-#
-# # Extend a given object with all of the properties in a source object.
-# _.extend = function(destination, source) {
-# for (var property in source) destination[property] = source[property];
-# return destination;
-# };
-#
-# # Create a (shallow-cloned) duplicate of an object.
-# _.clone = function(obj) {
-# if (_.isArray(obj)) return obj.slice(0);
-# return _.extend({}, obj);
-# };
+# ------------------------- Object Functions: ----------------------------
+
+# Retrieve the names of an object's properties.
+_.keys: obj =>
+ return _.range(0, obj.length) if _.isArray(obj)
+ key for val, key in obj
+
+# Retrieve the values of an object's properties.
+_.values: obj =>
+ _.map(obj, _.identity)
+
+# Return a sorted list of the function names available in Underscore.
+_.functions: obj =>
+ _.select(_.keys(obj), key => _.isFunction(obj[key])).sort()
+
+# Extend a given object with all of the properties in a source object.
+_.extend: destination, source =>
+ destination[key]: val for val, key in source
+ destination
+
+# Create a (shallow-cloned) duplicate of an object.
+_.clone: obj =>
+ return obj.slice(0) if _.isArray(ob)
+ _.extend({}, obj)
# Perform a deep comparison to check if two objects are equal.
_.isEqual: a, b =>
@@ -444,16 +420,6 @@ _.tap: obj, interceptor =>
interceptor(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: --------------------------
# Run Underscore.js in noConflict mode, returning the '_' variable to its
@@ -507,11 +473,11 @@ _.tail: _.rest
_.methods: _.functions
# /*------------------------ Setup the OOP Wrapper: --------------------------*/
-#
-# # Helper function to continue chaining intermediate results.
-# var result = function(obj, chain) {
-# return chain ? _(obj).chain() : obj;
-# };
+
+# 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(_), function(name) {
@@ -530,24 +496,17 @@ _.methods: _.functions
# return result(this._wrapped, this._chain);
# };
# });
-#
-# # Add all accessor Array functions to the wrapper.
-# _.each(['concat', 'join', 'slice'], function(name) {
-# var method = Array.prototype[name];
-# wrapper.prototype[name] = function() {
-# return result(method.apply(this._wrapped, arguments), this._chain);
-# };
-# });
-#
-# # Start chaining a wrapped Underscore object.
-# wrapper.prototype.chain = function() {
-# this._chain = true;
-# return this;
-# };
-#
-# # Extracts the result from a wrapped and chained object.
-# wrapper.prototype.value = function() {
-# return this._wrapped;
-# };
-#
-#
+
+# Add all accessor Array functions to the wrapper.
+_.each(['concat', 'join', 'slice']) name =>
+ method: Array.prototype[name]
+ wrapper.prototype[name]: =>
+ result(method.apply(this._wrapped, arguments), this._chain)
+
+# Start chaining a wrapped Underscore object.
+wrapper.prototype.chain: =>
+ this._chain: true
+ this
+
+# Extracts the result from a wrapped and chained object.
+wrapper.prototype.value: => this._wrapped
View
11 lib/coffee_script/grammar.y
@@ -8,8 +8,8 @@ token IDENTIFIER PROPERTY_ACCESS
token CODE PARAM PARAM_SPLAT NEW RETURN
token TRY CATCH FINALLY THROW
token BREAK CONTINUE
-token FOR IN BY WHILE
-token SWITCH WHEN
+token FOR IN BY WHEN WHILE
+token SWITCH LEADING_WHEN
token DELETE INSTANCEOF TYPEOF
token SUPER EXTENDS
token NEWLINE
@@ -32,7 +32,7 @@ prechigh
left '.'
right INDENT
left OUTDENT
- right WHEN IN BY
+ right WHEN LEADING_WHEN IN BY
right THROW FOR NEW SUPER
left EXTENDS
left ASSIGN '||=' '&&='
@@ -367,8 +367,9 @@ rule
# An individual when.
When:
- 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 { 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
;
View
1 lib/coffee_script/lexer.rb
@@ -85,6 +85,7 @@ def identifier_token
# Keywords are special identifiers tagged with their own name,
# 'if' will result in an [:IF, "if"] token.
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] == '.')
token(tag, identifier)
@i += identifier.length
View
2 lib/coffee_script/rewriter.rb
@@ -27,7 +27,7 @@ class Rewriter
# Single-line flavors of block expressions that have unclosed endings.
# The grammar can't disambiguate them, so we insert the implicit indentation.
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
# a time. This could certainly be changed into a single pass through the

0 comments on commit 672dd70

Please sign in to comment.
Something went wrong with that request. Please try again.