From 3f30712ca147ab0827ca9bb496e48e564bbe76aa Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 00:16:38 -0500 Subject: [PATCH] fixing a nasty little bug with not dup'ing a string in Scope.rb, causing later functions to start their free_variables where previous functions left off, because they shared their ancestor's @temp_variable string --- examples/underscore.coffee | 332 +++++++++++++++++++------------------ lib/coffee_script/scope.rb | 8 +- 2 files changed, 172 insertions(+), 168 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 8e2328e6..ea1ae34e 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -17,7 +17,9 @@ 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 +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 @@ -36,7 +38,7 @@ hasOwnProperty: Object.prototype.hasOwnProperty propertyIsEnumerable: Object.prototype.propertyIsEnumerable # Current version. -_.VERSION: '0.5.1' +_.VERSION: '0.5.2' # ------------------------ Collection Functions: --------------------------- @@ -92,15 +94,15 @@ _.detect: obj, iterator, context => _.select: obj, iterator, context => if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context) results: [] - _.each(obj, (value, index, list => - results.push(value) if iterator.call(context, value, index, list))) + _.each(obj) value, index, list => + results.push(value) if iterator.call(context, value, index, list) results # Return all the elements for which a truth test fails. _.reject: obj, iterator, context => results: [] - _.each(obj, (value, index, list => - results.push(value) if not iterator.call(context, value, index, list))) + _.each(obj) 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 @@ -109,8 +111,8 @@ _.all: obj, iterator, context => iterator ||= _.identity return obj.every(iterator, context) if obj and _.isFunction(obj.every) result: true - _.each(obj, (value, index, list => - _.breakLoop() unless result: result and iterator.call(context, value, index, list))) + _.each(obj) value, index, list => + _.breakLoop() unless (result: result and iterator.call(context, value, index, list)) result # Determine if at least one element in the object matches a truth test. Use @@ -119,8 +121,8 @@ _.any: obj, iterator, context => iterator ||= _.identity return obj.some(iterator, context) if obj and _.isFunction(obj.some) result: false - _.each(obj, (value, index, list => - _.breakLoop() if (result: iterator.call(context, value, index, list)))) + _.each(obj) value, index, list => + _.breakLoop() if (result: iterator.call(context, value, index, list)) result # Determine if a given value is included in the array or object, @@ -128,15 +130,15 @@ _.any: obj, iterator, context => _.include: obj, target => return _.indexOf(obj, target) isnt -1 if _.isArray(obj) found: false - _.each(obj, (value => - _.breakLoop() if (found: value is target))) + _.each(obj) value => + _.breakLoop() if found: value is target found # Invoke a method with arguments on every item in a collection. _.invoke: obj, method => args: _.rest(arguments, 2) - _.map(obj, (value => - (if method then value[method] else value).apply(value, args))) + _.map(obj) value => + (if method then value[method] else value).apply(value, args) # Convenience version of a common use case of map: fetching a property. _.pluck: obj, key => @@ -144,48 +146,40 @@ _.pluck: obj, key => # Return the maximum item or (item-based computation). _.max: obj, iterator, context => - return Math.max.apply(Math, obj) if !iterator and _.isArray(obj) + return Math.max.apply(Math, obj) if not iterator and _.isArray(obj) result: {computed: -Infinity} - _.each(obj, (value, index, list => + _.each(obj) value, index, list => computed: if iterator then iterator.call(context, value, index, list) else value - computed >= result.computed and (result: {value: value, computed: computed}))) + computed >= result.computed and (result: {value: value, computed: computed}) result.value -# # Return the minimum element (or element-based computation). -# _.min = function(obj, iterator, context) { -# if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); -# var result = {computed : Infinity}; -# _.each(obj, function(value, index, list) { -# var computed = iterator ? iterator.call(context, value, index, list) : value; -# computed < result.computed && (result = {value : value, computed : computed}); -# }); -# return result.value; -# }; -# -# # Sort the object's values by a criteria produced by an iterator. -# _.sortBy = function(obj, iterator, context) { -# return _.pluck(_.map(obj, function(value, index, list) { -# return { -# value : value, -# criteria : iterator.call(context, value, index, list) -# }; -# }).sort(function(left, right) { -# var a = left.criteria, b = right.criteria; -# return a < b ? -1 : a > b ? 1 : 0; -# }), 'value'); -# }; -# -# # Use a comparator function to figure out at what index an object should -# # be inserted so as to maintain order. Uses binary search. -# _.sortedIndex = function(array, obj, iterator) { -# iterator = iterator || _.identity; -# var low = 0, high = array.length; -# while (low < high) { -# var mid = (low + high) >> 1; -# iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; -# } -# return low; -# }; +# Return the minimum element (or element-based computation). +_.min: obj, iterator, context => + return Math.min.apply(Math, obj) if not iterator and _.isArray(obj) + result: {computed: Infinity} + _.each(obj) value, index, list => + computed: if iterator then iterator.call(context, value, index, list) else value + computed < result.computed and (result: {value: value, computed: computed}) + result.value + +# Sort the object's values by a criteria produced by an iterator. +_.sortBy: obj, iterator, context => + _.pluck(((_.map(obj) value, index, list => + {value: value, criteria: iterator.call(context, value, index, list)} + ).sort() left, right => + a: left.criteria; b: right.criteria + if a < b then -1 else if a > b then 1 else 0 + ), 'value') + +# Use a comparator function to figure out at what index an object should +# be inserted so as to maintain order. Uses binary search. +_.sortedIndex: array, obj, iterator => + iterator ||= _.identity + 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 + low # Convert anything iterable into a real, live array. _.toArray: iterable => @@ -239,60 +233,60 @@ _.uniq: array, isSorted => memo.push(el) memo -# # Produce an array that contains every item shared between all the -# # passed-in arrays. -# _.intersect = function(array) { -# var rest = _.rest(arguments); -# return _.select(_.uniq(array), function(item) { -# return _.all(rest, function(other) { -# return _.indexOf(other, item) >= 0; -# }); -# }); -# }; -# -# # Zip together multiple lists into a single array -- elements that share -# # an index go together. -# _.zip = function() { -# var args = _.toArray(arguments); -# var length = _.max(_.pluck(args, 'length')); -# var results = new Array(length); -# for (var i=0; i 0 ? i - stop : stop - i) >= 0) return range; -# range[idx++] = i; -# } -# }; +# Produce an array that contains every item shared between all the +# passed-in arrays. +_.intersect: array => + rest: _.rest(arguments) + _.select(_.uniq(array)) item => + _.all(rest) other => + _.indexOf(other, item) >= 0 + +# Zip together multiple lists into a single array -- elements that share +# an index go together. +_.zip: => + args: _.toArray(arguments) + length: _.max(_.pluck(args, 'length')) + results: new Array(length) + results[i]: _.pluck(args, String(i)) for i in [0...length] + results + +# If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), +# 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 + i: 0; l: array.length + while l - i + if array[i] is item then return i else i++ + -1 + +# Provide JavaScript 1.6's lastIndexOf, delegating to the native function, +# if possible. +_.lastIndexOf: array, item => + return array.lastIndexOf(item) if array.lastIndexOf + i: array.length + while i + if array[i] is item then return i else i-- + -1 + +# Generate an integer Array containing an arithmetic progression. A port of +# the native Python range() function. See: +# http://docs.python.org/library/functions.html#range +_.range: start, stop, step => + a: _.toArray(arguments) + solo: a.length <= 1 + i: start: if solo then 0 else a[0]; + stop: if solo then a[0] else a[1]; + step: a[2] or 1 + len: Math.ceil((stop - start) / step) + return [] if len <= 0 + range: new Array(len) + idx: 0 + while true + return range if (if step > 0 then i - stop else stop - i) >= 0 + idx++ + range[idx]: i + i+= step # ----------------------- Function Functions: ----------------------------- @@ -332,7 +326,7 @@ _.compose: => funcs: _.toArray(arguments) => args: _.toArray(arguments) - 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] # ------------------------- Object Functions: ---------------------------- @@ -352,14 +346,20 @@ _.functions: obj => # 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[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) + 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) + obj + # Perform a deep comparison to check if two objects are equal. _.isEqual: a, b => # Check object identity. @@ -396,29 +396,41 @@ _.isEqual: a, b => return true # Is a given array or object empty? -_.isEmpty: obj => _.keys(obj).length is 0 +_.isEmpty: obj => _.keys(obj).length is 0 # Is a given value a DOM element? -_.isElement: obj => !!(obj and obj.nodeType is 1) +_.isElement: obj => !!(obj and obj.nodeType is 1) + +# Is a given value an array? +_.isArray: obj => !!(obj and obj.concat and obj.unshift) # Is a given variable an arguments object? -_.isArguments: obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length') +_.isArguments: obj => !!(obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length')) + +# Is the given value a function? +_.isFunction: obj => !!(obj and obj.constructor and obj.call and obj.apply) + +# Is the given value a string? +_.isString: obj => !!(obj is '' or (obj and obj.charCodeAt and obj.substr)) + +# Is a given value a number? +_.isNumber: obj => !!(toString.call(obj) is '[object Number]') + +# Is a given value a Date? +_.isDate: obj => !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear) + +# Is the given value a regular expression? +_.isRegExp: obj => !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false)) # Is the given value NaN -- this one is interesting. NaN != NaN, and # isNaN(undefined) == true, so we make sure it's a number first. -_.isNaN: obj => _.isNumber(obj) and isNaN(obj) +_.isNaN: obj => !!(_.isNumber(obj) and window.isNaN(obj)) # Is a given value equal to null? -_.isNull: obj => obj is null +_.isNull: obj => obj is null # Is a given variable undefined? -_.isUndefined: obj => typeof obj is 'undefined' - -# 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) - obj +_.isUndefined: obj => typeof obj is 'undefined' # -------------------------- Utility Functions: -------------------------- @@ -434,31 +446,28 @@ _.identity: value => value # Break out of the middle of an iteration. _.breakLoop: => throw breaker -# # Generate a unique integer id (unique within the entire client session). -# # Useful for temporary DOM ids. -# var idCounter = 0; -# _.uniqueId = function(prefix) { -# var id = idCounter++; -# return prefix ? prefix + id : id; -# }; -# -# # JavaScript templating a-la ERB, pilfered from John Resig's -# # "Secrets of the JavaScript Ninja", page 83. -# _.template = function(str, data) { -# var fn = new Function('obj', -# 'var p=[],print=function(){p.push.apply(p,arguments);};' + -# 'with(obj){p.push(\'' + -# str -# .replace(/[\r\t\n]/g, " ") -# .split("<%").join("\t") -# .replace(/((^|%>)[^\t]*)'/g, "$1\r") -# .replace(/\t=(.*?)%>/g, "',$1,'") -# .split("\t").join("');") -# .split("%>").join("p.push('") -# .split("\r").join("\\'") -# + "');}return p.join('');"); -# return data ? fn(data) : fn; -# }; +# Generate a unique integer id (unique within the entire client session). +# Useful for temporary DOM ids. +idCounter: 0 +_.uniqueId: prefix => + (prefix or '') + idCounter++ + +# JavaScript templating a-la ERB, pilfered from John Resig's +# "Secrets of the JavaScript Ninja", page 83. +_.template: str, data => + `var fn = new Function('obj', + 'var p=[],print=function(){p.push.apply(p,arguments);};' + + 'with(obj){p.push(\'' + + str. + replace(/[\r\t\n]/g, " "). + split("<%").join("\t"). + replace(/((^|%>)[^\t]*)'/g, "$1\r"). + replace(/\t=(.*?)%>/g, "',$1,'"). + split("\t").join("');"). + split("%>").join("p.push('"). + split("\r").join("\\'") + + "');}return p.join('');")` + if data then fn(data) else fn # ------------------------------- Aliases ---------------------------------- @@ -478,24 +487,19 @@ _.methods: _.functions result: obj, chain => if chain then _(obj).chain() else obj -# -# # Add all of the Underscore functions to the wrapper object. -# _.each(_.functions(_), function(name) { -# var method = _[name]; -# wrapper.prototype[name] = function() { -# unshift.call(arguments, this._wrapped); -# return result(method.apply(_, arguments), this._chain); -# }; -# }); -# -# # Add all mutator Array functions to the wrapper. -# _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { -# var method = Array.prototype[name]; -# wrapper.prototype[name] = function() { -# method.apply(this._wrapped, arguments); -# return result(this._wrapped, this._chain); -# }; -# }); +# Add all of the Underscore functions to the wrapper object. +_.each(_.functions(_)) name => + method: _[name] + wrapper.prototype[name]: => + unshift.call(arguments, this._wrapped) + result(method.apply(_, arguments), this._chain) + +# Add all mutator Array functions to the wrapper. +_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']) name => + method: Array.prototype[name] + wrapper.prototype[name]: => + method.apply(this._wrapped, arguments) + result(this._wrapped, this._chain) # Add all accessor Array functions to the wrapper. _.each(['concat', 'join', 'slice']) name => diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index 303e5f2b..5fe57c3a 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -12,12 +12,12 @@ module CoffeeScript def initialize(parent, expressions) @parent, @expressions = parent, expressions @variables = {} - @temp_variable = @parent ? @parent.temp_variable : '__a' + @temp_variable = @parent ? @parent.temp_variable.dup : '__a' end # Look up a variable in lexical scope, or declare it if not found. def find(name, remote=false) - found = check(name, remote) + found = check(name) return found if found || remote @variables[name.to_sym] = :var found @@ -30,9 +30,9 @@ module CoffeeScript end # Just check to see if a variable has already been declared. - def check(name, remote=false) + def check(name) return true if @variables[name.to_sym] - @parent && @parent.find(name, true) + !!(@parent && @parent.check(name)) end # You can reset a found variable on the immediate scope.