From 9cc7d6af27a206c8d6a393985c7965fb718bc53a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 08:46:50 -0500 Subject: [PATCH 01/11] little lexer tweak --- documentation/index.html.erb | 2 +- lib/coffee_script/lexer.rb | 2 +- test/fixtures/execution/test_array_comprehension.coffee | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index f79ff39fb6..47622c9236 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -592,7 +592,7 @@ coffee --print app/scripts/*.coffee > concatenation.js

0.2.3 Axed the unsatisfactory ino keyword, replacing it with of for - object comprehensions. They now look like: for key, value of object. + object comprehensions. They now look like: for prop, value of object.

diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 575d710858..2abe4b8ae6 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -87,7 +87,7 @@ def identifier_token # '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] == '.') + @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2] && @tokens[-2][1] == '.') @tokens[-1][0] = :PROTOTYPE_ACCESS if tag == :IDENTIFIER && last_value == '::' token(tag, identifier) @i += identifier.length diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 5599ba2fbe..d39ce6d439 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -5,8 +5,8 @@ print(results.join(',') is '2,18') obj: {one: 1, two: 2, three: 3} -names: key + '!' for key of obj -odds: key + '!' for key, value of obj when value % 2 isnt 0 +names: prop + '!' for prop of obj +odds: prop + '!' for prop, value of obj when value % 2 isnt 0 print(names.join(' ') is "one! two! three!") print(odds.join(' ') is "one! three!") From c7cb308b6dab49add9c357bb3da7f1f38c6e16c7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 09:09:06 -0500 Subject: [PATCH 02/11] adding note about parens-around-ambiguous-function-defs to the docs --- documentation/index.html.erb | 10 ++++++++-- index.html | 12 +++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 47622c9236..22e997d23d 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -499,11 +499,17 @@ coffee --print app/scripts/*.coffee > concatenation.js so you don't have to close the parentheses on the other side.

<%= code_for('blocks') %> +

+ If you prefer not to use blocks, you'll need to add a pair of parentheses + to help distinguish the arguments from the definition of the function: + _.map(array, (num => num * 2)) +

Embedded JavaScript - If you ever need to interpolate literal JavaScript snippets, you can - use backticks to pass JavaScript straight through. + Hopefully, you'll never need to use it, but if you ever need to intersperse + snippets of JavaScript within your CoffeeScript, you can + use backticks to pass it straight through.

<%= code_for('embedded', 'hi()') %> diff --git a/index.html b/index.html index 63bfc3907f..6060bc0f54 100644 --- a/index.html +++ b/index.html @@ -1119,11 +1119,17 @@

Language Reference

}); });
+

+ If you prefer not to use blocks, you'll need to add a pair of parentheses + to help distinguish the arguments from the definition of the function: + _.map(array, (num => num * 2)) +

Embedded JavaScript - If you ever need to interpolate literal JavaScript snippets, you can - use backticks to pass JavaScript straight through. + Hopefully, you'll never need to use it, but if you ever need to intersperse + snippets of JavaScript within your CoffeeScript, you can + use backticks to pass it straight through.

hi: `function() {
   return [document.title, "Hello JavaScript"].join(": ");
@@ -1285,7 +1291,7 @@ 

Change Log

0.2.3 Axed the unsatisfactory ino keyword, replacing it with of for - object comprehensions. They now look like: for key, value of object. + object comprehensions. They now look like: for prop, value of object.

From 2f63439bff58a1ffe0c9be3055fd81ad430854ac Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 09:16:08 -0500 Subject: [PATCH 03/11] swapping around the order of variable declaration in array comprehensions, so that it comes out in the proper order: __a, __b, __c --- documentation/js/array_comprehensions.js | 10 +- documentation/js/expressions_comprehension.js | 10 +- documentation/js/object_comprehensions.js | 12 +-- documentation/js/overview.js | 10 +- documentation/js/range_comprehensions.js | 12 +-- index.html | 98 +++++++++---------- lib/coffee_script/nodes.rb | 2 +- 7 files changed, 77 insertions(+), 77 deletions(-) diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index f7ce5e09ca..fe7d5db668 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -2,12 +2,12 @@ var __a, __b, __c, __d, __e, __f, __g, food, lunch, roid, roid2; // Eat lunch. lunch = (function() { - __c = []; __a = ['toast', 'cheese', 'wine']; - for (__b=0; __b<__a.length; __b++) { - food = __a[__b]; - __c.push(eat(food)); + __a = []; __b = ['toast', 'cheese', 'wine']; + for (__c=0; __c<__b.length; __c++) { + food = __b[__c]; + __a.push(eat(food)); } - return __c; + return __a; })(); // Naive collision detection. __d = asteroids; diff --git a/documentation/js/expressions_comprehension.js b/documentation/js/expressions_comprehension.js index 262bab7ee5..2d159d9f4f 100644 --- a/documentation/js/expressions_comprehension.js +++ b/documentation/js/expressions_comprehension.js @@ -2,12 +2,12 @@ var __a, __b, globals, name; // The first ten global properties. globals = ((function() { - __b = []; __a = window; - for (name in __a) { - if (__a.hasOwnProperty(name)) { - __b.push(name); + __a = []; __b = window; + for (name in __b) { + if (__b.hasOwnProperty(name)) { + __a.push(name); } } - return __b; + return __a; })()).slice(0, 10); })(); \ No newline at end of file diff --git a/documentation/js/object_comprehensions.js b/documentation/js/object_comprehensions.js index c3865f6ed4..81d94a417c 100644 --- a/documentation/js/object_comprehensions.js +++ b/documentation/js/object_comprehensions.js @@ -6,13 +6,13 @@ tim: 11 }; ages = (function() { - __b = []; __a = years_old; - for (child in __a) { - age = __a[child]; - if (__a.hasOwnProperty(child)) { - __b.push(child + " is " + age); + __a = []; __b = years_old; + for (child in __b) { + age = __b[child]; + if (__b.hasOwnProperty(child)) { + __a.push(child + " is " + age); } } - return __b; + return __a; })(); })(); \ No newline at end of file diff --git a/documentation/js/overview.js b/documentation/js/overview.js index 6ecf2944d8..1e61b37199 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -33,11 +33,11 @@ } // Array comprehensions: cubed_list = (function() { - __c = []; __a = list; - for (__b=0; __b<__a.length; __b++) { - num = __a[__b]; - __c.push(math.cube(num)); + __a = []; __b = list; + for (__c=0; __c<__b.length; __c++) { + num = __b[__c]; + __a.push(math.cube(num)); } - return __c; + return __a; })(); })(); \ No newline at end of file diff --git a/documentation/js/range_comprehensions.js b/documentation/js/range_comprehensions.js index 8631bb6cc2..9272dcff38 100644 --- a/documentation/js/range_comprehensions.js +++ b/documentation/js/range_comprehensions.js @@ -1,21 +1,21 @@ (function(){ var __a, __b, __c, __d, __e, countdown, egg_delivery, num; countdown = (function() { - __b = []; __d = 10; __e = 1; + __a = []; __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); + __a.push(num); } - return __b; + return __a; })(); egg_delivery = function egg_delivery() { var __f, __g, __h, __i, __j, dozen_eggs, i; - __g = []; __i = 0; __j = eggs.length; + __f = []; __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() { + __f.push((function() { dozen_eggs = eggs.slice(i, i + 12); return deliver(new egg_carton(dozen)); })()); } - return __g; + return __f; }; })(); \ No newline at end of file diff --git a/index.html b/index.html index 6060bc0f54..43a1b61188 100644 --- a/index.html +++ b/index.html @@ -137,12 +137,12 @@

Mini Overview

} // Array comprehensions: cubed_list = (function() { - __c = []; __a = list; - for (__b=0; __b<__a.length; __b++) { - num = __a[__b]; - __c.push(math.cube(num)); + __a = []; __b = list; + for (__c=0; __c<__b.length; __c++) { + num = __b[__c]; + __a.push(math.cube(num)); } - return __c; + return __a; })();

@@ -707,12 +707,12 @@

Language Reference

var __a, __b, __c, __d, __e, __f, __g, food, lunch, roid, roid2;
 // Eat lunch.
 lunch = (function() {
-  __c = []; __a = ['toast', 'cheese', 'wine'];
-  for (__b=0; __b<__a.length; __b++) {
-    food = __a[__b];
-    __c.push(eat(food));
+  __a = []; __b = ['toast', 'cheese', 'wine'];
+  for (__c=0; __c<__b.length; __c++) {
+    food = __b[__c];
+    __a.push(eat(food));
   }
-  return __c;
+  return __a;
 })();
 // Naive collision detection.
 __d = asteroids;
@@ -743,41 +743,41 @@ 

Language Reference

deliver(new egg_carton(dozen))
var __a, __b, __c, __d, __e, countdown, egg_delivery, num;
 countdown = (function() {
-  __b = []; __d = 10; __e = 1;
+  __a = []; __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);
+    __a.push(num);
   }
-  return __b;
+  return __a;
 })();
 egg_delivery = function egg_delivery() {
   var __f, __g, __h, __i, __j, dozen_eggs, i;
-  __g = []; __i = 0; __j = eggs.length;
+  __f = []; __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() {
+    __f.push((function() {
       dozen_eggs = eggs.slice(i, i + 12);
       return deliver(new egg_carton(dozen));
     })());
   }
-  return __g;
+  return __f;
 };
 

@@ -796,14 +796,14 @@

Language Reference

tim: 11 }; ages = (function() { - __b = []; __a = years_old; - for (child in __a) { - age = __a[child]; - if (__a.hasOwnProperty(child)) { - __b.push(child + " is " + age); + __a = []; __b = years_old; + for (child in __b) { + age = __b[child]; + if (__b.hasOwnProperty(child)) { + __a.push(child + " is " + age); } } - return __b; + return __a; })();
@@ -932,24 +932,24 @@

Language Reference

var __a, __b, globals, name;
 // The first ten global properties.
 globals = ((function() {
-  __b = []; __a = window;
-  for (name in __a) {
-    if (__a.hasOwnProperty(name)) {
-      __b.push(name);
+  __a = []; __b = window;
+  for (name in __b) {
+    if (__b.hasOwnProperty(name)) {
+      __a.push(name);
     }
   }
-  return __b;
+  return __a;
 })()).slice(0, 10);
 

diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 9fae471f88..55d85ae6c2 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -655,9 +655,9 @@ def compile_node(o) name_found = @name && scope.find(@name) index_found = @index && scope.find(@index) body_dent = idt(1) + rvar = scope.free_variable unless top_level svar = scope.free_variable ivar = range ? name : @index ? @index : scope.free_variable - rvar = scope.free_variable unless top_level if range index_var = scope.free_variable source_part = source.compile_variables(o) From 6c980d8adc9e81a485987b6ca0868dee1d341be2 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 21:44:47 -0500 Subject: [PATCH 04/11] first draft of destructuring assignment -- working out the wrinkles -- not sure if we want to do the full spec --- lib/coffee_script/nodes.rb | 41 +++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 55d85ae6c2..8a965b9634 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -320,6 +320,18 @@ def properties? return !@properties.empty? end + def array? + @base.is_a?(ArrayNode) && !properties? + end + + def object? + @base.is_a?(ObjectNode) && !properties? + end + + def splice? + properties? && @properties.last.is_a?(SliceNode) + end + # Values are statements if their base is a statement. def statement? @base.is_a?(Node) && @base.statement? && !properties? @@ -435,7 +447,9 @@ def initialize(variable, value, context=nil) end def compile_node(o) - return compile_splice(o) if @variable.properties.last.is_a?(SliceNode) + return compile_pattern_match(o) if @variable.array? || @variable.object? + return compile_splice(o) if @variable.splice? + stmt = o.delete(:as_statement) name = @variable.compile(o) last = @variable.last.to_s.sub(LEADING_DOT, '') proto = name[PROTO_ASSIGN, 1] @@ -444,9 +458,33 @@ def compile_node(o) return write("#{name}: #{@value.compile(o)}") if @context == :object o[:scope].find(name) unless @variable.properties? val = "#{name} = #{@value.compile(o)}" + return write("#{idt}#{val};") if stmt write(o[:return] ? "#{idt}return (#{val})" : val) end + def statement? + @variable.array? || @variable.object? + end + + def compile_pattern_match(o) + val_var = o[:scope].free_variable + assigns = ["#{idt}#{val_var} = #{@value.compile(o)};"] + @variable.base.objects.each_with_index do |obj, i| + if @variable.array? + assigns << AssignNode.new(obj, ValueNode.new(Value.new(val_var), [IndexNode.new(Value.new(i.to_s))])).compile(o.merge(:top => true, :as_statement => true)) + elsif @variable.object? + obj, i = obj.value, obj.variable.base + assigns << AssignNode.new(obj, ValueNode.new(Value.new(val_var), [AccessorNode.new(Value.new(i.to_s))])).compile(o.merge(:top => true, :as_statement => true)) + else + left = obj.compile(o) + o[:scope].find(left) if obj.base.is_a?(Value) + access = @variable.object? ? ".#{i}" : "[#{i}]" + assigns << "#{idt}#{left} = #{val_var}#{access};" + end + end + write(assigns.join("\n")) + end + def compile_splice(o) var = @variable.compile(o.merge(:only_first => true)) range = @variable.properties.last.range @@ -564,6 +602,7 @@ def compile_node(o={}) # An object literal. class ObjectNode < Node attr_reader :properties + alias_method :objects, :properties def initialize(properties = []) @properties = properties From 5e1e949bf650b676f52af6de325cb66abe219e46 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 22:04:25 -0500 Subject: [PATCH 05/11] a passing test for destructuring assignment (it needs a better name) --- .../test_destructuring_assignment.coffee | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/fixtures/execution/test_destructuring_assignment.coffee diff --git a/test/fixtures/execution/test_destructuring_assignment.coffee b/test/fixtures/execution/test_destructuring_assignment.coffee new file mode 100644 index 0000000000..6998a700df --- /dev/null +++ b/test/fixtures/execution/test_destructuring_assignment.coffee @@ -0,0 +1,46 @@ +a: -1 +b: -2 + +[a, b]: [b, a] + +print(a is -2) +print(b is -1) + + +arr: [1, 2, 3] + +[a, b, c]: arr + +print(a is 1) +print(b is 2) +print(c is 3) + + +obj: {x: 10, y: 20, z: 30} + +{x: a, y: b, z: c}: obj + +print(a is 10) +print(b is 20) +print(c is 30) + + +person: { + name: "Bob" + family: { + brother: { + addresses: [ + "first" + { + street: "101 Deercreek Ln." + city: "Moquasset NY, 10021" + } + ] + } + } +} + +{name: a, family: {brother: {addresses: [one, {city: b}]}}}: person + +print(a is "Bob") +print(b is "Moquasset NY, 10021") \ No newline at end of file From d54fa2f2a18b3be923ae978c613dbbeb2fd363f0 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 22:12:18 -0500 Subject: [PATCH 06/11] a whole chunk of compile_pattern_match was redundant, axed it -- along with the array/vs/object split --- lib/coffee_script/nodes.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 8a965b9634..11fcc55936 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -466,21 +466,19 @@ def statement? @variable.array? || @variable.object? end + # Implementation of recursive pattern matching, when assigning array or + # object literals to a value. Peeks at their properties to assign inner names. + # See: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring def compile_pattern_match(o) val_var = o[:scope].free_variable assigns = ["#{idt}#{val_var} = #{@value.compile(o)};"] @variable.base.objects.each_with_index do |obj, i| - if @variable.array? - assigns << AssignNode.new(obj, ValueNode.new(Value.new(val_var), [IndexNode.new(Value.new(i.to_s))])).compile(o.merge(:top => true, :as_statement => true)) - elsif @variable.object? - obj, i = obj.value, obj.variable.base - assigns << AssignNode.new(obj, ValueNode.new(Value.new(val_var), [AccessorNode.new(Value.new(i.to_s))])).compile(o.merge(:top => true, :as_statement => true)) - else - left = obj.compile(o) - o[:scope].find(left) if obj.base.is_a?(Value) - access = @variable.object? ? ".#{i}" : "[#{i}]" - assigns << "#{idt}#{left} = #{val_var}#{access};" - end + obj, i = obj.value, obj.variable.base if @variable.object? + access_class = @variable.array? ? IndexNode : AccessorNode + assigns << AssignNode.new( + obj, + ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))]) + ).compile(o.merge(:top => true, :as_statement => true)) end write(assigns.join("\n")) end From 186797a7459f9f2161e86e11388191246b339dce Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 22:16:23 -0500 Subject: [PATCH 07/11] got compile_pattern_match about as small as its going to get --- lib/coffee_script/nodes.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 11fcc55936..ff208a9e1f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -472,13 +472,13 @@ def statement? def compile_pattern_match(o) val_var = o[:scope].free_variable assigns = ["#{idt}#{val_var} = #{@value.compile(o)};"] + o.merge!(:top => true, :as_statement => true) @variable.base.objects.each_with_index do |obj, i| obj, i = obj.value, obj.variable.base if @variable.object? access_class = @variable.array? ? IndexNode : AccessorNode assigns << AssignNode.new( - obj, - ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))]) - ).compile(o.merge(:top => true, :as_statement => true)) + obj, ValueNode.new(Value.new(val_var), [access_class.new(Value.new(i.to_s))]) + ).compile(o) end write(assigns.join("\n")) end From c3029faca783f8c17ba333f4a5d49ee5af864884 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 22:55:01 -0500 Subject: [PATCH 08/11] documentation for pattern matching --- .../coffee/multiple_return_values.coffee | 5 + documentation/coffee/object_extraction.coffee | 13 ++ .../coffee/parallel_assignment.coffee | 4 + documentation/index.html.erb | 39 ++++-- documentation/js/multiple_return_values.js | 11 ++ documentation/js/object_extraction.js | 17 +++ documentation/js/parallel_assignment.js | 8 ++ index.html | 121 ++++++++++++++++-- 8 files changed, 202 insertions(+), 16 deletions(-) create mode 100644 documentation/coffee/multiple_return_values.coffee create mode 100644 documentation/coffee/object_extraction.coffee create mode 100644 documentation/coffee/parallel_assignment.coffee create mode 100644 documentation/js/multiple_return_values.js create mode 100644 documentation/js/object_extraction.js create mode 100644 documentation/js/parallel_assignment.js diff --git a/documentation/coffee/multiple_return_values.coffee b/documentation/coffee/multiple_return_values.coffee new file mode 100644 index 0000000000..d175cc8043 --- /dev/null +++ b/documentation/coffee/multiple_return_values.coffee @@ -0,0 +1,5 @@ +weather_report: location => + # Make an Ajax request to fetch the weather... + [location, 72, "Mostly Sunny"] + +[city, temp, forecast]: weather_report("Berkeley, CA") \ No newline at end of file diff --git a/documentation/coffee/object_extraction.coffee b/documentation/coffee/object_extraction.coffee new file mode 100644 index 0000000000..109f81e266 --- /dev/null +++ b/documentation/coffee/object_extraction.coffee @@ -0,0 +1,13 @@ +futurists: { + sculptor: "Umberto Boccioni" + painter: "Vladimir Burliuk" + poet: { + name: "F.T. Marinetti" + address: [ + "Via Roma 42R" + "Bellagio, Italy 22021" + ] + } +} + +{poet: {name: poet, address: [street, city]}}: futurists \ No newline at end of file diff --git a/documentation/coffee/parallel_assignment.coffee b/documentation/coffee/parallel_assignment.coffee new file mode 100644 index 0000000000..ea005b3b25 --- /dev/null +++ b/documentation/coffee/parallel_assignment.coffee @@ -0,0 +1,4 @@ +bait: 1000 +and_switch: 0 + +[bait, and_switch]: [and_switch, bait] \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 22e997d23d..1a27ae0ee9 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -75,6 +75,7 @@ Everything is an Expression
Inheritance, and Calling Super from a Subclass
Blocks
+ Pattern Matching
Embedded JavaScript
Switch/When/Else
Try/Catch/Finally
@@ -95,7 +96,7 @@ Underscore.coffee, a port of the Underscore.js library of helper functions. Underscore.coffee can pass the entire Underscore.js - test suite. The CoffeeScript version is faster than the original for a number + test suite. The CoffeeScript version is faster than the original for a number of methods (in general, due to the speed of CoffeeScript's array comprehensions), and after being minified and gzipped, is only 241 bytes larger than the original JavaScript version. @@ -415,7 +416,7 @@ coffee --print app/scripts/*.coffee > concatenation.js <%= code_for('range_comprehensions', 'countdown') %>

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

<%= code_for('object_comprehensions', 'ages.join(", ")') %> @@ -481,12 +482,12 @@ coffee --print app/scripts/*.coffee > concatenation.js be completely usable if it weren't for a couple of small exceptions: it's awkward to call super (the prototype object's implementation of the current function), and it's awkward to correctly - set the prototype chain. + set the prototype chain.

CoffeeScript provides extends to help with prototype setup, :: for quick access to an - object's prototype, and converts super() into a call against + object's prototype, and converts super() into a call against the immediate ancestor's method of the same name.

<%= code_for('super', true) %> @@ -500,14 +501,36 @@ coffee --print app/scripts/*.coffee > concatenation.js

<%= code_for('blocks') %>

- If you prefer not to use blocks, you'll need to add a pair of parentheses + If you prefer not to use blocks, you'll need to add a pair of parentheses to help distinguish the arguments from the definition of the function: _.map(array, (num => num * 2))

+

+ Pattern Matching (Destructuring Assignment) + To make working with complex arrays and objects more convenient, + CoffeeScript provides ECMAScript Harmony's proposed + destructuring assignment + syntax. When you assign an array or object literal to a value, CoffeeScript + breaks up and matches both sides against each other, assigning the values + on the right to the variables on the left. In the simplest case, it can be + used for parallel assignment: +

+ <%= code_for('parallel_assignment', 'bait') %> +

+ But it's also helpful for dealing with functions that return multiple + values. +

+ <%= code_for('multiple_return_values', 'forecast') %> +

+ Pattern matching can be used with any depth of array and object nesting, + to help cleanly extract deeply nested properties. +

+ <%= code_for('object_extraction', 'poet + " — " + street') %> +

Embedded JavaScript - Hopefully, you'll never need to use it, but if you ever need to intersperse + Hopefully, you'll never need to use it, but if you ever need to intersperse snippets of JavaScript within your CoffeeScript, you can use backticks to pass it straight through.

@@ -545,7 +568,7 @@ coffee --print app/scripts/*.coffee > concatenation.js Source Code
After checking out the source, make sure to run rake build:parser to generate an up-to-date version of the Racc parser. - Use bin/coffee to test your changes, + Use bin/coffee to test your changes, rake test to run the test suite, and rake gem:install to create and install a custom version of the gem. @@ -594,7 +617,7 @@ coffee --print app/scripts/*.coffee > concatenation.js

Change Log

- +

0.2.3 Axed the unsatisfactory ino keyword, replacing it with of for diff --git a/documentation/js/multiple_return_values.js b/documentation/js/multiple_return_values.js new file mode 100644 index 0000000000..5ea8d80fbd --- /dev/null +++ b/documentation/js/multiple_return_values.js @@ -0,0 +1,11 @@ +(function(){ + var __a, city, forecast, temp, weather_report; + weather_report = function weather_report(location) { + // Make an Ajax request to fetch the weather... + return [location, 72, "Mostly Sunny"]; + }; + __a = weather_report("Berkeley, CA"); + city = __a[0]; + temp = __a[1]; + forecast = __a[2]; +})(); \ No newline at end of file diff --git a/documentation/js/object_extraction.js b/documentation/js/object_extraction.js new file mode 100644 index 0000000000..ca41aa189d --- /dev/null +++ b/documentation/js/object_extraction.js @@ -0,0 +1,17 @@ +(function(){ + var __a, __b, __c, city, futurists, poet, street; + futurists = { + sculptor: "Umberto Boccioni", + painter: "Vladimir Burliuk", + poet: { + name: "F.T. Marinetti", + address: ["Via Roma 42R", "Bellagio, Italy 22021"] + } + }; + __a = futurists; + __b = __a.poet; + poet = __b.name; + __c = __b.address; + street = __c[0]; + city = __c[1]; +})(); \ No newline at end of file diff --git a/documentation/js/parallel_assignment.js b/documentation/js/parallel_assignment.js new file mode 100644 index 0000000000..5286f1539e --- /dev/null +++ b/documentation/js/parallel_assignment.js @@ -0,0 +1,8 @@ +(function(){ + var __a, and_switch, bait; + bait = 1000; + and_switch = 0; + __a = [and_switch, bait]; + bait = __a[0]; + and_switch = __a[1]; +})(); \ No newline at end of file diff --git a/index.html b/index.html index 43a1b61188..ac16100727 100644 --- a/index.html +++ b/index.html @@ -61,6 +61,7 @@

Table of Contents

Everything is an Expression
Inheritance, and Calling Super from a Subclass
Blocks
+ Pattern Matching
Embedded JavaScript
Switch/When/Else
Try/Catch/Finally
@@ -192,7 +193,7 @@

Mini Overview

Underscore.coffee, a port of the Underscore.js library of helper functions. Underscore.coffee can pass the entire Underscore.js - test suite. The CoffeeScript version is faster than the original for a number + test suite. The CoffeeScript version is faster than the original for a number of methods (in general, due to the speed of CoffeeScript's array comprehensions), and after being minified and gzipped, is only 241 bytes larger than the original JavaScript version. @@ -782,7 +783,7 @@

Language Reference

;alert(countdown);'>run: countdown

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

years_old: {max: 10, ida: 9, tim: 11}
@@ -990,12 +991,12 @@ 

Language Reference

be completely usable if it weren't for a couple of small exceptions: it's awkward to call super (the prototype object's implementation of the current function), and it's awkward to correctly - set the prototype chain. + set the prototype chain.

CoffeeScript provides extends to help with prototype setup, :: for quick access to an - object's prototype, and converts super() into a call against + object's prototype, and converts super() into a call against the immediate ancestor's method of the same name.

Animal: =>
@@ -1120,14 +1121,118 @@ 

Language Reference

});

- If you prefer not to use blocks, you'll need to add a pair of parentheses + If you prefer not to use blocks, you'll need to add a pair of parentheses to help distinguish the arguments from the definition of the function: _.map(array, (num => num * 2))

+

+ Pattern Matching (Destructuring Assignment) + To make working with complex arrays and objects more convenient, + CoffeeScript provides ECMAScript Harmony's proposed + destructuring assignment + syntax. When you assign an array or object literal to a value, CoffeeScript + breaks up and matches both sides against each other, assigning the values + on the right to the variables on the left. In the simplest case, it can be + used for parallel assignment: +

+
bait: 1000
+and_switch: 0
+
+[bait, and_switch]: [and_switch, bait]
+
var __a, and_switch, bait;
+bait = 1000;
+and_switch = 0;
+__a = [and_switch, bait];
+bait = __a[0];
+and_switch = __a[1];
+

+

+ But it's also helpful for dealing with functions that return multiple + values. +

+
weather_report: location =>
+  # Make an Ajax request to fetch the weather...
+  [location, 72, "Mostly Sunny"]
+
+[city, temp, forecast]: weather_report("Berkeley, CA")
+
var __a, city, forecast, temp, weather_report;
+weather_report = function weather_report(location) {
+  // Make an Ajax request to fetch the weather...
+  return [location, 72, "Mostly Sunny"];
+};
+__a = weather_report("Berkeley, CA");
+city = __a[0];
+temp = __a[1];
+forecast = __a[2];
+

+

+ Pattern matching can be used with any depth of array and object nesting, + to help cleanly extract deeply nested properties. +

+
futurists: {
+  sculptor: "Umberto Boccioni"
+  painter:  "Vladimir Burliuk"
+  poet: {
+    name:   "F.T. Marinetti"
+    address: [
+      "Via Roma 42R"
+      "Bellagio, Italy 22021"
+    ]
+  }
+}
+
+{poet: {name: poet, address: [street, city]}}: futurists
+
var __a, __b, __c, city, futurists, poet, street;
+futurists = {
+  sculptor: "Umberto Boccioni",
+  painter: "Vladimir Burliuk",
+  poet: {
+    name: "F.T. Marinetti",
+    address: ["Via Roma 42R", "Bellagio, Italy 22021"]
+  }
+};
+__a = futurists;
+__b = __a.poet;
+poet = __b.name;
+__c = __b.address;
+street = __c[0];
+city = __c[1];
+

+

Embedded JavaScript - Hopefully, you'll never need to use it, but if you ever need to intersperse + Hopefully, you'll never need to use it, but if you ever need to intersperse snippets of JavaScript within your CoffeeScript, you can use backticks to pass it straight through.

@@ -1238,7 +1343,7 @@

Resources

Source Code
After checking out the source, make sure to run rake build:parser to generate an up-to-date version of the Racc parser. - Use bin/coffee to test your changes, + Use bin/coffee to test your changes, rake test to run the test suite, and rake gem:install to create and install a custom version of the gem. @@ -1287,7 +1392,7 @@

Contributing

Change Log

- +

0.2.3 Axed the unsatisfactory ino keyword, replacing it with of for From 477c5103454e7b4d2ad076cf80aa2c0766f77e5a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 11 Jan 2010 23:53:50 -0500 Subject: [PATCH 09/11] adding heredocs, with tests --- .../Syntaxes/CoffeeScript.tmLanguage | 24 ++++++++++++++++ lib/coffee_script/lexer.rb | 20 ++++++++++--- test/fixtures/execution/test_heredocs.coffee | 28 +++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/execution/test_heredocs.coffee diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 5a60ab242e..311872442d 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -93,6 +93,30 @@ name constant.numeric.coffee + + name + string.quoted.heredoc.coffee + begin + ("""|''') + end + ("""|''') + beginCaptures + + 0 + + name + punctuation.definition.string.begin.coffee + + + endCaptures + + 0 + + name + punctuation.definition.string.end.coffee + + + begin ' diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 2abe4b8ae6..6c41dde154 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -22,6 +22,7 @@ class Lexer 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 + HEREDOC = /\A("{6}|'{6}|"{3}\n?(\s*)(.*?)\n?(\s*)"{3}|'{3}\n?(\s*)(.*?)\n?(\s*)'{3})/m JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t]+)/ @@ -69,6 +70,7 @@ def tokenize(code) def extract_next_token return if identifier_token return if number_token + return if heredoc_token return if string_token return if js_token return if regex_token @@ -103,14 +105,24 @@ def number_token # Matches strings, including multi-line strings. def string_token return false unless string = @chunk[STRING, 1] - escaped = string.gsub(MULTILINER) do |match| - @line += 1 - " \\\n" - end + escaped = string.gsub(MULTILINER, " \\\n") token(:STRING, escaped) + @line += string.count("\n") @i += string.length end + # Matches heredocs, adjusting indentation to the correct level. + def heredoc_token + return false unless match = @chunk.match(HEREDOC) + indent = match[2] || match[5] + doc = match[3] || match[6] + doc.gsub!(/\n#{indent}/, "\\n") + doc.gsub!('"', '\\"') + token(:STRING, "\"#{doc}\"") + @line += match[1].count("\n") + @i += match[1].length + end + # Matches interpolated JavaScript. def js_token return false unless script = @chunk[JS, 1] diff --git a/test/fixtures/execution/test_heredocs.coffee b/test/fixtures/execution/test_heredocs.coffee new file mode 100644 index 0000000000..07a55d0541 --- /dev/null +++ b/test/fixtures/execution/test_heredocs.coffee @@ -0,0 +1,28 @@ +a: """ + basic heredoc + on two lines + """ + +print(a is "basic heredoc\non two lines") + + +a: ''' + a + b + c + ''' + +print(a is "a\n b\nc") + + +a: '''one-liner''' + +print(a is 'one-liner') + + +a: """ + out + here +""" + +print(a is "out\nhere") \ No newline at end of file From c8d505e85dd4de1a1bf09e181a7e9d4f5629ec39 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 12 Jan 2010 00:00:38 -0500 Subject: [PATCH 10/11] heredoc docs --- documentation/coffee/heredocs.coffee | 5 +++++ documentation/index.html.erb | 11 +++++++++-- documentation/js/heredocs.js | 4 ++++ index.html | 18 ++++++++++++++++-- test/fixtures/execution/test_heredocs.coffee | 4 ++-- 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 documentation/coffee/heredocs.coffee create mode 100644 documentation/js/heredocs.js diff --git a/documentation/coffee/heredocs.coffee b/documentation/coffee/heredocs.coffee new file mode 100644 index 0000000000..10769ea61a --- /dev/null +++ b/documentation/coffee/heredocs.coffee @@ -0,0 +1,5 @@ +html: ''' + + cup of coffeescript + + ''' \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 1a27ae0ee9..b95d6d18ba 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -79,7 +79,7 @@ Embedded JavaScript
Switch/When/Else
Try/Catch/Finally
- Multiline Strings
+ Multiline Strings and Heredocs
Resources
Contributing
Change Log
@@ -556,10 +556,17 @@ coffee --print app/scripts/*.coffee > concatenation.js

<%= code_for('try') %>

- Multiline Strings + Multiline Strings and Heredocs Multiline strings are allowed in CoffeeScript.

<%= code_for('strings', 'moby_dick') %> +

+ Heredocs can be used to hold formatted or indentation-sensitive text + (or, if you just don't feel like escaping quotes and apostrophes). The + indentation level that begins the heredoc is maintained throughout, so + you can keep it all aligned with the body of your code. +

+ <%= code_for('heredocs') %>

Resources

diff --git a/documentation/js/heredocs.js b/documentation/js/heredocs.js new file mode 100644 index 0000000000..a523ff72f5 --- /dev/null +++ b/documentation/js/heredocs.js @@ -0,0 +1,4 @@ +(function(){ + var html; + html = "\n cup of coffeescript\n"; +})(); \ No newline at end of file diff --git a/index.html b/index.html index ac16100727..94c971d38f 100644 --- a/index.html +++ b/index.html @@ -65,7 +65,7 @@

Table of Contents

Embedded JavaScript
Switch/When/Else
Try/Catch/Finally
- Multiline Strings
+ Multiline Strings and Heredocs
Resources
Contributing
Change Log
@@ -1309,7 +1309,7 @@

Language Reference


- Multiline Strings + Multiline Strings and Heredocs Multiline strings are allowed in CoffeeScript.

moby_dick: "Call me Ishmael. Some years ago --
@@ -1335,6 +1335,20 @@ 

Language Reference

about a little and see the watery part of the \ world..."; ;alert(moby_dick);'>run: moby_dick
+

+ Heredocs can be used to hold formatted or indentation-sensitive text + (or, if you just don't feel like escaping quotes and apostrophes). The + indentation level that begins the heredoc is maintained throughout, so + you can keep it all aligned with the body of your code. +

+
html: '''
+      <strong>
+        cup of coffeescript
+      </strong>
+      '''
+
var html;
+html = "<strong>\n  cup of coffeescript\n</strong>";
+

Resources

diff --git a/test/fixtures/execution/test_heredocs.coffee b/test/fixtures/execution/test_heredocs.coffee index 07a55d0541..c28ddb5d7e 100644 --- a/test/fixtures/execution/test_heredocs.coffee +++ b/test/fixtures/execution/test_heredocs.coffee @@ -8,11 +8,11 @@ print(a is "basic heredoc\non two lines") a: ''' a - b + "b c ''' -print(a is "a\n b\nc") +print(a is "a\n \"b\nc") a: '''one-liner''' From 9a61bbf005457d5ea136f2352f1542bcf0d23e16 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 12 Jan 2010 00:09:23 -0500 Subject: [PATCH 11/11] CoffeeScript 0.2.4, with pattern matching and heredocs --- coffee-script.gemspec | 4 ++-- documentation/index.html.erb | 15 +++++++++++---- index.html | 15 +++++++++++---- lib/coffee-script.rb | 2 +- package.json | 2 +- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index 902be5bd43..8a4aecc4ef 100644 --- a/coffee-script.gemspec +++ b/coffee-script.gemspec @@ -1,7 +1,7 @@ Gem::Specification.new do |s| s.name = 'coffee-script' - s.version = '0.2.3' # Keep version in sync with coffee-script.rb - s.date = '2010-1-10' + s.version = '0.2.4' # Keep version in sync with coffee-script.rb + s.date = '2010-1-12' s.homepage = "http://jashkenas.github.com/coffee-script/" s.summary = "The CoffeeScript Compiler" diff --git a/documentation/index.html.erb b/documentation/index.html.erb index b95d6d18ba..f2ae351358 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -51,7 +51,7 @@

Latest Version: - 0.2.3 + 0.2.4

Table of Contents

@@ -508,8 +508,8 @@ coffee --print app/scripts/*.coffee > concatenation.js

Pattern Matching (Destructuring Assignment) - To make working with complex arrays and objects more convenient, - CoffeeScript provides ECMAScript Harmony's proposed + To make extracting values from complex arrays and objects more convenient, + CoffeeScript implements ECMAScript Harmony's proposed destructuring assignment syntax. When you assign an array or object literal to a value, CoffeeScript breaks up and matches both sides against each other, assigning the values @@ -524,7 +524,7 @@ coffee --print app/scripts/*.coffee > concatenation.js <%= code_for('multiple_return_values', 'forecast') %>

Pattern matching can be used with any depth of array and object nesting, - to help cleanly extract deeply nested properties. + to help pull out deeply nested properties.

<%= code_for('object_extraction', 'poet + " — " + street') %> @@ -624,6 +624,13 @@ coffee --print app/scripts/*.coffee > concatenation.js

Change Log

+ +

+ 0.2.4 + Added ECMAScript Harmony style destructuring assignment, for dealing with + extracting values from nested arrays and objects. Added indentation-sensitive + heredocs for nicely formatted strings or chunks of code. +

0.2.3 diff --git a/index.html b/index.html index 94c971d38f..dd74befcc5 100644 --- a/index.html +++ b/index.html @@ -37,7 +37,7 @@

CoffeeScript

Latest Version: - 0.2.3 + 0.2.4

Table of Contents

@@ -1128,8 +1128,8 @@

Language Reference

Pattern Matching (Destructuring Assignment) - To make working with complex arrays and objects more convenient, - CoffeeScript provides ECMAScript Harmony's proposed + To make extracting values from complex arrays and objects more convenient, + CoffeeScript implements ECMAScript Harmony's proposed destructuring assignment syntax. When you assign an array or object literal to a value, CoffeeScript breaks up and matches both sides against each other, assigning the values @@ -1183,7 +1183,7 @@

Language Reference

;alert(forecast);'>run: forecast

Pattern matching can be used with any depth of array and object nesting, - to help cleanly extract deeply nested properties. + to help pull out deeply nested properties.

futurists: {
   sculptor: "Umberto Boccioni"
@@ -1406,6 +1406,13 @@ 

Contributing

Change Log

+ +

+ 0.2.4 + Added ECMAScript Harmony style destructuring assignment, for dealing with + extracting values from nested arrays and objects. Added indentation-sensitive + heredocs for nicely formatted strings or chunks of code. +

0.2.3 diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index e694423103..3fc2d6ad32 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -10,7 +10,7 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.2.3' # Keep in sync with the gemspec. + VERSION = '0.2.4' # Keep in sync with the gemspec. # Compile a script (String or IO) to JavaScript. def self.compile(script, options={}) diff --git a/package.json b/package.json index 59d5d3a3ed..0d1478fa3f 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,5 @@ "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", - "version": "0.2.3" + "version": "0.2.4" }