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/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/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 f79ff39fb6..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

@@ -75,10 +75,11 @@ Everything is an Expression
Inheritance, and Calling Super from a Subclass
Blocks
+ Pattern Matching
Embedded JavaScript
Switch/When/Else
Try/Catch/Finally
- Multiline Strings
+ Multiline Strings and Heredocs
Resources
Contributing
Change Log
@@ -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) %> @@ -499,11 +500,39 @@ 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)) +

+ +

+ Pattern Matching (Destructuring Assignment) + 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 + 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 pull out deeply nested properties. +

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

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()') %> @@ -527,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

@@ -539,7 +575,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. @@ -589,10 +625,17 @@ 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 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/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/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/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_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/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/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/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/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 63bfc3907f..dd74befcc5 100644 --- a/index.html +++ b/index.html @@ -37,7 +37,7 @@

CoffeeScript

Latest Version: - 0.2.3 + 0.2.4

Table of Contents

@@ -61,10 +61,11 @@

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
- Multiline Strings
+ Multiline Strings and Heredocs
Resources
Contributing
Change Log
@@ -137,12 +138,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; })();
@@ -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. @@ -707,12 +708,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,46 +744,46 @@ 

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;
 };
 

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}
@@ -796,14 +797,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 +933,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);
 

@@ -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: =>
@@ -1119,11 +1120,121 @@ 

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)) +

+ +

+ Pattern Matching (Destructuring Assignment) + 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 + 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 pull out 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 - 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(": ");
@@ -1198,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 --
@@ -1224,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

@@ -1232,7 +1357,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. @@ -1282,10 +1407,17 @@

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 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.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/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 575d710858..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 @@ -87,7 +89,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 @@ -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/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 9fae471f88..ff208a9e1f 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,31 @@ 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 + + # 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)};"] + 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) + 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 +600,7 @@ def compile_node(o={}) # An object literal. class ObjectNode < Node attr_reader :properties + alias_method :objects, :properties def initialize(properties = []) @properties = properties @@ -655,9 +692,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) 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" } 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!") 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 diff --git a/test/fixtures/execution/test_heredocs.coffee b/test/fixtures/execution/test_heredocs.coffee new file mode 100644 index 0000000000..c28ddb5d7e --- /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