diff --git a/documentation/coffee/expressions_try.coffee b/documentation/coffee/expressions_try.coffee index 3fe3a7f4..7b635287 100644 --- a/documentation/coffee/expressions_try.coffee +++ b/documentation/coffee/expressions_try.coffee @@ -2,5 +2,5 @@ alert( try nonexistent / undefined catch error - "The error is: " + error + "Caught an error: " + error ) \ No newline at end of file diff --git a/documentation/coffee/object_comprehensions.coffee b/documentation/coffee/object_comprehensions.coffee new file mode 100644 index 00000000..6e5b8b6e --- /dev/null +++ b/documentation/coffee/object_comprehensions.coffee @@ -0,0 +1,3 @@ +years_old: {max: 10, ida: 9, tim: 11} + +ages: child + " is " + age for child, age ino years_old \ No newline at end of file diff --git a/documentation/coffee/range_comprehensions.coffee b/documentation/coffee/range_comprehensions.coffee index d13a669a..63ee92e2 100644 --- a/documentation/coffee/range_comprehensions.coffee +++ b/documentation/coffee/range_comprehensions.coffee @@ -1,3 +1,6 @@ -for i in [0...eggs.length] by 12 - dozen_eggs: eggs[i...i+12] - deliver(new egg_carton(dozen)) +countdown: num for num in [10..1] + +egg_delivery: => + for i in [0...eggs.length] by 12 + dozen_eggs: eggs[i...i+12] + deliver(new egg_carton(dozen)) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index d50bc725..a396ce3a 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -399,12 +399,14 @@ coffee --print app/scripts/*.coffee > concatenation.js

If you know the start and end of your loop, or would like to step through in fixed-size increments, you can use a range to specify the start and - end of your comprehension: + end of your comprehension. (The long line-breaking "for" definitions in + the compiled JS below allow ranges to count downwards, as well as upwards).

- <%= code_for('range_comprehensions') %> + <%= code_for('range_comprehensions', 'countdown') %>

- Comprehensions can also be used to iterate over the values and keys in - an object: + Comprehensions can also be used to iterate over the keys and values in + an object. Use ino to signal comprehension over an object instead + of an array.

<%= code_for('object_comprehensions', 'ages.join(", ")') %> diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index c6557905..f7ce5e09 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -1,5 +1,5 @@ (function(){ - var __a, __b, __c, __d, __e, __f, __g, lunch; + var __a, __b, __c, __d, __e, __f, __g, food, lunch, roid, roid2; // Eat lunch. lunch = (function() { __c = []; __a = ['toast', 'cheese', 'wine']; diff --git a/documentation/js/expressions_comprehension.js b/documentation/js/expressions_comprehension.js index 7cdcbe7e..3442ff60 100644 --- a/documentation/js/expressions_comprehension.js +++ b/documentation/js/expressions_comprehension.js @@ -1,5 +1,5 @@ (function(){ - var __a, __b, globals, name; + var __a, __b, globals, name, property; // The first ten global properties. globals = ((function() { __b = []; __a = window; diff --git a/documentation/js/expressions_try.js b/documentation/js/expressions_try.js index 9c236f65..7787cfb7 100644 --- a/documentation/js/expressions_try.js +++ b/documentation/js/expressions_try.js @@ -3,7 +3,7 @@ try { return nonexistent / undefined; } catch (error) { - return "The error is: " + error; + return "Caught an error: " + error; } })()); })(); \ No newline at end of file diff --git a/documentation/js/object_comprehensions.js b/documentation/js/object_comprehensions.js new file mode 100644 index 00000000..c3865f6e --- /dev/null +++ b/documentation/js/object_comprehensions.js @@ -0,0 +1,18 @@ +(function(){ + var __a, __b, age, ages, child, years_old; + years_old = { + max: 10, + ida: 9, + tim: 11 + }; + ages = (function() { + __b = []; __a = years_old; + for (child in __a) { + age = __a[child]; + if (__a.hasOwnProperty(child)) { + __b.push(child + " is " + age); + } + } + return __b; + })(); +})(); \ No newline at end of file diff --git a/documentation/js/overview.js b/documentation/js/overview.js index cdb3d0a0..6ecf2944 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -1,5 +1,5 @@ (function(){ - var __a, __b, __c, cubed_list, list, math, number, opposite_day, race, square; + var __a, __b, __c, cubed_list, list, math, num, number, opposite_day, race, square; // Assignment: number = 42; opposite_day = true; diff --git a/documentation/js/range_comprehensions.js b/documentation/js/range_comprehensions.js index c2563558..8631bb6c 100644 --- a/documentation/js/range_comprehensions.js +++ b/documentation/js/range_comprehensions.js @@ -1,8 +1,21 @@ (function(){ - var __a, __b, __c, __d, dozen_eggs; - __c = 0; __d = eggs.length; - for (__b=0, i=__c; (__c <= __d ? i < __d : i > __d); (__c <= __d ? i += 12 : i -= 12), __b++) { - dozen_eggs = eggs.slice(i, i + 12); - deliver(new egg_carton(dozen)); - } + var __a, __b, __c, __d, __e, countdown, egg_delivery, num; + countdown = (function() { + __b = []; __d = 10; __e = 1; + for (__c=0, num=__d; (__d <= __e ? num <= __e : num >= __e); (__d <= __e ? num += 1 : num -= 1), __c++) { + __b.push(num); + } + return __b; + })(); + egg_delivery = function egg_delivery() { + var __f, __g, __h, __i, __j, dozen_eggs, i; + __g = []; __i = 0; __j = eggs.length; + for (__h=0, i=__i; (__i <= __j ? i < __j : i > __j); (__i <= __j ? i += 12 : i -= 12), __h++) { + __g.push((function() { + dozen_eggs = eggs.slice(i, i + 12); + return deliver(new egg_carton(dozen)); + })()); + } + return __g; + }; })(); \ No newline at end of file diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 71e56d03..31881a34 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -30,7 +30,7 @@ breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration - # Create a safe reference to the Underscore object for reference below. + # Create a safe reference to the Underscore object forreference below. _: root._: obj => new wrapper(obj) @@ -60,7 +60,7 @@ return obj.forEach(iterator, context) if obj.forEach if _.isArray(obj) or _.isArguments(obj) return iterator.call(context, obj[i], i, obj) for i in [0...obj.length] - iterator.call(context, val, key, obj) for val, key in obj + iterator.call(context, val, key, obj) for key, val ino obj catch e throw e if e isnt breaker obj @@ -148,7 +148,7 @@ # based on '==='. _.include: obj, target => return _.indexOf(obj, target) isnt -1 if _.isArray(obj) - for val in obj + for key, val ino obj return true if val is target false @@ -380,7 +380,7 @@ # Retrieve the names of an object's properties. _.keys: obj => return _.range(0, obj.length) if _.isArray(obj) - key for val, key in obj + key for key, val ino obj # Retrieve the values of an object's properties. @@ -395,7 +395,7 @@ # Extend a given object with all of the properties in a source object. _.extend: destination, source => - for val, key in source + for key, val ino source destination[key]: val destination @@ -564,8 +564,9 @@ _.each(_.functions(_)) name => method: _[name] wrapper.prototype[name]: => - unshift.call(arguments, this._wrapped) - result(method.apply(_, arguments), this._chain) + args: _.toArray(arguments) + unshift.call(args, this._wrapped) + result(method.apply(_, args), this._chain) # Add all mutator Array functions to the wrapper. diff --git a/index.html b/index.html index fe1bbc09..3f284d67 100644 --- a/index.html +++ b/index.html @@ -103,7 +103,7 @@ alert("I knew it!# Array comprehensions: cubed_list: math.cube(num) for num in list -
var __a, __b, __c, cubed_list, list, math, number, opposite_day, race, square;
+
var __a, __b, __c, cubed_list, list, math, num, number, opposite_day, race, square;
 // Assignment:
 number = 42;
 opposite_day = true;
@@ -144,7 +144,7 @@ cubed_list = (function
   }
   return __c;
 })();
-

- Comprehensions can also be used to iterate over the values and keys in - an object: + Comprehensions can also be used to iterate over the keys and values in + an object. Use ino to signal comprehension over an object instead + of an array.

- +
years_old: {max: 10, ida: 9, tim: 11}
+
+ages: child + " is " + age for child, age ino years_old
+
var __a, __b, age, ages, child, years_old;
+years_old = {
+  max: 10,
+  ida: 9,
+  tim: 11
+};
+ages = (function() {
+  __b = []; __a = years_old;
+  for (child in __a) {
+    age = __a[child];
+    if (__a.hasOwnProperty(child)) {
+      __b.push(child + " is " + age);
+    }
+  }
+  return __b;
+})();
+

Array Slicing and Splicing with Ranges @@ -841,7 +913,7 @@ six = (one = 1) + (two = 2) + (three = 3);

# The first ten global properties.
 
 globals: (name for property, name in window)[0...10]
-
var __a, __b, globals, name;
+
var __a, __b, globals, name, property;
 // The first ten global properties.
 globals = ((function() {
   __b = []; __a = window;
@@ -851,7 +923,7 @@ globals = ((function()
   }
   return __b;
 })()).slice(0, 10);
-

diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index af21e2e0..034d410e 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -214,7 +214,7 @@ match - \b([a-zA-Z$_]\w*)(\:)\s + \b([a-zA-Z$_](\w|\$)*)(\:)\s name variable.assignment.coffee captures @@ -224,7 +224,7 @@ name entity.name.function.coffee - 2 + 3 name keyword.operator.coffee @@ -263,7 +263,7 @@ match - !|\$|%|&|\*|\/|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b + !|%|&|\*|\/|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|ino|instanceof|new|delete|typeof|and|or|is|isnt|not)\b name keyword.operator.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 5bacc2aa..0b270cfa 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -8,7 +8,7 @@ token IDENTIFIER PROPERTY_ACCESS token CODE PARAM NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE -token FOR IN BY WHEN WHILE +token FOR IN INO BY WHEN WHILE token SWITCH LEADING_WHEN token DELETE INSTANCEOF TYPEOF token SUPER EXTENDS @@ -34,7 +34,7 @@ prechigh left '.' right INDENT left OUTDENT - right WHEN LEADING_WHEN IN BY + right WHEN LEADING_WHEN IN INO BY right THROW FOR NEW SUPER left EXTENDS left ASSIGN '||=' '&&=' @@ -360,6 +360,7 @@ rule # The source of the array comprehension can optionally be filtered. ForSource: IN Expression { result = {:source => val[1]} } + | INO Expression { result = {:source => val[1], :object => true} } | ForSource WHEN Expression { result = val[0].merge(:filter => val[2]) } | ForSource diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 1b682a54..9e75beac 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -12,14 +12,14 @@ module CoffeeScript "new", "return", "try", "catch", "finally", "throw", "break", "continue", - "for", "in", "by", "where", "while", + "for", "in", "ino", "by", "where", "while", "switch", "when", "super", "extends", "arguments", "delete", "instanceof", "typeof"] # Token matching regexes. - IDENTIFIER = /\A([a-zA-Z$_]\w*)/ + IDENTIFIER = /\A([a-zA-Z$_](\w|\$)*)/ NUMBER = /\A(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 718c4843..02ce4707 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -153,6 +153,10 @@ module CoffeeScript attr_reader :value + def self.wrap(string) + self.new(Value.new(string)) + end + def initialize(value) @value = value end @@ -384,7 +388,7 @@ module CoffeeScript # part of a comprehension, slice, or splice. # TODO: This generates pretty ugly code ... shrink it. def compile_array(o) - body = Expressions.wrap(LiteralNode.new(Value.new('i'))) + body = Expressions.wrap(LiteralNode.wrap('i')) arr = Expressions.wrap(ForNode.new(body, {:source => ValueNode.new(self)}, Value.new('i'))) ParentheticalNode.new(CallNode.new(CodeNode.new([], arr))).compile(o) end @@ -629,6 +633,8 @@ module CoffeeScript @source = source[:source] @filter = source[:filter] @step = source[:step] + @object = !!source[:object] + @name, @index = @index, @name if @object end def compile_node(o) @@ -636,6 +642,7 @@ module CoffeeScript range = @source.is_a?(ValueNode) && @source.literal.is_a?(RangeNode) && @source.properties.empty? source = range ? @source.literal : @source scope = o[:scope] + scope.find(@name) index_found = @index && scope.find(@index) body_dent = idt(1) svar = scope.free_variable @@ -650,6 +657,7 @@ module CoffeeScript index_var = nil source_part = "#{svar} = #{source.compile(o)};\n#{idt}" for_part = "#{ivar}=0; #{ivar}<#{svar}.length; #{ivar}++" + for_part = "#{@index} in #{svar}" if @object var_part = "#{body_dent}#{@name} = #{svar}[#{ivar}];\n" end body = @body @@ -659,7 +667,7 @@ module CoffeeScript body = Expressions.wrap(body) else body = Expressions.wrap(CallNode.new( - ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body.unwrap] + ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [body.unwrap] )) end if o[:return] @@ -667,7 +675,15 @@ module CoffeeScript o.delete(:return) body = IfNode.new(@filter, body, nil, :statement => true) if @filter elsif @filter - body = Expressions.wrap(IfNode.new(@filter, @body)) + body = Expressions.wrap(IfNode.new(@filter, body)) + end + if @object + body = Expressions.wrap(IfNode.new( + CallNode.new(ValueNode.new(LiteralNode.wrap(svar), [AccessorNode.new(Value.new('hasOwnProperty'))]), [LiteralNode.wrap(@index)]), + Expressions.wrap(body), + nil, + {:statement => true} + )) end return_result = "\n#{idt}#{return_result};" unless top_level diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 2469b407..5663c335 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -4,6 +4,14 @@ results: n * 2 for n in nums print(results.join(',') is '2,18') +obj: {one: 1, two: 2, three: 3} +names: key + '!' for key, value ino obj +odds: key + '!' for key, value ino obj when value % 2 isnt 0 + +print(names.join(' ') is "one! two! three!") +print(odds.join(' ') is "one! three!") + + evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0 num *= -1 num -= 2 diff --git a/test/fixtures/execution/test_literals.coffee b/test/fixtures/execution/test_literals.coffee index 2262434f..89271965 100644 --- a/test/fixtures/execution/test_literals.coffee +++ b/test/fixtures/execution/test_literals.coffee @@ -29,4 +29,9 @@ print(reg(str) and str is '\\') i: 10 while i -= 1 -print(i is 0) \ No newline at end of file +print(i is 0) + + +money$: 'dollars' + +print(money$ is 'dollars') \ No newline at end of file