From 290aa257de41c579dc8b66dc84b0629f1f080786 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 16 Dec 2009 22:42:53 -0500 Subject: [PATCH 001/303] completely reorganized for a gem and the 'coffee-script' command --- LICENSE | 22 + Rakefile | 15 + WISHLIST => TODO | 5 +- bin/coffee-script | 5 + code.cs => examples/code.cs | 2 - documents.cs => examples/documents.cs | 0 poignant.cs => examples/poignant.cs | 0 underscore.cs => examples/underscore.cs | 0 lib/coffee-script.rb | 16 + lib/coffee_script/command_line.rb | 99 ++ grammar.y => lib/coffee_script/grammar.y | 4 - lexer.rb => lib/coffee_script/lexer.rb | 0 nodes.rb => lib/coffee_script/nodes.rb | 0 parser.rb | 1475 ---------------------- lexer_test.rb => test/lexer_test.rb | 0 parser_test.rb => test/parser_test.rb | 0 16 files changed, 161 insertions(+), 1482 deletions(-) create mode 100644 LICENSE create mode 100644 Rakefile rename WISHLIST => TODO (86%) create mode 100755 bin/coffee-script rename code.cs => examples/code.cs (97%) rename documents.cs => examples/documents.cs (100%) rename poignant.cs => examples/poignant.cs (100%) rename underscore.cs => examples/underscore.cs (100%) create mode 100644 lib/coffee-script.rb create mode 100644 lib/coffee_script/command_line.rb rename grammar.y => lib/coffee_script/grammar.y (99%) rename lexer.rb => lib/coffee_script/lexer.rb (100%) rename nodes.rb => lib/coffee_script/nodes.rb (100%) delete mode 100644 parser.rb rename lexer_test.rb => test/lexer_test.rb (100%) rename parser_test.rb => test/parser_test.rb (100%) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..7f5ff55dd7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2009 Jeremy Ashkenas + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..2d2fd8d3cd --- /dev/null +++ b/Rakefile @@ -0,0 +1,15 @@ +desc "Recompile the Racc parser (pass -v and -g for verbose debugging)" +task :build, :extra_args do |t, args| + sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" +end + + +# # Pipe compiled JS through JSLint. +# puts "\n\n" +# require 'open3' +# stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin') +# stdin.write(js) +# stdin.close +# puts stdout.read +# stdout.close +# stderr.close \ No newline at end of file diff --git a/WISHLIST b/TODO similarity index 86% rename from WISHLIST rename to TODO index 6630b1f4f6..93dbd7f72c 100644 --- a/WISHLIST +++ b/TODO @@ -1,6 +1,9 @@ +TODO: + * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? * Is it possible to pass comments through cleanly and have them show up on the other end? This includes comments in the middle of array and object - literals, and argument lists. \ No newline at end of file + literals, and argument lists. + \ No newline at end of file diff --git a/bin/coffee-script b/bin/coffee-script new file mode 100755 index 0000000000..51184c421c --- /dev/null +++ b/bin/coffee-script @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +require "#{File.dirname(__FILE__)}/../lib/coffee_script/command_line.rb" + +CoffeeScript::CommandLine.new \ No newline at end of file diff --git a/code.cs b/examples/code.cs similarity index 97% rename from code.cs rename to examples/code.cs index a0299bd4fe..756ee24a63 100644 --- a/code.cs +++ b/examples/code.cs @@ -1,5 +1,3 @@ -# TODO: Add range indexing: array[5..7] => array.slice(5, 7) - # Functions: square: x => x * x. diff --git a/documents.cs b/examples/documents.cs similarity index 100% rename from documents.cs rename to examples/documents.cs diff --git a/poignant.cs b/examples/poignant.cs similarity index 100% rename from poignant.cs rename to examples/poignant.cs diff --git a/underscore.cs b/examples/underscore.cs similarity index 100% rename from underscore.cs rename to examples/underscore.cs diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb new file mode 100644 index 0000000000..09be81f7a0 --- /dev/null +++ b/lib/coffee-script.rb @@ -0,0 +1,16 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__)) +require "coffee_script/lexer" +require "coffee_script/parser" +require "coffee_script/nodes" + +# Namespace for all CoffeeScript internal classes. +module CoffeeScript + + VERSION = '0.1.0' + + def self.compile(script) + script = script.read if script.respond_to?(:read) + Parser.new.parse(script).compile + end + +end diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb new file mode 100644 index 0000000000..ecf81da467 --- /dev/null +++ b/lib/coffee_script/command_line.rb @@ -0,0 +1,99 @@ +require 'optparse' +require 'fileutils' +require 'open3' +require File.expand_path(File.dirname(__FILE__) + '/../coffee-script') + +module CoffeeScript + + class CommandLine + + BANNER = <<-EOS +coffee-script compiles CoffeeScript files into JavaScript. + +Usage: + coffee-script path/to/script.cs + EOS + + def initialize + parse_options + check_sources + compile_javascript + end + + def usage + puts "\n#{@option_parser}\n" + exit + end + + + private + + def compile_javascript + @sources.each do |source| + contents = CoffeeScript.compile(File.open(source)) + next puts(contents) if @options[:print] + next lint(contents) if @options[:lint] + File.open(path_for(source), 'w+') {|f| f.write(contents) } + end + end + + def check_sources + usage if @sources.empty? + missing = @sources.detect {|s| !File.exists?(s) } + if missing + STDERR.puts("File not found: '#{missing}'") + exit(1) + end + end + + # Pipe compiled JS through JSLint. + def lint(js) + stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin') + stdin.write(js) + stdin.close + print stdout.read + stdout.close and stderr.close + end + + # Write out JavaScript alongside CoffeeScript unless an output directory + # is specified. + def path_for(source) + filename = File.basename(source, File.extname(source)) + '.js' + dir = @options[:output] || File.dirname(source) + File.join(dir, filename) + end + + def parse_options + @options = {} + @option_parser = OptionParser.new do |opts| + opts.on('-o', '--output [DIR]', 'set the directory for compiled javascript') do |d| + @options[:output] = d + FileUtils.mkdir_p(d) unless File.exists?(d) + end + opts.on('-p', '--print', 'print the compiled javascript to stdout') do |d| + @options[:print] = true + end + opts.on('-l', '--lint', 'pipe the compiled javascript through JSLint') do |l| + @options[:lint] = true + end + opts.on_tail('-v', '--version', 'display coffee-script version') do + puts "coffee-script version #{CoffeeScript::VERSION}" + exit + end + opts.on_tail('-h', '--help', 'display this help message') do + usage + end + end + @option_parser.banner = BANNER + begin + @option_parser.parse!(ARGV) + rescue OptionParser::InvalidOption => e + puts e.message + exit(1) + end + @sources = ARGV + end + + end + +end diff --git a/grammar.y b/lib/coffee_script/grammar.y similarity index 99% rename from grammar.y rename to lib/coffee_script/grammar.y index 1eba1d0ce2..2ff987d33a 100644 --- a/grammar.y +++ b/lib/coffee_script/grammar.y @@ -281,10 +281,6 @@ rule end ----- header - require "lexer" - require "nodes" - ---- inner def parse(code, show_tokens=false) # @yydebug = true diff --git a/lexer.rb b/lib/coffee_script/lexer.rb similarity index 100% rename from lexer.rb rename to lib/coffee_script/lexer.rb diff --git a/nodes.rb b/lib/coffee_script/nodes.rb similarity index 100% rename from nodes.rb rename to lib/coffee_script/nodes.rb diff --git a/parser.rb b/parser.rb deleted file mode 100644 index 48e2e9c767..0000000000 --- a/parser.rb +++ /dev/null @@ -1,1475 +0,0 @@ -# -# DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.6 -# from Racc grammer file "". -# - -require 'racc/parser.rb' - - require "lexer" - require "nodes" - -class Parser < Racc::Parser - -module_eval(<<'...end grammar.y/module_eval...', 'grammar.y', 294) - def parse(code, show_tokens=false) - # @yydebug = true - @tokens = Lexer.new.tokenize(code) - puts @tokens.inspect if show_tokens - do_parse - end - - def next_token - @tokens.shift - end -...end grammar.y/module_eval... -##### State transition tables begin ### - -clist = [ -'12,7,195,56,29,35,39,43,48,4,7,174,135,18,21,28,33,171,184,47,3,9,198', -'56,16,20,23,30,115,44,56,2,8,179,123,118,189,27,129,23,30,194,23,30', -'181,23,30,56,183,175,199,23,30,42,59,1,126,11,23,30,34,-2,42,12,1,72', -'11,29,35,39,43,48,4,7,59,113,18,21,28,33,52,59,47,3,9,55,58,16,20,180', -'23,30,44,52,2,8,127,128,59,171,27,52,55,88,91,93,96,98,100,101,103,105', -'81,83,87,90,92,95,97,99,172,23,30,34,112,42,12,1,110,11,29,35,39,43', -'48,4,7,23,30,18,21,28,33,23,30,47,3,9,23,30,16,20,,23,30,44,158,2,8', -'200,23,30,163,27,88,91,93,96,98,100,101,103,105,81,83,87,90,23,30,,158', -',159,167,23,30,34,,42,12,1,,11,29,35,39,43,48,4,7,23,30,18,21,28,33', -'66,67,47,3,9,,,16,20,205,23,30,44,,2,8,88,91,93,,27,88,91,93,96,98,100', -'101,103,105,81,83,87,90,88,91,93,96,98,,,23,30,34,,42,12,1,,11,29,35', -'39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,88,91,93,44,,2,8,187,23', -'30,,27,88,91,93,96,98,100,101,103,105,81,83,87,90,88,91,93,96,98,,,', -',34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,', -',,44,,2,8,,,,,27,88,91,93,96,98,100,101,103,105,81,83,87,90,88,91,93', -'96,98,,,23,30,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47', -'3,9,,,16,20,,,,44,,2,8,,,,,27,88,91,93,96,98,100,101,103,105,81,83,87', -'90,88,91,93,96,98,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28', -'33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,88,91,93,96,98,100,101,103,105', -'81,83,87,90,,,,,,,,23,30,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21', -'28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,88,91,93,96,98,100,101,103', -'105,81,83,87,90,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21', -'28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,88,91,93,96,98,100,101,103', -'105,81,83,87,90,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21', -'28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,23,30,34', -',42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44', -',2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7', -',,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,', -',,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20', -',,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43', -'48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,', -',,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9', -',,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,23,30,34,,42,12,1,,11', -'29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27', -',,,,,,,,,,,,,,,,,,,,23,30,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21', -'28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,23,30,34', -',42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44', -',2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,23,30,34,,42,12,1,,11,29,35,39,43,48', -'4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,', -',,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16', -'20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39', -'43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,', -',,,,,,,,,,,23,30,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,', -'47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,', -'11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,', -'27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21', -'28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42', -'12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2', -'8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,', -'18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,23', -'30,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20', -',,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43', -'48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,', -',,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9', -',,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,23,30,34,,42,12,1,,11', -'29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27', -',,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28', -'33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,23,30,34,', -'42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44', -',2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7', -',,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,', -',,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20', -',,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43', -'48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,', -',,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9', -',,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35', -'39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,', -',,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47', -'3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11', -'29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27', -',,,,,,,,,,,,,,,,,,,,23,30,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21', -'28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42', -'12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2', -'8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,', -'18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,', -',34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,', -',,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48', -'4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,', -',,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16', -'20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39', -'43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,', -',,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3', -'9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29', -'35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,', -',,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33', -',,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1', -',11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,', -',27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21', -'28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42', -'12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2', -'8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,', -'18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,', -',34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,', -',,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48', -'4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,', -',,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16', -'20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39', -'43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,', -',,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33,,,47,3', -'9,,,16,20,,,,44,,2,8,,,,,27,,,,,,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29', -'35,39,43,48,4,7,,,18,21,28,33,,,47,3,9,,,16,20,,,,44,,2,8,,,,,27,,,', -',,,,,,,,,,,,,,,,,,,34,,42,12,1,,11,29,35,39,43,48,4,7,,,18,21,28,33', -',,47,3,9,,,16,20,,,,44,,2,8,,,85,,27,94,,,,,,,,,,,,,,,,,,,84,,,34,,42', -',1,,11,,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102', -'104,80,82,86,89,85,,,94,164,,165,,,,,,,,,,,,,,,,84,,,,,,,,,,,88,91,93', -'96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102,104,80,82,86,89,85', -',120,94,,,190,,,,,,,,,,,,,,,,84,,,,,,85,,120,94,,88,91,93,96,98,100', -'101,103,105,81,83,87,90,92,95,97,99,84,102,104,80,82,86,89,,23,30,,88', -'91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102,104,80,82,86', -'89,,23,30,85,,120,94,,,,,,,,,,,,,,,,,,,84,,,,,,85,,120,94,,88,91,93', -'96,98,100,101,103,105,81,83,87,90,92,95,97,99,84,102,104,80,82,86,89', -',23,30,,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102', -'104,80,82,86,89,207,23,30,94,,,,,,,,,,,,,,,,,,,84,,,,,,196,,,94,,88', -'91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,84,102,104,80,82', -'86,89,208,,,,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99', -',102,104,80,82,86,89,197,85,,,94,,,,,,,,,,,,,,,,,,,84,,,,,,85,,,94,', -'88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,84,102,104,80', -'82,86,89,,,,,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99', -',102,104,80,82,86,89,85,,,94,,,,,,,,,,,,,,,,,,,84,,,,,,85,,,94,,88,91', -'93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,84,102,104,80,82,86', -'89,85,,,,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102', -'104,80,82,86,89,85,,,94,,88,91,93,96,98,100,101,103,105,81,83,87,90', -'92,95,97,99,84,102,104,80,82,86,89,-112,,,,88,91,93,96,98,100,101,103', -'105,81,83,87,90,92,95,97,99,,102,104,80,82,86,89,85,,,94,,88,91,93,96', -'98,100,101,103,105,81,83,87,90,92,95,97,99,84,102,104,80,82,86,89,-112', -',,,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102,104,80', -'82,86,89,85,,,94,,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97', -'99,84,102,104,80,82,86,89,-112,,,,88,91,93,96,98,100,101,103,105,81', -'83,87,90,92,95,97,99,,102,104,80,82,86,89,85,,,94,,88,91,93,96,98,100', -'101,103,105,81,83,87,90,92,95,97,99,84,102,104,80,82,86,89,,,,,88,91', -'93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102,104,80,82,86,89', -'88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102,104,80,82', -'86,89,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,,102,104', -'80,82,86,89,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95,97,99,', -'102,104,80,82,86,89,88,91,93,96,98,100,101,103,105,81,83,87,90,92,95', -'97,99,,102,104,80,82,86,89,88,91,93,96,98,100,101,103,105,81,83,87,90', -'92,95,97,99,,102,104,80,82,86,89,88,91,93,96,98,100,101,103,105,81,83', -'87,90,92,95,97,99,,102,104,80,82,86,89' ] - racc_action_table = arr = Array.new(4730, nil) - idx = 0 - clist.each do |str| - str.split(',', -1).each do |i| - arr[idx] = i.to_i unless i.empty? - idx += 1 - end - end - -clist = [ -'0,21,186,38,0,0,0,0,0,0,0,135,84,0,0,0,0,169,169,0,0,0,189,71,0,0,64', -'64,56,0,70,0,0,166,67,64,175,0,76,76,76,186,186,186,168,168,168,6,169', -'135,191,191,191,21,38,21,72,21,0,0,0,32,0,207,0,25,0,207,207,207,207', -'207,207,207,71,52,207,207,207,207,111,70,207,207,207,70,6,207,207,166', -'166,166,207,112,207,207,75,75,6,125,207,1,6,116,116,116,116,116,116', -'116,116,116,116,116,116,116,116,116,116,116,127,51,51,207,51,207,2,207', -'51,207,2,2,2,2,2,2,2,75,75,2,2,2,2,37,37,2,2,2,201,201,2,2,,114,114', -'2,114,2,2,192,192,192,114,2,138,138,138,138,138,138,138,138,138,138', -'138,138,138,106,106,,106,,106,122,122,122,2,,2,124,2,,2,124,124,124', -'124,124,124,124,108,108,124,124,124,124,14,14,124,124,124,,,124,124', -'202,202,202,124,,124,124,149,149,149,,124,148,148,148,148,148,148,148', -'148,148,148,148,148,148,154,154,154,154,154,,,124,124,124,,124,8,124', -',124,8,8,8,8,8,8,8,,,8,8,8,8,,,8,8,8,,,8,8,147,147,147,8,,8,8,173,173', -'173,,8,141,141,141,141,141,141,141,141,141,141,141,141,141,151,151,151', -'151,151,,,,,8,,8,11,8,,8,11,11,11,11,11,11,11,,,11,11,11,11,,,11,11', -'11,,,11,11,,,,11,,11,11,,,,,11,150,150,150,150,150,150,150,150,150,150', -'150,150,150,152,152,152,152,152,,,11,11,11,,11,12,11,,11,12,12,12,12', -'12,12,12,,,12,12,12,12,,,12,12,12,,,12,12,,,,12,,12,12,,,,,12,146,146', -'146,146,146,146,146,146,146,146,146,146,146,156,156,156,156,156,,,,', -'12,,12,119,12,,12,119,119,119,119,119,119,119,,,119,119,119,119,,,119', -'119,119,,,119,119,,,,119,,119,119,,,,,119,132,132,132,132,132,132,132', -'132,132,132,132,132,132,,,,,,,,119,119,119,,119,16,119,,119,16,16,16', -'16,16,16,16,,,16,16,16,16,,,16,16,16,,,16,16,,,,16,,16,16,,,,,16,134', -'134,134,134,134,134,134,134,134,134,134,134,134,,,,,,,,,,16,,16,20,16', -',16,20,20,20,20,20,20,20,,,20,20,20,20,,,20,20,20,,,20,20,,,,20,,20', -'20,,,,,20,143,143,143,143,143,143,143,143,143,143,143,143,143,,,,,,', -',,,20,,20,128,20,,20,128,128,128,128,128,128,128,,,128,128,128,128,', -',128,128,128,,,128,128,,,,128,,128,128,,,,,128,,,,,,,,,,,,,,,,,,,,,128', -'128,128,,128,198,128,,128,198,198,198,198,198,198,198,,,198,198,198', -'198,,,198,198,198,,,198,198,,,,198,,198,198,,,,,198,,,,,,,,,,,,,,,,', -',,,,,,198,,198,27,198,,198,27,27,27,27,27,27,27,,,27,27,27,27,,,27,27', -'27,,,27,27,,,,27,,27,27,,,,,27,,,,,,,,,,,,,,,,,,,,,,,27,,27,28,27,,27', -'28,28,28,28,28,28,28,,,28,28,28,28,,,28,28,28,,,28,28,,,,28,,28,28,', -',,,28,,,,,,,,,,,,,,,,,,,,,,,28,,28,196,28,,28,196,196,196,196,196,196', -'196,,,196,196,196,196,,,196,196,196,,,196,196,,,,196,,196,196,,,,,196', -',,,,,,,,,,,,,,,,,,,,,,196,,196,33,196,,196,33,33,33,33,33,33,33,,,33', -'33,33,33,,,33,33,33,,,33,33,,,,33,,33,33,,,,,33,,,,,,,,,,,,,,,,,,,,', -'33,33,33,,33,34,33,,33,34,34,34,34,34,34,34,,,34,34,34,34,,,34,34,34', -',,34,34,,,,34,,34,34,,,,,34,,,,,,,,,,,,,,,,,,,,,34,34,34,,34,195,34', -',34,195,195,195,195,195,195,195,,,195,195,195,195,,,195,195,195,,,195', -'195,,,,195,,195,195,,,,,195,,,,,,,,,,,,,,,,,,,,,195,195,195,,195,193', -'195,,195,193,193,193,193,193,193,193,,,193,193,193,193,,,193,193,193', -',,193,193,,,,193,,193,193,,,,,193,,,,,,,,,,,,,,,,,,,,,193,193,193,,193', -'113,193,,193,113,113,113,113,113,113,113,,,113,113,113,113,,,113,113', -'113,,,113,113,,,,113,,113,113,,,,,113,,,,,,,,,,,,,,,,,,,,,,,113,,113', -'42,113,,113,42,42,42,42,42,42,42,,,42,42,42,42,,,42,42,42,,,42,42,,', -',42,,42,42,,,,,42,,,,,,,,,,,,,,,,,,,,,,,42,,42,46,42,,42,46,46,46,46', -'46,46,46,,,46,46,46,46,,,46,46,46,,,46,46,,,,46,,46,46,,,,,46,,,,,,', -',,,,,,,,,,,,,,46,46,46,,46,47,46,,46,47,47,47,47,47,47,47,,,47,47,47', -'47,,,47,47,47,,,47,47,,,,47,,47,47,,,,,47,,,,,,,,,,,,,,,,,,,,,,,47,', -'47,157,47,,47,157,157,157,157,157,157,157,,,157,157,157,157,,,157,157', -'157,,,157,157,,,,157,,157,157,,,,,157,,,,,,,,,,,,,,,,,,,,,,,157,,157', -'158,157,,157,158,158,158,158,158,158,158,,,158,158,158,158,,,158,158', -'158,,,158,158,,,,158,,158,158,,,,,158,,,,,,,,,,,,,,,,,,,,,,,158,,158', -'55,158,,158,55,55,55,55,55,55,55,,,55,55,55,55,,,55,55,55,,,55,55,,', -',55,,55,55,,,,,55,,,,,,,,,,,,,,,,,,,,,,,55,,55,184,55,,55,184,184,184', -'184,184,184,184,,,184,184,184,184,,,184,184,184,,,184,184,,,,184,,184', -'184,,,,,184,,,,,,,,,,,,,,,,,,,,,184,184,184,,184,58,184,,184,58,58,58', -'58,58,58,58,,,58,58,58,58,,,58,58,58,,,58,58,,,,58,,58,58,,,,,58,,,', -',,,,,,,,,,,,,,,,,,,58,,58,59,58,,58,59,59,59,59,59,59,59,,,59,59,59', -'59,,,59,59,59,,,59,59,,,,59,,59,59,,,,,59,,,,,,,,,,,,,,,,,,,,,,,59,', -'59,179,59,,59,179,179,179,179,179,179,179,,,179,179,179,179,,,179,179', -'179,,,179,179,,,,179,,179,179,,,,,179,,,,,,,,,,,,,,,,,,,,,179,179,179', -',179,164,179,,179,164,164,164,164,164,164,164,,,164,164,164,164,,,164', -'164,164,,,164,164,,,,164,,164,164,,,,,164,,,,,,,,,,,,,,,,,,,,,,,164', -',164,66,164,,164,66,66,66,66,66,66,66,,,66,66,66,66,,,66,66,66,,,66', -'66,,,,66,,66,66,,,,,66,,,,,,,,,,,,,,,,,,,,,66,66,66,,66,105,66,,66,105', -'105,105,105,105,105,105,,,105,105,105,105,,,105,105,105,,,105,105,,', -',105,,105,105,,,,,105,,,,,,,,,,,,,,,,,,,,,,,105,,105,104,105,,105,104', -'104,104,104,104,104,104,,,104,104,104,104,,,104,104,104,,,104,104,,', -',104,,104,104,,,,,104,,,,,,,,,,,,,,,,,,,,,,,104,,104,103,104,,104,103', -'103,103,103,103,103,103,,,103,103,103,103,,,103,103,103,,,103,103,,', -',103,,103,103,,,,,103,,,,,,,,,,,,,,,,,,,,,,,103,,103,102,103,,103,102', -'102,102,102,102,102,102,,,102,102,102,102,,,102,102,102,,,102,102,,', -',102,,102,102,,,,,102,,,,,,,,,,,,,,,,,,,,,,,102,,102,101,102,,102,101', -'101,101,101,101,101,101,,,101,101,101,101,,,101,101,101,,,101,101,,', -',101,,101,101,,,,,101,,,,,,,,,,,,,,,,,,,,,,,101,,101,174,101,,101,174', -'174,174,174,174,174,174,,,174,174,174,174,,,174,174,174,,,174,174,,', -',174,,174,174,,,,,174,,,,,,,,,,,,,,,,,,,,,,,174,,174,100,174,,174,100', -'100,100,100,100,100,100,,,100,100,100,100,,,100,100,100,,,100,100,,', -',100,,100,100,,,,,100,,,,,,,,,,,,,,,,,,,,,,,100,,100,172,100,,100,172', -'172,172,172,172,172,172,,,172,172,172,172,,,172,172,172,,,172,172,,', -',172,,172,172,,,,,172,,,,,,,,,,,,,,,,,,,,,172,172,172,,172,171,172,', -'172,171,171,171,171,171,171,171,,,171,171,171,171,,,171,171,171,,,171', -'171,,,,171,,171,171,,,,,171,,,,,,,,,,,,,,,,,,,,,,,171,,171,77,171,,171', -'77,77,77,77,77,77,77,,,77,77,77,77,,,77,77,77,,,77,77,,,,77,,77,77,', -',,,77,,,,,,,,,,,,,,,,,,,,,,,77,,77,80,77,,77,80,80,80,80,80,80,80,,', -'80,80,80,80,,,80,80,80,,,80,80,,,,80,,80,80,,,,,80,,,,,,,,,,,,,,,,,', -',,,,,80,,80,81,80,,80,81,81,81,81,81,81,81,,,81,81,81,81,,,81,81,81', -',,81,81,,,,81,,81,81,,,,,81,,,,,,,,,,,,,,,,,,,,,,,81,,81,82,81,,81,82', -'82,82,82,82,82,82,,,82,82,82,82,,,82,82,82,,,82,82,,,,82,,82,82,,,,', -'82,,,,,,,,,,,,,,,,,,,,,,,82,,82,83,82,,82,83,83,83,83,83,83,83,,,83', -'83,83,83,,,83,83,83,,,83,83,,,,83,,83,83,,,,,83,,,,,,,,,,,,,,,,,,,,', -',,83,,83,98,83,,83,98,98,98,98,98,98,98,,,98,98,98,98,,,98,98,98,,,98', -'98,,,,98,,98,98,,,,,98,,,,,,,,,,,,,,,,,,,,,,,98,,98,85,98,,98,85,85', -'85,85,85,85,85,,,85,85,85,85,,,85,85,85,,,85,85,,,,85,,85,85,,,,,85', -',,,,,,,,,,,,,,,,,,,,,,85,,85,86,85,,85,86,86,86,86,86,86,86,,,86,86', -'86,86,,,86,86,86,,,86,86,,,,86,,86,86,,,,,86,,,,,,,,,,,,,,,,,,,,,,,86', -',86,87,86,,86,87,87,87,87,87,87,87,,,87,87,87,87,,,87,87,87,,,87,87', -',,,87,,87,87,,,,,87,,,,,,,,,,,,,,,,,,,,,,,87,,87,88,87,,87,88,88,88', -'88,88,88,88,,,88,88,88,88,,,88,88,88,,,88,88,,,,88,,88,88,,,,,88,,,', -',,,,,,,,,,,,,,,,,,,88,,88,89,88,,88,89,89,89,89,89,89,89,,,89,89,89', -'89,,,89,89,89,,,89,89,,,,89,,89,89,,,,,89,,,,,,,,,,,,,,,,,,,,,,,89,', -'89,90,89,,89,90,90,90,90,90,90,90,,,90,90,90,90,,,90,90,90,,,90,90,', -',,90,,90,90,,,,,90,,,,,,,,,,,,,,,,,,,,,,,90,,90,91,90,,90,91,91,91,91', -'91,91,91,,,91,91,91,91,,,91,91,91,,,91,91,,,,91,,91,91,,,,,91,,,,,,', -',,,,,,,,,,,,,,,,91,,91,92,91,,91,92,92,92,92,92,92,92,,,92,92,92,92', -',,92,92,92,,,92,92,,,,92,,92,92,,,,,92,,,,,,,,,,,,,,,,,,,,,,,92,,92', -'93,92,,92,93,93,93,93,93,93,93,,,93,93,93,93,,,93,93,93,,,93,93,,,,93', -',93,93,,,,,93,,,,,,,,,,,,,,,,,,,,,,,93,,93,94,93,,93,94,94,94,94,94', -'94,94,,,94,94,94,94,,,94,94,94,,,94,94,,,,94,,94,94,,,,,94,,,,,,,,,', -',,,,,,,,,,,,,94,,94,95,94,,94,95,95,95,95,95,95,95,,,95,95,95,95,,,95', -'95,95,,,95,95,,,,95,,95,95,,,,,95,,,,,,,,,,,,,,,,,,,,,,,95,,95,96,95', -',95,96,96,96,96,96,96,96,,,96,96,96,96,,,96,96,96,,,96,96,,,,96,,96', -'96,,,,,96,,,,,,,,,,,,,,,,,,,,,,,96,,96,97,96,,96,97,97,97,97,97,97,97', -',,97,97,97,97,,,97,97,97,,,97,97,,,,97,,97,97,,,,,97,,,,,,,,,,,,,,,', -',,,,,,,97,,97,99,97,,97,99,99,99,99,99,99,99,,,99,99,99,99,,,99,99,99', -',,99,99,,,,99,,99,99,,,117,,99,117,,,,,,,,,,,,,,,,,,,117,,,99,,99,,99', -',99,,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117,117', -'117,,117,117,117,117,117,117,178,,,178,117,,117,,,,,,,,,,,,,,,,178,', -',,,,,,,,,178,178,178,178,178,178,178,178,178,178,178,178,178,178,178', -'178,178,,178,178,178,178,178,178,185,,185,185,,,178,,,,,,,,,,,,,,,,185', -',,,,,65,,65,65,,185,185,185,185,185,185,185,185,185,185,185,185,185', -'185,185,185,185,65,185,185,185,185,185,185,,185,185,,65,65,65,65,65', -'65,65,65,65,65,65,65,65,65,65,65,65,,65,65,65,65,65,65,,65,65,68,,68', -'68,,,,,,,,,,,,,,,,,,,68,,,,,,69,,69,69,,68,68,68,68,68,68,68,68,68,68', -'68,68,68,68,68,68,68,69,68,68,68,68,68,68,,68,68,,69,69,69,69,69,69', -'69,69,69,69,69,69,69,69,69,69,69,,69,69,69,69,69,69,204,69,69,204,,', -',,,,,,,,,,,,,,,,204,,,,,,188,,,188,,204,204,204,204,204,204,204,204', -'204,204,204,204,204,204,204,204,204,188,204,204,204,204,204,204,204', -',,,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188', -',188,188,188,188,188,188,188,109,,,109,,,,,,,,,,,,,,,,,,,109,,,,,,162', -',,162,,109,109,109,109,109,109,109,109,109,109,109,109,109,109,109,109', -'109,162,109,109,109,109,109,109,,,,,162,162,162,162,162,162,162,162', -'162,162,162,162,162,162,162,162,162,,162,162,162,162,162,162,107,,,107', -',,,,,,,,,,,,,,,,,,107,,,,,,177,,,177,,107,107,107,107,107,107,107,107', -'107,107,107,107,107,107,107,107,107,177,107,107,107,107,107,107,145', -',,,177,177,177,177,177,177,177,177,177,177,177,177,177,177,177,177,177', -',177,177,177,177,177,177,41,,,41,,145,145,145,145,145,145,145,145,145', -'145,145,145,145,145,145,145,145,41,145,145,145,145,145,145,203,,,,41', -'41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,,41,41,41,41,41,41,176', -',,176,,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203', -'203,176,203,203,203,203,203,203,136,,,,176,176,176,176,176,176,176,176', -'176,176,176,176,176,176,176,176,176,,176,176,176,176,176,176,74,,,74', -',136,136,136,136,136,136,136,136,136,136,136,136,136,136,136,136,136', -'74,136,136,136,136,136,136,209,,,,74,74,74,74,74,74,74,74,74,74,74,74', -'74,74,74,74,74,,74,74,74,74,74,74,130,,,130,,209,209,209,209,209,209', -'209,209,209,209,209,209,209,209,209,209,209,130,209,209,209,209,209', -'209,,,,,130,130,130,130,130,130,130,130,130,130,130,130,130,130,130', -'130,130,,130,130,130,130,130,130,137,137,137,137,137,137,137,137,137', -'137,137,137,137,137,137,137,137,,137,137,137,137,137,137,133,133,133', -'133,133,133,133,133,133,133,133,133,133,133,133,133,133,,133,133,133', -'133,133,133,140,140,140,140,140,140,140,140,140,140,140,140,140,140', -'140,140,140,,140,140,140,140,140,140,131,131,131,131,131,131,131,131', -'131,131,131,131,131,131,131,131,131,,131,131,131,131,131,131,153,153', -'153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,,153,153', -'153,153,153,153,155,155,155,155,155,155,155,155,155,155,155,155,155', -'155,155,155,155,,155,155,155,155,155,155' ] - racc_action_check = arr = Array.new(4730, nil) - idx = 0 - clist.each do |str| - str.split(',', -1).each do |i| - arr[idx] = i.to_i unless i.empty? - idx += 1 - end - end - -racc_action_pointer = [ - -2, 89, 124, nil, nil, nil, 34, nil, 250, nil, - nil, 313, 376, nil, 144, nil, 502, nil, nil, nil, - 565, -11, nil, nil, nil, 65, nil, 754, 817, nil, - nil, nil, 61, 943, 1006, nil, nil, 83, -10, nil, - nil, 4356, 1258, nil, nil, nil, 1321, 1384, nil, nil, - nil, 61, 23, nil, nil, 1573, 16, nil, 1699, 1762, - nil, nil, nil, nil, -34, 3955, 1951, 19, 4015, 4043, - 17, 10, 56, nil, 4470, 77, -21, 2581, nil, nil, - 2644, 2707, 2770, 2833, 0, 2959, 3022, 3085, 3148, 3211, - 3274, 3337, 3400, 3463, 3526, 3589, 3652, 3715, 2896, 3778, - 2392, 2266, 2203, 2140, 2077, 2014, 117, 4271, 140, 4186, - nil, 68, 81, 1195, 93, nil, 68, 3813, nil, 439, - nil, nil, 124, nil, 187, 71, nil, 108, 628, nil, - 4527, 4623, 444, 4575, 507, -14, 4442, 4551, 129, nil, - 4599, 255, nil, 570, nil, 4328, 381, 243, 192, 187, - 318, 268, 331, 4647, 205, 4671, 394, 1447, 1510, nil, - nil, nil, 4214, nil, 1888, nil, 30, nil, -15, -11, - nil, 2518, 2455, 226, 2329, 24, 4413, 4299, 3870, 1825, - nil, nil, nil, nil, 1636, 3927, -18, nil, 4128, -3, - nil, -9, 100, 1132, nil, 1069, 880, nil, 691, nil, - nil, 88, 156, 4385, 4100, nil, nil, 61, nil, 4499, - nil ] - -racc_action_default = [ - -1, -81, -112, -34, -33, -20, -9, -69, -112, -35, - -10, -112, -112, -11, -112, -12, -112, -70, -67, -13, - -112, -112, -71, -21, -14, -112, -72, -112, -112, -27, - -22, -15, -26, -112, -112, -28, -16, -3, -85, -30, - -17, -4, -89, -31, -29, -18, -112, -112, -32, -19, - -8, -112, -112, -82, -41, -89, -112, -73, -112, -112, - -76, -77, -39, -26, -112, -112, -112, -112, -112, -112, - -112, -86, -112, -40, -38, -112, -112, -26, -6, -74, - -112, -112, -112, -112, -112, -112, -112, -112, -112, -112, - -112, -112, -112, -112, -112, -112, -112, -112, -112, -112, - -112, -112, -112, -112, -112, -112, -112, -90, -7, -100, - -80, -112, -112, -112, -112, -75, -36, -112, -101, -112, - -23, -24, -112, -68, -112, -112, 211, -112, -112, -66, - -5, -61, -51, -62, -52, -112, -95, -63, -53, -42, - -64, -54, -43, -55, -44, -96, -56, -45, -57, -46, - -58, -47, -48, -59, -49, -60, -50, -112, -112, -88, - -84, -83, -37, -87, -112, -78, -112, -65, -112, -112, - -109, -112, -112, -112, -112, -112, -92, -91, -112, -112, - -93, -102, -110, -107, -112, -112, -112, -98, -112, -112, - -79, -112, -112, -112, -97, -112, -112, -103, -112, -94, - -108, -25, -112, -95, -112, -99, -105, -112, -104, -95, - -106 ] - -racc_goto_table = [ - 54, 79, 53, 170, 32, 119, 62, 106, 124, 125, - 65, 51, 71, 70, 68, 63, 169, 25, 69, nil, - 114, nil, nil, nil, nil, 73, 74, nil, nil, nil, - nil, nil, nil, nil, 79, nil, nil, 63, 63, nil, - 107, nil, nil, nil, nil, 109, nil, 182, nil, nil, - 63, nil, nil, 107, nil, 111, 116, 117, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 121, - 63, nil, 121, 121, nil, 130, nil, nil, 131, 132, - 133, 134, 78, 136, 137, 138, 139, 140, 141, 142, - 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, - 153, 154, 155, 156, 37, nil, nil, nil, nil, 78, - 157, 162, 160, 161, nil, 64, nil, nil, 157, nil, - 78, 78, nil, 63, nil, 193, nil, nil, 63, nil, - nil, nil, 63, nil, nil, nil, nil, 75, 76, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 108, nil, nil, 78, nil, 176, 177, nil, nil, nil, - nil, nil, 178, nil, nil, nil, nil, 78, nil, 185, - 122, nil, 188, nil, nil, nil, 63, nil, nil, nil, - nil, nil, nil, 63, nil, nil, nil, nil, 63, 121, - nil, nil, nil, nil, 203, nil, 204, 63, nil, 63, - nil, nil, nil, nil, nil, 209, nil, nil, nil, nil, - nil, 78, nil, 78, nil, nil, nil, nil, 78, nil, - nil, nil, nil, 166, nil, nil, nil, nil, 168, nil, - nil, 78, 173, nil, nil, nil, 78, 78, nil, nil, - nil, nil, nil, nil, nil, nil, 78, 78, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 186, nil, nil, nil, - nil, nil, nil, 191, nil, nil, nil, nil, 192, nil, - nil, nil, nil, nil, nil, nil, nil, 201, nil, 202 ] - -racc_goto_check = [ - 4, 25, 20, 32, 2, 19, 4, 30, 19, 19, - 4, 29, 26, 7, 4, 2, 31, 1, 4, nil, - 30, nil, nil, nil, nil, 4, 4, nil, nil, nil, - nil, nil, nil, nil, 25, nil, nil, 2, 2, nil, - 4, nil, nil, nil, nil, 4, nil, 32, nil, nil, - 2, nil, nil, 4, nil, 2, 4, 4, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, - 2, nil, 2, 2, nil, 4, nil, nil, 4, 4, - 4, 4, 5, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 3, nil, nil, nil, nil, 5, - 2, 4, 20, 20, nil, 3, nil, nil, 2, nil, - 5, 5, nil, 2, nil, 19, nil, nil, 2, nil, - nil, nil, 2, nil, nil, nil, nil, 3, 3, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 3, nil, nil, 5, nil, 4, 4, nil, nil, nil, - nil, nil, 4, nil, nil, nil, nil, 5, nil, 4, - 3, nil, 4, nil, nil, nil, 2, nil, nil, nil, - nil, nil, nil, 2, nil, nil, nil, nil, 2, 2, - nil, nil, nil, nil, 4, nil, 4, 2, nil, 2, - nil, nil, nil, nil, nil, 4, nil, nil, nil, nil, - nil, 5, nil, 5, nil, nil, nil, nil, 5, nil, - nil, nil, nil, 3, nil, nil, nil, nil, 3, nil, - nil, 5, 3, nil, nil, nil, 5, 5, nil, nil, - nil, nil, nil, nil, nil, nil, 5, 5, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 3, nil, nil, nil, - nil, nil, nil, 3, nil, nil, nil, nil, 3, nil, - nil, nil, nil, nil, nil, nil, nil, 3, nil, 3 ] - -racc_goto_pointer = [ - nil, 17, 4, 104, -2, 45, nil, -8, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, -60, - 1, nil, nil, nil, nil, -37, -9, nil, nil, 10, - -35, -109, -122 ] - -racc_goto_default = [ - nil, nil, 77, nil, 41, 46, 50, 6, 10, 13, - 15, 19, 24, 31, 36, 40, 45, 49, 5, nil, - nil, 14, 17, 22, 26, 57, 38, 60, 61, nil, - nil, nil, nil ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 0, 71, :_reduce_1, - 1, 71, :_reduce_2, - 1, 71, :_reduce_3, - 1, 73, :_reduce_4, - 3, 73, :_reduce_5, - 2, 73, :_reduce_6, - 2, 73, :_reduce_7, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 74, :_reduce_none, - 1, 72, :_reduce_none, - 1, 72, :_reduce_none, - 1, 89, :_reduce_none, - 1, 89, :_reduce_none, - 0, 75, :_reduce_none, - 1, 75, :_reduce_none, - 1, 76, :_reduce_27, - 1, 76, :_reduce_28, - 1, 76, :_reduce_29, - 1, 76, :_reduce_30, - 1, 76, :_reduce_31, - 1, 76, :_reduce_32, - 1, 76, :_reduce_33, - 1, 76, :_reduce_34, - 1, 76, :_reduce_35, - 3, 79, :_reduce_36, - 3, 90, :_reduce_37, - 2, 85, :_reduce_38, - 2, 81, :_reduce_39, - 2, 81, :_reduce_40, - 2, 81, :_reduce_41, - 3, 81, :_reduce_42, - 3, 81, :_reduce_43, - 3, 81, :_reduce_44, - 3, 81, :_reduce_45, - 3, 81, :_reduce_46, - 3, 81, :_reduce_47, - 3, 81, :_reduce_48, - 3, 81, :_reduce_49, - 3, 81, :_reduce_50, - 3, 81, :_reduce_51, - 3, 81, :_reduce_52, - 3, 81, :_reduce_53, - 3, 81, :_reduce_54, - 3, 81, :_reduce_55, - 3, 81, :_reduce_56, - 3, 81, :_reduce_57, - 3, 81, :_reduce_58, - 3, 81, :_reduce_59, - 3, 81, :_reduce_60, - 3, 81, :_reduce_61, - 3, 81, :_reduce_62, - 3, 81, :_reduce_63, - 3, 81, :_reduce_64, - 4, 80, :_reduce_65, - 3, 80, :_reduce_66, - 1, 91, :_reduce_67, - 3, 91, :_reduce_68, - 1, 77, :_reduce_69, - 1, 77, :_reduce_70, - 1, 77, :_reduce_71, - 1, 77, :_reduce_72, - 2, 77, :_reduce_73, - 2, 77, :_reduce_74, - 2, 95, :_reduce_75, - 1, 95, :_reduce_76, - 1, 95, :_reduce_77, - 3, 97, :_reduce_78, - 5, 98, :_reduce_79, - 3, 93, :_reduce_80, - 0, 99, :_reduce_81, - 1, 99, :_reduce_82, - 3, 99, :_reduce_83, - 3, 99, :_reduce_84, - 1, 78, :_reduce_85, - 2, 78, :_reduce_86, - 4, 96, :_reduce_87, - 3, 92, :_reduce_88, - 0, 100, :_reduce_89, - 1, 100, :_reduce_90, - 3, 100, :_reduce_91, - 3, 100, :_reduce_92, - 5, 82, :_reduce_93, - 7, 82, :_reduce_94, - 3, 82, :_reduce_95, - 3, 82, :_reduce_96, - 6, 83, :_reduce_97, - 5, 83, :_reduce_98, - 8, 83, :_reduce_99, - 2, 84, :_reduce_100, - 3, 94, :_reduce_101, - 5, 86, :_reduce_102, - 6, 87, :_reduce_103, - 8, 87, :_reduce_104, - 8, 87, :_reduce_105, - 10, 87, :_reduce_106, - 5, 88, :_reduce_107, - 7, 88, :_reduce_108, - 1, 101, :_reduce_109, - 2, 101, :_reduce_110, - 4, 102, :_reduce_111 ] - -racc_reduce_n = 112 - -racc_shift_n = 211 - -racc_token_table = { - false => 0, - :error => 1, - :IF => 2, - :ELSE => 3, - :THEN => 4, - :UNLESS => 5, - :NUMBER => 6, - :STRING => 7, - :REGEX => 8, - :TRUE => 9, - :FALSE => 10, - :NULL => 11, - :IDENTIFIER => 12, - :PROPERTY_ACCESS => 13, - :CODE => 14, - :PARAM => 15, - :NEW => 16, - :RETURN => 17, - :TRY => 18, - :CATCH => 19, - :FINALLY => 20, - :THROW => 21, - :BREAK => 22, - :CONTINUE => 23, - :FOR => 24, - :IN => 25, - :WHILE => 26, - :SWITCH => 27, - :CASE => 28, - :DEFAULT => 29, - :NEWLINE => 30, - :JS => 31, - :UMINUS => 32, - :NOT => 33, - "!" => 34, - "*" => 35, - "/" => 36, - "%" => 37, - "+" => 38, - "-" => 39, - "<=" => 40, - "<" => 41, - ">" => 42, - ">=" => 43, - "==" => 44, - "!=" => 45, - :IS => 46, - :AINT => 47, - "&&" => 48, - "||" => 49, - :AND => 50, - :OR => 51, - ":" => 52, - "-=" => 53, - "+=" => 54, - "/=" => 55, - "*=" => 56, - "||=" => 57, - "&&=" => 58, - "." => 59, - "\n" => 60, - ";" => 61, - "=>" => 62, - "," => 63, - "[" => 64, - "]" => 65, - "{" => 66, - "}" => 67, - "(" => 68, - ")" => 69 } - -racc_nt_base = 70 - -racc_use_result_var = true - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - -Racc_token_to_s_table = [ - "$end", - "error", - "IF", - "ELSE", - "THEN", - "UNLESS", - "NUMBER", - "STRING", - "REGEX", - "TRUE", - "FALSE", - "NULL", - "IDENTIFIER", - "PROPERTY_ACCESS", - "CODE", - "PARAM", - "NEW", - "RETURN", - "TRY", - "CATCH", - "FINALLY", - "THROW", - "BREAK", - "CONTINUE", - "FOR", - "IN", - "WHILE", - "SWITCH", - "CASE", - "DEFAULT", - "NEWLINE", - "JS", - "UMINUS", - "NOT", - "\"!\"", - "\"*\"", - "\"/\"", - "\"%\"", - "\"+\"", - "\"-\"", - "\"<=\"", - "\"<\"", - "\">\"", - "\">=\"", - "\"==\"", - "\"!=\"", - "IS", - "AINT", - "\"&&\"", - "\"||\"", - "AND", - "OR", - "\":\"", - "\"-=\"", - "\"+=\"", - "\"/=\"", - "\"*=\"", - "\"||=\"", - "\"&&=\"", - "\".\"", - "\"\\n\"", - "\";\"", - "\"=>\"", - "\",\"", - "\"[\"", - "\"]\"", - "\"{\"", - "\"}\"", - "\"(\"", - "\")\"", - "$start", - "Root", - "Terminator", - "Expressions", - "Expression", - "OptTerminator", - "Literal", - "Value", - "Call", - "Assign", - "Code", - "Operation", - "If", - "Try", - "Throw", - "Return", - "While", - "For", - "Switch", - "Then", - "AssignObj", - "ParamList", - "Array", - "Object", - "Parenthetical", - "Accessor", - "Invocation", - "Index", - "Slice", - "AssignList", - "ArgList", - "Cases", - "Case" ] - -Racc_debug_parser = false - -##### State transition tables end ##### - -# reduce 0 omitted - -module_eval(<<'.,.,', 'grammar.y', 39) - def _reduce_1(val, _values, result) - result = Nodes.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 40) - def _reduce_2(val, _values, result) - result = Nodes.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 41) - def _reduce_3(val, _values, result) - result = val[0] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 46) - def _reduce_4(val, _values, result) - result = Nodes.new(val) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 47) - def _reduce_5(val, _values, result) - result = val[0] << val[2] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 48) - def _reduce_6(val, _values, result) - result = val[0] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 49) - def _reduce_7(val, _values, result) - result = val[1] - result - end -.,., - -# reduce 8 omitted - -# reduce 9 omitted - -# reduce 10 omitted - -# reduce 11 omitted - -# reduce 12 omitted - -# reduce 13 omitted - -# reduce 14 omitted - -# reduce 15 omitted - -# reduce 16 omitted - -# reduce 17 omitted - -# reduce 18 omitted - -# reduce 19 omitted - -# reduce 20 omitted - -# reduce 21 omitted - -# reduce 22 omitted - -# reduce 23 omitted - -# reduce 24 omitted - -# reduce 25 omitted - -# reduce 26 omitted - -module_eval(<<'.,.,', 'grammar.y', 88) - def _reduce_27(val, _values, result) - result = LiteralNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 89) - def _reduce_28(val, _values, result) - result = LiteralNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 90) - def _reduce_29(val, _values, result) - result = LiteralNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 91) - def _reduce_30(val, _values, result) - result = LiteralNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 92) - def _reduce_31(val, _values, result) - result = LiteralNode.new(true) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 93) - def _reduce_32(val, _values, result) - result = LiteralNode.new(false) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 94) - def _reduce_33(val, _values, result) - result = LiteralNode.new(nil) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 95) - def _reduce_34(val, _values, result) - result = LiteralNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 96) - def _reduce_35(val, _values, result) - result = LiteralNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 101) - def _reduce_36(val, _values, result) - result = AssignNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 106) - def _reduce_37(val, _values, result) - result = AssignNode.new(val[0], val[2], :object) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 111) - def _reduce_38(val, _values, result) - result = ReturnNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 118) - def _reduce_39(val, _values, result) - result = OpNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 119) - def _reduce_40(val, _values, result) - result = OpNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 120) - def _reduce_41(val, _values, result) - result = OpNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 123) - def _reduce_42(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 124) - def _reduce_43(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 125) - def _reduce_44(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 127) - def _reduce_45(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 128) - def _reduce_46(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 130) - def _reduce_47(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 131) - def _reduce_48(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 132) - def _reduce_49(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 133) - def _reduce_50(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 135) - def _reduce_51(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 136) - def _reduce_52(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 137) - def _reduce_53(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 138) - def _reduce_54(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 140) - def _reduce_55(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 141) - def _reduce_56(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 142) - def _reduce_57(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 143) - def _reduce_58(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 145) - def _reduce_59(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 146) - def _reduce_60(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 147) - def _reduce_61(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 148) - def _reduce_62(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 149) - def _reduce_63(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 150) - def _reduce_64(val, _values, result) - result = OpNode.new(val[1], val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 156) - def _reduce_65(val, _values, result) - result = CodeNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 157) - def _reduce_66(val, _values, result) - result = CodeNode.new([], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 161) - def _reduce_67(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 162) - def _reduce_68(val, _values, result) - result = val[0] << val[2] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 166) - def _reduce_69(val, _values, result) - result = ValueNode.new(val) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 167) - def _reduce_70(val, _values, result) - result = ValueNode.new(val) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 168) - def _reduce_71(val, _values, result) - result = ValueNode.new(val) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 169) - def _reduce_72(val, _values, result) - result = ValueNode.new(val) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 170) - def _reduce_73(val, _values, result) - result = val[0] << val[1] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 171) - def _reduce_74(val, _values, result) - result = ValueNode.new(val[0], [val[1]]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 175) - def _reduce_75(val, _values, result) - result = AccessorNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 176) - def _reduce_76(val, _values, result) - result = val[0] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 177) - def _reduce_77(val, _values, result) - result = val[0] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 181) - def _reduce_78(val, _values, result) - result = IndexNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 185) - def _reduce_79(val, _values, result) - result = SliceNode.new(val[1], val[3]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 189) - def _reduce_80(val, _values, result) - result = ObjectNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 193) - def _reduce_81(val, _values, result) - result = [] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 194) - def _reduce_82(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 195) - def _reduce_83(val, _values, result) - result = val[0] << val[2] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 196) - def _reduce_84(val, _values, result) - result = val[0] << val[2] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 201) - def _reduce_85(val, _values, result) - result = val[0] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 202) - def _reduce_86(val, _values, result) - result = val[1].new_instance - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 206) - def _reduce_87(val, _values, result) - result = CallNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 211) - def _reduce_88(val, _values, result) - result = ArrayNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 216) - def _reduce_89(val, _values, result) - result = [] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 217) - def _reduce_90(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 218) - def _reduce_91(val, _values, result) - result = val[0] << val[2] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 219) - def _reduce_92(val, _values, result) - result = val[0] << val[2] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 224) - def _reduce_93(val, _values, result) - result = IfNode.new(val[1], val[3]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 227) - def _reduce_94(val, _values, result) - result = IfNode.new(val[1], val[3], val[5]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 228) - def _reduce_95(val, _values, result) - result = IfNode.new(val[2], Nodes.new([val[0]])) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 229) - def _reduce_96(val, _values, result) - result = IfNode.new(val[2], Nodes.new([val[0]]), nil, :invert) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 234) - def _reduce_97(val, _values, result) - result = TryNode.new(val[1], val[3], val[4]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 236) - def _reduce_98(val, _values, result) - result = TryNode.new(val[1], nil, nil, val[3]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 239) - def _reduce_99(val, _values, result) - result = TryNode.new(val[1], val[3], val[4], val[6]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 243) - def _reduce_100(val, _values, result) - result = ThrowNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 247) - def _reduce_101(val, _values, result) - result = ParentheticalNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 252) - def _reduce_102(val, _values, result) - result = WhileNode.new(val[1], val[3]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 257) - def _reduce_103(val, _values, result) - result = ForNode.new(val[0], val[4], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 260) - def _reduce_104(val, _values, result) - result = ForNode.new(val[0], val[6], val[2], val[4]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 263) - def _reduce_105(val, _values, result) - result = ForNode.new(IfNode.new(val[6], Nodes.new([val[0]])), val[4], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 267) - def _reduce_106(val, _values, result) - result = ForNode.new(IfNode.new(val[8], Nodes.new([val[0]])), val[6], val[2], val[4]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 272) - def _reduce_107(val, _values, result) - result = val[3].rewrite_condition(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 274) - def _reduce_108(val, _values, result) - result = val[3].rewrite_condition(val[1]).add_default(val[5]) - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 278) - def _reduce_109(val, _values, result) - result = val[0] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 279) - def _reduce_110(val, _values, result) - result = val[0] << val[1] - result - end -.,., - -module_eval(<<'.,.,', 'grammar.y', 283) - def _reduce_111(val, _values, result) - result = IfNode.new(val[1], val[3]) - result - end -.,., - -def _reduce_none(val, _values, result) - val[0] -end - -end # class Parser diff --git a/lexer_test.rb b/test/lexer_test.rb similarity index 100% rename from lexer_test.rb rename to test/lexer_test.rb diff --git a/parser_test.rb b/test/parser_test.rb similarity index 100% rename from parser_test.rb rename to test/parser_test.rb From c6f11fbfeb63a4a020bb74d737c290d018180442 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 16 Dec 2009 23:10:03 -0500 Subject: [PATCH 002/303] built the first gem -- works just fine --- .gitignore | 3 ++- README | 0 Rakefile | 22 +++++++++++++--------- SYNTAX | 30 ------------------------------ TODO | 2 ++ coffee-script.gemspec | 21 +++++++++++++++++++++ 6 files changed, 38 insertions(+), 40 deletions(-) create mode 100644 README delete mode 100644 SYNTAX create mode 100644 coffee-script.gemspec diff --git a/.gitignore b/.gitignore index 789e9d0da5..2eb797553a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -parser.output \ No newline at end of file +parser.output +*.gem \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Rakefile b/Rakefile index 2d2fd8d3cd..fdc1f4ca33 100644 --- a/Rakefile +++ b/Rakefile @@ -3,13 +3,17 @@ task :build, :extra_args do |t, args| sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" end +namespace :gem do -# # Pipe compiled JS through JSLint. -# puts "\n\n" -# require 'open3' -# stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin') -# stdin.write(js) -# stdin.close -# puts stdout.read -# stdout.close -# stderr.close \ No newline at end of file + desc 'Build and install the coffee-script gem' + task :install do + sh "gem build coffee-script.gemspec" + sh "sudo gem install #{Dir['*.gem'].join(' ')} --local --no-ri --no-rdoc" + end + + desc 'Uninstall the coffee-script gem' + task :uninstall do + sh "sudo gem uninstall -x coffee-script" + end + +end \ No newline at end of file diff --git a/SYNTAX b/SYNTAX deleted file mode 100644 index cd788ee26d..0000000000 --- a/SYNTAX +++ /dev/null @@ -1,30 +0,0 @@ -Every line is an expression. Multiple expressions on a single line can be -separated by a ";" character. - -NUM: 1 - 1.0 - -STRING: "hello" - 'hello' - -OBJECT: {one : 1, two : 2} - -ARRAY: [1, 2, 3] - -CODE: a, b => a * b. - -IF: return x if x > 1 - - if (x > 1) return x - -ASSIGN: a : b - -LOGICAL: x && y - x and y - x || y - x or y - - - - - diff --git a/TODO b/TODO index 93dbd7f72c..8fc6ae7ce3 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,7 @@ TODO: +* Need *way* better syntax errors. + * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/coffee-script.gemspec b/coffee-script.gemspec new file mode 100644 index 0000000000..6fcf2ea1d9 --- /dev/null +++ b/coffee-script.gemspec @@ -0,0 +1,21 @@ +Gem::Specification.new do |s| + s.name = 'coffee-script' + s.version = '0.1.0' # Keep version in sync with coffee-script.rb + s.date = '2009-12-16' + + s.homepage = "http://jashkenas.github.com/coffee-script/" + s.summary = "The CoffeeScript Compiler" + s.description = <<-EOS + ... + EOS + + s.authors = ['Jeremy Ashkenas'] + s.email = 'jashkenas@gmail.com' + s.rubyforge_project = 'coffee-script' + s.has_rdoc = false + + s.require_paths = ['lib'] + s.executables = ['coffee-script'] + + s.files = Dir['bin/*', 'examples/*', 'lib/**/*', 'coffee-script.gemspec', 'LICENSE', 'README'] +end \ No newline at end of file From d1682f5b3f70f897f1e86834b0bf4520b2f6a6d9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 08:23:07 -0500 Subject: [PATCH 003/303] multiline strings --- examples/code.cs | 7 ++++++- lib/coffee_script/command_line.rb | 8 ++++++++ lib/coffee_script/lexer.rb | 5 +++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index 756ee24a63..43e9223138 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -121,4 +121,9 @@ # Array slice literals. zero_to_nine: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -three_to_six: zero_to_nine[3, 6] \ No newline at end of file +three_to_six: zero_to_nine[3, 6] + +# Multiline strings. +story: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, +sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna +aliquam erat volutpat. Ut wisi enim ad." \ No newline at end of file diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index ecf81da467..70e590045d 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -30,6 +30,7 @@ def usage def compile_javascript @sources.each do |source| + next tokens(source) if @options[:tokens] contents = CoffeeScript.compile(File.open(source)) next puts(contents) if @options[:print] next lint(contents) if @options[:lint] @@ -55,6 +56,10 @@ def lint(js) stdout.close and stderr.close end + def tokens(source) + puts Lexer.new.tokenize(File.read(source)).inspect + end + # Write out JavaScript alongside CoffeeScript unless an output directory # is specified. def path_for(source) @@ -76,6 +81,9 @@ def parse_options opts.on('-l', '--lint', 'pipe the compiled javascript through JSLint') do |l| @options[:lint] = true end + opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t| + @options[:tokens] = true + end opts.on_tail('-v', '--version', 'display coffee-script version') do puts "coffee-script version #{CoffeeScript::VERSION}" exit diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index b86d5e4eda..5c5bd31dd0 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -11,7 +11,7 @@ class Lexer IDENTIFIER = /\A([a-zA-Z$_]\w*)/ NUMBER = /\A([0-9]+(\.[0-9]+)?)/ - STRING = /\A("(.*?)"|'(.*?)')/ + STRING = /\A("(.*?)"|'(.*?)')/m JS = /\A(`(.*?)`)/ OPERATOR = /\A([+\*&|\/\-%=<>]+)/ WHITESPACE = /\A([ \t\r]+)/ @@ -21,6 +21,7 @@ class Lexer REGEX = /\A(\/(.*?)\/[imgy]{0,4})/ JS_CLEANER = /(\A`|`\Z)/ + MULTILINER = /[\r\n]/ EXP_START = ['{', '(', '['] EXP_END = ['}', ')', ']'] @@ -71,7 +72,7 @@ def number_token def string_token return false unless string = @chunk[STRING, 1] - @tokens << [:STRING, string] + @tokens << [:STRING, string.gsub(MULTILINER, "\\\n")] @i += string.length end From 962885444e178e8529fe38d8452e3d209d9a6da3 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 08:23:17 -0500 Subject: [PATCH 004/303] multiline strings --- examples/code.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/code.cs b/examples/code.cs index 43e9223138..0502b6a6b7 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -124,6 +124,6 @@ three_to_six: zero_to_nine[3, 6] # Multiline strings. -story: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, +story: "Lorem ips\"um dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad." \ No newline at end of file From 3dac0f6d84c52393e7c3e9797b5b24ebcd22ca63 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 08:26:20 -0500 Subject: [PATCH 005/303] supporting escaped quotes in strings --- lib/coffee_script/lexer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 5c5bd31dd0..6f3137da5f 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -11,7 +11,7 @@ class Lexer IDENTIFIER = /\A([a-zA-Z$_]\w*)/ NUMBER = /\A([0-9]+(\.[0-9]+)?)/ - STRING = /\A("(.*?)"|'(.*?)')/m + STRING = /\A("(.*?)[^\\]"|'(.*?)[^\\]')/m JS = /\A(`(.*?)`)/ OPERATOR = /\A([+\*&|\/\-%=<>]+)/ WHITESPACE = /\A([ \t\r]+)/ From 4e1e119f58c571d75ed267093cb285cd3c60faa6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 08:26:46 -0500 Subject: [PATCH 006/303] supporting escaped quotes in strings --- examples/code.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index 0502b6a6b7..f063ec98d0 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -123,7 +123,7 @@ zero_to_nine: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] three_to_six: zero_to_nine[3, 6] -# Multiline strings. -story: "Lorem ips\"um dolor sit amet, consectetuer adipiscing elit, +# Multiline strings with inner quotes. +story: "Lorem ipsum dolor \"sit\" amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad." \ No newline at end of file From 1a8311b9d088d459cb21bc6d5d543ba04c0b89a7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 08:29:19 -0500 Subject: [PATCH 007/303] allowing inner slashes in regexes --- examples/code.cs | 2 ++ lib/coffee_script/lexer.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/code.cs b/examples/code.cs index f063ec98d0..621e8e17e7 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -21,6 +21,8 @@ list: [1, 2, 3, 4] + regex: /match[ing](every|thing|\/)/gi + three: new Idea() inner_obj: { diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 6f3137da5f..debd3aeb0c 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -18,7 +18,7 @@ class Lexer NEWLINE = /\A([\r\n]+)/ COMMENT = /\A(#[^\r\n]*)/ CODE = /\A(=>)/ - REGEX = /\A(\/(.*?)\/[imgy]{0,4})/ + REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ JS_CLEANER = /(\A`|`\Z)/ MULTILINER = /[\r\n]/ From 1d35910567f6f5a5262e7493837b6510aeb68279 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 09:07:42 -0500 Subject: [PATCH 008/303] added the ability to super() --- TODO | 2 ++ examples/code.cs | 10 ++++++++- lib/coffee_script/grammar.y | 16 +++++++++++++-- lib/coffee_script/lexer.rb | 3 ++- lib/coffee_script/nodes.rb | 41 +++++++++++++++++++++++++++---------- 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/TODO b/TODO index 8fc6ae7ce3..9878620e00 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,7 @@ TODO: +* Super in methods. (reserve it). + * Need *way* better syntax errors. * Is it possible to close blocks (functions, ifs, trys) without an explicit diff --git a/examples/code.cs b/examples/code.cs index 621e8e17e7..aa497f1e6f 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -128,4 +128,12 @@ # Multiline strings with inner quotes. story: "Lorem ipsum dolor \"sit\" amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna -aliquam erat volutpat. Ut wisi enim ad." \ No newline at end of file +aliquam erat volutpat. Ut wisi enim ad." + +# Calling super from an overridden method. +Greeter: => . # Create the parent object. +Greeter.prototype.hello: name => alert('Hello ' + name). # Define a "hello" method. +Exclaimer: name => this.name: name. # Create the child object. +Exclaimer.prototype: new Greeter() # Set the child to inherit from the parent. +Exclaimer.prototype.hello: => super(this.name + "!"). # The child's "hello" calls the parent's via "super". +(new Exclaimer('Bob')).hello() # Run it. diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 2ff987d33a..2fb3fe7ffb 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -10,6 +10,7 @@ token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE token SWITCH CASE DEFAULT +token SUPER token NEWLINE token JS @@ -149,10 +150,16 @@ rule # Method definition Code: - ParamList "=>" Expressions "." { result = CodeNode.new(val[0], val[2]) } - | "=>" Expressions "." { result = CodeNode.new([], val[1]) } + ParamList "=>" CodeBody "." { result = CodeNode.new(val[0], val[2]) } + | "=>" CodeBody "." { result = CodeNode.new([], val[1]) } ; + CodeBody: + /* nothing */ { result = Nodes.new([]) } + | Expressions { result = val[0] } + ; + + ParamList: PARAM { result = val } | ParamList "," PARAM { result = val[0] << val[2] } @@ -196,12 +203,17 @@ rule Call: Invocation { result = val[0] } | NEW Invocation { result = val[1].new_instance } + | Super { result = val[0] } ; Invocation: Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } ; + Super: + SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) } + ; + # An Array. Array: "[" ArgList "]" { result = ArrayNode.new(val[1]) } diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index debd3aeb0c..7269d54cc9 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -7,7 +7,8 @@ class Lexer "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", - "switch", "case", "default"] + "switch", "case", "default", + "super"] IDENTIFIER = /\A([a-zA-Z$_]\w*)/ NUMBER = /\A([0-9]+(\.[0-9]+)?)/ diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 9a9bfc0947..96c1da764e 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -122,6 +122,8 @@ def compile(indent, scope, opts={}) # receiver.method(argument1, argument2) # class CallNode < Node + LEADING_DOT = /\A\./ + def initialize(variable, arguments=[]) @variable, @arguments = variable, arguments end @@ -131,14 +133,26 @@ def new_instance self end + def super? + @variable == :super + end + def compile(indent, scope, opts={}) args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ') + return compile_super(args, indent, scope, opts) if super? prefix = @new ? "new " : '' "#{prefix}#{@variable.compile(indent, scope)}(#{args})" end + + def compile_super(args, indent, scope, opts) + methname = opts[:last_assign].sub(LEADING_DOT, '') + "this.constructor.prototype.#{methname}.call(this, #{args})" + end end class ValueNode < Node + attr_reader :last + def initialize(name, properties=[]) @name, @properties = name, properties end @@ -153,9 +167,11 @@ def properties? end def compile(indent, scope, opts={}) - [@name, @properties].flatten.map { |v| + parts = [@name, @properties].flatten.map do |v| v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s - }.join('') + end + @last = parts.last + parts.join('') end end @@ -200,16 +216,18 @@ def custom_return? end def compile(indent, scope, opts={}) - value = @value.compile(indent + TAB, scope) + name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) + last = @variable.respond_to?(:last) ? @variable.last : name + opts = opts.merge({:assign => name, :last_assign => last}) + value = @value.compile(indent, scope, opts) return "#{@variable}: #{value}" if @context == :object - name = @variable.compile(indent, scope) return "#{name} = #{value}" if @variable.properties? - defined = scope.find(name) - postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' - def_part = defined ? "" : "var #{name};\n#{indent}" - return def_part + @value.compile(indent, scope, opts.merge(:assign => name)) if @value.custom_assign? - def_part = defined ? name : "var #{name}" - "#{def_part} = #{@value.compile(indent, scope)}#{postfix}" + defined = scope.find(name) + postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' + def_part = defined ? "" : "var #{name};\n#{indent}" + return def_part + @value.compile(indent, scope, opts) if @value.custom_assign? + def_part = defined ? name : "var #{name}" + "#{def_part} = #{@value.compile(indent, scope, opts)}#{postfix}" end end @@ -260,7 +278,8 @@ def initialize(params, body) end def compile(indent, scope, opts={}) - code = @body.compile(indent + TAB, Scope.new(scope), {:return => true}) + opts = opts.merge(:return => true) + code = @body.compile(indent + TAB, Scope.new(scope), opts) "function(#{@params.join(', ')}) {\n#{code}\n#{indent}}" end end From 146b5694c263271edc0d26a144e30abce8c2e1a4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 09:29:49 -0500 Subject: [PATCH 009/303] cleaned up lexer in order to add line numbers --- TODO | 2 -- examples/syntax_errors.cs | 2 ++ lib/coffee_script/lexer.rb | 39 ++++++++++++++++++++++++++------------ 3 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 examples/syntax_errors.cs diff --git a/TODO b/TODO index 9878620e00..8fc6ae7ce3 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ TODO: -* Super in methods. (reserve it). - * Need *way* better syntax errors. * Is it possible to close blocks (functions, ifs, trys) without an explicit diff --git a/examples/syntax_errors.cs b/examples/syntax_errors.cs new file mode 100644 index 0000000000..5df5956c0d --- /dev/null +++ b/examples/syntax_errors.cs @@ -0,0 +1,2 @@ +# Identifiers run together: +a b c \ No newline at end of file diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 7269d54cc9..62de56b745 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -32,6 +32,7 @@ class Lexer def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @i = 0 # Current character position we're parsing + @line = 1 # The current line. @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] while @i < @code.length @chunk = @code[@i..-1] @@ -57,35 +58,37 @@ def identifier_token # Keywords are special identifiers tagged with their own name, 'if' will result # in an [:IF, "if"] token tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER - if tag == :IDENTIFIER && @tokens[-1] && @tokens[-1][1] == '.' - @tokens[-1] = [:PROPERTY_ACCESS, '.'] - end - @tokens << [tag, identifier] + @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' + token(tag, identifier) @i += identifier.length end def number_token return false unless number = @chunk[NUMBER, 1] float = number.include?('.') - @tokens << [:NUMBER, float ? number.to_f : number.to_i] + token(:NUMBER, float ? number.to_f : number.to_i) @i += number.length end def string_token return false unless string = @chunk[STRING, 1] - @tokens << [:STRING, string.gsub(MULTILINER, "\\\n")] + escaped = string.gsub(MULTILINER) do |match| + @line += 1 + "\\\n" + end + token(:STRING, escaped) @i += string.length end def js_token return false unless script = @chunk[JS, 1] - @tokens << [:JS, script.gsub(JS_CLEANER, '')] + token(:JS, script.gsub(JS_CLEANER, '')) @i += script.length end def regex_token return false unless regex = @chunk[REGEX, 1] - @tokens << [:REGEX, regex] + token(:REGEX, regex) @i += regex.length end @@ -106,7 +109,8 @@ def whitespace_token def literal_token value = @chunk[NEWLINE, 1] if value - @tokens << ["\n", "\n"] unless @tokens.last && @tokens.last[0] == "\n" + @line += value.length + token("\n", "\n") unless last_value == "\n" return @i += value.length end value = @chunk[OPERATOR, 1] @@ -114,10 +118,18 @@ def literal_token value ||= @chunk[0,1] skip_following_newlines if EXP_START.include?(value) remove_leading_newlines if EXP_END.include?(value) - @tokens << [value, value] + token(value, value) @i += value.length end + def token(tag, value) + @tokens << [tag, value] + end + + def last_value + @tokens.last && @tokens.last[1] + end + # The main source of ambiguity in our grammar was Parameter lists (as opposed # to argument lists in method calls). Tag parameter identifiers to avoid this. def tag_parameters @@ -132,11 +144,14 @@ def tag_parameters def skip_following_newlines newlines = @code[(@i+1)..-1][NEWLINE, 1] - @i += newlines.length if newlines + if newlines + @line += newlines.length + @i += newlines.length + end end def remove_leading_newlines - @tokens.pop if @tokens.last[1] == "\n" + @tokens.pop if last_value == "\n" end end \ No newline at end of file From 1590713576d95f22531b384b3f8f82ce37e8c573 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 09:37:42 -0500 Subject: [PATCH 010/303] passing through values with line number information that look and act like Ruby natives --- lib/coffee-script.rb | 2 ++ lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 33 --------------------------------- lib/coffee_script/scope.rb | 33 +++++++++++++++++++++++++++++++++ lib/coffee_script/value.rb | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 34 deletions(-) create mode 100644 lib/coffee_script/scope.rb create mode 100644 lib/coffee_script/value.rb diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 09be81f7a0..cfcf7de93a 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -1,4 +1,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) +require "coffee_script/value" +require "coffee_script/scope" require "coffee_script/lexer" require "coffee_script/parser" require "coffee_script/nodes" diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 62de56b745..17b2d486ab 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -123,7 +123,7 @@ def literal_token end def token(tag, value) - @tokens << [tag, value] + @tokens << [tag, Value.new(value, @line)] end def last_value diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 96c1da764e..d21c5edd93 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -1,36 +1,3 @@ -class Scope - - attr_reader :parent, :temp_variable - - def initialize(parent=nil) - @parent = parent - @variables = {} - @temp_variable = @parent ? @parent.temp_variable : 'a' - end - - # Look up a variable in lexical scope, or declare it if not found. - def find(name, remote=false) - found = check(name, remote) - return found if found || remote - @variables[name] = true - found - end - - # Just check for the pre-definition of a variable. - def check(name, remote=false) - return true if @variables[name] - @parent && @parent.find(name, true) - end - - # Find an available, short variable name. - def free_variable - @temp_variable.succ! while check(@temp_variable) - @variables[@temp_variable] = true - @temp_variable.dup - end - -end - class Node # Tabs are two spaces for pretty-printing. TAB = ' ' diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb new file mode 100644 index 0000000000..a09fb7c97d --- /dev/null +++ b/lib/coffee_script/scope.rb @@ -0,0 +1,33 @@ +# A class to handle lookups for lexically scoped variables. +class Scope + + attr_reader :parent, :temp_variable + + def initialize(parent=nil) + @parent = parent + @variables = {} + @temp_variable = @parent ? @parent.temp_variable : 'a' + end + + # Look up a variable in lexical scope, or declare it if not found. + def find(name, remote=false) + found = check(name, remote) + return found if found || remote + @variables[name] = true + found + end + + # Just check for the pre-definition of a variable. + def check(name, remote=false) + return true if @variables[name] + @parent && @parent.find(name, true) + end + + # Find an available, short variable name. + def free_variable + @temp_variable.succ! while check(@temp_variable) + @variables[@temp_variable] = true + @temp_variable.dup + end + +end \ No newline at end of file diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb new file mode 100644 index 0000000000..19b56ed666 --- /dev/null +++ b/lib/coffee_script/value.rb @@ -0,0 +1,34 @@ +# Instead of producing raw Ruby objects, the Lexer produces values of this +# class, tagged with line number information. +class Value + attr_reader :line + + def initialize(value, line) + @value, @line = value, line + end + + def to_str + @value.to_s + end + alias_method :to_s, :to_str + + def inspect + @value.inspect + end + + def ==(other) + @value == other + end + + def [](index) + @value[index] + end + + def eql?(other) + @value.eql?(other) + end + + def hash + @value.hash + end +end \ No newline at end of file From 1eec05d23a967d68d6eb3776ef5b7305ee3f9f03 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 10:04:43 -0500 Subject: [PATCH 011/303] added nice syntax errors --- TODO | 2 -- examples/syntax_errors.cs | 20 +++++++++++++++++++- lib/coffee-script.rb | 5 +++-- lib/coffee_script/command_line.rb | 11 ++++++++++- lib/coffee_script/grammar.y | 7 +++++-- lib/coffee_script/parse_error.rb | 19 +++++++++++++++++++ 6 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 lib/coffee_script/parse_error.rb diff --git a/TODO b/TODO index 8fc6ae7ce3..93dbd7f72c 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ TODO: -* Need *way* better syntax errors. - * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/examples/syntax_errors.cs b/examples/syntax_errors.cs index 5df5956c0d..aa72cd71ee 100644 --- a/examples/syntax_errors.cs +++ b/examples/syntax_errors.cs @@ -1,2 +1,20 @@ # Identifiers run together: -a b c \ No newline at end of file +# a b c + +# Trailing comma in array: +# array: [1, 2, 3, 4, 5,] + +# Unterminated object literal: +# obj: { one: 1, two: 2 + +# Numbers run together: +# 101 202 + +# Strings run together: +# str: "broken" "words" + +# Forgot to terminate a function: +# obj: { +# first: a => a[0]. +# last: a => a[a.length-1] +# } \ No newline at end of file diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index cfcf7de93a..aad0fc07c8 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -1,9 +1,10 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) -require "coffee_script/value" -require "coffee_script/scope" require "coffee_script/lexer" require "coffee_script/parser" require "coffee_script/nodes" +require "coffee_script/value" +require "coffee_script/scope" +require "coffee_script/parse_error" # Namespace for all CoffeeScript internal classes. module CoffeeScript diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 70e590045d..66c02af77c 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -31,7 +31,7 @@ def usage def compile_javascript @sources.each do |source| next tokens(source) if @options[:tokens] - contents = CoffeeScript.compile(File.open(source)) + contents = compile(source) next puts(contents) if @options[:print] next lint(contents) if @options[:lint] File.open(path_for(source), 'w+') {|f| f.write(contents) } @@ -60,6 +60,15 @@ def tokens(source) puts Lexer.new.tokenize(File.read(source)).inspect end + def compile(source) + begin + CoffeeScript.compile(File.open(source)) + rescue CoffeeScript::ParseError => e + STDERR.puts e.message(source) + exit(1) + end + end + # Write out JavaScript alongside CoffeeScript unless an output directory # is specified. def path_for(source) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 2fb3fe7ffb..cee409fbb3 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -294,13 +294,16 @@ rule end ---- inner - def parse(code, show_tokens=false) + def parse(code) # @yydebug = true @tokens = Lexer.new.tokenize(code) - puts @tokens.inspect if show_tokens do_parse end def next_token @tokens.shift + end + + def on_error(error_token_id, error_value, value_stack) + raise CoffeeScript::ParseError.new(token_to_str(error_token_id), error_value, value_stack) end \ No newline at end of file diff --git a/lib/coffee_script/parse_error.rb b/lib/coffee_script/parse_error.rb new file mode 100644 index 0000000000..9f49f0bb03 --- /dev/null +++ b/lib/coffee_script/parse_error.rb @@ -0,0 +1,19 @@ +module CoffeeScript + + class ParseError < Racc::ParseError + + def initialize(token_id, value, stack) + @token_id, @value, @stack = token_id, value, stack + end + + def message(source_file=nil) + line = @value.respond_to?(:line) ? @value.line : "END" + line_part = source_file ? "#{source_file}:#{line}:" : "line #{line}:" + id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.downcase}" : "" + "#{line_part} syntax error for '#{@value.to_s}'#{id_part}" + end + alias_method :inspect, :message + + end + +end \ No newline at end of file From 9249ceaef5b8252ff6d734be10d266c999ff0011 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 10:33:57 -0500 Subject: [PATCH 012/303] cleanups getting underscore to compile --- examples/underscore.cs | 1192 +++++++++++++++++------------------ lib/coffee_script/grammar.y | 4 +- lib/coffee_script/nodes.rb | 130 ++-- 3 files changed, 663 insertions(+), 663 deletions(-) diff --git a/examples/underscore.cs b/examples/underscore.cs index fa7b8741f1..20b1fafebf 100644 --- a/examples/underscore.cs +++ b/examples/underscore.cs @@ -6,602 +6,596 @@ # For all details and documentation: # http://documentcloud.github.com/underscore/ -=> - - # ------------------------- Baseline setup --------------------------------- - - # Establish the root object, "window" in the browser, or "global" on the server. - root: this - - # Save the previous value of the "_" variable. - 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. - - # Establish the object that gets thrown to break out of a loop iteration. - breaker: if typeof StopIteration is 'undefined' then '__break__' else StopIteration. - - # Create a safe reference to the Underscore object for reference below. - _: root._: obj => new wrapper(obj). - - # Export the Underscore object for CommonJS. - exports._: _ if typeof exports aint 'undefined' - - # Create quick reference variables for speed access to core prototypes. - slice: Array.prototype.slice - unshift: Array.prototype.unshift - toString: Object.prototype.toString - hasOwnProperty: Object.prototype.hasOwnProperty - propertyIsEnumerable: Object.prototype.propertyIsEnumerable - - # Current version. - _.VERSION: '0.5.1' - - # ------------------------ Collection Functions: --------------------------- - - # The cornerstone, an each implementation. - # Handles objects implementing forEach, arrays, and raw objects. - _.each: obj, iterator, context => - index: 0 - try - if obj.forEach then return obj.forEach(iterator, context). - if _.isArray(obj) or _.isArguments(obj) then return iterator.call(context, item, i, obj) for item, i in obj.. - iterator.call(context, obj[key], key, obj) for key in _.keys(obj). - catch e - throw e if e aint breaker. - obj. - - # Return the results of applying the iterator to each element. Use JavaScript - # 1.6's version of map, if possible. - _.map = obj, iterator, context => - return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) - results = [] - _.each(obj, (value, index, list => - results.push(iterator.call(context, value, index, list)) - )) - results - - # Reduce builds up a single result from a list of values. Also known as - # inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. - _.reduce = obj, memo, iterator, context => - return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce)) - _.each(obj, (value, index, list => - memo = iterator.call(context, memo, value, index, list) - )) - memo - - # The right-associative version of reduce, also known as foldr. Uses - # JavaScript 1.8's version of reduceRight, if available. - _.reduceRight = function(obj, memo, iterator, context) { - if (obj && _.isFunction(obj.reduceRight)) return obj.reduceRight(_.bind(iterator, context), memo); - var reversed = _.clone(_.toArray(obj)).reverse(); - _.each(reversed, function(value, index) { - memo = iterator.call(context, memo, value, index, obj); - }); - return memo; - }; - - # Return the first value which passes a truth test. - _.detect = function(obj, iterator, context) { - var result; - _.each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - _.breakLoop(); - } - }); - return result; - }; - - # Return all the elements that pass a truth test. Use JavaScript 1.6's - # filter(), if it exists. - _.select = function(obj, iterator, context) { - if (obj && _.isFunction(obj.filter)) return obj.filter(iterator, context); - var results = []; - _.each(obj, function(value, index, list) { - iterator.call(context, value, index, list) && results.push(value); - }); - return results; - }; - - # Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - var results = []; - _.each(obj, function(value, index, list) { - !iterator.call(context, value, index, list) && results.push(value); - }); - return results; - }; - - # Determine whether all of the elements match a truth test. Delegate to - # JavaScript 1.6's every(), if it is present. - _.all = function(obj, iterator, context) { - iterator = iterator || _.identity; - if (obj && _.isFunction(obj.every)) return obj.every(iterator, context); - var result = true; - _.each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); - }); - return result; - }; - - # Determine if at least one element in the object matches a truth test. Use - # JavaScript 1.6's some(), if it exists. - _.any = function(obj, iterator, context) { - iterator = iterator || _.identity; - if (obj && _.isFunction(obj.some)) return obj.some(iterator, context); - var result = false; - _.each(obj, function(value, index, list) { - if (result = iterator.call(context, value, index, list)) _.breakLoop(); - }); - return result; - }; - - # Determine if a given value is included in the array or object, - # based on '==='. - _.include = function(obj, target) { - if (_.isArray(obj)) return _.indexOf(obj, target) != -1; - var found = false; - _.each(obj, function(value) { - if (found = value === target) _.breakLoop(); - }); - return found; - }; - - # Invoke a method with arguments on every item in a collection. - _.invoke = function(obj, method) { - var args = _.rest(arguments, 2); - return _.map(obj, function(value) { - return (method ? value[method] : value).apply(value, args); - }); - }; - - # Convenience version of a common use case of map: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - # Return the maximum item or (item-based computation). - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj)) return Math.max.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; - }; - - # 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; - }; - - # Convert anything iterable into a real, live array. - _.toArray = function(iterable) { - if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - if (_.isArray(iterable)) return iterable; - if (_.isArguments(iterable)) return slice.call(iterable); - return _.map(iterable, function(val){ return val; }); - }; - - # Return the number of elements in an object. - _.size = function(obj) { - return _.toArray(obj).length; - }; - - /*-------------------------- Array Functions: ------------------------------*/ - - # Get the first element of an array. Passing "n" will return the first N - # values in the array. Aliased as "head". The "guard" check allows it to work - # with _.map. - _.first = function(array, n, guard) { - return n && !guard ? slice.call(array, 0, n) : array[0]; - }; - - # Returns everything but the first entry of the array. Aliased as "tail". - # Especially useful on the arguments object. Passing an "index" will return - # the rest of the values in the array from that index onward. The "guard" - //check allows it to work with _.map. - _.rest = function(array, index, guard) { - return slice.call(array, _.isUndefined(index) || guard ? 1 : index); - }; - - # Get the last element of an array. - _.last = function(array) { - return array[array.length - 1]; - }; - - # Trim out all falsy values from an array. - _.compact = function(array) { - return _.select(array, function(value){ return !!value; }); - }; - - # Return a completely flattened version of an array. - _.flatten = function(array) { - return _.reduce(array, [], function(memo, value) { - if (_.isArray(value)) return memo.concat(_.flatten(value)); - memo.push(value); - return memo; - }); - }; - - # Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - var values = _.rest(arguments); - return _.select(array, function(value){ return !_.include(values, value); }); - }; - - # Produce a duplicate-free version of the array. If the array has already - # been sorted, you have the option of using a faster algorithm. - _.uniq = function(array, isSorted) { - return _.reduce(array, [], function(memo, el, i) { - if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); - return memo; - }); - }; - - # 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; - } - }; - - /* ----------------------- Function Functions: -----------------------------*/ - - # Create a function bound to a given object (assigning 'this', and arguments, - # optionally). Binding with arguments is also known as 'curry'. - _.bind = function(func, obj) { - var args = _.rest(arguments, 2); - return function() { - return func.apply(obj || root, args.concat(_.toArray(arguments))); - }; - }; - - # Bind all of an object's methods to that object. Useful for ensuring that - # all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = _.rest(arguments); - if (funcs.length == 0) funcs = _.functions(obj); - _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - # Delays a function for the given number of milliseconds, and then calls - # it with the arguments supplied. - _.delay = function(func, wait) { - var args = _.rest(arguments, 2); - return setTimeout(function(){ return func.apply(func, args); }, wait); - }; - - # Defers a function, scheduling it to run after the current call stack has - # cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(_.rest(arguments))); - }; - - # Returns the first function passed as an argument to the second, - # allowing you to adjust arguments, run code before and after, and - # conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func].concat(_.toArray(arguments)); - return wrapper.apply(wrapper, args); - }; - }; - - # Returns a function that is the composition of a list of functions, each - # consuming the return value of the function that follows. - _.compose = function() { - var funcs = _.toArray(arguments); - return function() { - var args = _.toArray(arguments); - for (var i=funcs.length-1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - /* ------------------------- Object Functions: ---------------------------- */ - - # Retrieve the names of an object's properties. - _.keys = function(obj) { - if(_.isArray(obj)) return _.range(0, obj.length); - var keys = []; - for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); - return keys; - }; - - # Retrieve the values of an object's properties. - _.values = function(obj) { - return _.map(obj, _.identity); - }; - - # Return a sorted list of the function names available in Underscore. - _.functions = function(obj) { - return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); - }; - - # Extend a given object with all of the properties in a source object. - _.extend = function(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; - }; - - # Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (_.isArray(obj)) return obj.slice(0); - return _.extend({}, obj); - }; - - # Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - # Check object identity. - if (a === b) return true; - # Different types? - var atype = typeof(a), btype = typeof(b); - if (atype != btype) return false; - # Basic equality test (watch out for coercions). - if (a == b) return true; - # One is falsy and the other truthy. - if ((!a && b) || (a && !b)) return false; - # One of them implements an isEqual()? - if (a.isEqual) return a.isEqual(b); - # Check dates' integer values. - if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); - # Both are NaN? - if (_.isNaN(a) && _.isNaN(b)) return true; - # Compare regular expressions. - if (_.isRegExp(a) && _.isRegExp(b)) - return a.source === b.source && - a.global === b.global && - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - # If a is not an object by this point, we can't handle it. - if (atype !== 'object') return false; - # Check for different array lengths before comparing contents. - if (a.length && (a.length !== b.length)) return false; - # Nothing else worked, deep compare the contents. - var aKeys = _.keys(a), bKeys = _.keys(b); - # Different object sizes? - if (aKeys.length != bKeys.length) return false; - # Recursive comparison of contents. - for (var key in a) if (!_.isEqual(a[key], b[key])) return false; - return true; - }; - - # Is a given array or object empty? - _.isEmpty = function(obj) { - return _.keys(obj).length == 0; - }; - - # Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType == 1); - }; - - # Is a given variable an arguments object? - _.isArguments = function(obj) { - return obj && _.isNumber(obj.length) && !_.isArray(obj) && !propertyIsEnumerable.call(obj, 'length'); - }; - - # 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 = function(obj) { - return _.isNumber(obj) && isNaN(obj); - }; - - # Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - # Is a given variable undefined? - _.isUndefined = function(obj) { - return typeof obj == '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 = function(obj, interceptor) { - interceptor(obj); - return obj; - } - - # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString - # functions based on their toString identifiers. - var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String']; - for (var i=0, l=types.length; i)[^\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; - }; - - /*------------------------------- Aliases ----------------------------------*/ - - _.forEach = _.each; - _.foldl = _.inject = _.reduce; - _.foldr = _.reduceRight; - _.filter = _.select; - _.every = _.all; - _.some = _.any; - _.head = _.first; - _.tail = _.rest; - _.methods = _.functions; - - /*------------------------ Setup the OOP Wrapper: --------------------------*/ - - # Helper function to continue chaining intermediate results. - var result = function(obj, chain) { - return chain ? _(obj).chain() : obj; - }; - - # 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 accessor Array functions to the wrapper. - _.each(['concat', 'join', 'slice'], function(name) { - var method = Array.prototype[name]; - wrapper.prototype[name] = function() { - return result(method.apply(this._wrapped, arguments), this._chain); - }; - }); - - # Start chaining a wrapped Underscore object. - wrapper.prototype.chain = function() { - this._chain = true; - return this; - }; - - # Extracts the result from a wrapped and chained object. - wrapper.prototype.value = function() { - return this._wrapped; - }; - -() +# ------------------------- Baseline setup --------------------------------- + +# Establish the root object, "window" in the browser, or "global" on the server. +root: this + +# Save the previous value of the "_" variable. +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. + +# Establish the object that gets thrown to break out of a loop iteration. +breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration. + +# Create a safe reference to the Underscore object for reference below. +_: root._: obj => new wrapper(obj). + +# Export the Underscore object for CommonJS. +exports._: _ if typeof(exports) aint 'undefined' + +# Create quick reference variables for speed access to core prototypes. +slice: Array.prototype.slice +unshift: Array.prototype.unshift +toString: Object.prototype.toString +hasOwnProperty: Object.prototype.hasOwnProperty +propertyIsEnumerable: Object.prototype.propertyIsEnumerable + +# Current version. +_.VERSION: '0.5.1' + +# ------------------------ Collection Functions: --------------------------- + +# The cornerstone, an each implementation. +# Handles objects implementing forEach, arrays, and raw objects. +_.each: obj, iterator, context => + index: 0 + try + return obj.forEach(iterator, context) if obj.forEach + return iterator.call(context, item, i, obj) for item, i in obj. if _.isArray(obj) or _.isArguments(obj) + iterator.call(context, obj[key], key, obj) for key in _.keys(obj). + catch e + throw e if e aint breaker. + obj. + +# Return the results of applying the iterator to each element. Use JavaScript +# 1.6's version of map, if possible. +_.map: obj, iterator, context => + return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) + results: [] + mapper: value, index, list => results.push(iterator.call(context, value, index, list)). + _.each(obj, mapper) + results. + +# Reduce builds up a single result from a list of values. Also known as +# inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. +_.reduce: obj, memo, iterator, context => + return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce)) + reducer: value, index, list => memo: iterator.call(context, memo, value, index, list). + _.each(obj, reducer) + memo. + +# The right-associative version of reduce, also known as foldr. Uses +# JavaScript 1.8's version of reduceRight, if available. +_.reduceRight: obj, memo, iterator, context => + return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight)) + reversed: _.clone(_.toArray(obj)).reverse() + reverser: value, index => memo: iterator.call(context, memo, value, index, obj). + _.each(reversed, reverser) + memo. + +# # Return the first value which passes a truth test. +# _.detect = function(obj, iterator, context) { +# var result; +# _.each(obj, function(value, index, list) { +# if (iterator.call(context, value, index, list)) { +# result = value; +# _.breakLoop(); +# } +# }); +# return result; +# }; +# +# # Return all the elements that pass a truth test. Use JavaScript 1.6's +# # filter(), if it exists. +# _.select = function(obj, iterator, context) { +# if (obj && _.isFunction(obj.filter)) return obj.filter(iterator, context); +# var results = []; +# _.each(obj, function(value, index, list) { +# iterator.call(context, value, index, list) && results.push(value); +# }); +# return results; +# }; +# +# # Return all the elements for which a truth test fails. +# _.reject = function(obj, iterator, context) { +# var results = []; +# _.each(obj, function(value, index, list) { +# !iterator.call(context, value, index, list) && results.push(value); +# }); +# return results; +# }; +# +# # Determine whether all of the elements match a truth test. Delegate to +# # JavaScript 1.6's every(), if it is present. +# _.all = function(obj, iterator, context) { +# iterator = iterator || _.identity; +# if (obj && _.isFunction(obj.every)) return obj.every(iterator, context); +# var result = true; +# _.each(obj, function(value, index, list) { +# if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); +# }); +# return result; +# }; +# +# # Determine if at least one element in the object matches a truth test. Use +# # JavaScript 1.6's some(), if it exists. +# _.any = function(obj, iterator, context) { +# iterator = iterator || _.identity; +# if (obj && _.isFunction(obj.some)) return obj.some(iterator, context); +# var result = false; +# _.each(obj, function(value, index, list) { +# if (result = iterator.call(context, value, index, list)) _.breakLoop(); +# }); +# return result; +# }; +# +# # Determine if a given value is included in the array or object, +# # based on '==='. +# _.include = function(obj, target) { +# if (_.isArray(obj)) return _.indexOf(obj, target) != -1; +# var found = false; +# _.each(obj, function(value) { +# if (found = value === target) _.breakLoop(); +# }); +# return found; +# }; +# +# # Invoke a method with arguments on every item in a collection. +# _.invoke = function(obj, method) { +# var args = _.rest(arguments, 2); +# return _.map(obj, function(value) { +# return (method ? value[method] : value).apply(value, args); +# }); +# }; +# +# # Convenience version of a common use case of map: fetching a property. +# _.pluck = function(obj, key) { +# return _.map(obj, function(value){ return value[key]; }); +# }; +# +# # Return the maximum item or (item-based computation). +# _.max = function(obj, iterator, context) { +# if (!iterator && _.isArray(obj)) return Math.max.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; +# }; +# +# # 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; +# }; +# +# # Convert anything iterable into a real, live array. +# _.toArray = function(iterable) { +# if (!iterable) return []; +# if (iterable.toArray) return iterable.toArray(); +# if (_.isArray(iterable)) return iterable; +# if (_.isArguments(iterable)) return slice.call(iterable); +# return _.map(iterable, function(val){ return val; }); +# }; +# +# # Return the number of elements in an object. +# _.size = function(obj) { +# return _.toArray(obj).length; +# }; +# +# /*-------------------------- Array Functions: ------------------------------*/ +# +# # Get the first element of an array. Passing "n" will return the first N +# # values in the array. Aliased as "head". The "guard" check allows it to work +# # with _.map. +# _.first = function(array, n, guard) { +# return n && !guard ? slice.call(array, 0, n) : array[0]; +# }; +# +# # Returns everything but the first entry of the array. Aliased as "tail". +# # Especially useful on the arguments object. Passing an "index" will return +# # the rest of the values in the array from that index onward. The "guard" +# //check allows it to work with _.map. +# _.rest = function(array, index, guard) { +# return slice.call(array, _.isUndefined(index) || guard ? 1 : index); +# }; +# +# # Get the last element of an array. +# _.last = function(array) { +# return array[array.length - 1]; +# }; +# +# # Trim out all falsy values from an array. +# _.compact = function(array) { +# return _.select(array, function(value){ return !!value; }); +# }; +# +# # Return a completely flattened version of an array. +# _.flatten = function(array) { +# return _.reduce(array, [], function(memo, value) { +# if (_.isArray(value)) return memo.concat(_.flatten(value)); +# memo.push(value); +# return memo; +# }); +# }; +# +# # Return a version of the array that does not contain the specified value(s). +# _.without = function(array) { +# var values = _.rest(arguments); +# return _.select(array, function(value){ return !_.include(values, value); }); +# }; +# +# # Produce a duplicate-free version of the array. If the array has already +# # been sorted, you have the option of using a faster algorithm. +# _.uniq = function(array, isSorted) { +# return _.reduce(array, [], function(memo, el, i) { +# if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); +# return memo; +# }); +# }; +# +# # 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; +# } +# }; +# +# /* ----------------------- Function Functions: -----------------------------*/ +# +# # Create a function bound to a given object (assigning 'this', and arguments, +# # optionally). Binding with arguments is also known as 'curry'. +# _.bind = function(func, obj) { +# var args = _.rest(arguments, 2); +# return function() { +# return func.apply(obj || root, args.concat(_.toArray(arguments))); +# }; +# }; +# +# # Bind all of an object's methods to that object. Useful for ensuring that +# # all callbacks defined on an object belong to it. +# _.bindAll = function(obj) { +# var funcs = _.rest(arguments); +# if (funcs.length == 0) funcs = _.functions(obj); +# _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); +# return obj; +# }; +# +# # Delays a function for the given number of milliseconds, and then calls +# # it with the arguments supplied. +# _.delay = function(func, wait) { +# var args = _.rest(arguments, 2); +# return setTimeout(function(){ return func.apply(func, args); }, wait); +# }; +# +# # Defers a function, scheduling it to run after the current call stack has +# # cleared. +# _.defer = function(func) { +# return _.delay.apply(_, [func, 1].concat(_.rest(arguments))); +# }; +# +# # Returns the first function passed as an argument to the second, +# # allowing you to adjust arguments, run code before and after, and +# # conditionally execute the original function. +# _.wrap = function(func, wrapper) { +# return function() { +# var args = [func].concat(_.toArray(arguments)); +# return wrapper.apply(wrapper, args); +# }; +# }; +# +# # Returns a function that is the composition of a list of functions, each +# # consuming the return value of the function that follows. +# _.compose = function() { +# var funcs = _.toArray(arguments); +# return function() { +# var args = _.toArray(arguments); +# for (var i=funcs.length-1; i >= 0; i--) { +# args = [funcs[i].apply(this, args)]; +# } +# return args[0]; +# }; +# }; +# +# /* ------------------------- Object Functions: ---------------------------- */ +# +# # Retrieve the names of an object's properties. +# _.keys = function(obj) { +# if(_.isArray(obj)) return _.range(0, obj.length); +# var keys = []; +# for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); +# return keys; +# }; +# +# # Retrieve the values of an object's properties. +# _.values = function(obj) { +# return _.map(obj, _.identity); +# }; +# +# # Return a sorted list of the function names available in Underscore. +# _.functions = function(obj) { +# return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); +# }; +# +# # Extend a given object with all of the properties in a source object. +# _.extend = function(destination, source) { +# for (var property in source) destination[property] = source[property]; +# return destination; +# }; +# +# # Create a (shallow-cloned) duplicate of an object. +# _.clone = function(obj) { +# if (_.isArray(obj)) return obj.slice(0); +# return _.extend({}, obj); +# }; +# +# # Perform a deep comparison to check if two objects are equal. +# _.isEqual = function(a, b) { +# # Check object identity. +# if (a === b) return true; +# # Different types? +# var atype = typeof(a), btype = typeof(b); +# if (atype != btype) return false; +# # Basic equality test (watch out for coercions). +# if (a == b) return true; +# # One is falsy and the other truthy. +# if ((!a && b) || (a && !b)) return false; +# # One of them implements an isEqual()? +# if (a.isEqual) return a.isEqual(b); +# # Check dates' integer values. +# if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); +# # Both are NaN? +# if (_.isNaN(a) && _.isNaN(b)) return true; +# # Compare regular expressions. +# if (_.isRegExp(a) && _.isRegExp(b)) +# return a.source === b.source && +# a.global === b.global && +# a.ignoreCase === b.ignoreCase && +# a.multiline === b.multiline; +# # If a is not an object by this point, we can't handle it. +# if (atype !== 'object') return false; +# # Check for different array lengths before comparing contents. +# if (a.length && (a.length !== b.length)) return false; +# # Nothing else worked, deep compare the contents. +# var aKeys = _.keys(a), bKeys = _.keys(b); +# # Different object sizes? +# if (aKeys.length != bKeys.length) return false; +# # Recursive comparison of contents. +# for (var key in a) if (!_.isEqual(a[key], b[key])) return false; +# return true; +# }; +# +# # Is a given array or object empty? +# _.isEmpty = function(obj) { +# return _.keys(obj).length == 0; +# }; +# +# # Is a given value a DOM element? +# _.isElement = function(obj) { +# return !!(obj && obj.nodeType == 1); +# }; +# +# # Is a given variable an arguments object? +# _.isArguments = function(obj) { +# return obj && _.isNumber(obj.length) && !_.isArray(obj) && !propertyIsEnumerable.call(obj, 'length'); +# }; +# +# # 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 = function(obj) { +# return _.isNumber(obj) && isNaN(obj); +# }; +# +# # Is a given value equal to null? +# _.isNull = function(obj) { +# return obj === null; +# }; +# +# # Is a given variable undefined? +# _.isUndefined = function(obj) { +# return typeof obj == '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 = function(obj, interceptor) { +# interceptor(obj); +# return obj; +# } +# +# # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString +# # functions based on their toString identifiers. +# var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String']; +# for (var i=0, l=types.length; i)[^\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; +# }; +# +# /*------------------------------- Aliases ----------------------------------*/ +# +# _.forEach = _.each; +# _.foldl = _.inject = _.reduce; +# _.foldr = _.reduceRight; +# _.filter = _.select; +# _.every = _.all; +# _.some = _.any; +# _.head = _.first; +# _.tail = _.rest; +# _.methods = _.functions; +# +# /*------------------------ Setup the OOP Wrapper: --------------------------*/ +# +# # Helper function to continue chaining intermediate results. +# var result = function(obj, chain) { +# return chain ? _(obj).chain() : obj; +# }; +# +# # 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 accessor Array functions to the wrapper. +# _.each(['concat', 'join', 'slice'], function(name) { +# var method = Array.prototype[name]; +# wrapper.prototype[name] = function() { +# return result(method.apply(this._wrapped, arguments), this._chain); +# }; +# }); +# +# # Start chaining a wrapped Underscore object. +# wrapper.prototype.chain = function() { +# this._chain = true; +# return this; +# }; +# +# # Extracts the result from a wrapped and chained object. +# wrapper.prototype.value = function() { +# return this._wrapped; +# }; +# +# () diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index cee409fbb3..3e45aba37a 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -24,9 +24,9 @@ prechigh left '&&' '||' AND OR left ':' right '-=' '+=' '/=' '*=' '||=' '&&=' - nonassoc IF - left UNLESS right RETURN THROW FOR WHILE + left UNLESS + nonassoc IF nonassoc "." preclow diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index d21c5edd93..548f981642 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -245,8 +245,10 @@ def initialize(params, body) end def compile(indent, scope, opts={}) - opts = opts.merge(:return => true) - code = @body.compile(indent + TAB, Scope.new(scope), opts) + scope = Scope.new(scope) + @params.each {|id| scope.find(id) } + opts = opts.merge(:return => true) + code = @body.compile(indent + TAB, scope, opts) "function(#{@params.join(', ')}) {\n#{code}\n#{indent}}" end end @@ -273,66 +275,6 @@ def compile(indent, scope, opts={}) end end -# "if-else" control structure. Look at this node if you want to implement other control -# structures like while, for, loop, etc. -class IfNode < Node - FORCE_STATEMENT = [Nodes, ReturnNode, AssignNode, IfNode] - - def initialize(condition, body, else_body=nil, tag=nil) - @condition = condition - @body = body && body.flatten - @else_body = else_body && else_body.flatten - @condition = OpNode.new("!", @condition) if tag == :invert - end - - def <<(else_body) - eb = else_body.flatten - @else_body ? @else_body << eb : @else_body = eb - self - end - - # Rewrite a chain of IfNodes with their switch condition for equality. - def rewrite_condition(expression) - @condition = OpNode.new("is", expression, @condition) - @else_body.rewrite_condition(expression) if chain? - self - end - - # Rewrite a chain of IfNodes to add a default case as the final else. - def add_default(expressions) - chain? ? @else_body.add_default(expressions) : @else_body = expressions - self - end - - def chain? - @chain ||= @else_body && @else_body.is_a?(IfNode) - end - - def statement? - @is_statement ||= (FORCE_STATEMENT.include?(@body.class) || FORCE_STATEMENT.include?(@else_body.class)) - end - - def line_ending - statement? ? '' : ';' - end - - def compile(indent, scope, opts={}) - statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) - end - - def compile_statement(indent, scope, opts) - if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Nodes.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" - else_part = @else_body ? " else {\n#{Nodes.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : '' - if_part + else_part - end - - def compile_ternary(indent, scope) - if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}" - else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null' - "#{if_part} : #{else_part}" - end -end - class WhileNode < Node def initialize(condition, body) @condition, @body = condition, body @@ -369,6 +311,10 @@ def custom_assign? true end + def statement? + true + end + def compile(indent, scope, opts={}) svar = scope.free_variable ivar = scope.free_variable @@ -439,3 +385,63 @@ def compile(indent, scope, opts={}) opts[:no_paren] ? compiled : "(#{compiled})" end end + +# "if-else" control structure. Look at this node if you want to implement other control +# structures like while, for, loop, etc. +class IfNode < Node + FORCE_STATEMENT = [Nodes, ReturnNode, AssignNode, IfNode, ForNode, ThrowNode, WhileNode] + + def initialize(condition, body, else_body=nil, tag=nil) + @condition = condition + @body = body && body.flatten + @else_body = else_body && else_body.flatten + @condition = OpNode.new("!", @condition) if tag == :invert + end + + def <<(else_body) + eb = else_body.flatten + @else_body ? @else_body << eb : @else_body = eb + self + end + + # Rewrite a chain of IfNodes with their switch condition for equality. + def rewrite_condition(expression) + @condition = OpNode.new("is", expression, @condition) + @else_body.rewrite_condition(expression) if chain? + self + end + + # Rewrite a chain of IfNodes to add a default case as the final else. + def add_default(expressions) + chain? ? @else_body.add_default(expressions) : @else_body = expressions + self + end + + def chain? + @chain ||= @else_body && @else_body.is_a?(IfNode) + end + + def statement? + @is_statement ||= (FORCE_STATEMENT.include?(@body.class) || FORCE_STATEMENT.include?(@else_body.class)) + end + + def line_ending + statement? ? '' : ';' + end + + def compile(indent, scope, opts={}) + statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) + end + + def compile_statement(indent, scope, opts) + if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Nodes.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" + else_part = @else_body ? " else {\n#{Nodes.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : '' + if_part + else_part + end + + def compile_ternary(indent, scope) + if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}" + else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null' + "#{if_part} : #{else_part}" + end +end From 398251ff90ab7789a165d8c0bd0213ae93c0d372 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 20:37:39 -0500 Subject: [PATCH 013/303] added a nice --watch mode to continually recompile or relint (or reprint) your coffeescripts --- lib/coffee_script/command_line.rb | 44 ++++++++++++++++++++++++------- lib/coffee_script/nodes.rb | 1 + 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 66c02af77c..99b4f32150 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -14,10 +14,14 @@ class CommandLine coffee-script path/to/script.cs EOS + WATCH_INTERVAL = 0.5 + def initialize + @mtimes = {} parse_options check_sources - compile_javascript + @sources.each {|source| compile_javascript(source) } + watch_coffee_scripts if @options[:watch] end def usage @@ -28,14 +32,30 @@ def usage private - def compile_javascript - @sources.each do |source| - next tokens(source) if @options[:tokens] - contents = compile(source) - next puts(contents) if @options[:print] - next lint(contents) if @options[:lint] - File.open(path_for(source), 'w+') {|f| f.write(contents) } + def compile_javascript(source) + return tokens(source) if @options[:tokens] + contents = compile(source) + return unless contents + return puts(contents) if @options[:print] + return lint(contents) if @options[:lint] + File.open(path_for(source), 'w+') {|f| f.write(contents) } + end + + def watch_coffee_scripts + watch_thread = Thread.start do + loop do + @sources.each do |source| + mtime = File.stat(source).mtime + @mtimes[source] ||= mtime + if mtime > @mtimes[source] + @mtimes[source] = mtime + compile_javascript(source) + end + end + sleep WATCH_INTERVAL + end end + watch_thread.join end def check_sources @@ -52,7 +72,7 @@ def lint(js) stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin') stdin.write(js) stdin.close - print stdout.read + puts stdout.read.tr("\n", '') stdout.close and stderr.close end @@ -65,7 +85,8 @@ def compile(source) CoffeeScript.compile(File.open(source)) rescue CoffeeScript::ParseError => e STDERR.puts e.message(source) - exit(1) + exit(1) unless @options[:watch] + nil end end @@ -84,6 +105,9 @@ def parse_options @options[:output] = d FileUtils.mkdir_p(d) unless File.exists?(d) end + opts.on('-w', '--watch', 'watch scripts for changes, and recompile') do |w| + @options[:watch] = true + end opts.on('-p', '--print', 'print the compiled javascript to stdout') do |d| @options[:print] = true end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 548f981642..61532d47d5 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -76,6 +76,7 @@ def custom_return? end def compile(indent, scope, opts={}) + return @expression.compile(indent, scope, opts.merge(:return => true)) if @expression.custom_return? compiled = @expression.compile(indent, scope) @expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}" end From 2c90e8b002fe5a33d3153b39924b5457dcae96bc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 20:59:19 -0500 Subject: [PATCH 014/303] added exponential and hex numbers --- TODO | 6 ++++++ examples/code.cs | 4 +++- lib/coffee_script/lexer.rb | 5 ++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index 93dbd7f72c..3eedb434eb 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,11 @@ TODO: +* Add TextMate syntax highlighter to the download. + +* Code Cleanup. + +* Add JS-style exponential number literals. + * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/examples/code.cs b/examples/code.cs index aa497f1e6f..67899925fc 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -19,7 +19,9 @@ pi: 3.14159 - list: [1, 2, 3, 4] + a_googol: 1e100 + + list: [-1, 0, 1, 2, 3, 4] regex: /match[ing](every|thing|\/)/gi diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 17b2d486ab..41bec24078 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -11,7 +11,7 @@ class Lexer "super"] IDENTIFIER = /\A([a-zA-Z$_]\w*)/ - NUMBER = /\A([0-9]+(\.[0-9]+)?)/ + NUMBER = /\A\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b/i STRING = /\A("(.*?)[^\\]"|'(.*?)[^\\]')/m JS = /\A(`(.*?)`)/ OPERATOR = /\A([+\*&|\/\-%=<>]+)/ @@ -65,8 +65,7 @@ def identifier_token def number_token return false unless number = @chunk[NUMBER, 1] - float = number.include?('.') - token(:NUMBER, float ? number.to_f : number.to_i) + token(:NUMBER, number) @i += number.length end From e3c667d49d45f864221e83ec39847f3acb52a41e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 21:00:31 -0500 Subject: [PATCH 015/303] number examples --- examples/code.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index 67899925fc..39f301bc74 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -19,9 +19,7 @@ pi: 3.14159 - a_googol: 1e100 - - list: [-1, 0, 1, 2, 3, 4] + list: [1, 2, 3, 4] regex: /match[ing](every|thing|\/)/gi @@ -139,3 +137,10 @@ aliquam erat volutpat. Ut wisi enim ad." Exclaimer.prototype: new Greeter() # Set the child to inherit from the parent. Exclaimer.prototype.hello: => super(this.name + "!"). # The child's "hello" calls the parent's via "super". (new Exclaimer('Bob')).hello() # Run it. + +# Numbers. +a_googol: 1e100 + +hex: 0xff0000 + +negative: -1.0 \ No newline at end of file From 3ffbf541dfdbd96b223e04c77222a5ee90838352 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 21:10:49 -0500 Subject: [PATCH 016/303] removed class checks in favor of statement? --- examples/code.cs | 6 +++++- lib/coffee_script/nodes.rb | 26 +++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index 39f301bc74..29737c45c9 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -85,13 +85,17 @@ try all_hell_breaks_loose() catch error print(error) finally clean_up(). -# While loops. +# While loops, break and continue. while demand > supply sell() restock(). while supply > demand then buy(). +while true + break if broken + continue if continuing. + # Unary operators. !!true diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 61532d47d5..d2eb2eaa54 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -36,6 +36,10 @@ def begin_compile "(function(){\n#{compile(TAB, Scope.new)}\n})();" end + def statement? + true + end + # Fancy to handle pushing down returns recursively to the final lines of # inner statements (to make expressions out of them). def compile(indent='', scope=nil, opts={}) @@ -57,10 +61,16 @@ def compile(indent='', scope=nil, opts={}) # Literals are static values that have a Ruby representation, eg.: a string, a number, # true, false, nil, etc. class LiteralNode < Node + STATEMENTS = ['break', 'continue'] + def initialize(value) @value = value end + def statement? + STATEMENTS.include?(@value.to_s) + end + def compile(indent, scope, opts={}) @value.to_s end @@ -71,6 +81,10 @@ def initialize(expression) @expression = expression end + def statement? + true + end + def custom_return? true end @@ -183,6 +197,10 @@ def custom_return? true end + def statement? + true + end + def compile(indent, scope, opts={}) name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) last = @variable.respond_to?(:last) ? @variable.last : name @@ -370,6 +388,10 @@ def initialize(expression) @expression = expression end + def statement? + true + end + def compile(indent, scope, opts={}) "throw #{@expression.compile(indent, scope)}" end @@ -390,8 +412,6 @@ def compile(indent, scope, opts={}) # "if-else" control structure. Look at this node if you want to implement other control # structures like while, for, loop, etc. class IfNode < Node - FORCE_STATEMENT = [Nodes, ReturnNode, AssignNode, IfNode, ForNode, ThrowNode, WhileNode] - def initialize(condition, body, else_body=nil, tag=nil) @condition = condition @body = body && body.flatten @@ -423,7 +443,7 @@ def chain? end def statement? - @is_statement ||= (FORCE_STATEMENT.include?(@body.class) || FORCE_STATEMENT.include?(@else_body.class)) + @is_statement ||= (@body.statement? || (@else_body && @else_body.statement?)) end def line_ending From 0dc445138bea11f6f5e4062efd7e34a3cd8fb42c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 21:14:36 -0500 Subject: [PATCH 017/303] removed the 'default' keyword in favor of an 'else' --- TODO | 2 -- examples/code.cs | 4 ++-- lib/coffee_script/grammar.y | 4 ++-- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 4 ++-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/TODO b/TODO index 3eedb434eb..318a495dfe 100644 --- a/TODO +++ b/TODO @@ -4,8 +4,6 @@ TODO: * Code Cleanup. -* Add JS-style exponential number literals. - * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/examples/code.cs b/examples/code.cs index 29737c45c9..b458dc3d24 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -111,7 +111,7 @@ drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i). -# Switch statements. +# Switch statements ("else" serves as a default). switch day case "Tuesday" then eat_breakfast() case "Sunday" then go_to_church() @@ -120,7 +120,7 @@ eat_breakfast() go_to_work() eat_dinner() -default go_to_work(). +else go_to_work(). # Semicolons can optionally be used instead of newlines. wednesday: => eat_breakfast(); go_to_work(); eat_dinner(); . diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 3e45aba37a..539a494c02 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -9,7 +9,7 @@ token CODE PARAM NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE -token SWITCH CASE DEFAULT +token SWITCH CASE token SUPER token NEWLINE token JS @@ -279,7 +279,7 @@ rule SWITCH Expression Then Cases "." { result = val[3].rewrite_condition(val[1]) } | SWITCH Expression Then - Cases DEFAULT Expressions "." { result = val[3].rewrite_condition(val[1]).add_default(val[5]) } + Cases ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } ; Cases: diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 41bec24078..9d61142005 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -7,7 +7,7 @@ class Lexer "try", "catch", "finally", "throw", "break", "continue", "for", "in", "while", - "switch", "case", "default", + "switch", "case", "super"] IDENTIFIER = /\A([a-zA-Z$_]\w*)/ diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index d2eb2eaa54..9103b3b8ad 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -433,8 +433,8 @@ def rewrite_condition(expression) end # Rewrite a chain of IfNodes to add a default case as the final else. - def add_default(expressions) - chain? ? @else_body.add_default(expressions) : @else_body = expressions + def add_else(expressions) + chain? ? @else_body.add_else(expressions) : @else_body = expressions self end From 9976de76f548e73042371b2a2d9cd61d53f9c4e7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 21:21:07 -0500 Subject: [PATCH 018/303] added the 'delete' operator --- examples/code.cs | 5 ++++- lib/coffee_script/grammar.y | 5 ++++- lib/coffee_script/lexer.rb | 3 ++- lib/coffee_script/nodes.rb | 3 ++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index b458dc3d24..d976b55dd0 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -147,4 +147,7 @@ aliquam erat volutpat. Ut wisi enim ad." hex: 0xff0000 -negative: -1.0 \ No newline at end of file +negative: -1.0 + +# Deleting. +delete secret.identity \ No newline at end of file diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 539a494c02..ef8bc8a68b 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -11,6 +11,7 @@ token BREAK CONTINUE token FOR IN WHILE token SWITCH CASE token SUPER +token DELETE token NEWLINE token JS @@ -24,6 +25,7 @@ prechigh left '&&' '||' AND OR left ':' right '-=' '+=' '/=' '*=' '||=' '&&=' + right DELETE right RETURN THROW FOR WHILE left UNLESS nonassoc IF @@ -116,7 +118,6 @@ rule | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } | NOT Expression { result = OpNode.new(val[0], val[1]) } - | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) } @@ -145,6 +146,8 @@ rule | Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | DELETE Expression { result = OpNode.new(val[0], val[1]) } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 9d61142005..dcf82bc171 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -8,7 +8,8 @@ class Lexer "break", "continue", "for", "in", "while", "switch", "case", - "super"] + "super", + "delete"] 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 diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 9103b3b8ad..6eb8090092 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -252,7 +252,8 @@ def compile_conditional(indent, scope) end def compile_unary(indent, scope) - "#{@operator}#{@first.compile(indent, scope)}" + space = @operator == 'delete' ? ' ' : '' + "#{@operator}#{space}#{@first.compile(indent, scope)}" end end From 6050cad0f8237194b1e993573849a04b2c35cf9c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 21:46:12 -0500 Subject: [PATCH 019/303] moved the TextMate bundle into the gem, added a command to install it --- examples/code.cs | 12 +- .../Preferences/CoffeeScript.tmPreferences | 24 ++ .../Syntaxes/CoffeeScript.tmLanguage | 329 ++++++++++++++++++ .../CoffeeScript.tmbundle/info.plist | 10 + lib/coffee_script/command_line.rb | 15 +- 5 files changed, 381 insertions(+), 9 deletions(-) create mode 100644 lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences create mode 100644 lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage create mode 100644 lib/coffee_script/CoffeeScript.tmbundle/info.plist diff --git a/examples/code.cs b/examples/code.cs index d976b55dd0..b7735800b8 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -59,7 +59,7 @@ if tired then return sleep(). race(). -# Conditional operators: +# Conditional assignment: good ||= evil wine &&= cheese @@ -143,11 +143,11 @@ aliquam erat volutpat. Ut wisi enim ad." (new Exclaimer('Bob')).hello() # Run it. # Numbers. -a_googol: 1e100 - -hex: 0xff0000 - -negative: -1.0 +a_googol: 1e100 +hex: 0xff0000 +negative: -1.0 +infinity: Infinity +nan: NaN # Deleting. delete secret.identity \ No newline at end of file diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences b/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences new file mode 100644 index 0000000000..5847e50b39 --- /dev/null +++ b/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences @@ -0,0 +1,24 @@ + + + + + name + comments + scope + source.cs + settings + + shellVariables + + + name + TM_COMMENT_START + value + # + + + + uuid + 0A92C6F6-4D73-4859-B38C-4CC19CBC191F + + diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage new file mode 100644 index 0000000000..82c83b7320 --- /dev/null +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -0,0 +1,329 @@ + + + + + comment + CoffeeScript Syntax: version 1 + fileTypes + + cs + coffeescript + + name + CoffeeScript + patterns + + + captures + + 1 + + name + entity.name.function.cs + + 2 + + name + keyword.operator.cs + + 3 + + name + variable.parameter.function.cs + + 4 + + name + storage.type.function.cs + + + comment + match stuff like: funcName: => … + match + ([a-zA-Z_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) + name + meta.function.cs + + + captures + + 1 + + name + variable.parameter.function.cs + + 2 + + name + storage.type.function.cs + + + comment + match stuff like: a => … + match + ([a-zA-Z_?.$]*)\s*(=>) + name + meta.inline.function.cs + + + captures + + 1 + + name + keyword.operator.new.cs + + 2 + + name + entity.name.type.instance.cs + + + match + (new)\s+(\w+(?:\.\w*)?) + name + meta.class.instance.constructor + + + match + \b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b + name + constant.numeric.cs + + + begin + ' + beginCaptures + + 0 + + name + punctuation.definition.string.begin.cs + + + end + ' + endCaptures + + 0 + + name + punctuation.definition.string.end.cs + + + name + string.quoted.single.cs + patterns + + + match + \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.) + name + constant.character.escape.cs + + + + + begin + " + beginCaptures + + 0 + + name + punctuation.definition.string.begin.cs + + + end + " + endCaptures + + 0 + + name + punctuation.definition.string.end.cs + + + name + string.quoted.double.cs + patterns + + + match + \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.) + name + constant.character.escape.cs + + + + + begin + ` + beginCaptures + + 0 + + name + punctuation.definition.string.begin.cs + + + end + ` + endCaptures + + 0 + + name + punctuation.definition.string.end.cs + + + name + string.quoted.script.cs + patterns + + + match + \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.) + name + constant.character.escape.cs + + + + + captures + + 1 + + name + punctuation.definition.comment.cs + + + match + (#).*$\n? + name + comment.line.cs + + + match + \b(break|case|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|while)\b + name + keyword.control.cs + + + match + \btrue\b + name + constant.language.boolean.true.cs + + + match + \bfalse\b + name + constant.language.boolean.false.cs + + + match + \bnull\b + name + constant.language.null.cs + + + match + \b(super|this)\b + name + variable.language.cs + + + match + \b(debugger)\b + name + keyword.other.cs + + + match + !|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|aint|not)\b + name + keyword.operator.cs + + + match + \b(Infinity|NaN|undefined)\b + name + constant.language.cs + + + begin + (?<=[=(:]|^|return)\s*(/)(?![/*+{}?]) + beginCaptures + + 1 + + name + punctuation.definition.string.begin.cs + + + end + (/)[igm]* + endCaptures + + 1 + + name + punctuation.definition.string.end.cs + + + name + string.regexp.cs + patterns + + + match + \\. + name + constant.character.escape.cs + + + + + match + \; + name + punctuation.terminator.statement.cs + + + match + ,[ |\t]* + name + meta.delimiter.object.comma.cs + + + match + \. + name + meta.delimiter.method.period.cs + + + match + \{|\} + name + meta.brace.curly.cs + + + match + \(|\) + name + meta.brace.round.cs + + + match + \[|\] + name + meta.brace.square.cs + + + scopeName + source.cs + uuid + 5B520980-A7D5-4E10-8582-1A4C889A8DE5 + + diff --git a/lib/coffee_script/CoffeeScript.tmbundle/info.plist b/lib/coffee_script/CoffeeScript.tmbundle/info.plist new file mode 100644 index 0000000000..7a63123f07 --- /dev/null +++ b/lib/coffee_script/CoffeeScript.tmbundle/info.plist @@ -0,0 +1,10 @@ + + + + + name + CoffeeScript + uuid + A46E4382-F1AC-405B-8F22-65FF470F34D7 + + diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 99b4f32150..a9d479cc39 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -98,25 +98,34 @@ def path_for(source) File.join(dir, filename) end + def install_bundle + bundle_dir = File.expand_path('~/Library/Application Support/TextMate/Bundles/') + FileUtils.cp_r(File.dirname(__FILE__) + '/CoffeeScript.tmbundle', bundle_dir) + end + def parse_options @options = {} @option_parser = OptionParser.new do |opts| - opts.on('-o', '--output [DIR]', 'set the directory for compiled javascript') do |d| + opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d| @options[:output] = d FileUtils.mkdir_p(d) unless File.exists?(d) end opts.on('-w', '--watch', 'watch scripts for changes, and recompile') do |w| @options[:watch] = true end - opts.on('-p', '--print', 'print the compiled javascript to stdout') do |d| + opts.on('-p', '--print', 'print the compiled JavaScript to stdout') do |d| @options[:print] = true end - opts.on('-l', '--lint', 'pipe the compiled javascript through JSLint') do |l| + opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l| @options[:lint] = true end opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t| @options[:tokens] = true end + opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i| + install_bundle + exit + end opts.on_tail('-v', '--version', 'display coffee-script version') do puts "coffee-script version #{CoffeeScript::VERSION}" exit From 8511a33b1e1d90fa78bdcee0df54190da5173680 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 21:57:21 -0500 Subject: [PATCH 020/303] commented the command-line interface --- TODO | 2 -- lib/coffee-script.rb | 3 ++- lib/coffee_script/command_line.rb | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index 318a495dfe..b8e9fc4a86 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ TODO: -* Add TextMate syntax highlighter to the download. - * Code Cleanup. * Is it possible to close blocks (functions, ifs, trys) without an explicit diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index aad0fc07c8..ee77f129c7 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -9,8 +9,9 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.1.0' + VERSION = '0.1.0' # Keep in sync with the gemspec. + # Compile a script (String or IO) to JavaScript. def self.compile(script) script = script.read if script.respond_to?(:read) Parser.new.parse(script).compile diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index a9d479cc39..3f88655aa3 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -5,6 +5,8 @@ module CoffeeScript + # The CommandLine handles all of the functionality of the `coffee-script` + # utility. class CommandLine BANNER = <<-EOS @@ -14,8 +16,10 @@ class CommandLine coffee-script path/to/script.cs EOS + # Seconds to pause between checks for changed source files. WATCH_INTERVAL = 0.5 + # Run the CommandLine off the contents of ARGV. def initialize @mtimes = {} parse_options @@ -24,6 +28,7 @@ def initialize watch_coffee_scripts if @options[:watch] end + # The "--help" usage message. def usage puts "\n#{@option_parser}\n" exit @@ -32,6 +37,8 @@ def usage private + # Compiles (or partially compiles) the source CoffeeScript file, returning + # the desired JS, tokens, or lint results. def compile_javascript(source) return tokens(source) if @options[:tokens] contents = compile(source) @@ -41,6 +48,8 @@ def compile_javascript(source) File.open(path_for(source), 'w+') {|f| f.write(contents) } end + # Spins up a watcher thread to keep track of the modification times of the + # source files, recompiling them whenever they're saved. def watch_coffee_scripts watch_thread = Thread.start do loop do @@ -58,6 +67,7 @@ def watch_coffee_scripts watch_thread.join end + # Ensure that all of the source files exist. def check_sources usage if @sources.empty? missing = @sources.detect {|s| !File.exists?(s) } @@ -67,19 +77,23 @@ def check_sources end end - # Pipe compiled JS through JSLint. + # Pipe compiled JS through JSLint (requires a working 'jsl' command). def lint(js) stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin') stdin.write(js) stdin.close puts stdout.read.tr("\n", '') + errs = stderr.read.chomp + puts errs unless errs.empty? stdout.close and stderr.close end + # Print the tokens that the lexer generates from a source script. def tokens(source) puts Lexer.new.tokenize(File.read(source)).inspect end + # Compile a single source file to JavaScript. def compile(source) begin CoffeeScript.compile(File.open(source)) @@ -98,11 +112,13 @@ def path_for(source) File.join(dir, filename) end + # Install the CoffeeScript TextMate bundle to ~/Library. def install_bundle bundle_dir = File.expand_path('~/Library/Application Support/TextMate/Bundles/') FileUtils.cp_r(File.dirname(__FILE__) + '/CoffeeScript.tmbundle', bundle_dir) end + # Use OptionParser for all the options. def parse_options @options = {} @option_parser = OptionParser.new do |opts| From 68bc68c1ace3ebba4a81773d858d2c7b14ebbadf Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 22:13:29 -0500 Subject: [PATCH 021/303] many more comments, plus a fix for inner-assignment indentation --- TODO | 2 + examples/code.cs | 12 +- lib/coffee_script/grammar.y | 42 ++++-- lib/coffee_script/lexer.rb | 274 ++++++++++++++++++------------------ lib/coffee_script/nodes.rb | 2 +- test/lexer_test.rb | 2 - test/parser_test.rb | 19 --- 7 files changed, 176 insertions(+), 177 deletions(-) delete mode 100644 test/lexer_test.rb delete mode 100644 test/parser_test.rb diff --git a/TODO b/TODO index b8e9fc4a86..2abba2f568 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,7 @@ TODO: +* Write some tests. + * Code Cleanup. * Is it possible to close blocks (functions, ifs, trys) without an explicit diff --git a/examples/code.cs b/examples/code.cs index b7735800b8..7e732c2d4f 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -16,19 +16,14 @@ dense_object_literal: {one: 1, two: 2, three: 3} spaced_out_multiline_object: { - pi: 3.14159 - list: [1, 2, 3, 4] - regex: /match[ing](every|thing|\/)/gi - three: new Idea() inner_obj: { freedom: => _.freedom(). } - } # Arrays: @@ -38,6 +33,11 @@ empty: [] +multiline: [ + 'line one' + 'line two' +] + # Conditionals and ternaries. if submarine.shields_up full_speed_ahead() @@ -64,7 +64,7 @@ wine &&= cheese # Nested property access and calls. -((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position +((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position['top'].offset('x') a: b: c: 5 diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index ef8bc8a68b..adb408cfca 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -53,7 +53,7 @@ rule | Terminator Expressions { result = val[1] } ; - # All types of expressions in our language + # All types of expressions in our language. Expression: Literal | Value @@ -70,19 +70,19 @@ rule | Switch ; - # All tokens that can terminate an expression + # All tokens that can terminate an expression. Terminator: "\n" | ";" ; - # All tokens that can serve to begin the second block + # All tokens that can serve to begin the second block of a multi-part expression. Then: THEN | Terminator ; - # All hard-coded values + # All hard-coded values. Literal: NUMBER { result = LiteralNode.new(val[0]) } | STRING { result = LiteralNode.new(val[0]) } @@ -95,7 +95,7 @@ rule | CONTINUE { result = LiteralNode.new(val[0]) } ; - # Assign to a variable + # Assignment to a variable. Assign: Value ":" Expression { result = AssignNode.new(val[0], val[2]) } ; @@ -105,7 +105,7 @@ rule IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) } ; - # A Return statement. + # A return statement. Return: RETURN Expression { result = ReturnNode.new(val[1]) } ; @@ -150,24 +150,25 @@ rule | DELETE Expression { result = OpNode.new(val[0], val[1]) } ; - - # Method definition + # Function definition. Code: ParamList "=>" CodeBody "." { result = CodeNode.new(val[0], val[2]) } | "=>" CodeBody "." { result = CodeNode.new([], val[1]) } ; + # The body of a function. CodeBody: /* nothing */ { result = Nodes.new([]) } | Expressions { result = val[0] } ; - + # The parameters to a function definition. ParamList: PARAM { result = val } | ParamList "," PARAM { result = val[0] << val[2] } ; + # Expressions that can be treated as values. Value: IDENTIFIER { result = ValueNode.new(val) } | Array { result = ValueNode.new(val) } @@ -177,24 +178,29 @@ rule | Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) } ; + # Accessing into an object or array, through dot or index notation. Accessor: PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) } | Index { result = val[0] } | Slice { result = val[0] } ; + # Indexing into an object or array. Index: "[" Expression "]" { result = IndexNode.new(val[1]) } ; + # Array slice literal. Slice: "[" Expression "," Expression "]" { result = SliceNode.new(val[1], val[3]) } ; + # An object literal. Object: "{" AssignList "}" { result = ObjectNode.new(val[1]) } ; + # Assignment within an object literal (comma or newline separated). AssignList: /* nothing */ { result = []} | AssignObj { result = val } @@ -202,27 +208,29 @@ rule | AssignList Terminator AssignObj { result = val[0] << val[2] } ; - # A method call. + # All flavors of function call (instantiation, super, and regular). Call: Invocation { result = val[0] } | NEW Invocation { result = val[1].new_instance } | Super { result = val[0] } ; + # A generic function invocation. Invocation: Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } ; + # Calling super. Super: SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) } ; - # An Array. + # The array literal. Array: "[" ArgList "]" { result = ArrayNode.new(val[1]) } ; - # A list of arguments to a method call. + # A list of arguments to a method call, or as the contents of an array. ArgList: /* nothing */ { result = [] } | Expression { result = val } @@ -296,6 +304,9 @@ rule end +---- header +module CoffeeScript + ---- inner def parse(code) # @yydebug = true @@ -308,5 +319,8 @@ end end def on_error(error_token_id, error_value, value_stack) - raise CoffeeScript::ParseError.new(token_to_str(error_token_id), error_value, value_stack) - end \ No newline at end of file + raise ParseError.new(token_to_str(error_token_id), error_value, value_stack) + end + +---- footer +end \ No newline at end of file diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index dcf82bc171..dd0f50a6ea 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -1,157 +1,161 @@ -class Lexer - - KEYWORDS = ["if", "else", "then", "unless", - "true", "false", "null", - "and", "or", "is", "aint", "not", - "new", "return", - "try", "catch", "finally", "throw", - "break", "continue", - "for", "in", "while", - "switch", "case", - "super", - "delete"] - - 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(`(.*?)`)/ - OPERATOR = /\A([+\*&|\/\-%=<>]+)/ - WHITESPACE = /\A([ \t\r]+)/ - NEWLINE = /\A([\r\n]+)/ - COMMENT = /\A(#[^\r\n]*)/ - CODE = /\A(=>)/ - REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - - JS_CLEANER = /(\A`|`\Z)/ - MULTILINER = /[\r\n]/ - - EXP_START = ['{', '(', '['] - EXP_END = ['}', ')', ']'] - - # This is how to implement a very simple scanner. - # Scan one caracter at the time until you find something to parse. - def tokenize(code) - @code = code.chomp # Cleanup code by remove extra line breaks - @i = 0 # Current character position we're parsing - @line = 1 # The current line. - @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] - while @i < @code.length - @chunk = @code[@i..-1] - extract_next_token +module CoffeeScript + + class Lexer + + KEYWORDS = ["if", "else", "then", "unless", + "true", "false", "null", + "and", "or", "is", "aint", "not", + "new", "return", + "try", "catch", "finally", "throw", + "break", "continue", + "for", "in", "while", + "switch", "case", + "super", + "delete"] + + 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(`(.*?)`)/ + OPERATOR = /\A([+\*&|\/\-%=<>]+)/ + WHITESPACE = /\A([ \t\r]+)/ + NEWLINE = /\A([\r\n]+)/ + COMMENT = /\A(#[^\r\n]*)/ + CODE = /\A(=>)/ + REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ + + JS_CLEANER = /(\A`|`\Z)/ + MULTILINER = /[\r\n]/ + + EXP_START = ['{', '(', '['] + EXP_END = ['}', ')', ']'] + + # This is how to implement a very simple scanner. + # Scan one caracter at the time until you find something to parse. + def tokenize(code) + @code = code.chomp # Cleanup code by remove extra line breaks + @i = 0 # Current character position we're parsing + @line = 1 # The current line. + @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] + while @i < @code.length + @chunk = @code[@i..-1] + extract_next_token + end + @tokens end - @tokens - end - def extract_next_token - return if identifier_token - return if number_token - return if string_token - return if js_token - return if regex_token - return if remove_comment - return if whitespace_token - return literal_token - end + def extract_next_token + return if identifier_token + return if number_token + return if string_token + return if js_token + return if regex_token + return if remove_comment + return if whitespace_token + return literal_token + end - # Matching if, print, method names, etc. - def identifier_token - return false unless identifier = @chunk[IDENTIFIER, 1] - # Keywords are special identifiers tagged with their own name, 'if' will result - # in an [:IF, "if"] token - tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER - @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' - token(tag, identifier) - @i += identifier.length - end + # Matching if, print, method names, etc. + def identifier_token + return false unless identifier = @chunk[IDENTIFIER, 1] + # Keywords are special identifiers tagged with their own name, 'if' will result + # in an [:IF, "if"] token + tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER + @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' + token(tag, identifier) + @i += identifier.length + end - def number_token - return false unless number = @chunk[NUMBER, 1] - token(:NUMBER, number) - @i += number.length - end + def number_token + return false unless number = @chunk[NUMBER, 1] + token(:NUMBER, number) + @i += number.length + end - def string_token - return false unless string = @chunk[STRING, 1] - escaped = string.gsub(MULTILINER) do |match| - @line += 1 - "\\\n" + def string_token + return false unless string = @chunk[STRING, 1] + escaped = string.gsub(MULTILINER) do |match| + @line += 1 + "\\\n" + end + token(:STRING, escaped) + @i += string.length end - token(:STRING, escaped) - @i += string.length - end - def js_token - return false unless script = @chunk[JS, 1] - token(:JS, script.gsub(JS_CLEANER, '')) - @i += script.length - end + def js_token + return false unless script = @chunk[JS, 1] + token(:JS, script.gsub(JS_CLEANER, '')) + @i += script.length + end - def regex_token - return false unless regex = @chunk[REGEX, 1] - token(:REGEX, regex) - @i += regex.length - end + def regex_token + return false unless regex = @chunk[REGEX, 1] + token(:REGEX, regex) + @i += regex.length + end - def remove_comment - return false unless comment = @chunk[COMMENT, 1] - @i += comment.length - end + def remove_comment + return false unless comment = @chunk[COMMENT, 1] + @i += comment.length + end - # Ignore whitespace - def whitespace_token - return false unless whitespace = @chunk[WHITESPACE, 1] - @i += whitespace.length - end + # Ignore whitespace + def whitespace_token + return false unless whitespace = @chunk[WHITESPACE, 1] + @i += whitespace.length + end - # We treat all other single characters as a token. Eg.: ( ) , . ! - # Multi-character operators are also literal tokens, so that Racc can assign - # the proper order of operations. Multiple newlines get merged. - def literal_token - value = @chunk[NEWLINE, 1] - if value - @line += value.length - token("\n", "\n") unless last_value == "\n" - return @i += value.length + # We treat all other single characters as a token. Eg.: ( ) , . ! + # Multi-character operators are also literal tokens, so that Racc can assign + # the proper order of operations. Multiple newlines get merged. + def literal_token + value = @chunk[NEWLINE, 1] + if value + @line += value.length + token("\n", "\n") unless last_value == "\n" + return @i += value.length + end + value = @chunk[OPERATOR, 1] + tag_parameters if value && value.match(CODE) + value ||= @chunk[0,1] + skip_following_newlines if EXP_START.include?(value) + remove_leading_newlines if EXP_END.include?(value) + token(value, value) + @i += value.length end - value = @chunk[OPERATOR, 1] - tag_parameters if value && value.match(CODE) - value ||= @chunk[0,1] - skip_following_newlines if EXP_START.include?(value) - remove_leading_newlines if EXP_END.include?(value) - token(value, value) - @i += value.length - end - def token(tag, value) - @tokens << [tag, Value.new(value, @line)] - end + def token(tag, value) + @tokens << [tag, Value.new(value, @line)] + end - def last_value - @tokens.last && @tokens.last[1] - end + def last_value + @tokens.last && @tokens.last[1] + end - # The main source of ambiguity in our grammar was Parameter lists (as opposed - # to argument lists in method calls). Tag parameter identifiers to avoid this. - def tag_parameters - index = 0 - loop do - tok = @tokens[index -= 1] - next if tok[0] == ',' - return if tok[0] != :IDENTIFIER - tok[0] = :PARAM + # The main source of ambiguity in our grammar was Parameter lists (as opposed + # to argument lists in method calls). Tag parameter identifiers to avoid this. + def tag_parameters + index = 0 + loop do + tok = @tokens[index -= 1] + next if tok[0] == ',' + return if tok[0] != :IDENTIFIER + tok[0] = :PARAM + end end - end - def skip_following_newlines - newlines = @code[(@i+1)..-1][NEWLINE, 1] - if newlines - @line += newlines.length - @i += newlines.length + def skip_following_newlines + newlines = @code[(@i+1)..-1][NEWLINE, 1] + if newlines + @line += newlines.length + @i += newlines.length + end + end + + def remove_leading_newlines + @tokens.pop if last_value == "\n" end - end - def remove_leading_newlines - @tokens.pop if last_value == "\n" end end \ No newline at end of file diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 6eb8090092..05644abb2f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -205,7 +205,7 @@ def compile(indent, scope, opts={}) name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) last = @variable.respond_to?(:last) ? @variable.last : name opts = opts.merge({:assign => name, :last_assign => last}) - value = @value.compile(indent, scope, opts) + value = @value.compile(indent + TAB, scope, opts) return "#{@variable}: #{value}" if @context == :object return "#{name} = #{value}" if @variable.properties? defined = scope.find(name) diff --git a/test/lexer_test.rb b/test/lexer_test.rb deleted file mode 100644 index 7821295d51..0000000000 --- a/test/lexer_test.rb +++ /dev/null @@ -1,2 +0,0 @@ -require "lexer" -p Lexer.new.tokenize(File.read('code.cs')) diff --git a/test/parser_test.rb b/test/parser_test.rb deleted file mode 100644 index e9ede388a6..0000000000 --- a/test/parser_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Recompile the Parser. -# With debugging and verbose: -v -g -`racc -v -o parser.rb grammar.y` - -# Parse and print the compiled CoffeeScript source. -require "parser.rb" -js = Parser.new.parse(File.read('code.cs')).compile -puts "\n\n" -puts js - -# Pipe compiled JS through JSLint. -puts "\n\n" -require 'open3' -stdin, stdout, stderr = Open3.popen3('/Users/jashkenas/Library/Application\ Support/TextMate/Bundles/JavaScript\ Tools.tmbundle/Support/bin/jsl -nologo -stdin') -stdin.write(js) -stdin.close -puts stdout.read -stdout.close -stderr.close \ No newline at end of file From 77704d24a2386b758db980cac55e8212f3475a68 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 22:22:35 -0500 Subject: [PATCH 022/303] finished commenting the grammar --- TODO | 2 ++ lib/coffee_script/grammar.y | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/TODO b/TODO index 2abba2f568..e003525b92 100644 --- a/TODO +++ b/TODO @@ -4,6 +4,8 @@ TODO: * Code Cleanup. +* Figure out how not to have to close each if statement individually. + * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index adb408cfca..673a4af545 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -238,6 +238,7 @@ rule | ArgList Terminator Expression { result = val[0] << val[2] } ; + # If statements, including post-fix ifs and unlesses. If: IF Expression Then Expressions "." { result = IfNode.new(val[1], val[3]) } @@ -248,6 +249,7 @@ rule | Expression UNLESS Expression { result = IfNode.new(val[2], Nodes.new([val[0]]), nil, :invert) } ; + # Try/catch/finally exception handling blocks. Try: TRY Expressions CATCH IDENTIFIER Expressions "." { result = TryNode.new(val[1], val[3], val[4]) } @@ -258,19 +260,23 @@ rule FINALLY Expressions "." { result = TryNode.new(val[1], val[3], val[4], val[6]) } ; + # Throw an exception. Throw: THROW Expression { result = ThrowNode.new(val[1]) } ; + # Parenthetical expressions. Parenthetical: "(" Expressions ")" { result = ParentheticalNode.new(val[1]) } ; + # The while loop. (there is no do..while). While: WHILE Expression Then Expressions "." { result = WhileNode.new(val[1], val[3]) } ; + # Array comprehensions, including guard and current index. For: Expression FOR IDENTIFIER IN Expression "." { result = ForNode.new(val[0], val[4], val[2]) } @@ -286,6 +292,7 @@ rule IF Expression "." { result = ForNode.new(IfNode.new(val[8], Nodes.new([val[0]])), val[6], val[2], val[4]) } ; + # Switch/Case blocks. Switch: SWITCH Expression Then Cases "." { result = val[3].rewrite_condition(val[1]) } @@ -293,11 +300,13 @@ rule Cases ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } ; + # The inner list of cases. Cases: Case { result = val[0] } | Cases Case { result = val[0] << val[1] } ; + # An individual case. Case: CASE Expression Then Expressions { result = IfNode.new(val[1], val[3]) } ; @@ -308,16 +317,21 @@ end module CoffeeScript ---- inner + # Lex and parse a CoffeeScript. def parse(code) + # Uncomment the following line to enable grammar debugging, in combination + # with the -g flag in the Rake build task. # @yydebug = true @tokens = Lexer.new.tokenize(code) do_parse end + # Retrieve the next token from the list. def next_token @tokens.shift end + # Raise a custom error class that knows about line numbers. def on_error(error_token_id, error_value, value_stack) raise ParseError.new(token_to_str(error_token_id), error_value, value_stack) end From d124f7fc0d6176a37c8631c41f7eb776268e01f4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 22:54:24 -0500 Subject: [PATCH 023/303] finished commenting everything but the nodes -- they're up next --- lib/coffee_script/lexer.rb | 43 +- lib/coffee_script/nodes.rb | 746 ++++++++++++++++--------------- lib/coffee_script/parse_error.rb | 3 + lib/coffee_script/scope.rb | 57 +-- lib/coffee_script/value.rb | 54 +-- 5 files changed, 471 insertions(+), 432 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index dd0f50a6ea..d000297a02 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -1,7 +1,11 @@ module CoffeeScript + # The lexer reads a stream of CoffeeScript and divvys it up into tagged + # tokens. A minor bit of the ambiguity in the grammar has been avoided by + # pushing some extra smarts into the Lexer. class Lexer + # The list of keywords passed verbatim to the parser. KEYWORDS = ["if", "else", "then", "unless", "true", "false", "null", "and", "or", "is", "aint", "not", @@ -13,6 +17,7 @@ class Lexer "super", "delete"] + # Token matching regexes. 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 @@ -24,19 +29,22 @@ class Lexer CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ + # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ MULTILINER = /[\r\n]/ + # Tokens that always constitute the start of an expression. EXP_START = ['{', '(', '['] + + # Tokens that always constitute the end of an expression. EXP_END = ['}', ')', ']'] - # This is how to implement a very simple scanner. - # Scan one caracter at the time until you find something to parse. + # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) - @code = code.chomp # Cleanup code by remove extra line breaks - @i = 0 # Current character position we're parsing - @line = 1 # The current line. - @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] + @code = code.chomp # Cleanup code by remove extra line breaks + @i = 0 # Current character position we're parsing + @line = 1 # The current line. + @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] while @i < @code.length @chunk = @code[@i..-1] extract_next_token @@ -44,6 +52,8 @@ def tokenize(code) @tokens end + # At every position, run this list of match attempts, short-circuiting if + # any of them succeed. def extract_next_token return if identifier_token return if number_token @@ -55,7 +65,7 @@ def extract_next_token return literal_token end - # Matching if, print, method names, etc. + # Matches identifying literals: variables, keywords, method names, etc. def identifier_token return false unless identifier = @chunk[IDENTIFIER, 1] # Keywords are special identifiers tagged with their own name, 'if' will result @@ -66,12 +76,14 @@ def identifier_token @i += identifier.length end + # Matches numbers, including decimals, hex, and exponential notation. def number_token return false unless number = @chunk[NUMBER, 1] token(:NUMBER, number) @i += number.length end + # Matches strings, including multi-line strings. def string_token return false unless string = @chunk[STRING, 1] escaped = string.gsub(MULTILINER) do |match| @@ -82,24 +94,27 @@ def string_token @i += string.length end + # Matches interpolated JavaScript. def js_token return false unless script = @chunk[JS, 1] token(:JS, script.gsub(JS_CLEANER, '')) @i += script.length end + # Matches regular expression literals. def regex_token return false unless regex = @chunk[REGEX, 1] token(:REGEX, regex) @i += regex.length end + # Matches and consumes comments. def remove_comment return false unless comment = @chunk[COMMENT, 1] @i += comment.length end - # Ignore whitespace + # Matches and consumes non-meaningful whitespace. def whitespace_token return false unless whitespace = @chunk[WHITESPACE, 1] @i += whitespace.length @@ -107,7 +122,7 @@ def whitespace_token # We treat all other single characters as a token. Eg.: ( ) , . ! # Multi-character operators are also literal tokens, so that Racc can assign - # the proper order of operations. Multiple newlines get merged. + # the proper order of operations. Multiple newlines get merged together. def literal_token value = @chunk[NEWLINE, 1] if value @@ -124,16 +139,20 @@ def literal_token @i += value.length end + # Add a token to the results, taking note of the line number for syntax + # errors later in the parse. def token(tag, value) @tokens << [tag, Value.new(value, @line)] end + # Peek at the previous token. def last_value @tokens.last && @tokens.last[1] end - # The main source of ambiguity in our grammar was Parameter lists (as opposed - # to argument lists in method calls). Tag parameter identifiers to avoid this. + # A source of ambiguity in our grammar was parameter lists in function + # definitions (as opposed to argument lists in function calls). Tag + # parameter identifiers in order to avoid this. def tag_parameters index = 0 loop do @@ -144,6 +163,7 @@ def tag_parameters end end + # Consume and ignore newlines immediately after this point. def skip_following_newlines newlines = @code[(@i+1)..-1][NEWLINE, 1] if newlines @@ -152,6 +172,7 @@ def skip_following_newlines end end + # Discard newlines immediately before this point. def remove_leading_newlines @tokens.pop if last_value == "\n" end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 05644abb2f..cfe38204b8 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -1,469 +1,473 @@ -class Node - # Tabs are two spaces for pretty-printing. - TAB = ' ' +module CoffeeScript - def flatten; self; end - def line_ending; ';'; end - def statement?; false; end - def custom_return?; false; end - def custom_assign?; false; end + class Node + # Tabs are two spaces for pretty-printing. + TAB = ' ' - def compile(indent='', scope=nil, opts={}); end -end + def flatten; self; end + def line_ending; ';'; end + def statement?; false; end + def custom_return?; false; end + def custom_assign?; false; end -# Collection of nodes each one representing an expression. -class Nodes < Node - attr_reader :nodes - - def self.wrap(node) - node.is_a?(Nodes) ? node : Nodes.new([node]) + def compile(indent='', scope=nil, opts={}); end end - def initialize(nodes) - @nodes = nodes - end + # Collection of nodes each one representing an expression. + class Nodes < Node + attr_reader :nodes - def <<(node) - @nodes << node - self - end + def self.wrap(node) + node.is_a?(Nodes) ? node : Nodes.new([node]) + end - def flatten - @nodes.length == 1 ? @nodes.first : self - end + def initialize(nodes) + @nodes = nodes + end - def begin_compile - "(function(){\n#{compile(TAB, Scope.new)}\n})();" - end + def <<(node) + @nodes << node + self + end - def statement? - true - end + def flatten + @nodes.length == 1 ? @nodes.first : self + end + + def begin_compile + "(function(){\n#{compile(TAB, Scope.new)}\n})();" + end + + def statement? + true + end - # Fancy to handle pushing down returns recursively to the final lines of - # inner statements (to make expressions out of them). - def compile(indent='', scope=nil, opts={}) - return begin_compile unless scope - @nodes.map { |n| - if opts[:return] && n == @nodes.last - if n.statement? || n.custom_return? - "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" + # Fancy to handle pushing down returns recursively to the final lines of + # inner statements (to make expressions out of them). + def compile(indent='', scope=nil, opts={}) + return begin_compile unless scope + @nodes.map { |n| + if opts[:return] && n == @nodes.last + if n.statement? || n.custom_return? + "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" + else + "#{indent}return #{n.compile(indent, scope, opts)}#{n.line_ending}" + end else - "#{indent}return #{n.compile(indent, scope, opts)}#{n.line_ending}" + "#{indent}#{n.compile(indent, scope)}#{n.line_ending}" end - else - "#{indent}#{n.compile(indent, scope)}#{n.line_ending}" - end - }.join("\n") + }.join("\n") + end end -end -# Literals are static values that have a Ruby representation, eg.: a string, a number, -# true, false, nil, etc. -class LiteralNode < Node - STATEMENTS = ['break', 'continue'] + # Literals are static values that have a Ruby representation, eg.: a string, a number, + # true, false, nil, etc. + class LiteralNode < Node + STATEMENTS = ['break', 'continue'] - def initialize(value) - @value = value - end + def initialize(value) + @value = value + end - def statement? - STATEMENTS.include?(@value.to_s) - end + def statement? + STATEMENTS.include?(@value.to_s) + end - def compile(indent, scope, opts={}) - @value.to_s + def compile(indent, scope, opts={}) + @value.to_s + end end -end -class ReturnNode < Node - def initialize(expression) - @expression = expression - end + class ReturnNode < Node + def initialize(expression) + @expression = expression + end - def statement? - true - end + def statement? + true + end - def custom_return? - true - end + def custom_return? + true + end - def compile(indent, scope, opts={}) - return @expression.compile(indent, scope, opts.merge(:return => true)) if @expression.custom_return? - compiled = @expression.compile(indent, scope) - @expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}" - end -end - -# Node of a method call or local variable access, can take any of these forms: -# -# method # this form can also be a local variable -# method(argument1, argument2) -# receiver.method -# receiver.method(argument1, argument2) -# -class CallNode < Node - LEADING_DOT = /\A\./ - - def initialize(variable, arguments=[]) - @variable, @arguments = variable, arguments + def compile(indent, scope, opts={}) + return @expression.compile(indent, scope, opts.merge(:return => true)) if @expression.custom_return? + compiled = @expression.compile(indent, scope) + @expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}" + end end - def new_instance - @new = true - self - end + # Node of a method call or local variable access, can take any of these forms: + # + # method # this form can also be a local variable + # method(argument1, argument2) + # receiver.method + # receiver.method(argument1, argument2) + # + class CallNode < Node + LEADING_DOT = /\A\./ - def super? - @variable == :super - end + def initialize(variable, arguments=[]) + @variable, @arguments = variable, arguments + end - def compile(indent, scope, opts={}) - args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ') - return compile_super(args, indent, scope, opts) if super? - prefix = @new ? "new " : '' - "#{prefix}#{@variable.compile(indent, scope)}(#{args})" - end + def new_instance + @new = true + self + end - def compile_super(args, indent, scope, opts) - methname = opts[:last_assign].sub(LEADING_DOT, '') - "this.constructor.prototype.#{methname}.call(this, #{args})" - end -end + def super? + @variable == :super + end -class ValueNode < Node - attr_reader :last + def compile(indent, scope, opts={}) + args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ') + return compile_super(args, indent, scope, opts) if super? + prefix = @new ? "new " : '' + "#{prefix}#{@variable.compile(indent, scope)}(#{args})" + end - def initialize(name, properties=[]) - @name, @properties = name, properties + def compile_super(args, indent, scope, opts) + methname = opts[:last_assign].sub(LEADING_DOT, '') + "this.constructor.prototype.#{methname}.call(this, #{args})" + end end - def <<(other) - @properties << other - self - end + class ValueNode < Node + attr_reader :last - def properties? - return !@properties.empty? - end + def initialize(name, properties=[]) + @name, @properties = name, properties + end - def compile(indent, scope, opts={}) - parts = [@name, @properties].flatten.map do |v| - v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s + def <<(other) + @properties << other + self end - @last = parts.last - parts.join('') - end -end -class AccessorNode - def initialize(name) - @name = name - end + def properties? + return !@properties.empty? + end - def compile(indent, scope, opts={}) - ".#{@name}" + def compile(indent, scope, opts={}) + parts = [@name, @properties].flatten.map do |v| + v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s + end + @last = parts.last + parts.join('') + end end -end -class IndexNode - def initialize(index) - @index = index - end + class AccessorNode + def initialize(name) + @name = name + end - def compile(indent, scope, opts={}) - "[#{@index.compile(indent, scope)}]" + def compile(indent, scope, opts={}) + ".#{@name}" + end end -end -class SliceNode - def initialize(from, to) - @from, @to = from, to - end + class IndexNode + def initialize(index) + @index = index + end - def compile(indent, scope, opts={}) - ".slice(#{@from.compile(indent, scope, opts)}, #{@to.compile(indent, scope, opts)} + 1)" + def compile(indent, scope, opts={}) + "[#{@index.compile(indent, scope)}]" + end end -end -# Setting the value of a local variable. -class AssignNode < Node - def initialize(variable, value, context=nil) - @variable, @value, @context = variable, value, context - end + class SliceNode + def initialize(from, to) + @from, @to = from, to + end - def custom_return? - true + def compile(indent, scope, opts={}) + ".slice(#{@from.compile(indent, scope, opts)}, #{@to.compile(indent, scope, opts)} + 1)" + end end - def statement? - true - end + # Setting the value of a local variable. + class AssignNode < Node + def initialize(variable, value, context=nil) + @variable, @value, @context = variable, value, context + end - def compile(indent, scope, opts={}) - name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) - last = @variable.respond_to?(:last) ? @variable.last : name - opts = opts.merge({:assign => name, :last_assign => last}) - value = @value.compile(indent + TAB, scope, opts) - return "#{@variable}: #{value}" if @context == :object - return "#{name} = #{value}" if @variable.properties? - defined = scope.find(name) - postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' - def_part = defined ? "" : "var #{name};\n#{indent}" - return def_part + @value.compile(indent, scope, opts) if @value.custom_assign? - def_part = defined ? name : "var #{name}" - "#{def_part} = #{@value.compile(indent, scope, opts)}#{postfix}" - end -end - -# Simple Arithmetic and logical operations -class OpNode < Node - CONVERSIONS = { - "==" => "===", - "!=" => "!==", - 'and' => '&&', - 'or' => '||', - 'is' => '===', - "aint" => "!==", - 'not' => '!', - } - CONDITIONALS = ['||=', '&&='] - - def initialize(operator, first, second=nil) - @first, @second = first, second - @operator = CONVERSIONS[operator] || operator - end + def custom_return? + true + end - def unary? - @second.nil? - end + def statement? + true + end - def compile(indent, scope, opts={}) - return compile_conditional(indent, scope) if CONDITIONALS.include?(@operator) - return compile_unary(indent, scope) if unary? - "#{@first.compile(indent, scope)} #{@operator} #{@second.compile(indent, scope)}" + def compile(indent, scope, opts={}) + name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) + last = @variable.respond_to?(:last) ? @variable.last : name + opts = opts.merge({:assign => name, :last_assign => last}) + value = @value.compile(indent + TAB, scope, opts) + return "#{@variable}: #{value}" if @context == :object + return "#{name} = #{value}" if @variable.properties? + defined = scope.find(name) + postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' + def_part = defined ? "" : "var #{name};\n#{indent}" + return def_part + @value.compile(indent, scope, opts) if @value.custom_assign? + def_part = defined ? name : "var #{name}" + "#{def_part} = #{@value.compile(indent, scope, opts)}#{postfix}" + end end - def compile_conditional(indent, scope) - first, second = @first.compile(indent, scope), @second.compile(indent, scope) - sym = @operator[0..1] - "#{first} = #{first} #{sym} #{second}" - end + # Simple Arithmetic and logical operations + class OpNode < Node + CONVERSIONS = { + "==" => "===", + "!=" => "!==", + 'and' => '&&', + 'or' => '||', + 'is' => '===', + "aint" => "!==", + 'not' => '!', + } + CONDITIONALS = ['||=', '&&='] + + def initialize(operator, first, second=nil) + @first, @second = first, second + @operator = CONVERSIONS[operator] || operator + end - def compile_unary(indent, scope) - space = @operator == 'delete' ? ' ' : '' - "#{@operator}#{space}#{@first.compile(indent, scope)}" - end -end + def unary? + @second.nil? + end -# Method definition. -class CodeNode < Node - def initialize(params, body) - @params = params - @body = body - end + def compile(indent, scope, opts={}) + return compile_conditional(indent, scope) if CONDITIONALS.include?(@operator) + return compile_unary(indent, scope) if unary? + "#{@first.compile(indent, scope)} #{@operator} #{@second.compile(indent, scope)}" + end - def compile(indent, scope, opts={}) - scope = Scope.new(scope) - @params.each {|id| scope.find(id) } - opts = opts.merge(:return => true) - code = @body.compile(indent + TAB, scope, opts) - "function(#{@params.join(', ')}) {\n#{code}\n#{indent}}" - end -end + def compile_conditional(indent, scope) + first, second = @first.compile(indent, scope), @second.compile(indent, scope) + sym = @operator[0..1] + "#{first} = #{first} #{sym} #{second}" + end -class ObjectNode < Node - def initialize(properties = []) - @properties = properties + def compile_unary(indent, scope) + space = @operator == 'delete' ? ' ' : '' + "#{@operator}#{space}#{@first.compile(indent, scope)}" + end end - def compile(indent, scope, opts={}) - props = @properties.map {|p| indent + TAB + p.compile(indent, scope) }.join(",\n") - "{\n#{props}\n#{indent}}" - end -end + # Method definition. + class CodeNode < Node + def initialize(params, body) + @params = params + @body = body + end -class ArrayNode < Node - def initialize(objects=[]) - @objects = objects + def compile(indent, scope, opts={}) + scope = Scope.new(scope) + @params.each {|id| scope.find(id) } + opts = opts.merge(:return => true) + code = @body.compile(indent + TAB, scope, opts) + "function(#{@params.join(', ')}) {\n#{code}\n#{indent}}" + end end - def compile(indent, scope, opts={}) - objects = @objects.map {|o| o.compile(indent, scope) }.join(', ') - "[#{objects}]" - end -end + class ObjectNode < Node + def initialize(properties = []) + @properties = properties + end -class WhileNode < Node - def initialize(condition, body) - @condition, @body = condition, body + def compile(indent, scope, opts={}) + props = @properties.map {|p| indent + TAB + p.compile(indent, scope) }.join(",\n") + "{\n#{props}\n#{indent}}" + end end - def line_ending - '' - end + class ArrayNode < Node + def initialize(objects=[]) + @objects = objects + end - def statement? - true + def compile(indent, scope, opts={}) + objects = @objects.map {|o| o.compile(indent, scope) }.join(', ') + "[#{objects}]" + end end - def compile(indent, scope, opts={}) - "while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}" - end -end + class WhileNode < Node + def initialize(condition, body) + @condition, @body = condition, body + end -class ForNode < Node + def line_ending + '' + end - def initialize(body, source, name, index=nil) - @body, @source, @name, @index = body, source, name, index - end + def statement? + true + end - def line_ending - '' + def compile(indent, scope, opts={}) + "while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}" + end end - def custom_return? - true - end + class ForNode < Node - def custom_assign? - true - end + def initialize(body, source, name, index=nil) + @body, @source, @name, @index = body, source, name, index + end - def statement? - true - end + def line_ending + '' + end - def compile(indent, scope, opts={}) - svar = scope.free_variable - ivar = scope.free_variable - lvar = scope.free_variable - name_part = scope.find(@name) ? @name : "var #{@name}" - index_name = @index ? (scope.find(@index) ? @index : "var #{@index}") : nil - source_part = "var #{svar} = #{@source.compile(indent, scope)};" - for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" - var_part = "\n#{indent + TAB}#{name_part} = #{svar}[#{ivar}];\n" - index_part = @index ? "#{indent + TAB}#{index_name} = #{ivar};\n" : '' - - set_result = '' - save_result = '' - return_result = '' - if opts[:return] || opts[:assign] - rvar = scope.free_variable - set_result = "var #{rvar} = [];\n#{indent}" - save_result = "#{rvar}[#{ivar}] = " - return_result = rvar - return_result = "#{opts[:assign]} = #{return_result}" if opts[:assign] - return_result = "return #{return_result}" if opts[:return] - return_result = "\n#{indent}#{return_result}" - end - - body = @body.compile(indent + TAB, scope) - "#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body};\n#{indent}}#{return_result}" - end -end + def custom_return? + true + end -class TryNode < Node - def initialize(try, error, recovery, finally=nil) - @try, @error, @recovery, @finally = try, error, recovery, finally - end + def custom_assign? + true + end - def line_ending - '' - end + def statement? + true + end - def statement? - true - end + def compile(indent, scope, opts={}) + svar = scope.free_variable + ivar = scope.free_variable + lvar = scope.free_variable + name_part = scope.find(@name) ? @name : "var #{@name}" + index_name = @index ? (scope.find(@index) ? @index : "var #{@index}") : nil + source_part = "var #{svar} = #{@source.compile(indent, scope)};" + for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" + var_part = "\n#{indent + TAB}#{name_part} = #{svar}[#{ivar}];\n" + index_part = @index ? "#{indent + TAB}#{index_name} = #{ivar};\n" : '' + + set_result = '' + save_result = '' + return_result = '' + if opts[:return] || opts[:assign] + rvar = scope.free_variable + set_result = "var #{rvar} = [];\n#{indent}" + save_result = "#{rvar}[#{ivar}] = " + return_result = rvar + return_result = "#{opts[:assign]} = #{return_result}" if opts[:assign] + return_result = "return #{return_result}" if opts[:return] + return_result = "\n#{indent}#{return_result}" + end - def compile(indent, scope, opts={}) - catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(indent + TAB, scope, opts)}\n#{indent}}" - finally_part = @finally && " finally {\n#{@finally.compile(indent + TAB, scope, opts)}\n#{indent}}" - "try {\n#{@try.compile(indent + TAB, scope, opts)}\n#{indent}}#{catch_part}#{finally_part}" + body = @body.compile(indent + TAB, scope) + "#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body};\n#{indent}}#{return_result}" + end end -end -class ThrowNode < Node - def initialize(expression) - @expression = expression - end + class TryNode < Node + def initialize(try, error, recovery, finally=nil) + @try, @error, @recovery, @finally = try, error, recovery, finally + end - def statement? - true - end + def line_ending + '' + end - def compile(indent, scope, opts={}) - "throw #{@expression.compile(indent, scope)}" - end -end + def statement? + true + end -class ParentheticalNode < Node - def initialize(expressions) - @expressions = expressions + def compile(indent, scope, opts={}) + catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(indent + TAB, scope, opts)}\n#{indent}}" + finally_part = @finally && " finally {\n#{@finally.compile(indent + TAB, scope, opts)}\n#{indent}}" + "try {\n#{@try.compile(indent + TAB, scope, opts)}\n#{indent}}#{catch_part}#{finally_part}" + end end - def compile(indent, scope, opts={}) - compiled = @expressions.flatten.compile(indent, scope) - compiled = compiled[0...-1] if compiled[-1..-1] == ';' - opts[:no_paren] ? compiled : "(#{compiled})" - end -end - -# "if-else" control structure. Look at this node if you want to implement other control -# structures like while, for, loop, etc. -class IfNode < Node - def initialize(condition, body, else_body=nil, tag=nil) - @condition = condition - @body = body && body.flatten - @else_body = else_body && else_body.flatten - @condition = OpNode.new("!", @condition) if tag == :invert - end + class ThrowNode < Node + def initialize(expression) + @expression = expression + end - def <<(else_body) - eb = else_body.flatten - @else_body ? @else_body << eb : @else_body = eb - self - end + def statement? + true + end - # Rewrite a chain of IfNodes with their switch condition for equality. - def rewrite_condition(expression) - @condition = OpNode.new("is", expression, @condition) - @else_body.rewrite_condition(expression) if chain? - self + def compile(indent, scope, opts={}) + "throw #{@expression.compile(indent, scope)}" + end end - # Rewrite a chain of IfNodes to add a default case as the final else. - def add_else(expressions) - chain? ? @else_body.add_else(expressions) : @else_body = expressions - self - end + class ParentheticalNode < Node + def initialize(expressions) + @expressions = expressions + end - def chain? - @chain ||= @else_body && @else_body.is_a?(IfNode) + def compile(indent, scope, opts={}) + compiled = @expressions.flatten.compile(indent, scope) + compiled = compiled[0...-1] if compiled[-1..-1] == ';' + opts[:no_paren] ? compiled : "(#{compiled})" + end end - def statement? - @is_statement ||= (@body.statement? || (@else_body && @else_body.statement?)) - end + # "if-else" control structure. Look at this node if you want to implement other control + # structures like while, for, loop, etc. + class IfNode < Node + def initialize(condition, body, else_body=nil, tag=nil) + @condition = condition + @body = body && body.flatten + @else_body = else_body && else_body.flatten + @condition = OpNode.new("!", @condition) if tag == :invert + end - def line_ending - statement? ? '' : ';' - end + def <<(else_body) + eb = else_body.flatten + @else_body ? @else_body << eb : @else_body = eb + self + end - def compile(indent, scope, opts={}) - statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) - end + # Rewrite a chain of IfNodes with their switch condition for equality. + def rewrite_condition(expression) + @condition = OpNode.new("is", expression, @condition) + @else_body.rewrite_condition(expression) if chain? + self + end - def compile_statement(indent, scope, opts) - if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Nodes.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" - else_part = @else_body ? " else {\n#{Nodes.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : '' - if_part + else_part - end + # Rewrite a chain of IfNodes to add a default case as the final else. + def add_else(expressions) + chain? ? @else_body.add_else(expressions) : @else_body = expressions + self + end + + def chain? + @chain ||= @else_body && @else_body.is_a?(IfNode) + end + + def statement? + @is_statement ||= (@body.statement? || (@else_body && @else_body.statement?)) + end - def compile_ternary(indent, scope) - if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}" - else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null' - "#{if_part} : #{else_part}" + def line_ending + statement? ? '' : ';' + end + + def compile(indent, scope, opts={}) + statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) + end + + def compile_statement(indent, scope, opts) + if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Nodes.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" + else_part = @else_body ? " else {\n#{Nodes.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : '' + if_part + else_part + end + + def compile_ternary(indent, scope) + if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}" + else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null' + "#{if_part} : #{else_part}" + end end -end + +end \ No newline at end of file diff --git a/lib/coffee_script/parse_error.rb b/lib/coffee_script/parse_error.rb index 9f49f0bb03..88e1e2d209 100644 --- a/lib/coffee_script/parse_error.rb +++ b/lib/coffee_script/parse_error.rb @@ -1,5 +1,8 @@ module CoffeeScript + # Racc will raise this Exception whenever a syntax error occurs. The main + # benefit over the Racc::ParseError is that the CoffeeScript::ParseError is + # line-number aware. class ParseError < Racc::ParseError def initialize(token_id, value, stack) diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index a09fb7c97d..22d3698ee5 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -1,33 +1,40 @@ -# A class to handle lookups for lexically scoped variables. -class Scope +module CoffeeScript - attr_reader :parent, :temp_variable + # Scope objects form a tree corresponding to the shape of the function + # definitions present in the script. They provide lexical scope, to determine + # whether a variable has been seen before or if it needs to be declared. + class Scope - def initialize(parent=nil) - @parent = parent - @variables = {} - @temp_variable = @parent ? @parent.temp_variable : 'a' - end + attr_reader :parent, :temp_variable - # Look up a variable in lexical scope, or declare it if not found. - def find(name, remote=false) - found = check(name, remote) - return found if found || remote - @variables[name] = true - found - end + # Initialize a scope with its parent, for lookups up the chain. + def initialize(parent=nil) + @parent = parent + @variables = {} + @temp_variable = @parent ? @parent.temp_variable : 'a' + end - # Just check for the pre-definition of a variable. - def check(name, remote=false) - return true if @variables[name] - @parent && @parent.find(name, true) - end + # Look up a variable in lexical scope, or declare it if not found. + def find(name, remote=false) + found = check(name, remote) + return found if found || remote + @variables[name] = true + found + end + + # Just check to see if a variable has already been declared. + def check(name, remote=false) + return true if @variables[name] + @parent && @parent.find(name, true) + end + + # Find an available, short, name for a compiler-generated variable. + def free_variable + @temp_variable.succ! while check(@temp_variable) + @variables[@temp_variable] = true + @temp_variable.dup + end - # Find an available, short variable name. - def free_variable - @temp_variable.succ! while check(@temp_variable) - @variables[@temp_variable] = true - @temp_variable.dup end end \ No newline at end of file diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb index 19b56ed666..2f0cae3c46 100644 --- a/lib/coffee_script/value.rb +++ b/lib/coffee_script/value.rb @@ -1,34 +1,38 @@ -# Instead of producing raw Ruby objects, the Lexer produces values of this -# class, tagged with line number information. -class Value - attr_reader :line +module CoffeeScript - def initialize(value, line) - @value, @line = value, line - end + # Instead of producing raw Ruby objects, the Lexer produces values of this + # class, wrapping native objects tagged with line number information. + class Value + attr_reader :line - def to_str - @value.to_s - end - alias_method :to_s, :to_str + def initialize(value, line) + @value, @line = value, line + end - def inspect - @value.inspect - end + def to_str + @value.to_s + end + alias_method :to_s, :to_str - def ==(other) - @value == other - end + def inspect + @value.inspect + end - def [](index) - @value[index] - end + def ==(other) + @value == other + end - def eql?(other) - @value.eql?(other) - end + def [](index) + @value[index] + end - def hash - @value.hash + def eql?(other) + @value.eql?(other) + end + + def hash + @value.hash + end end + end \ No newline at end of file From 83944950ac2c667a798a3ddfd8066434b8c92ce7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 22:58:40 -0500 Subject: [PATCH 024/303] renamed Nodes to Expressions --- lib/coffee_script/grammar.y | 18 +++++++++--------- lib/coffee_script/nodes.rb | 23 ++++++++++++----------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 673a4af545..a13b9d162c 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -40,14 +40,14 @@ rule # All parsing will end in this rule, being the trunk of the AST. Root: - /* nothing */ { result = Nodes.new([]) } - | Terminator { result = Nodes.new([]) } + /* nothing */ { result = Expressions.new([]) } + | Terminator { result = Expressions.new([]) } | Expressions { result = val[0] } ; # Any list of expressions or method body, seperated by line breaks or semis. Expressions: - Expression { result = Nodes.new(val) } + Expression { result = Expressions.new(val) } | Expressions Terminator Expression { result = val[0] << val[2] } | Expressions Terminator { result = val[0] } | Terminator Expressions { result = val[1] } @@ -158,7 +158,7 @@ rule # The body of a function. CodeBody: - /* nothing */ { result = Nodes.new([]) } + /* nothing */ { result = Expressions.new([]) } | Expressions { result = val[0] } ; @@ -245,8 +245,8 @@ rule | IF Expression Then Expressions ELSE Expressions "." { result = IfNode.new(val[1], val[3], val[5]) } - | Expression IF Expression { result = IfNode.new(val[2], Nodes.new([val[0]])) } - | Expression UNLESS Expression { result = IfNode.new(val[2], Nodes.new([val[0]]), nil, :invert) } + | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]])) } + | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, :invert) } ; # Try/catch/finally exception handling blocks. @@ -285,11 +285,11 @@ rule IN Expression "." { result = ForNode.new(val[0], val[6], val[2], val[4]) } | Expression FOR IDENTIFIER IN Expression - IF Expression "." { result = ForNode.new(IfNode.new(val[6], Nodes.new([val[0]])), val[4], val[2]) } + IF Expression "." { result = ForNode.new(IfNode.new(val[6], Expressions.new([val[0]])), val[4], val[2]) } | Expression FOR IDENTIFIER "," IDENTIFIER IN Expression - IF Expression "." { result = ForNode.new(IfNode.new(val[8], Nodes.new([val[0]])), val[6], val[2], val[4]) } + IF Expression "." { result = ForNode.new(IfNode.new(val[8], Expressions.new([val[0]])), val[6], val[2], val[4]) } ; # Switch/Case blocks. @@ -297,7 +297,7 @@ rule SWITCH Expression Then Cases "." { result = val[3].rewrite_condition(val[1]) } | SWITCH Expression Then - Cases ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } + Cases ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } ; # The inner list of cases. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index cfe38204b8..3b09e61779 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -1,5 +1,6 @@ module CoffeeScript + # The abstract base class for all CoffeeScript nodes. class Node # Tabs are two spaces for pretty-printing. TAB = ' ' @@ -13,25 +14,25 @@ def custom_assign?; false; end def compile(indent='', scope=nil, opts={}); end end - # Collection of nodes each one representing an expression. - class Nodes < Node - attr_reader :nodes + # A collection of nodes, each one representing an expression. + class Expressions < Node + attr_reader :expressions def self.wrap(node) - node.is_a?(Nodes) ? node : Nodes.new([node]) + node.is_a?(Expressions) ? node : Expressions.new([node]) end def initialize(nodes) - @nodes = nodes + @expressions = nodes end def <<(node) - @nodes << node + @expressions << node self end def flatten - @nodes.length == 1 ? @nodes.first : self + @expressions.length == 1 ? @expressions.first : self end def begin_compile @@ -46,8 +47,8 @@ def statement? # inner statements (to make expressions out of them). def compile(indent='', scope=nil, opts={}) return begin_compile unless scope - @nodes.map { |n| - if opts[:return] && n == @nodes.last + @expressions.map { |n| + if opts[:return] && n == @expressions.last if n.statement? || n.custom_return? "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" else @@ -458,8 +459,8 @@ def compile(indent, scope, opts={}) end def compile_statement(indent, scope, opts) - if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Nodes.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" - else_part = @else_body ? " else {\n#{Nodes.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : '' + if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Expressions.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" + else_part = @else_body ? " else {\n#{Expressions.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : '' if_part + else_part end From 1395b05d360610775ac0b13b7c990afb6dc29626 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 23:22:02 -0500 Subject: [PATCH 025/303] first major rework of the nodes -- still need more comments and templatish cleanup, but character tagging is all settled --- lib/coffee_script/nodes.rb | 153 ++++++++++++++++++++----------------- 1 file changed, 82 insertions(+), 71 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 3b09e61779..ba91619ee8 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -5,19 +5,40 @@ class Node # Tabs are two spaces for pretty-printing. TAB = ' ' - def flatten; self; end - def line_ending; ';'; end - def statement?; false; end - def custom_return?; false; end - def custom_assign?; false; end + # Tag this node as a statement, meaning that it can't be used directly as + # the result of an expression. + def self.statement + class_eval "def statement?; true; end" + end + + # Tag this node as having a custom return, meaning that instead of returning + # it from the outside, you ask it to return itself, and it obliges. + def self.custom_return + class_eval "def custom_return?; true; end" + end + + # Tag this node as having a custom assignment, meaning that instead of + # assigning it to a variable name from the outside, you pass it the variable + # name and let it take care of it. + def self.custom_assign + class_eval "def custom_assign?; true; end" + end - def compile(indent='', scope=nil, opts={}); end + # Default implementations of the common node methods. + def unwrap; self; end + def line_ending; ';'; end + def statement?; false; end + def custom_return?; false; end + def custom_assign?; false; end + def compile(indent='', scope=nil, opts={}); end end # A collection of nodes, each one representing an expression. class Expressions < Node + statement attr_reader :expressions + # Wrap up a node as an Expressions, unless it already is. def self.wrap(node) node.is_a?(Expressions) ? node : Expressions.new([node]) end @@ -26,27 +47,26 @@ def initialize(nodes) @expressions = nodes end + # Tack an expression onto the end of this node. def <<(node) @expressions << node self end - def flatten + # If this Expressions consists of a single node, pull it back out. + def unwrap @expressions.length == 1 ? @expressions.first : self end - def begin_compile + # If this is the top-level Expressions, wrap everything in a safety closure. + def root_compile "(function(){\n#{compile(TAB, Scope.new)}\n})();" end - def statement? - true - end - - # Fancy to handle pushing down returns recursively to the final lines of - # inner statements (to make expressions out of them). + # The extra fancy is to handle pushing down returns recursively to the + # final lines of inner statements (so as to make expressions out of them). def compile(indent='', scope=nil, opts={}) - return begin_compile unless scope + return root_compile unless scope @expressions.map { |n| if opts[:return] && n == @expressions.last if n.statement? || n.custom_return? @@ -79,19 +99,15 @@ def compile(indent, scope, opts={}) end end + # Try to return your expression, or tell it to return itself. class ReturnNode < Node + statement + custom_return + def initialize(expression) @expression = expression end - def statement? - true - end - - def custom_return? - true - end - def compile(indent, scope, opts={}) return @expression.compile(indent, scope, opts.merge(:return => true)) if @expression.custom_return? compiled = @expression.compile(indent, scope) @@ -99,13 +115,8 @@ def compile(indent, scope, opts={}) end end - # Node of a method call or local variable access, can take any of these forms: - # - # method # this form can also be a local variable - # method(argument1, argument2) - # receiver.method - # receiver.method(argument1, argument2) - # + # Node for a function invocation. Takes care of converting super() calls into + # calls against the prototype's function of the same name. class CallNode < Node LEADING_DOT = /\A\./ @@ -135,6 +146,7 @@ def compile_super(args, indent, scope, opts) end end + # A value, indexed or dotted into or vanilla. class ValueNode < Node attr_reader :last @@ -160,6 +172,7 @@ def compile(indent, scope, opts={}) end end + # A dotted accessor into a part of a value. class AccessorNode def initialize(name) @name = name @@ -170,6 +183,7 @@ def compile(indent, scope, opts={}) end end + # An indexed accessor into a part of an array or object. class IndexNode def initialize(index) @index = index @@ -180,6 +194,9 @@ def compile(indent, scope, opts={}) end end + # An array slice literal. Unlike JavaScript's Array#slice, the second parameter + # specifies the index of the end of the slice (just like the first parameter) + # is the index of the beginning. class SliceNode def initialize(from, to) @from, @to = from, to @@ -190,20 +207,15 @@ def compile(indent, scope, opts={}) end end - # Setting the value of a local variable. + # Setting the value of a local variable, or the value of an object property. class AssignNode < Node + statement + custom_return + def initialize(variable, value, context=nil) @variable, @value, @context = variable, value, context end - def custom_return? - true - end - - def statement? - true - end - def compile(indent, scope, opts={}) name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) last = @variable.respond_to?(:last) ? @variable.last : name @@ -220,7 +232,8 @@ def compile(indent, scope, opts={}) end end - # Simple Arithmetic and logical operations + # Simple Arithmetic and logical operations. Performs some conversion from + # CoffeeScript operations into their JavaScript equivalents. class OpNode < Node CONVERSIONS = { "==" => "===", @@ -260,7 +273,7 @@ def compile_unary(indent, scope) end end - # Method definition. + # A function definition. The only node that creates a new Scope. class CodeNode < Node def initialize(params, body) @params = params @@ -276,6 +289,7 @@ def compile(indent, scope, opts={}) end end + # An object literal. class ObjectNode < Node def initialize(properties = []) @properties = properties @@ -287,6 +301,7 @@ def compile(indent, scope, opts={}) end end + # An array literal. class ArrayNode < Node def initialize(objects=[]) @objects = objects @@ -298,7 +313,11 @@ def compile(indent, scope, opts={}) end end + # A while loop, the only sort of low-level loop exposed by CoffeeScript. From + # it, all other loops can be manufactured. class WhileNode < Node + statement + def initialize(condition, body) @condition, @body = condition, body end @@ -307,16 +326,19 @@ def line_ending '' end - def statement? - true - end - def compile(indent, scope, opts={}) "while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}" end end + # The replacement for the for loop is an array comprehension (that compiles) + # into a for loop. Also acts as an expression, able to return the result + # of the comprehenion. Unlike Python array comprehensions, it's able to pass + # the current index of the loop as a second parameter. class ForNode < Node + statement + custom_return + custom_assign def initialize(body, source, name, index=nil) @body, @source, @name, @index = body, source, name, index @@ -326,18 +348,6 @@ def line_ending '' end - def custom_return? - true - end - - def custom_assign? - true - end - - def statement? - true - end - def compile(indent, scope, opts={}) svar = scope.free_variable ivar = scope.free_variable @@ -367,7 +377,10 @@ def compile(indent, scope, opts={}) end end + # A try/catch/finally block. class TryNode < Node + statement + def initialize(try, error, recovery, finally=nil) @try, @error, @recovery, @finally = try, error, recovery, finally end @@ -376,10 +389,6 @@ def line_ending '' end - def statement? - true - end - def compile(indent, scope, opts={}) catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(indent + TAB, scope, opts)}\n#{indent}}" finally_part = @finally && " finally {\n#{@finally.compile(indent + TAB, scope, opts)}\n#{indent}}" @@ -387,44 +396,46 @@ def compile(indent, scope, opts={}) end end + # Throw an exception. class ThrowNode < Node + statement + def initialize(expression) @expression = expression end - def statement? - true - end - def compile(indent, scope, opts={}) "throw #{@expression.compile(indent, scope)}" end end + # An extra set of parenthesis, supplied by the script source. class ParentheticalNode < Node def initialize(expressions) @expressions = expressions end def compile(indent, scope, opts={}) - compiled = @expressions.flatten.compile(indent, scope) + compiled = @expressions.unwrap.compile(indent, scope) compiled = compiled[0...-1] if compiled[-1..-1] == ';' opts[:no_paren] ? compiled : "(#{compiled})" end end - # "if-else" control structure. Look at this node if you want to implement other control - # structures like while, for, loop, etc. + # If/else statements. Switch/cases get compiled into these. Acts as an + # expression by pushing down requested returns to the expression bodies. + # Single-expression IfNodes are compiled into ternary operators if possible, + # because ternaries are first-class returnable assignable expressions. class IfNode < Node def initialize(condition, body, else_body=nil, tag=nil) @condition = condition - @body = body && body.flatten - @else_body = else_body && else_body.flatten + @body = body && body.unwrap + @else_body = else_body && else_body.unwrap @condition = OpNode.new("!", @condition) if tag == :invert end def <<(else_body) - eb = else_body.flatten + eb = else_body.unwrap @else_body ? @else_body << eb : @else_body = eb self end From 67bb49ed04058eb0e7f1bdcc75a9d20eb07745d3 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 23:34:52 -0500 Subject: [PATCH 026/303] compiling if-else chains into nice flat ones --- lib/coffee_script/nodes.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index ba91619ee8..0ad5f949f5 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -453,10 +453,13 @@ def add_else(expressions) self end + # If the else_body is an IfNode itself, then we've got an if-else chain. def chain? @chain ||= @else_body && @else_body.is_a?(IfNode) end + # The IfNode only compiles into a statement if either of the bodies needs + # to be a statement. def statement? @is_statement ||= (@body.statement? || (@else_body && @else_body.statement?)) end @@ -469,12 +472,17 @@ def compile(indent, scope, opts={}) statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) end + # Compile the IfNode as a regular if-else statement. def compile_statement(indent, scope, opts) if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Expressions.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" - else_part = @else_body ? " else {\n#{Expressions.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" : '' + return if_part unless @else_body + else_part = chain? ? + " else #{@else_body.compile(indent, scope, opts)}" : + " else {\n#{Expressions.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" if_part + else_part end + # Compile the IfNode into a ternary operator. def compile_ternary(indent, scope) if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}" else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null' From a50446a0ca396890ed324d194183c32ab2a0f521 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 17 Dec 2009 23:45:24 -0500 Subject: [PATCH 027/303] finally got the function/object/variable assignment indentation straightened out, I think --- lib/coffee_script/grammar.y | 2 +- lib/coffee_script/nodes.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index a13b9d162c..661a02eed2 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -28,7 +28,7 @@ prechigh right DELETE right RETURN THROW FOR WHILE left UNLESS - nonassoc IF + left IF nonassoc "." preclow diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 0ad5f949f5..75424e0b03 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -220,7 +220,7 @@ def compile(indent, scope, opts={}) name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) last = @variable.respond_to?(:last) ? @variable.last : name opts = opts.merge({:assign => name, :last_assign => last}) - value = @value.compile(indent + TAB, scope, opts) + value = @value.compile(indent, scope, opts) return "#{@variable}: #{value}" if @context == :object return "#{name} = #{value}" if @variable.properties? defined = scope.find(name) @@ -296,7 +296,7 @@ def initialize(properties = []) end def compile(indent, scope, opts={}) - props = @properties.map {|p| indent + TAB + p.compile(indent, scope) }.join(",\n") + props = @properties.map {|p| indent + TAB + p.compile(indent + TAB, scope) }.join(",\n") "{\n#{props}\n#{indent}}" end end From 049358d006877b1609801f9eea13748bd03a1ec7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 00:49:23 -0500 Subject: [PATCH 028/303] after a lot of grammar wrestling, got the if-else chains to parse unambiguously. Now you only need a single period to close chains of any length. --- examples/code.cs | 2 ++ lib/coffee_script/grammar.y | 49 +++++++++++++++++++++++++++---------- lib/coffee_script/nodes.rb | 11 ++++++--- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index 7e732c2d4f..a06dbb7e7d 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -42,6 +42,8 @@ if submarine.shields_up full_speed_ahead() fire_torpedos() +else if submarine.sinking + abandon_ship() else run_away(). diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 661a02eed2..766b90bd3e 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -27,8 +27,7 @@ prechigh right '-=' '+=' '/=' '*=' '||=' '&&=' right DELETE right RETURN THROW FOR WHILE - left UNLESS - left IF + left UNLESS IF ELSE nonassoc "." preclow @@ -238,17 +237,6 @@ rule | ArgList Terminator Expression { result = val[0] << val[2] } ; - # If statements, including post-fix ifs and unlesses. - If: - IF Expression - Then Expressions "." { result = IfNode.new(val[1], val[3]) } - | IF Expression - Then Expressions - ELSE Expressions "." { result = IfNode.new(val[1], val[3], val[5]) } - | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]])) } - | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, :invert) } - ; - # Try/catch/finally exception handling blocks. Try: TRY Expressions CATCH IDENTIFIER @@ -311,6 +299,41 @@ rule CASE Expression Then Expressions { result = IfNode.new(val[1], val[3]) } ; + # All of the following nutso if-else destructuring is to make the + # grammar expand unambiguously. + + # An elsif portion of an if-else block. + ElsIf: + ELSE IF Expression + Then Expressions { result = IfNode.new(val[2], val[4]) } + ; + + # Multiple elsifs can be chained together. + ElsIfs: + ElsIf { result = val[0] } + | ElsIfs ElsIf { result = val[0].add_else(val[1]) } + ; + + # Terminating else bodies are strictly optional. + ElseBody + "." { result = nil } + | ELSE Expressions "." { result = val[1] } + ; + + # All the alternatives for ending an if-else block. + IfEnd: + ElseBody { result = val[0] } + | ElsIfs ElseBody { result = val[0].add_else(val[1]) } + ; + + # The full complement of if blocks, including postfix one-liner ifs and unlesses. + If: + IF Expression + Then Expressions IfEnd { result = IfNode.new(val[1], val[3], val[4]) } + | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]])) } + | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, :invert) } + ; + end ---- header diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 75424e0b03..af38b332f4 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -464,20 +464,25 @@ def statement? @is_statement ||= (@body.statement? || (@else_body && @else_body.statement?)) end + def custom_return? + statement? + end + def line_ending statement? ? '' : ';' end def compile(indent, scope, opts={}) - statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) + opts[:statement] || statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) end - # Compile the IfNode as a regular if-else statement. + # Compile the IfNode as a regular if-else statement. Flattened chains + # force sub-else bodies into statement form. def compile_statement(indent, scope, opts) if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Expressions.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" return if_part unless @else_body else_part = chain? ? - " else #{@else_body.compile(indent, scope, opts)}" : + " else #{@else_body.compile(indent, scope, opts.merge(:statement => true))}" : " else {\n#{Expressions.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" if_part + else_part end From 92adabdddc11bf3b85fed8ad3b69d1d067f77388 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 06:59:06 -0500 Subject: [PATCH 029/303] adding an initial lexer test --- Rakefile | 15 ++++++++++++- TODO | 4 ++-- lib/coffee_script/lexer.rb | 2 +- test/fixtures/each.cs | 11 ++++++++++ test/fixtures/each.tokens | 1 + test/test_helper.rb | 5 +++++ test/unit/test_lexer.rb | 43 ++++++++++++++++++++++++++++++++++++++ test/unit/test_parser.rb | 43 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/each.cs create mode 100644 test/fixtures/each.tokens create mode 100644 test/test_helper.rb create mode 100644 test/unit/test_lexer.rb create mode 100644 test/unit/test_parser.rb diff --git a/Rakefile b/Rakefile index fdc1f4ca33..422ba98904 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,14 @@ +require 'fileutils' +require 'rake/testtask' + +desc "Run all tests" +task :test do + $LOAD_PATH.unshift(File.expand_path('test')) + require 'redgreen' if Gem.available?('redgreen') + require 'test/unit' + Dir['test/*/**/test_*.rb'].each {|test| require test } +end + desc "Recompile the Racc parser (pass -v and -g for verbose debugging)" task :build, :extra_args do |t, args| sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" @@ -16,4 +27,6 @@ namespace :gem do sh "sudo gem uninstall -x coffee-script" end -end \ No newline at end of file +end + +task :default => :test \ No newline at end of file diff --git a/TODO b/TODO index e003525b92..c0c5d13913 100644 --- a/TODO +++ b/TODO @@ -2,9 +2,9 @@ TODO: * Write some tests. -* Code Cleanup. +* Finish the examples. -* Figure out how not to have to close each if statement individually. +* Create the documentation page. * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index d000297a02..f90d1bd550 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -157,8 +157,8 @@ def tag_parameters index = 0 loop do tok = @tokens[index -= 1] + return if !tok || tok[0] != :IDENTIFIER next if tok[0] == ',' - return if tok[0] != :IDENTIFIER tok[0] = :PARAM end end diff --git a/test/fixtures/each.cs b/test/fixtures/each.cs new file mode 100644 index 0000000000..76c0e38521 --- /dev/null +++ b/test/fixtures/each.cs @@ -0,0 +1,11 @@ +# The cornerstone, an each implementation. +# Handles objects implementing forEach, arrays, and raw objects. +_.each: obj, iterator, context => + index: 0 + try + return obj.forEach(iterator, context) if obj.forEach + return iterator.call(context, item, i, obj) for item, i in obj. if _.isArray(obj) or _.isArguments(obj) + iterator.call(context, obj[key], key, obj) for key in _.keys(obj). + catch e + throw e if e aint breaker. + obj. \ No newline at end of file diff --git a/test/fixtures/each.tokens b/test/fixtures/each.tokens new file mode 100644 index 0000000000..079be19257 --- /dev/null +++ b/test/fixtures/each.tokens @@ -0,0 +1 @@ +[["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:IDENTIFIER, "obj"], [",", ","], [:IDENTIFIER, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000..2941a191c4 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,5 @@ +require 'lib/coffee-script' + +class Test::Unit::TestCase + include CoffeeScript +end \ No newline at end of file diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb new file mode 100644 index 0000000000..d989a32436 --- /dev/null +++ b/test/unit/test_lexer.rb @@ -0,0 +1,43 @@ +require 'test_helper' + +class LexerTest < Test::Unit::TestCase + + def setup + @lex = Lexer.new + end + + def test_lexing_an_empty_string + assert @lex.tokenize("") == [] + end + + def test_lexing_basic_assignment + code = "a: 'one'; b: [1, 2]" + assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"], + [:STRING, "'one'"], [";", ";"], [:IDENTIFIER, "b"], [":", ":"], + ["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"]] + end + + def test_lexing_object_literal + code = "{one : 1}" + assert @lex.tokenize(code) == [["{", "{"], [:IDENTIFIER, "one"], [":", ":"], + [:NUMBER, "1"], ["}", "}"]] + end + + def test_lexing_function_definition + code = "x => x * x." + assert @lex.tokenize(code) == [[:PARAM, "x"], ["=>", "=>"], + [:IDENTIFIER, "x"], ["*", "*"], [:IDENTIFIER, "x"], [".", "."]] + end + + def test_lexing_if_statement + code = "clap_your_hands() if happy" + assert @lex.tokenize(code) == [[:IDENTIFIER, "clap_your_hands"], ["(", "("], + [")", ")"], [:IF, "if"], [:IDENTIFIER, "happy"]] + end + + def test_lexing + tokens = @lex.tokenize(File.read('test/fixtures/each.cs')) + assert tokens.inspect == File.read('test/fixtures/each.tokens') + end + +end diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb new file mode 100644 index 0000000000..2c9c8ba74b --- /dev/null +++ b/test/unit/test_parser.rb @@ -0,0 +1,43 @@ +require 'test_helper' + +class ParserTest < Test::Unit::TestCase + + def setup + @par = Parser.new + end + + def test_parsing_an_empty_string + puts @par.parse("").inspect + end + + # def test_lexing_basic_assignment + # code = "a: 'one'; b: [1, 2]" + # assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"], + # [:STRING, "'one'"], [";", ";"], [:IDENTIFIER, "b"], [":", ":"], + # ["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"]] + # end + # + # def test_lexing_object_literal + # code = "{one : 1}" + # assert @lex.tokenize(code) == [["{", "{"], [:IDENTIFIER, "one"], [":", ":"], + # [:NUMBER, "1"], ["}", "}"]] + # end + # + # def test_lexing_function_definition + # code = "x => x * x." + # assert @lex.tokenize(code) == [[:PARAM, "x"], ["=>", "=>"], + # [:IDENTIFIER, "x"], ["*", "*"], [:IDENTIFIER, "x"], [".", "."]] + # end + # + # def test_lexing_if_statement + # code = "clap_your_hands() if happy" + # assert @lex.tokenize(code) == [[:IDENTIFIER, "clap_your_hands"], ["(", "("], + # [")", ")"], [:IF, "if"], [:IDENTIFIER, "happy"]] + # end + # + # def test_lexing + # tokens = @lex.tokenize(File.read('test/fixtures/each.cs')) + # assert tokens.inspect == File.read('test/fixtures/each.tokens') + # end + +end From 669c065dd731c13295b947a0234df6a565b20ea6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 07:11:01 -0500 Subject: [PATCH 030/303] parser test raises some minor improvements (remove unnecessary ValueNode arrays, etc --- lib/coffee_script/grammar.y | 8 ++++---- lib/coffee_script/lexer.rb | 3 ++- lib/coffee_script/nodes.rb | 4 +++- test/fixtures/each.tokens | 2 +- test/unit/test_lexer.rb | 6 +++--- test/unit/test_parser.rb | 17 ++++++++++------- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 766b90bd3e..d01c38d498 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -169,10 +169,10 @@ rule # Expressions that can be treated as values. Value: - IDENTIFIER { result = ValueNode.new(val) } - | Array { result = ValueNode.new(val) } - | Object { result = ValueNode.new(val) } - | Parenthetical { result = ValueNode.new(val) } + IDENTIFIER { result = ValueNode.new(val[0]) } + | Array { result = ValueNode.new(val[0]) } + | Object { result = ValueNode.new(val[0]) } + | Parenthetical { result = ValueNode.new(val[0]) } | Value Accessor { result = val[0] << val[1] } | Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index f90d1bd550..7c351fbe58 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -157,8 +157,9 @@ def tag_parameters index = 0 loop do tok = @tokens[index -= 1] - return if !tok || tok[0] != :IDENTIFIER + return if !tok next if tok[0] == ',' + return if tok[0] != :IDENTIFIER tok[0] = :PARAM end end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index af38b332f4..50df8f0cbb 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -148,7 +148,7 @@ def compile_super(args, indent, scope, opts) # A value, indexed or dotted into or vanilla. class ValueNode < Node - attr_reader :last + attr_reader :name, :properties, :last def initialize(name, properties=[]) @name, @properties = name, properties @@ -212,6 +212,8 @@ class AssignNode < Node statement custom_return + attr_reader :variable, :value, :context + def initialize(variable, value, context=nil) @variable, @value, @context = variable, value, context end diff --git a/test/fixtures/each.tokens b/test/fixtures/each.tokens index 079be19257..bbaa343282 100644 --- a/test/fixtures/each.tokens +++ b/test/fixtures/each.tokens @@ -1 +1 @@ -[["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:IDENTIFIER, "obj"], [",", ","], [:IDENTIFIER, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file +[["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index d989a32436..8c29b89632 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -24,9 +24,9 @@ def test_lexing_object_literal end def test_lexing_function_definition - code = "x => x * x." - assert @lex.tokenize(code) == [[:PARAM, "x"], ["=>", "=>"], - [:IDENTIFIER, "x"], ["*", "*"], [:IDENTIFIER, "x"], [".", "."]] + code = "x, y => x * y." + assert @lex.tokenize(code) == [[:PARAM, "x"], [",", ","], [:PARAM, "y"], + ["=>", "=>"], [:IDENTIFIER, "x"], ["*", "*"], [:IDENTIFIER, "y"], [".", "."]] end def test_lexing_if_statement diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 2c9c8ba74b..e74c6d5605 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -7,15 +7,18 @@ def setup end def test_parsing_an_empty_string - puts @par.parse("").inspect + nodes = @par.parse("") + assert nodes.is_a? Expressions + assert nodes.expressions.empty? end - # def test_lexing_basic_assignment - # code = "a: 'one'; b: [1, 2]" - # assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"], - # [:STRING, "'one'"], [";", ";"], [:IDENTIFIER, "b"], [":", ":"], - # ["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"]] - # end + def test_parsing_a_basic_assignment + nodes = @par.parse("a: 'one'") + assert nodes.expressions.length == 1 + assign = nodes.expressions.first + assert assign.is_a? AssignNode + assert assign.variable.name == 'a' + end # # def test_lexing_object_literal # code = "{one : 1}" From f154ab3d151a5fd042442ea2a1418d608abacfea Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 07:21:59 -0500 Subject: [PATCH 031/303] adding comprehensive attr_readers to the AST for testing --- lib/coffee_script/nodes.rb | 40 ++++++++++++++++++++++++++++++++++---- test/unit/test_parser.rb | 38 ++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 50df8f0cbb..a09a5bb97f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -86,6 +86,8 @@ def compile(indent='', scope=nil, opts={}) class LiteralNode < Node STATEMENTS = ['break', 'continue'] + attr_reader :value + def initialize(value) @value = value end @@ -104,6 +106,8 @@ class ReturnNode < Node statement custom_return + attr_reader :expression + def initialize(expression) @expression = expression end @@ -120,6 +124,8 @@ def compile(indent, scope, opts={}) class CallNode < Node LEADING_DOT = /\A\./ + attr_reader :variable, :arguments + def initialize(variable, arguments=[]) @variable, @arguments = variable, arguments end @@ -148,10 +154,10 @@ def compile_super(args, indent, scope, opts) # A value, indexed or dotted into or vanilla. class ValueNode < Node - attr_reader :name, :properties, :last + attr_reader :literal, :properties, :last - def initialize(name, properties=[]) - @name, @properties = name, properties + def initialize(literal, properties=[]) + @literal, @properties = literal, properties end def <<(other) @@ -164,7 +170,7 @@ def properties? end def compile(indent, scope, opts={}) - parts = [@name, @properties].flatten.map do |v| + parts = [@literal, @properties].flatten.map do |v| v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s end @last = parts.last @@ -174,6 +180,8 @@ def compile(indent, scope, opts={}) # A dotted accessor into a part of a value. class AccessorNode + attr_reader :name + def initialize(name) @name = name end @@ -185,6 +193,8 @@ def compile(indent, scope, opts={}) # An indexed accessor into a part of an array or object. class IndexNode + attr_reader :index + def initialize(index) @index = index end @@ -198,6 +208,8 @@ def compile(indent, scope, opts={}) # specifies the index of the end of the slice (just like the first parameter) # is the index of the beginning. class SliceNode + attr_reader :from, :to + def initialize(from, to) @from, @to = from, to end @@ -248,6 +260,8 @@ class OpNode < Node } CONDITIONALS = ['||=', '&&='] + attr_reader :operator, :first, :second + def initialize(operator, first, second=nil) @first, @second = first, second @operator = CONVERSIONS[operator] || operator @@ -277,6 +291,8 @@ def compile_unary(indent, scope) # A function definition. The only node that creates a new Scope. class CodeNode < Node + attr_reader :params, :body + def initialize(params, body) @params = params @body = body @@ -293,6 +309,8 @@ def compile(indent, scope, opts={}) # An object literal. class ObjectNode < Node + attr_reader :properties + def initialize(properties = []) @properties = properties end @@ -305,6 +323,8 @@ def compile(indent, scope, opts={}) # An array literal. class ArrayNode < Node + attr_reader :objects + def initialize(objects=[]) @objects = objects end @@ -320,6 +340,8 @@ def compile(indent, scope, opts={}) class WhileNode < Node statement + attr_reader :condition, :body + def initialize(condition, body) @condition, @body = condition, body end @@ -342,6 +364,8 @@ class ForNode < Node custom_return custom_assign + attr_reader :body, :source, :name, :index + def initialize(body, source, name, index=nil) @body, @source, @name, @index = body, source, name, index end @@ -383,6 +407,8 @@ def compile(indent, scope, opts={}) class TryNode < Node statement + attr_reader :try, :error, :recovery, :finally + def initialize(try, error, recovery, finally=nil) @try, @error, @recovery, @finally = try, error, recovery, finally end @@ -402,6 +428,8 @@ def compile(indent, scope, opts={}) class ThrowNode < Node statement + attr_reader :expression + def initialize(expression) @expression = expression end @@ -413,6 +441,8 @@ def compile(indent, scope, opts={}) # An extra set of parenthesis, supplied by the script source. class ParentheticalNode < Node + attr_reader :expressions + def initialize(expressions) @expressions = expressions end @@ -429,6 +459,8 @@ def compile(indent, scope, opts={}) # Single-expression IfNodes are compiled into ternary operators if possible, # because ternaries are first-class returnable assignable expressions. class IfNode < Node + attr_reader :condition, :body, :else_body + def initialize(condition, body, else_body=nil, tag=nil) @condition = condition @body = body && body.unwrap diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index e74c6d5605..d338783e70 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -13,25 +13,29 @@ def test_parsing_an_empty_string end def test_parsing_a_basic_assignment - nodes = @par.parse("a: 'one'") - assert nodes.expressions.length == 1 - assign = nodes.expressions.first + nodes = @par.parse("a: 'one'").expressions + assert nodes.length == 1 + assign = nodes.first assert assign.is_a? AssignNode - assert assign.variable.name == 'a' + assert assign.variable.literal == 'a' end - # - # def test_lexing_object_literal - # code = "{one : 1}" - # assert @lex.tokenize(code) == [["{", "{"], [:IDENTIFIER, "one"], [":", ":"], - # [:NUMBER, "1"], ["}", "}"]] - # end - # - # def test_lexing_function_definition - # code = "x => x * x." - # assert @lex.tokenize(code) == [[:PARAM, "x"], ["=>", "=>"], - # [:IDENTIFIER, "x"], ["*", "*"], [:IDENTIFIER, "x"], [".", "."]] - # end - # + + def test_parsing_an_object_literal + nodes = @par.parse("{one : 1 \n two : 2}").expressions + obj = nodes.first.literal + assert obj.is_a? ObjectNode + assert obj.properties.first.variable == "one" + assert obj.properties.last.variable == "two" + end + + def test_parsing_an_function_definition + code = @par.parse("x, y => x * y.").expressions.first + assert code.params == ['x', 'y'] + body = code.body.expressions.first + assert body.is_a? OpNode + assert body.operator == '*' + end + # def test_lexing_if_statement # code = "clap_your_hands() if happy" # assert @lex.tokenize(code) == [[:IDENTIFIER, "clap_your_hands"], ["(", "("], From 6ba5d45cbe332ce8499448b52c9f91bb7cf492dc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 07:28:26 -0500 Subject: [PATCH 032/303] finished the first draft of the parser test --- test/fixtures/each.js | 30 ++++++++++++++++++++++++++++++ test/unit/test_parser.rb | 27 +++++++++++++++++---------- 2 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/each.js diff --git a/test/fixtures/each.js b/test/fixtures/each.js new file mode 100644 index 0000000000..afdafb7e28 --- /dev/null +++ b/test/fixtures/each.js @@ -0,0 +1,30 @@ +(function(){ + _.each = function(obj, iterator, context) { + var index = 0; + try { + if (obj.forEach) { + return obj.forEach(iterator, context); + } + if (_.isArray(obj) || _.isArguments(obj)) { + var a = obj; + var d = []; + for (var b=0, c=a.length; b Date: Fri, 18 Dec 2009 07:40:26 -0500 Subject: [PATCH 033/303] todo to-done --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index c0c5d13913..f65bf01c0d 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ TODO: -* Write some tests. - * Finish the examples. * Create the documentation page. From 253e45fc54863c903ddb37f888977630695ec56c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 08:36:20 -0500 Subject: [PATCH 034/303] adding css for syntax highlighting --- TODO | 3 +- documentation/css/amy.css | 147 +++++++++++++++++++++++++++++++++++++ documentation/css/idle.css | 62 ++++++++++++++++ 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 documentation/css/amy.css create mode 100644 documentation/css/idle.css diff --git a/TODO b/TODO index f65bf01c0d..7fca8cc4e1 100644 --- a/TODO +++ b/TODO @@ -2,7 +2,8 @@ TODO: * Finish the examples. -* Create the documentation page. +* Create the documentation page. (amy, idle) + uv -c . -s coffeescript -t amy --no-lines examples/code.cs > code.html * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/documentation/css/amy.css b/documentation/css/amy.css new file mode 100644 index 0000000000..24c1e5b961 --- /dev/null +++ b/documentation/css/amy.css @@ -0,0 +1,147 @@ +pre.amy .PolymorphicVariants { + color: #60B0FF; + font-style: italic; +} +pre.amy .KeywordDecorator { + color: #D0D0FF; +} +pre.amy .Punctuation { + color: #805080; +} +pre.amy .InheritedClass { +} +pre.amy .InvalidDepricated { + background-color: #CC66FF; + color: #200020; +} +pre.amy .LibraryVariable { +} +pre.amy .TokenReferenceOcamlyacc { + color: #3CB0D0; +} +pre.amy .Storage { + color: #B0FFF0; +} +pre.amy .KeywordOperator { + color: #A0A0FF; +} +pre.amy .CharacterConstant { + color: #666666; +} +pre.amy .line-numbers { + background-color: #800000; + color: #000000; +} +pre.amy .ClassName { + color: #70E080; +} +pre.amy .Int64Constant { + font-style: italic; +} +pre.amy .NonTerminalReferenceOcamlyacc { + color: #C0F0F0; +} +pre.amy .TokenDefinitionOcamlyacc { + color: #3080A0; +} +pre.amy .ClassType { + color: #70E0A0; +} +pre.amy .ControlKeyword { + color: #80A0FF; +} +pre.amy .LineNumberDirectives { + text-decoration: underline; + color: #C080C0; +} +pre.amy .FloatingPointConstant { + text-decoration: underline; +} +pre.amy .Int32Constant { + font-weight: bold; +} +pre.amy .TagName { + color: #009090; +} +pre.amy .ModuleTypeDefinitions { + text-decoration: underline; + color: #B000B0; +} +pre.amy .Integer { + color: #7090B0; +} +pre.amy .Camlp4TempParser { +} +pre.amy .InvalidIllegal { + font-weight: bold; + background-color: #FFFF00; + color: #400080; +} +pre.amy .LibraryConstant { + background-color: #200020; +} +pre.amy .ModuleDefinitions { + color: #B000B0; +} +pre.amy .Variants { + color: #60B0FF; +} +pre.amy .CompilerDirectives { + color: #C080C0; +} +pre.amy .FloatingPointInfixOperator { + text-decoration: underline; +} +pre.amy .BuiltInConstant1 { +} +pre.amy { + background-color: #200020; + color: #D0D0FF; +} +pre.amy .FunctionArgument { + color: #80B0B0; +} +pre.amy .FloatingPointPrefixOperator { + text-decoration: underline; +} +pre.amy .NativeintConstant { + font-weight: bold; +} +pre.amy .BuiltInConstant { + color: #707090; +} +pre.amy .BooleanConstant { + color: #8080A0; +} +pre.amy .LibraryClassType { +} +pre.amy .TagAttribute { +} +pre.amy .Keyword { + color: #A080FF; +} +pre.amy .UserDefinedConstant { +} +pre.amy .String { + color: #999999; +} +pre.amy .Camlp4Code { + background-color: #350060; +} +pre.amy .NonTerminalDefinitionOcamlyacc { + color: #90E0E0; +} +pre.amy .FunctionName { + color: #50A0A0; +} +pre.amy .SupportModules { + color: #A00050; +} +pre.amy .Variable { + color: #008080; +} +pre.amy .Comment { + background-color: #200020; + color: #404080; + font-style: italic; +} diff --git a/documentation/css/idle.css b/documentation/css/idle.css new file mode 100644 index 0000000000..eca8faf960 --- /dev/null +++ b/documentation/css/idle.css @@ -0,0 +1,62 @@ +pre.idle .InheritedClass { +} +pre.idle .TypeName { + color: #21439C; +} +pre.idle .Number { +} +pre.idle .LibraryVariable { + color: #A535AE; +} +pre.idle .Storage { + color: #FF5600; +} +pre.idle .line-numbers { + background-color: #BAD6FD; + color: #000000; +} +pre.idle { + background-color: #FFFFFF; + color: #000000; +} +pre.idle .StringInterpolation { + color: #990000; +} +pre.idle .TagName { +} +pre.idle .LibraryConstant { + color: #A535AE; +} +pre.idle .FunctionArgument { +} +pre.idle .BuiltInConstant { + color: #A535AE; +} +pre.idle .Invalid { + background-color: #990000; + color: #FFFFFF; +} +pre.idle .LibraryClassType { + color: #A535AE; +} +pre.idle .LibraryFunction { + color: #A535AE; +} +pre.idle .TagAttribute { +} +pre.idle .Keyword { + color: #FF5600; +} +pre.idle .UserDefinedConstant { +} +pre.idle .String { + color: #00A33F; +} +pre.idle .FunctionName { + color: #21439C; +} +pre.idle .Variable { +} +pre.idle .Comment { + color: #919191; +} From 2f75854a61f2ed1349767b4db504c4f9147bb6be Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 09:55:31 -0500 Subject: [PATCH 035/303] little fixes more examples --- Rakefile | 7 +++ examples/poignant.cs | 50 ++++++++++++++++++- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 2 +- 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 422ba98904..432a9d164f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,4 @@ +require 'erb' require 'fileutils' require 'rake/testtask' @@ -14,6 +15,12 @@ task :build, :extra_args do |t, args| sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" end +desc "Build the documentation page" +task :doc do + rendered = ERB.new(File.read('documentation/index.html.erb')).result(binding) + File.open('index.html', 'w+') {|f| f.write(rendered) } +end + namespace :gem do desc 'Build and install the coffee-script gem' diff --git a/examples/poignant.cs b/examples/poignant.cs index d9e705b027..7c9b05f5da 100644 --- a/examples/poignant.cs +++ b/examples/poignant.cs @@ -102,4 +102,52 @@ puts( "[Your enemy hit with " + enemy_hit + "points of damage!]" ) this.hit( enemy_hit ).. -} \ No newline at end of file +} + + + +# # Get evil idea and swap in code words +# print "Enter your new idea: " +# idea = gets +# code_words.each do |real, code| +# idea.gsub!( real, code ) +# end +# +# # Save the jibberish to a new file +# print "File encoded. Please enter a name for this idea: " +# idea_name = gets.strip +# File::open( "idea-" + idea_name + ".txt", "w" ) do |f| +# f << idea +# end + +# Get evil idea and swap in code words +print("Enter your new idea: ") +idea: gets() +code_words.each( real, code => idea.replace(real, code). ) + +# Save the jibberish to a new file +print("File encoded. Please enter a name for this idea: ") +idea_name: gets().strip() +File.open("idea-" + idea_name + '.txt', 'w', file => file.write(idea). ) + + + +# def wipe_mutterings_from( sentence ) +# unless sentence.respond_to? :include? +# raise ArgumentError, +# "cannot wipe mutterings from a #{ sentence.class }" +# end +# while sentence.include? '(' +# open = sentence.index( '(' ) +# close = sentence.index( ')', open ) +# sentence[open..close] = '' if close +# end +# end + +wipe_mutterings_from: sentence => + throw new Error("cannot wipe mutterings") unless sentence.indexOf + while sentence.indexOf('(') >= 0 + open: sentence.indexOf('(') - 1 + close: sentence.indexOf(')') + 1 + sentence: sentence[0, open] + sentence[close, sentence.length]. + sentence. \ No newline at end of file diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 82c83b7320..b788c281ce 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -61,7 +61,7 @@ comment match stuff like: a => … match - ([a-zA-Z_?.$]*)\s*(=>) + ([a-zA-Z_?., $]*)\s*(=>) name meta.inline.function.cs diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 7c351fbe58..78208a1c52 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -20,7 +20,7 @@ class Lexer # Token matching regexes. 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 + STRING = /\A(""|''|"(.*?)[^\\]"|'(.*?)[^\\]')/m JS = /\A(`(.*?)`)/ OPERATOR = /\A([+\*&|\/\-%=<>]+)/ WHITESPACE = /\A([ \t\r]+)/ diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index a09a5bb97f..071324d8c2 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -300,7 +300,7 @@ def initialize(params, body) def compile(indent, scope, opts={}) scope = Scope.new(scope) - @params.each {|id| scope.find(id) } + @params.each {|id| scope.find(id.to_s) } opts = opts.merge(:return => true) code = @body.compile(indent + TAB, scope, opts) "function(#{@params.join(', ')}) {\n#{code}\n#{indent}}" From 6f81ac3684cca27b7bd88eef0a4b324ed1278e27 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 22:30:09 -0500 Subject: [PATCH 036/303] patched up array comprehensions somewhat. Parens are still a necessary evil, and there's still probably plenty of edge cases --- TODO | 7 +++++ examples/code.cs | 4 +-- lib/coffee_script/grammar.y | 30 ++++++++++++------- lib/coffee_script/nodes.rb | 59 +++++++++++++++++++++++++++++-------- test/unit/test_parser.rb | 10 ++++++- 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/TODO b/TODO index 7fca8cc4e1..7761c3a3c6 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,13 @@ TODO: * Finish the examples. +* Write a test suite that checks the JS evaluation. + +* Figure out a generic way to transform statements into expressions, and + use it recursively for returns and assigns on whiles, fors, ifs, etc. + +* If we manage to get array comprehensions working ... object comprehensions? + * Create the documentation page. (amy, idle) uv -c . -s coffeescript -t amy --no-lines examples/code.cs > code.html diff --git a/examples/code.cs b/examples/code.cs index a06dbb7e7d..4504aba9ae 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -109,9 +109,9 @@ b: 20 # Array comprehensions. -supper: food.capitalize() for food in ['toast', 'cheese', 'wine']. +supper: [food.capitalize() for food in ['toast', 'cheese', 'wine']] -drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i). +[drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i)] # Switch statements ("else" serves as a default). switch day diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index d01c38d498..d2999541ec 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -26,14 +26,14 @@ prechigh left ':' right '-=' '+=' '/=' '*=' '||=' '&&=' right DELETE - right RETURN THROW FOR WHILE + right RETURN THROW FOR IN WHILE left UNLESS IF ELSE nonassoc "." preclow -# We expect 2 shift/reduce errors for optional syntax. +# We expect 4 shift/reduce errors for optional syntax. # There used to be 252 -- greatly improved. -expect 2 +expect 4 rule @@ -54,12 +54,22 @@ rule # All types of expressions in our language. Expression: + PureExpression + | Statement + ; + + # The parts that are natural JavaScript expressions. + PureExpression: Literal | Value | Call - | Assign | Code | Operation + ; + + # We have to take extra care to convert these statements into expressions. + Statement: + Assign | If | Try | Throw @@ -267,17 +277,17 @@ rule # Array comprehensions, including guard and current index. For: Expression FOR IDENTIFIER - IN Expression "." { result = ForNode.new(val[0], val[4], val[2]) } + IN PureExpression "." { result = ForNode.new(val[0], val[4], val[2], nil) } | Expression FOR IDENTIFIER "," IDENTIFIER - IN Expression "." { result = ForNode.new(val[0], val[6], val[2], val[4]) } + IN PureExpression "." { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) } | Expression FOR IDENTIFIER - IN Expression - IF Expression "." { result = ForNode.new(IfNode.new(val[6], Expressions.new([val[0]])), val[4], val[2]) } + IN PureExpression + IF Expression "." { result = ForNode.new(val[0], val[4], val[2], val[6]) } | Expression FOR IDENTIFIER "," IDENTIFIER - IN Expression - IF Expression "." { result = ForNode.new(IfNode.new(val[8], Expressions.new([val[0]])), val[6], val[2], val[4]) } + IN PureExpression + IF Expression "." { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) } ; # Switch/Case blocks. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 071324d8c2..66c8f1d3ba 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -169,9 +169,21 @@ def properties? return !@properties.empty? end + def statement? + @literal.is_a?(Node) && @literal.statement? && !properties? + end + + def custom_assign? + @literal.is_a?(Node) && @literal.custom_assign? && !properties? + end + + def custom_return? + @literal.is_a?(Node) && @literal.custom_return? && !properties? + end + def compile(indent, scope, opts={}) parts = [@literal, @properties].flatten.map do |v| - v.respond_to?(:compile) ? v.compile(indent, scope) : v.to_s + v.respond_to?(:compile) ? v.compile(indent, scope, opts) : v.to_s end @last = parts.last parts.join('') @@ -364,10 +376,10 @@ class ForNode < Node custom_return custom_assign - attr_reader :body, :source, :name, :index + attr_reader :body, :source, :name, :filter, :index - def initialize(body, source, name, index=nil) - @body, @source, @name, @index = body, source, name, index + def initialize(body, source, name, filter, index=nil) + @body, @source, @name, @filter, @index = body, source, name, filter, index end def line_ending @@ -388,18 +400,28 @@ def compile(indent, scope, opts={}) set_result = '' save_result = '' return_result = '' + body = @body + suffix = ';' if opts[:return] || opts[:assign] rvar = scope.free_variable set_result = "var #{rvar} = [];\n#{indent}" - save_result = "#{rvar}[#{ivar}] = " + save_result += "#{rvar}[#{ivar}] = " return_result = rvar return_result = "#{opts[:assign]} = #{return_result}" if opts[:assign] return_result = "return #{return_result}" if opts[:return] return_result = "\n#{indent}#{return_result}" + if @filter + body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) + body = @filter ? IfNode.new(@filter, body, nil, :statement) : body + save_result = '' + suffix = '' + end + elsif @filter + body = IfNode.new(@filter, @body) end - body = @body.compile(indent + TAB, scope) - "#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body};\n#{indent}}#{return_result}" + body = body.compile(indent + TAB, scope) + "#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body}#{suffix}\n#{indent}}#{return_result}" end end @@ -444,13 +466,25 @@ class ParentheticalNode < Node attr_reader :expressions def initialize(expressions) - @expressions = expressions + @expressions = expressions.unwrap + end + + def statement? + @expressions.statement? + end + + def custom_assign? + @expressions.custom_assign? + end + + def custom_return? + @expressions.custom_return? end def compile(indent, scope, opts={}) - compiled = @expressions.unwrap.compile(indent, scope) + compiled = @expressions.compile(indent, scope, opts) compiled = compiled[0...-1] if compiled[-1..-1] == ';' - opts[:no_paren] ? compiled : "(#{compiled})" + opts[:no_paren] || statement? ? compiled : "(#{compiled})" end end @@ -465,7 +499,8 @@ def initialize(condition, body, else_body=nil, tag=nil) @condition = condition @body = body && body.unwrap @else_body = else_body && else_body.unwrap - @condition = OpNode.new("!", @condition) if tag == :invert + @tag = tag + @condition = OpNode.new("!", @condition) if @tag == :invert end def <<(else_body) @@ -495,7 +530,7 @@ def chain? # The IfNode only compiles into a statement if either of the bodies needs # to be a statement. def statement? - @is_statement ||= (@body.statement? || (@else_body && @else_body.statement?)) + @is_statement ||= ((@tag == :statement) || @body.statement? || (@else_body && @else_body.statement?)) end def custom_return? diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 6482550624..d7c1f457ee 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -36,7 +36,7 @@ def test_parsing_an_function_definition assert body.operator == '*' end - def test_lexing_if_statement + def test_parsing_if_statement the_if = @par.parse("clap_your_hands() if happy").expressions.first assert the_if.is_a? IfNode assert the_if.condition.literal == 'happy' @@ -44,6 +44,14 @@ def test_lexing_if_statement assert the_if.body.variable.literal == 'clap_your_hands' end + def test_parsing_array_comprehension + nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] if i % 2 is 0.").expressions + assert nodes.first.is_a? ForNode + assert nodes.first.body.literal == 'i' + assert nodes.first.filter.operator == '===' + assert nodes.first.source.literal.objects.last.value == "5" + end + def test_parsing nodes = @par.parse(File.read('test/fixtures/each.cs')) assign = nodes.expressions.first From ad3b887df4d054332f56d6b3e5c47b67c7ca66f5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 18 Dec 2009 23:13:59 -0500 Subject: [PATCH 037/303] lots of tweaks make the tests pass again --- examples/code.cs | 4 ++-- lib/coffee_script/grammar.y | 11 ++++++----- lib/coffee_script/nodes.rb | 18 +++++++++++------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index 4504aba9ae..a06dbb7e7d 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -109,9 +109,9 @@ b: 20 # Array comprehensions. -supper: [food.capitalize() for food in ['toast', 'cheese', 'wine']] +supper: food.capitalize() for food in ['toast', 'cheese', 'wine']. -[drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i)] +drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i). # Switch statements ("else" serves as a default). switch day diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index d2999541ec..b7cdfc21ed 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -23,12 +23,13 @@ prechigh left '<=' '<' '>' '>=' right '==' '!=' IS AINT left '&&' '||' AND OR - left ':' right '-=' '+=' '/=' '*=' '||=' '&&=' right DELETE - right RETURN THROW FOR IN WHILE + left "." + right THROW FOR IN WHILE left UNLESS IF ELSE - nonassoc "." + left ":" + right RETURN preclow # We expect 4 shift/reduce errors for optional syntax. @@ -340,8 +341,8 @@ rule If: IF Expression Then Expressions IfEnd { result = IfNode.new(val[1], val[3], val[4]) } - | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]])) } - | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, :invert) } + | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } + | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } ; end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 66c8f1d3ba..026d810fe0 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -112,6 +112,10 @@ def initialize(expression) @expression = expression end + def line_ending + @expression.custom_return? ? '' : ';' + end + def compile(indent, scope, opts={}) return @expression.compile(indent, scope, opts.merge(:return => true)) if @expression.custom_return? compiled = @expression.compile(indent, scope) @@ -407,12 +411,12 @@ def compile(indent, scope, opts={}) set_result = "var #{rvar} = [];\n#{indent}" save_result += "#{rvar}[#{ivar}] = " return_result = rvar - return_result = "#{opts[:assign]} = #{return_result}" if opts[:assign] - return_result = "return #{return_result}" if opts[:return] + return_result = "#{opts[:assign]} = #{return_result};" if opts[:assign] + return_result = "return #{return_result};" if opts[:return] return_result = "\n#{indent}#{return_result}" if @filter body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) - body = @filter ? IfNode.new(@filter, body, nil, :statement) : body + body = IfNode.new(@filter, body, nil, :statement) save_result = '' suffix = '' end @@ -495,12 +499,12 @@ def compile(indent, scope, opts={}) class IfNode < Node attr_reader :condition, :body, :else_body - def initialize(condition, body, else_body=nil, tag=nil) + def initialize(condition, body, else_body=nil, tags={}) @condition = condition @body = body && body.unwrap @else_body = else_body && else_body.unwrap - @tag = tag - @condition = OpNode.new("!", @condition) if @tag == :invert + @tags = tags + @condition = OpNode.new("!", @condition) if @tags[:invert] end def <<(else_body) @@ -530,7 +534,7 @@ def chain? # The IfNode only compiles into a statement if either of the bodies needs # to be a statement. def statement? - @is_statement ||= ((@tag == :statement) || @body.statement? || (@else_body && @else_body.statement?)) + @is_statement ||= !!(@tags[:statement] || @body.statement? || (@else_body && @else_body.statement?)) end def custom_return? From b2e6a34d4097e7b958c72a049969205ddcb7fcae Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 19 Dec 2009 00:33:34 -0500 Subject: [PATCH 038/303] more little fixes, lots of subtle things, added a verbose logging mode --- lib/coffee_script/nodes.rb | 103 +++++++++++++++++++++++-------------- lib/coffee_script/scope.rb | 11 ++-- lib/coffee_script/value.rb | 4 ++ 3 files changed, 77 insertions(+), 41 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 026d810fe0..22633bf39c 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -24,6 +24,11 @@ def self.custom_assign class_eval "def custom_assign?; true; end" end + def write(code) + puts "#{self.class.to_s}:\n#{code}\n\n" if ENV['VERBOSE'] + code + end + # Default implementations of the common node methods. def unwrap; self; end def line_ending; ';'; end @@ -67,17 +72,26 @@ def root_compile # final lines of inner statements (so as to make expressions out of them). def compile(indent='', scope=nil, opts={}) return root_compile unless scope - @expressions.map { |n| - if opts[:return] && n == @expressions.last - if n.statement? || n.custom_return? - "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" - else - "#{indent}return #{n.compile(indent, scope, opts)}#{n.line_ending}" + code = @expressions.map { |n| + if n == @expressions.last && (opts[:return] || opts[:assign]) + if opts[:return] + if n.statement? || n.custom_return? + "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" + else + "#{indent}return #{n.compile(indent, scope, opts)}#{n.line_ending}" + end + elsif opts[:assign] + if n.statement? || n.custom_assign? + "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" + else + "#{indent}#{AssignNode.new(ValueNode.new(LiteralNode.new(opts[:assign])), n).compile(indent, scope, opts)};" + end end else "#{indent}#{n.compile(indent, scope)}#{n.line_ending}" end }.join("\n") + write(code) end end @@ -97,7 +111,8 @@ def statement? end def compile(indent, scope, opts={}) - @value.to_s + code = @value.to_s + write(code) end end @@ -117,9 +132,9 @@ def line_ending end def compile(indent, scope, opts={}) - return @expression.compile(indent, scope, opts.merge(:return => true)) if @expression.custom_return? + return write(@expression.compile(indent, scope, opts.merge(:return => true))) if @expression.custom_return? compiled = @expression.compile(indent, scope) - @expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}" + write(@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}") end end @@ -145,9 +160,9 @@ def super? def compile(indent, scope, opts={}) args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ') - return compile_super(args, indent, scope, opts) if super? + return write(compile_super(args, indent, scope, opts)) if super? prefix = @new ? "new " : '' - "#{prefix}#{@variable.compile(indent, scope)}(#{args})" + write("#{prefix}#{@variable.compile(indent, scope)}(#{args})") end def compile_super(args, indent, scope, opts) @@ -190,12 +205,12 @@ def compile(indent, scope, opts={}) v.respond_to?(:compile) ? v.compile(indent, scope, opts) : v.to_s end @last = parts.last - parts.join('') + write(parts.join('')) end end # A dotted accessor into a part of a value. - class AccessorNode + class AccessorNode < Node attr_reader :name def initialize(name) @@ -203,12 +218,12 @@ def initialize(name) end def compile(indent, scope, opts={}) - ".#{@name}" + write(".#{@name}") end end # An indexed accessor into a part of an array or object. - class IndexNode + class IndexNode < Node attr_reader :index def initialize(index) @@ -216,14 +231,14 @@ def initialize(index) end def compile(indent, scope, opts={}) - "[#{@index.compile(indent, scope)}]" + write("[#{@index.compile(indent, scope)}]") end end # An array slice literal. Unlike JavaScript's Array#slice, the second parameter # specifies the index of the end of the slice (just like the first parameter) # is the index of the beginning. - class SliceNode + class SliceNode < Node attr_reader :from, :to def initialize(from, to) @@ -231,12 +246,14 @@ def initialize(from, to) end def compile(indent, scope, opts={}) - ".slice(#{@from.compile(indent, scope, opts)}, #{@to.compile(indent, scope, opts)} + 1)" + write(".slice(#{@from.compile(indent, scope, opts)}, #{@to.compile(indent, scope, opts)} + 1)") end end # Setting the value of a local variable, or the value of an object property. class AssignNode < Node + LEADING_VAR = /\Avar\s+/ + statement custom_return @@ -246,19 +263,23 @@ def initialize(variable, value, context=nil) @variable, @value, @context = variable, value, context end + def line_ending + @value.custom_assign? ? '' : ';' + end + def compile(indent, scope, opts={}) - name = @variable.compile(indent, scope) if @variable.respond_to?(:compile) + name = @variable.respond_to?(:compile) ? @variable.compile(indent, scope) : @variable last = @variable.respond_to?(:last) ? @variable.last : name opts = opts.merge({:assign => name, :last_assign => last}) - value = @value.compile(indent, scope, opts) - return "#{@variable}: #{value}" if @context == :object - return "#{name} = #{value}" if @variable.properties? + return write("#{@variable}: #{@value.compile(indent, scope, opts)}") if @context == :object + return write("#{name} = #{@value.compile(indent, scope, opts)}") if @variable.properties? defined = scope.find(name) postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' def_part = defined ? "" : "var #{name};\n#{indent}" - return def_part + @value.compile(indent, scope, opts) if @value.custom_assign? + return write(def_part + @value.compile(indent, scope, opts)) if @value.custom_assign? def_part = defined ? name : "var #{name}" - "#{def_part} = #{@value.compile(indent, scope, opts)}#{postfix}" + val_part = @value.compile(indent, scope, opts).sub(LEADING_VAR, '') + write("#{def_part} = #{val_part}#{postfix}") end end @@ -288,9 +309,9 @@ def unary? end def compile(indent, scope, opts={}) - return compile_conditional(indent, scope) if CONDITIONALS.include?(@operator) - return compile_unary(indent, scope) if unary? - "#{@first.compile(indent, scope)} #{@operator} #{@second.compile(indent, scope)}" + return write(compile_conditional(indent, scope)) if CONDITIONALS.include?(@operator) + return write(compile_unary(indent, scope)) if unary? + write("#{@first.compile(indent, scope)} #{@operator} #{@second.compile(indent, scope)}") end def compile_conditional(indent, scope) @@ -319,7 +340,7 @@ def compile(indent, scope, opts={}) @params.each {|id| scope.find(id.to_s) } opts = opts.merge(:return => true) code = @body.compile(indent + TAB, scope, opts) - "function(#{@params.join(', ')}) {\n#{code}\n#{indent}}" + write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end end @@ -333,7 +354,7 @@ def initialize(properties = []) def compile(indent, scope, opts={}) props = @properties.map {|p| indent + TAB + p.compile(indent + TAB, scope) }.join(",\n") - "{\n#{props}\n#{indent}}" + write("{\n#{props}\n#{indent}}") end end @@ -347,7 +368,7 @@ def initialize(objects=[]) def compile(indent, scope, opts={}) objects = @objects.map {|o| o.compile(indent, scope) }.join(', ') - "[#{objects}]" + write("[#{objects}]") end end @@ -367,7 +388,7 @@ def line_ending end def compile(indent, scope, opts={}) - "while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}" + write("while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}") end end @@ -391,11 +412,13 @@ def line_ending end def compile(indent, scope, opts={}) + name_found = scope.find(@name) + index_found = @index && scope.find(@index) svar = scope.free_variable ivar = scope.free_variable lvar = scope.free_variable - name_part = scope.find(@name) ? @name : "var #{@name}" - index_name = @index ? (scope.find(@index) ? @index : "var #{@index}") : nil + name_part = name_found ? @name : "var #{@name}" + index_name = @index ? (index_found ? @index : "var #{@index}") : nil source_part = "var #{svar} = #{@source.compile(indent, scope)};" for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" var_part = "\n#{indent + TAB}#{name_part} = #{svar}[#{ivar}];\n" @@ -425,7 +448,7 @@ def compile(indent, scope, opts={}) end body = body.compile(indent + TAB, scope) - "#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body}#{suffix}\n#{indent}}#{return_result}" + write("#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body}#{suffix}\n#{indent}}#{return_result}") end end @@ -446,7 +469,7 @@ def line_ending def compile(indent, scope, opts={}) catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(indent + TAB, scope, opts)}\n#{indent}}" finally_part = @finally && " finally {\n#{@finally.compile(indent + TAB, scope, opts)}\n#{indent}}" - "try {\n#{@try.compile(indent + TAB, scope, opts)}\n#{indent}}#{catch_part}#{finally_part}" + write("try {\n#{@try.compile(indent + TAB, scope, opts)}\n#{indent}}#{catch_part}#{finally_part}") end end @@ -461,7 +484,7 @@ def initialize(expression) end def compile(indent, scope, opts={}) - "throw #{@expression.compile(indent, scope)}" + write("throw #{@expression.compile(indent, scope)}") end end @@ -488,7 +511,7 @@ def custom_return? def compile(indent, scope, opts={}) compiled = @expressions.compile(indent, scope, opts) compiled = compiled[0...-1] if compiled[-1..-1] == ';' - opts[:no_paren] || statement? ? compiled : "(#{compiled})" + write(opts[:no_paren] || statement? ? compiled : "(#{compiled})") end end @@ -541,12 +564,16 @@ def custom_return? statement? end + def custom_assign? + statement? + end + def line_ending statement? ? '' : ';' end def compile(indent, scope, opts={}) - opts[:statement] || statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope) + write(opts[:statement] || statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope)) end # Compile the IfNode as a regular if-else statement. Flattened chains diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index 22d3698ee5..743552ea6c 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -18,20 +18,25 @@ def initialize(parent=nil) def find(name, remote=false) found = check(name, remote) return found if found || remote - @variables[name] = true + @variables[name.to_sym] = true found end # Just check to see if a variable has already been declared. def check(name, remote=false) - return true if @variables[name] + return true if @variables[name.to_sym] @parent && @parent.find(name, true) end + # You can reset a found variable on the immediate scope. + def reset(name) + @variables[name.to_sym] = false + end + # Find an available, short, name for a compiler-generated variable. def free_variable @temp_variable.succ! while check(@temp_variable) - @variables[@temp_variable] = true + @variables[@temp_variable.to_sym] = true @temp_variable.dup end diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb index 2f0cae3c46..c724b5ca00 100644 --- a/lib/coffee_script/value.rb +++ b/lib/coffee_script/value.rb @@ -14,6 +14,10 @@ def to_str end alias_method :to_s, :to_str + def to_sym + to_str.to_sym + end + def inspect @value.inspect end From 0b2e7f1e592e0ab61a8cb23a859b174a59823676 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 19 Dec 2009 00:37:54 -0500 Subject: [PATCH 039/303] added the verbose option to the CLI --- lib/coffee_script/command_line.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 3f88655aa3..bee6e07967 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -138,11 +138,14 @@ def parse_options opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t| @options[:tokens] = true end + opts.on('-v', '--verbose', 'print at every step of code generation') do |v| + ENV['VERBOSE'] = 'true' + end opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i| install_bundle exit end - opts.on_tail('-v', '--version', 'display coffee-script version') do + opts.on_tail('--version', 'display coffee-script version') do puts "coffee-script version #{CoffeeScript::VERSION}" exit end From 9ba1ffde215a73b9e59eaeffe6f53780d64cd8de Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 19 Dec 2009 00:45:36 -0500 Subject: [PATCH 040/303] making the each fixture a little more like underscore, and avoiding passing assignment into functions from the outside --- lib/coffee_script/nodes.rb | 3 ++- test/fixtures/each.cs | 9 ++++++--- test/fixtures/each.js | 20 +++++++++----------- test/fixtures/each.tokens | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 22633bf39c..7825dbca1a 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -338,7 +338,8 @@ def initialize(params, body) def compile(indent, scope, opts={}) scope = Scope.new(scope) @params.each {|id| scope.find(id.to_s) } - opts = opts.merge(:return => true) + opts[:return] = true + opts.delete(:assign) code = @body.compile(indent + TAB, scope, opts) write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end diff --git a/test/fixtures/each.cs b/test/fixtures/each.cs index 76c0e38521..5a345f12f5 100644 --- a/test/fixtures/each.cs +++ b/test/fixtures/each.cs @@ -3,9 +3,12 @@ _.each: obj, iterator, context => index: 0 try - return obj.forEach(iterator, context) if obj.forEach - return iterator.call(context, item, i, obj) for item, i in obj. if _.isArray(obj) or _.isArguments(obj) - iterator.call(context, obj[key], key, obj) for key in _.keys(obj). + if obj.forEach + obj.forEach(iterator, context) + else if _.isArray(obj) or _.isArguments(obj) + iterator.call(context, item, i, obj) for item, i in obj. + else + iterator.call(context, obj[key], key, obj) for key in _.keys(obj).. catch e throw e if e aint breaker. obj. \ No newline at end of file diff --git a/test/fixtures/each.js b/test/fixtures/each.js index afdafb7e28..56f0fabcd3 100644 --- a/test/fixtures/each.js +++ b/test/fixtures/each.js @@ -3,22 +3,20 @@ var index = 0; try { if (obj.forEach) { - return obj.forEach(iterator, context); - } - if (_.isArray(obj) || _.isArguments(obj)) { + obj.forEach(iterator, context); + } else if (_.isArray(obj) || _.isArguments(obj)) { var a = obj; - var d = []; for (var b=0, c=a.length; b", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:RETURN, "return"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file +[["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file From 93bdfcb72a7dae0606539c75a53a30f726516afe Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 19 Dec 2009 22:55:58 -0500 Subject: [PATCH 041/303] added full complement of bitwise operators --- Rakefile | 12 +++++-- TODO | 6 ++-- documentation/css/docs.css | 59 +++++++++++++++++++++++++++++++ documentation/index.html.erb | 23 ++++++++++++ index.html | 23 ++++++++++++ lib/coffee_script/command_line.rb | 9 +++++ lib/coffee_script/grammar.y | 13 ++++++- lib/coffee_script/nodes.rb | 6 ++-- 8 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 documentation/css/docs.css create mode 100644 documentation/index.html.erb create mode 100644 index.html diff --git a/Rakefile b/Rakefile index 432a9d164f..0616f0fccf 100644 --- a/Rakefile +++ b/Rakefile @@ -17,8 +17,16 @@ end desc "Build the documentation page" task :doc do - rendered = ERB.new(File.read('documentation/index.html.erb')).result(binding) - File.open('index.html', 'w+') {|f| f.write(rendered) } + source = 'documentation/index.html.erb' + loop do + mtime = File.stat(source).mtime + if !@mtime || mtime > @mtime + rendered = ERB.new(File.read(source)).result(binding) + File.open('index.html', 'w+') {|f| f.write(rendered) } + end + @mtime = mtime + sleep 1 + end end namespace :gem do diff --git a/TODO b/TODO index 7761c3a3c6..7ec3b61ee4 100644 --- a/TODO +++ b/TODO @@ -7,10 +7,12 @@ TODO: * Figure out a generic way to transform statements into expressions, and use it recursively for returns and assigns on whiles, fors, ifs, etc. -* If we manage to get array comprehensions working ... object comprehensions? - * Create the documentation page. (amy, idle) uv -c . -s coffeescript -t amy --no-lines examples/code.cs > code.html + +* Object comprehensions would be easy to add to array comprehensions -- if + we knew that the variable in question is, indeed, an object. Check for + length? Special syntax to tag it? * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/documentation/css/docs.css b/documentation/css/docs.css new file mode 100644 index 0000000000..f50ad3b186 --- /dev/null +++ b/documentation/css/docs.css @@ -0,0 +1,59 @@ +body { + font-size: 16px; + line-height: 24px; + background: #f0f0e5; + color: #252519; + font-family: "Palatino Linotype", "Book Antiqua", Palatino, FreeSerif, serif; +} +div.container { + width: 720px; + margin: 50px 0 50px 50px; +} +p { + width: 550px; +} + #documentation p { + margin-bottom: 4px; + } +a, a:visited { + padding: 0 2px; + text-decoration: none; + background: #dadaba; + color: #252519; +} +a:active, a:hover { + color: #000; + background: #f0c095; +} +h1, h2, h3, h4, h5, h6 { + margin-top: 40px; +} +b.header { + font-size: 18px; +} +span.alias { + font-size: 14px; + font-style: italic; + margin-left: 20px; +} +table, tr, td { + margin: 0; padding: 0; +} + td { + padding: 2px 12px 2px 0; + } +code, pre, tt { + font-family: Monaco, Consolas, "Lucida Console", monospace; + font-size: 12px; + line-height: 18px; + color: #555529; +} + code { + margin-left: 20px; + } + pre { + font-size: 12px; + padding: 2px 0 2px 12px; + border-left: 6px solid #aaaa99; + margin: 0px 0 30px; + } \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb new file mode 100644 index 0000000000..cb032b44f3 --- /dev/null +++ b/documentation/index.html.erb @@ -0,0 +1,23 @@ + + + + + CoffeeScript + + + + +
+ +

CoffeeScript

+ +

+ CoffeeScript is a little language that compiles into JavaScript. Think + of it as JavaScript's simpleminded kid brother — the same genes, + the same accent, but another kind of way of doing things. +

+ +
+ + + diff --git a/index.html b/index.html new file mode 100644 index 0000000000..cb032b44f3 --- /dev/null +++ b/index.html @@ -0,0 +1,23 @@ + + + + + CoffeeScript + + + + +
+ +

CoffeeScript

+ +

+ CoffeeScript is a little language that compiles into JavaScript. Think + of it as JavaScript's simpleminded kid brother — the same genes, + the same accent, but another kind of way of doing things. +

+ +
+ + + diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index bee6e07967..105292dd5f 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -23,6 +23,7 @@ class CommandLine def initialize @mtimes = {} parse_options + return eval_scriptlet if @options[:eval] check_sources @sources.each {|source| compile_javascript(source) } watch_coffee_scripts if @options[:watch] @@ -88,6 +89,11 @@ def lint(js) stdout.close and stderr.close end + # Eval a little piece of CoffeeScript directly from the command line. + def eval_scriptlet + puts CoffeeScript.compile(@sources.join(' ')) + end + # Print the tokens that the lexer generates from a source script. def tokens(source) puts Lexer.new.tokenize(File.read(source)).inspect @@ -135,6 +141,9 @@ def parse_options opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l| @options[:lint] = true end + opts.on('-e', '--eval', 'eval a little scriptlet directly from the cli') do |e| + @options[:eval] = true + end opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t| @options[:tokens] = true end diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index b7cdfc21ed..39be9602c4 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -17,9 +17,11 @@ token JS # Declare order of operations. prechigh - nonassoc UMINUS NOT '!' + nonassoc UMINUS NOT '!' '~' left '*' '/' '%' left '+' '-' + left '<<' '>>' '>>>' + left '&' '|' '^' left '<=' '<' '>' '>=' right '==' '!=' IS AINT left '&&' '||' AND OR @@ -127,6 +129,7 @@ rule '!' Expression { result = OpNode.new(val[0], val[1]) } | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } | NOT Expression { result = OpNode.new(val[0], val[1]) } + | '~' Expression { result = OpNode.new(val[0], val[1]) } | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) } @@ -135,6 +138,14 @@ rule | Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7825dbca1a..f91e75895c 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -68,8 +68,8 @@ def root_compile "(function(){\n#{compile(TAB, Scope.new)}\n})();" end - # The extra fancy is to handle pushing down returns recursively to the - # final lines of inner statements (so as to make expressions out of them). + # The extra fancy is to handle pushing down returns and assignments + # recursively to the final lines of inner statements. def compile(indent='', scope=nil, opts={}) return root_compile unless scope code = @expressions.map { |n| @@ -171,7 +171,7 @@ def compile_super(args, indent, scope, opts) end end - # A value, indexed or dotted into or vanilla. + # A value, indexed or dotted into, or vanilla. class ValueNode < Node attr_reader :literal, :properties, :last From dcc70e5ab0d2a5765d6595aefb1cc5db0bafacdb Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 19 Dec 2009 22:56:27 -0500 Subject: [PATCH 042/303] added full complement of bitwise operators --- documentation/index.html.erb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index cb032b44f3..5ea6ffde82 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -16,6 +16,10 @@ of it as JavaScript's simpleminded kid brother — the same genes, the same accent, but another kind of way of doing things.

+ +

+ +

From c7fa9c320af69bf1b4b564d30e691950a91af09a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 21 Dec 2009 11:41:45 -0500 Subject: [PATCH 043/303] documentation waypoint --- Rakefile | 1 + documentation/cs/array_comprehensions.cs | 5 + documentation/cs/assignment.cs | 2 + documentation/cs/conditionals.cs | 9 + documentation/cs/embedded.cs | 3 + documentation/cs/expressions.cs | 9 + documentation/cs/functions.cs | 2 + documentation/cs/intro.cs | 3 + documentation/cs/objects_and_arrays.cs | 6 + documentation/cs/punctuation.cs | 11 + documentation/cs/scope.cs | 5 + documentation/cs/slices.cs | 2 + documentation/cs/strings.cs | 6 + documentation/cs/super.cs | 21 + documentation/cs/switch.cs | 7 + documentation/cs/try.cs | 7 + documentation/cs/while.cs | 5 + documentation/css/docs.css | 67 +-- documentation/index.html.erb | 210 +++++++- documentation/js/array_comprehensions.js | 16 + documentation/js/assignment.js | 4 + documentation/js/conditionals.js | 12 + documentation/js/embedded.js | 8 + documentation/js/expressions.js | 12 + documentation/js/expressions_assignment.js | 3 + documentation/js/functions.js | 8 + documentation/js/intro.js | 5 + documentation/js/objects_and_arrays.js | 8 + documentation/js/punctuation.js | 4 + documentation/js/scope.js | 9 + documentation/js/slices.js | 4 + documentation/js/strings.js | 8 + documentation/js/super.js | 28 ++ documentation/js/switch.js | 13 + documentation/js/try.js | 10 + documentation/js/while.js | 9 + examples/code.cs | 29 +- index.html | 472 +++++++++++++++++- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 12 +- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 20 +- 42 files changed, 1026 insertions(+), 53 deletions(-) create mode 100644 documentation/cs/array_comprehensions.cs create mode 100644 documentation/cs/assignment.cs create mode 100644 documentation/cs/conditionals.cs create mode 100644 documentation/cs/embedded.cs create mode 100644 documentation/cs/expressions.cs create mode 100644 documentation/cs/functions.cs create mode 100644 documentation/cs/intro.cs create mode 100644 documentation/cs/objects_and_arrays.cs create mode 100644 documentation/cs/punctuation.cs create mode 100644 documentation/cs/scope.cs create mode 100644 documentation/cs/slices.cs create mode 100644 documentation/cs/strings.cs create mode 100644 documentation/cs/super.cs create mode 100644 documentation/cs/switch.cs create mode 100644 documentation/cs/try.cs create mode 100644 documentation/cs/while.cs create mode 100644 documentation/js/array_comprehensions.js create mode 100644 documentation/js/assignment.js create mode 100644 documentation/js/conditionals.js create mode 100644 documentation/js/embedded.js create mode 100644 documentation/js/expressions.js create mode 100644 documentation/js/expressions_assignment.js create mode 100644 documentation/js/functions.js create mode 100644 documentation/js/intro.js create mode 100644 documentation/js/objects_and_arrays.js create mode 100644 documentation/js/punctuation.js create mode 100644 documentation/js/scope.js create mode 100644 documentation/js/slices.js create mode 100644 documentation/js/strings.js create mode 100644 documentation/js/super.js create mode 100644 documentation/js/switch.js create mode 100644 documentation/js/try.js create mode 100644 documentation/js/while.js diff --git a/Rakefile b/Rakefile index 0616f0fccf..497d07e84c 100644 --- a/Rakefile +++ b/Rakefile @@ -18,6 +18,7 @@ end desc "Build the documentation page" task :doc do source = 'documentation/index.html.erb' + Thread.new { `bin/coffee-script documentation/cs/*.cs -o documentation/js -w` } loop do mtime = File.stat(source).mtime if !@mtime || mtime > @mtime diff --git a/documentation/cs/array_comprehensions.cs b/documentation/cs/array_comprehensions.cs new file mode 100644 index 0000000000..ffccbfeefb --- /dev/null +++ b/documentation/cs/array_comprehensions.cs @@ -0,0 +1,5 @@ +# Eat lunch. +lunch: food.eat() for food in ['toast', 'cheese', 'wine']. + +# Zebra-stripe a table. +highlight(row) for row, i in table if i % 2 is 0. diff --git a/documentation/cs/assignment.cs b/documentation/cs/assignment.cs new file mode 100644 index 0000000000..db979f5cb9 --- /dev/null +++ b/documentation/cs/assignment.cs @@ -0,0 +1,2 @@ +greeting: "Hello CoffeeScript" +difficulty: 0.5 diff --git a/documentation/cs/conditionals.cs b/documentation/cs/conditionals.cs new file mode 100644 index 0000000000..253d912c3b --- /dev/null +++ b/documentation/cs/conditionals.cs @@ -0,0 +1,9 @@ +mood: greatly_improved if singing + +if happy and knows_it + claps_hands() + cha_cha_cha(). + +date: if friday then sue else jill. + +expensive ||= do_the_math() \ No newline at end of file diff --git a/documentation/cs/embedded.cs b/documentation/cs/embedded.cs new file mode 100644 index 0000000000..5a4f0d2478 --- /dev/null +++ b/documentation/cs/embedded.cs @@ -0,0 +1,3 @@ +js: => `alert("Hello JavaScript");`. + +js() if 10 > 9 \ No newline at end of file diff --git a/documentation/cs/expressions.cs b/documentation/cs/expressions.cs new file mode 100644 index 0000000000..3309739aa3 --- /dev/null +++ b/documentation/cs/expressions.cs @@ -0,0 +1,9 @@ +grade: student => + if student.excellent_work + "A+" + else if student.okay_stuff + "B" + else + "C".. + +eldest: if 24 > 21 then "Liz" else "Ike". \ No newline at end of file diff --git a/documentation/cs/functions.cs b/documentation/cs/functions.cs new file mode 100644 index 0000000000..35f4415aa1 --- /dev/null +++ b/documentation/cs/functions.cs @@ -0,0 +1,2 @@ +square: x => x * x. +cube: x => square(x) * x. \ No newline at end of file diff --git a/documentation/cs/intro.cs b/documentation/cs/intro.cs new file mode 100644 index 0000000000..b0f13bb982 --- /dev/null +++ b/documentation/cs/intro.cs @@ -0,0 +1,3 @@ +# CoffeeScript on the left, JS on the right. + +square: x => x * x. diff --git a/documentation/cs/objects_and_arrays.cs b/documentation/cs/objects_and_arrays.cs new file mode 100644 index 0000000000..ff10c6ed37 --- /dev/null +++ b/documentation/cs/objects_and_arrays.cs @@ -0,0 +1,6 @@ +song: ["do", "re", "mi", "fa", "so"] +ages: { + max: 10 + ida: 9 + tim: 11 +} \ No newline at end of file diff --git a/documentation/cs/punctuation.cs b/documentation/cs/punctuation.cs new file mode 100644 index 0000000000..3ecf20736d --- /dev/null +++ b/documentation/cs/punctuation.cs @@ -0,0 +1,11 @@ +# Comments start with hash marks. + +# Periods mark the end of a block. +left_hand: if raining then umbrella else parasol. + +# To signal the beginning of the next expression, +# use "then", or a newline. +left_hand: if raining + umbrella +else + parasol. diff --git a/documentation/cs/scope.cs b/documentation/cs/scope.cs new file mode 100644 index 0000000000..f15b3eff12 --- /dev/null +++ b/documentation/cs/scope.cs @@ -0,0 +1,5 @@ +num: 1 +change_numbers: => + num: 2 + new_num: 3. +new_num: change_numbers() \ No newline at end of file diff --git a/documentation/cs/slices.cs b/documentation/cs/slices.cs new file mode 100644 index 0000000000..ac34b0b26c --- /dev/null +++ b/documentation/cs/slices.cs @@ -0,0 +1,2 @@ +nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +three_to_six: nums[3, 6] \ No newline at end of file diff --git a/documentation/cs/strings.cs b/documentation/cs/strings.cs new file mode 100644 index 0000000000..335d702bcc --- /dev/null +++ b/documentation/cs/strings.cs @@ -0,0 +1,6 @@ +moby_dick: "Call me Ishmael. Some years ago -- +never mind how long precisely -- having little +or no money in my purse, and nothing particular +to interest me on shore, I thought I would sail +about a little and see the watery part of the +world..." \ No newline at end of file diff --git a/documentation/cs/super.cs b/documentation/cs/super.cs new file mode 100644 index 0000000000..21c89a26d4 --- /dev/null +++ b/documentation/cs/super.cs @@ -0,0 +1,21 @@ +Animal: => . +Animal.prototype.move: meters => + alert(this.name + " moved " + meters + "m."). + +Snake: name => this.name: name. +Snake extends new Animal() +Snake.prototype.move: => + alert("Slithering...") + super(5). + +Horse: name => this.name: name. +Horse extends new Animal() +Horse.prototype.move: => + alert("Galloping...") + super(45). + +sam: new Snake("Sammy the Python") +tom: new Horse("Tommy the Palomino") + +sam.move() +tom.move() diff --git a/documentation/cs/switch.cs b/documentation/cs/switch.cs new file mode 100644 index 0000000000..41bf90451d --- /dev/null +++ b/documentation/cs/switch.cs @@ -0,0 +1,7 @@ +switch day +case "Tuesday" then eat_breakfast() +case "Wednesday" then go_to_the_park() +case "Saturday" + if day is bingo_day then go_to_bingo(). +case "Sunday" then go_to_church() +else go_to_work(). \ No newline at end of file diff --git a/documentation/cs/try.cs b/documentation/cs/try.cs new file mode 100644 index 0000000000..6664bbd10b --- /dev/null +++ b/documentation/cs/try.cs @@ -0,0 +1,7 @@ +try + all_hell_breaks_loose() + cats_and_dogs_living_together() +catch error + print( error ) +finally + clean_up(). \ No newline at end of file diff --git a/documentation/cs/while.cs b/documentation/cs/while.cs new file mode 100644 index 0000000000..ab0a850a0a --- /dev/null +++ b/documentation/cs/while.cs @@ -0,0 +1,5 @@ +while demand > supply + sell() + restock(). + +while supply > demand then buy(). \ No newline at end of file diff --git a/documentation/css/docs.css b/documentation/css/docs.css index f50ad3b186..cd45d0a860 100644 --- a/documentation/css/docs.css +++ b/documentation/css/docs.css @@ -1,40 +1,34 @@ body { - font-size: 16px; - line-height: 24px; - background: #f0f0e5; - color: #252519; - font-family: "Palatino Linotype", "Book Antiqua", Palatino, FreeSerif, serif; + font-size: 14px; + line-height: 20px; + background: #efefef; + color: #191933; + font-family: Arial, Helvetica, sans-serif; } div.container { - width: 720px; + width: 850px; margin: 50px 0 50px 50px; } p { - width: 550px; + padding-left: 13px; + width: 625px; } - #documentation p { - margin-bottom: 4px; - } -a, a:visited { - padding: 0 2px; - text-decoration: none; - background: #dadaba; - color: #252519; -} -a:active, a:hover { - color: #000; - background: #f0c095; +a { + color: #000055; } h1, h2, h3, h4, h5, h6 { + padding-left: 13px; margin-top: 40px; } -b.header { - font-size: 18px; +br.clear { + height: 0; + clear: both; } -span.alias { - font-size: 14px; - font-style: italic; - margin-left: 20px; +b.header { + color: #000055; + display: block; + margin: 40px 0 5px 0; + font-size: 16px; } table, tr, td { margin: 0; padding: 0; @@ -53,7 +47,24 @@ code, pre, tt { } pre { font-size: 12px; - padding: 2px 0 2px 12px; - border-left: 6px solid #aaaa99; - margin: 0px 0 30px; + padding: 0 0 0 12px; + margin: 0; + width: 410px; + float: left; + border-left: 1px dotted #559; + } + pre:first-child { + border-left: 0; + } +div.code { + position: relative; + border: 1px solid #cacaca; + background: #fff; + padding: 7px 0 10px 0; + -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; + -webkit-box-shadow: 0px 0px 7px #cacaca; +} + div.code button { + position: absolute; + right: 8px; bottom: 8px; } \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 5ea6ffde82..d571d13aa3 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -1,26 +1,222 @@ +<% + require 'uv' + def code_for(file, executable=false) + @stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\Z|^ )/ + cs = File.read("documentation/cs/#{file}.cs") + js = File.read("documentation/js/#{file}.js").gsub(@stripper, '') + cshtml = Uv.parse(cs, 'xhtml', 'coffeescript', false, 'idle', false) + jshtml = Uv.parse(js, 'xhtml', 'javascript', false, 'idle', false) + append = executable == true ? '' : "alert(#{executable});" + run = executable == true ? 'run' : "run: #{executable}" + button = executable ? "" : '' + "
#{cshtml}#{jshtml}#{button}
" + end +%> + + - CoffeeScript - + CoffeeScript, briefly... + +
-

CoffeeScript

- +

CoffeeScript

+

CoffeeScript is a little language that compiles into JavaScript. Think of it as JavaScript's simpleminded kid brother — the same genes, - the same accent, but another kind of way of doing things. + the same accent, but a different sense of style. Apart from a handful of + bonus goodies, statements in CoffeeScript correspond one-to-one with their + JavaScript equivalent, it's just another way of saying it. +

+ + + +

+ Disclaimer:
+ CoffeeScript is just for fun and seriously alpha. There is no guarantee, + explicit or implied, of its suitability for any purpose. That said, it + compiles into pretty-printed JavaScript (the good parts) that can pass through + JSLint warning-free. +

+ +

Table of Contents

+ +

+ + This document is structured so that it can be read from top to bottom, + if you like. Later sections use ideas and syntax previously introduced. + +

+ +

+ Punctuation Primer
+ Functions and Invocation
+ Objects and Arrays
+ Assignment
+ Lexical Scoping and Variable Safety
+ Conditionals, Ternaries, and Conditional Assignment
+ Everything is an Expression
+ While Loops
+ Array Comprehensions
+ Array Slice Literals
+ Inheritance, and Calling Super from a Subclass + Embedded JavaScript
+ Switch/Case/Else
+ Try/Catch/Finally
+ Multiline Strings

- +

- + In all of the following examples, the source CoffeeScript is provided on + the left, and the direct compilation into JavaScript is on the right.

+
+ +

+ Punctuation Primer + You don't need to use semicolons to (;) terminate expressions, ending + the line will do just as well. So newlines can matter, but whitespace is + not otherwise significant. Instead of using curly braces ({ }) + to delimit blocks of code, a period (.) marks the end of a + function, if statement, or try/catch. +

+ + +

+ Functions and Invocation + Let's start with the best part, shall we? Function literals are my + absolute favorite thing about CoffeeScript. +

+ <%= code_for('functions', 'cube(5)') %> + +

+ Objects and Arrays + Object and Array literals look very similar. When you spread out + each assignment on a separate line, the commas are optional. +

+ <%= code_for('objects_and_arrays', 'song.join(",")') %> +

+

+ +

+ Assignment + All assignment in CoffeeScript, whether to a variable or to an object + property, uses a colon. Equal signs are only needed for mathy things. +

+ <%= code_for('assignment', 'greeting') %> +

+

+ +

+ Lexical Scoping and Variable Safety + The CoffeeScript compiler takes care to make sure that all of your variables + are properly defined within lexical scope — you never need to declare + var yourself. +

+ <%= code_for('scope', 'new_num') %> +

+ Notice how the variables are declared with var the first time + they appear. The second reference of num, within the function, + is not redeclared because num is still in scope. As opposed + to the second new_num, in the last line. +

+ +

+ Conditionals, Ternaries, and Conditional Assignment +

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

+

+ +

+ Everything is an Expression + You might have noticed how even though we don't add return statements + to CoffeScript functions, they nonetheless return their final value. + The CoffeeScript compiler tries to make sure that every little language + construct can be used as an expression. +

+ <%= code_for('expressions', 'eldest') %> +

+ When compiling a function definition, CoffeeScript tries to push down + the return statement to each of the potential final lines of the function. + It uses the same mechanism to push down assignment statements. If statement + are compiled into ternary operators when possible, so that they can be used + as expressions. +

+ +

+ While Loops + The only low-level loop that CoffeeScript provides is the while loop. +

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

+ Array Comprehensions + Most of your looping needs should be handled by array comprehensions. + They replace (and compile into) for loops, handling + each/forEach style loops, as well as select/filter. + Unlike for loops, array comprehensions are expressions, and can be returned + and assigned. +

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

+ Array Slice Literals + CoffeeScript includes a literal syntax for extracting slices of arrays. + The first argument is the index of the first element in the slice, and + the second is the index of the last one. +

+ <%= code_for('slices', 'three_to_six') %> + +

+ Inheritance, and Calling Super from a Subclass + JavaScript's prototypal inheritance has always been a bit of a + brain-bender, with a whole family tree of libraries (Base2, Prototype + ). +

+ <%= code_for('super', true) %> + +

+ Embedded JavaScript + If you ever need to interpolate literal JavaScript snippets, you can + use backticks to pass JavaScript straight through. +

+ <%= code_for('embedded', true) %> + +

+ Switch/Case/Else + Switch statements in JavaScript are fundamentally broken. You can only + do string comparisons, and need to break at the end of each case + statment to prevent falling through to the default case. CoffeeScript + compiles switch statements into if-else chains, allowing you to + compare any object (via ===), preventing fall-through, and resulting + in a returnable expression. +

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

+ Try/Catch/Finally + Try/catch statements just about the same as JavaScript (although + they work as expressions). No braces required. +

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

+ Multiline Strings + Multiline strings are allowed in CoffeeScript. +

+ <%= code_for('strings') %> + +
+
diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js new file mode 100644 index 0000000000..75242df70f --- /dev/null +++ b/documentation/js/array_comprehensions.js @@ -0,0 +1,16 @@ +(function(){ + var lunch; + var a = ['toast', 'cheese', 'wine']; + var d = []; + for (var b=0, c=a.length; b 9) { + js(); + } +})(); \ No newline at end of file diff --git a/documentation/js/expressions.js b/documentation/js/expressions.js new file mode 100644 index 0000000000..b628748747 --- /dev/null +++ b/documentation/js/expressions.js @@ -0,0 +1,12 @@ +(function(){ + var grade = function(student) { + if (student.excellent_work) { + return "A+"; + } else if (student.okay_stuff) { + return "B"; + } else { + return "C"; + } + }; + var eldest = 24 > 21 ? "Liz" : "Ike"; +})(); \ No newline at end of file diff --git a/documentation/js/expressions_assignment.js b/documentation/js/expressions_assignment.js new file mode 100644 index 0000000000..97783f1b84 --- /dev/null +++ b/documentation/js/expressions_assignment.js @@ -0,0 +1,3 @@ +(function(){ + +})(); \ No newline at end of file diff --git a/documentation/js/functions.js b/documentation/js/functions.js new file mode 100644 index 0000000000..2fc0d2f82e --- /dev/null +++ b/documentation/js/functions.js @@ -0,0 +1,8 @@ +(function(){ + var square = function(x) { + return x * x; + }; + var cube = function(x) { + return square(x) * x; + }; +})(); \ No newline at end of file diff --git a/documentation/js/intro.js b/documentation/js/intro.js new file mode 100644 index 0000000000..7d544a2500 --- /dev/null +++ b/documentation/js/intro.js @@ -0,0 +1,5 @@ +(function(){ + var square = function(x) { + return x * x; + }; +})(); \ No newline at end of file diff --git a/documentation/js/objects_and_arrays.js b/documentation/js/objects_and_arrays.js new file mode 100644 index 0000000000..bd3276cc51 --- /dev/null +++ b/documentation/js/objects_and_arrays.js @@ -0,0 +1,8 @@ +(function(){ + var song = ["do", "re", "mi", "fa", "so"]; + var ages = { + max: 10, + ida: 9, + tim: 11 + }; +})(); \ No newline at end of file diff --git a/documentation/js/punctuation.js b/documentation/js/punctuation.js new file mode 100644 index 0000000000..5ac63fdb7c --- /dev/null +++ b/documentation/js/punctuation.js @@ -0,0 +1,4 @@ +(function(){ + var left_hand = raining ? umbrella : parasol; + left_hand = raining ? umbrella : parasol; +})(); \ No newline at end of file diff --git a/documentation/js/scope.js b/documentation/js/scope.js new file mode 100644 index 0000000000..1f110baa82 --- /dev/null +++ b/documentation/js/scope.js @@ -0,0 +1,9 @@ +(function(){ + var num = 1; + var change_numbers = function() { + num = 2; + var new_num = 3; + return new_num; + }; + var new_num = change_numbers(); +})(); \ No newline at end of file diff --git a/documentation/js/slices.js b/documentation/js/slices.js new file mode 100644 index 0000000000..a82ecdbec8 --- /dev/null +++ b/documentation/js/slices.js @@ -0,0 +1,4 @@ +(function(){ + var nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var three_to_six = nums.slice(3, 6 + 1); +})(); \ No newline at end of file diff --git a/documentation/js/strings.js b/documentation/js/strings.js new file mode 100644 index 0000000000..3787620821 --- /dev/null +++ b/documentation/js/strings.js @@ -0,0 +1,8 @@ +(function(){ + var moby_dick = "Call me Ishmael. Some years ago --\ +never mind how long precisely -- having little\ +or no money in my purse, and nothing particular\ +to interest me on shore, I thought I would sail\ +about a little and see the watery part of the\ +world..."; +})(); \ No newline at end of file diff --git a/documentation/js/super.js b/documentation/js/super.js new file mode 100644 index 0000000000..8989fafd9c --- /dev/null +++ b/documentation/js/super.js @@ -0,0 +1,28 @@ +(function(){ + var Animal = function() { + + }; + Animal.prototype.move = function(meters) { + return alert(this.name + " moved " + meters + "m."); + }; + var Snake = function(name) { + this.name = name; + }; + Snake.prototype = new Animal(); + Snake.prototype.move = function() { + alert("Slithering..."); + return this.constructor.prototype.move.call(this, 5); + }; + var Horse = function(name) { + this.name = name; + }; + Horse.prototype = new Animal(); + Horse.prototype.move = function() { + alert("Galloping..."); + return this.constructor.prototype.move.call(this, 45); + }; + var sam = new Snake("Sammy the Python"); + var tom = new Horse("Tommy the Palomino"); + sam.move(); + tom.move(); +})(); \ No newline at end of file diff --git a/documentation/js/switch.js b/documentation/js/switch.js new file mode 100644 index 0000000000..36a2a15e2e --- /dev/null +++ b/documentation/js/switch.js @@ -0,0 +1,13 @@ +(function(){ + if (day === "Tuesday") { + eat_breakfast(); + } else if (day === "Wednesday") { + go_to_the_park(); + } else if (day === "Saturday") { + day === bingo_day ? go_to_bingo() : null; + } else if (day === "Sunday") { + go_to_church(); + } else { + go_to_work(); + } +})(); \ No newline at end of file diff --git a/documentation/js/try.js b/documentation/js/try.js new file mode 100644 index 0000000000..a2995e6dfc --- /dev/null +++ b/documentation/js/try.js @@ -0,0 +1,10 @@ +(function(){ + try { + all_hell_breaks_loose(); + cats_and_dogs_living_together(); + } catch (error) { + print(error); + } finally { + clean_up(); + } +})(); \ No newline at end of file diff --git a/documentation/js/while.js b/documentation/js/while.js new file mode 100644 index 0000000000..cc83571984 --- /dev/null +++ b/documentation/js/while.js @@ -0,0 +1,9 @@ +(function(){ + while (demand > supply) { + sell(); + restock(); + } + while (supply > demand) { + buy(); + } +})(); \ No newline at end of file diff --git a/examples/code.cs b/examples/code.cs index a06dbb7e7d..6f533fcef3 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -136,13 +136,28 @@ sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad." -# Calling super from an overridden method. -Greeter: => . # Create the parent object. -Greeter.prototype.hello: name => alert('Hello ' + name). # Define a "hello" method. -Exclaimer: name => this.name: name. # Create the child object. -Exclaimer.prototype: new Greeter() # Set the child to inherit from the parent. -Exclaimer.prototype.hello: => super(this.name + "!"). # The child's "hello" calls the parent's via "super". -(new Exclaimer('Bob')).hello() # Run it. +# Inheritance and calling super. +Animal: => . +Animal.prototype.move: meters => + alert(this.name + " moved " + meters + "m."). + +Snake: name => this.name: name. +Snake extends Animal +Snake.prototype.move: => + alert('Slithering...') + super(5). + +Horse: name => this.name: name. +Horse extends Animal +Horse.prototype.move: => + alert('Galloping...') + super(45). + +sam: new Snake("Sammy the Snake") +tom: new Horse("Tommy the Horse") + +sam.move() +tom.move() # Numbers. a_googol: 1e100 diff --git a/index.html b/index.html index cb032b44f3..1148000306 100644 --- a/index.html +++ b/index.html @@ -1,22 +1,484 @@ + + + - CoffeeScript - + CoffeeScript, briefly... + +
-

CoffeeScript

- +

CoffeeScript

+

CoffeeScript is a little language that compiles into JavaScript. Think of it as JavaScript's simpleminded kid brother — the same genes, - the same accent, but another kind of way of doing things. + the same accent, but a different sense of style. Apart from a handful of + bonus goodies, statements in CoffeeScript correspond one-to-one with their + JavaScript equivalent, it's just another way of saying it. +

+ + + +

+ Disclaimer:
+ CoffeeScript is just for fun and seriously alpha. There is no guarantee, + explicit or implied, of its suitability for any purpose. That said, it + compiles into pretty-printed JavaScript (the good parts) that can pass through + JSLint warning-free. +

+ +

Table of Contents

+ +

+ + This document is structured so that it can be read from top to bottom, + if you like. Later sections use ideas and syntax previously introduced. +

+

+ Punctuation Primer
+ Functions and Invocation
+ Objects and Arrays
+ Assignment
+ Lexical Scoping and Variable Safety
+ Conditionals, Ternaries, and Conditional Assignment
+ Everything is an Expression
+ While Loops
+ Array Comprehensions
+ Array Slice Literals
+ Inheritance, and Calling Super from a Subclass + Embedded JavaScript
+ Switch/Case/Else
+ Try/Catch/Finally
+ Multiline Strings
+

+ +

+ In all of the following examples, the source CoffeeScript is provided on + the left, and the direct compilation into JavaScript is on the right. +

+ +
+ +

+ Punctuation Primer + You don't need to use semicolons to (;) terminate expressions, ending + the line will do just as well. So newlines can matter, but whitespace is + not otherwise significant. Instead of using curly braces ({ }) + to delimit blocks of code, a period (.) marks the end of a + function, if statement, or try/catch. +

+ + +

+ Functions and Invocation + Let's start with the best part, shall we? Function literals are my + absolute favorite thing about CoffeeScript. +

+
square: x => x * x.
+cube:   x => square(x) * x.
+
var square = function(x) {
+  return x * x;
+};
+var cube = function(x) {
+  return square(x) * x;
+};
+

+ +

+ Objects and Arrays + Object and Array literals look very similar. When you spread out + each assignment on a separate line, the commas are optional. +

+
song: ["do", "re", "mi", "fa", "so"]
+ages: {
+  max: 10
+  ida: 9
+  tim: 11
+}
+
var song = ["do", "re", "mi", "fa", "so"];
+var ages = {
+  max: 10,
+  ida: 9,
+  tim: 11
+};
+

+

+

+ +

+ Assignment + All assignment in CoffeeScript, whether to a variable or to an object + property, uses a colon. Equal signs are only needed for mathy things. +

+
greeting: "Hello CoffeeScript"
+difficulty: 0.5
+
var greeting = "Hello CoffeeScript";
+var difficulty = 0.5;
+

+

+

+ +

+ Lexical Scoping and Variable Safety + The CoffeeScript compiler takes care to make sure that all of your variables + are properly defined within lexical scope — you never need to declare + var yourself. +

+
num: 1
+change_numbers: =>
+  num: 2
+  new_num: 3.
+new_num: change_numbers()
+
var num = 1;
+var change_numbers = function() {
+  num = 2;
+  var new_num = 3;
+  return new_num;
+};
+var new_num = change_numbers();
+

+

+ Notice how the variables are declared with var the first time + they appear. The second reference of num, within the function, + is not redeclared because num is still in scope. As opposed + to the second new_num, in the last line. +

+ +

+ Conditionals, Ternaries, and Conditional Assignment +

+
mood: greatly_improved if singing
+
+if happy and knows_it
+  claps_hands()
+  cha_cha_cha().
+
+date: if friday then sue else jill.
+
+expensive ||= do_the_math()
+
var mood;
+if (singing) {
+  mood = greatly_improved;
+}
+if (happy && knows_it) {
+  claps_hands();
+  cha_cha_cha();
+}
+var date = friday ? sue : jill;
+expensive = expensive || do_the_math();
+

+

+

+ +

+ Everything is an Expression + You might have noticed how even though we don't add return statements + to CoffeScript functions, they nonetheless return their final value. + The CoffeeScript compiler tries to make sure that every little language + construct can be used as an expression. +

+
grade: student =>
+  if student.excellent_work
+    "A+"
+  else if student.okay_stuff
+    "B"
+  else
+    "C"..
+
+eldest: if 24 > 21 then "Liz" else "Ike".
+
var grade = function(student) {
+  if (student.excellent_work) {
+    return "A+";
+  } else if (student.okay_stuff) {
+    return "B";
+  } else {
+    return "C";
+  }
+};
+var eldest = 24 > 21 ? "Liz" : "Ike";
+

+

+ When compiling a function definition, CoffeeScript tries to push down + the return statement to each of the potential final lines of the function. + It uses the same mechanism to push down assignment statements. If statement + are compiled into ternary operators when possible, so that they can be used + as expressions. +

+ +

+ While Loops + The only low-level loop that CoffeeScript provides is the while loop. +

+
while demand > supply
+  sell()
+  restock().
+
+while supply > demand then buy().
+
while (demand > supply) {
+  sell();
+  restock();
+}
+while (supply > demand) {
+  buy();
+}
+

+ +

+ Array Comprehensions + Most of your looping needs should be handled by array comprehensions. + They replace (and compile into) for loops, handling + each/forEach style loops, as well as select/filter. + Unlike for loops, array comprehensions are expressions, and can be returned + and assigned. +

+
# Eat lunch.
+lunch: food.eat() for food in ['toast', 'cheese', 'wine'].
+
+# Zebra-stripe a table.
+highlight(row) for row, i in table if i % 2 is 0.
+
var lunch;
+var a = ['toast', 'cheese', 'wine'];
+var d = [];
+for (var b=0, c=a.length; b<c; b++) {
+  var food = a[b];
+  d[b] = food.eat();
+}
+lunch = d;
+var e = table;
+for (var f=0, g=e.length; f<g; f++) {
+  var row = e[f];
+  var i = f;
+  i % 2 === 0 ? highlight(row) : null;
+}
+

+ +

+ Array Slice Literals + CoffeeScript includes a literal syntax for extracting slices of arrays. + The first argument is the index of the first element in the slice, and + the second is the index of the last one. +

+
nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+three_to_six: nums[3, 6]
+
var nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+var three_to_six = nums.slice(3, 6 + 1);
+

+ +

+ Inheritance, and Calling Super from a Subclass + JavaScript's prototypal inheritance has always been a bit of a + brain-bender, with a whole family tree of libraries (Base2, Prototype + ). +

+
Animal: => .
+Animal.prototype.move: meters =>
+  alert(this.name + " moved " + meters + "m.").
+
+Snake: name => this.name: name.
+Snake extends new Animal()
+Snake.prototype.move: =>
+  alert("Slithering...")
+  super(5).
+
+Horse: name => this.name: name.
+Horse extends new Animal()
+Horse.prototype.move: =>
+  alert("Galloping...")
+  super(45).
+
+sam: new Snake("Sammy the Python")
+tom: new Horse("Tommy the Palomino")
+
+sam.move()
+tom.move()
+
var Animal = function() {
+
+};
+Animal.prototype.move = function(meters) {
+  return alert(this.name + " moved " + meters + "m.");
+};
+var Snake = function(name) {
+  this.name = name;
+};
+Snake.prototype = new Animal();
+Snake.prototype.move = function() {
+  alert("Slithering...");
+  return this.constructor.prototype.move.call(this, 5);
+};
+var Horse = function(name) {
+  this.name = name;
+};
+Horse.prototype = new Animal();
+Horse.prototype.move = function() {
+  alert("Galloping...");
+  return this.constructor.prototype.move.call(this, 45);
+};
+var sam = new Snake("Sammy the Python");
+var tom = new Horse("Tommy the Palomino");
+sam.move();
+tom.move();
+

+ +

+ Embedded JavaScript + If you ever need to interpolate literal JavaScript snippets, you can + use backticks to pass JavaScript straight through. +

+
js: => `alert("Hello JavaScript");`.
+
+js() if 10 > 9
+
var js = function() {
+  return alert("Hello JavaScript");
+};
+if (10 > 9) {
+  js();
+}
+

+ +

+ Switch/Case/Else + Switch statements in JavaScript are fundamentally broken. You can only + do string comparisons, and need to break at the end of each case + statment to prevent falling through to the default case. CoffeeScript + compiles switch statements into if-else chains, allowing you to + compare any object (via ===), preventing fall-through, and resulting + in a returnable expression. +

+
switch day
+case "Tuesday"   then eat_breakfast()
+case "Wednesday" then go_to_the_park()
+case "Saturday"
+  if day is bingo_day then go_to_bingo().
+case "Sunday"    then go_to_church()
+else go_to_work().
+
if (day === "Tuesday") {
+  eat_breakfast();
+} else if (day === "Wednesday") {
+  go_to_the_park();
+} else if (day === "Saturday") {
+  day === bingo_day ? go_to_bingo() : null;
+} else if (day === "Sunday") {
+  go_to_church();
+} else {
+  go_to_work();
+}
+

+ +

+ Try/Catch/Finally + Try/catch statements just about the same as JavaScript (although + they work as expressions). No braces required. +

+
try
+  all_hell_breaks_loose()
+  cats_and_dogs_living_together()
+catch error
+  print( error )
+finally
+  clean_up().
+
try {
+  all_hell_breaks_loose();
+  cats_and_dogs_living_together();
+} catch (error) {
+  print(error);
+} finally {
+  clean_up();
+}
+

+ +

+ Multiline Strings + Multiline strings are allowed in CoffeeScript. +

+
moby_dick: "Call me Ishmael. Some years ago --
+never mind how long precisely -- having little
+or no money in my purse, and nothing particular
+to interest me on shore, I thought I would sail
+about a little and see the watery part of the
+world..."
+
var moby_dick = "Call me Ishmael. Some years ago --\
+never mind how long precisely -- having little\
+or no money in my purse, and nothing particular\
+to interest me on shore, I thought I would sail\
+about a little and see the watery part of the\
+world...";
+

+ +
+
diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index b788c281ce..29f52154a2 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -229,7 +229,7 @@ match - \b(super|this)\b + \b(super|this|extends)\b name variable.language.cs diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 39be9602c4..a1d759dacd 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -10,7 +10,7 @@ token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE token SWITCH CASE -token SUPER +token EXTENDS SUPER token DELETE token NEWLINE token JS @@ -28,8 +28,8 @@ prechigh right '-=' '+=' '/=' '*=' '||=' '&&=' right DELETE left "." - right THROW FOR IN WHILE - left UNLESS IF ELSE + right THROW FOR IN WHILE NEW + left UNLESS IF ELSE EXTENDS left ":" right RETURN preclow @@ -68,6 +68,7 @@ rule | Call | Code | Operation + | Extend ; # We have to take extra care to convert these statements into expressions. @@ -246,6 +247,11 @@ rule SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) } ; + # Extending a class. + Extend: + IDENTIFIER EXTENDS Expression { result = ExtendNode.new(val[0], val[2]) } + ; + # The array literal. Array: "[" ArgList "]" { result = ArrayNode.new(val[1]) } diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 78208a1c52..fc363801d2 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -14,7 +14,7 @@ class Lexer "break", "continue", "for", "in", "while", "switch", "case", - "super", + "extends", "super", "delete"] # Token matching regexes. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index f91e75895c..ec69cc84b4 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -110,6 +110,10 @@ def statement? STATEMENTS.include?(@value.to_s) end + def line_ending + @value.to_s[-1..-1] == ';' ? '' : ';' + end + def compile(indent, scope, opts={}) code = @value.to_s write(code) @@ -171,6 +175,20 @@ def compile_super(args, indent, scope, opts) end end + class ExtendNode < Node + + attr_reader :subclass, :superclass + + def initialize(subclass, superclass) + @subclass, @superclass = subclass, superclass + end + + def compile(indent, scope, opts={}) + "#{@subclass}.prototype = #{@superclass.compile(indent, scope, opts)}" + end + + end + # A value, indexed or dotted into, or vanilla. class ValueNode < Node attr_reader :literal, :properties, :last @@ -269,7 +287,7 @@ def line_ending def compile(indent, scope, opts={}) name = @variable.respond_to?(:compile) ? @variable.compile(indent, scope) : @variable - last = @variable.respond_to?(:last) ? @variable.last : name + last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s opts = opts.merge({:assign => name, :last_assign => last}) return write("#{@variable}: #{@value.compile(indent, scope, opts)}") if @context == :object return write("#{name} = #{@value.compile(indent, scope, opts)}") if @variable.properties? From 9bb3e3fbe8c1dc746d307641b594d034a6ebe0ee Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 21 Dec 2009 12:15:13 -0500 Subject: [PATCH 044/303] clean up children at exit -- had about twenty processes all watching and recompiling the docs --- Rakefile | 4 +++- lib/coffee_script/command_line.rb | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 497d07e84c..87c11beef3 100644 --- a/Rakefile +++ b/Rakefile @@ -18,7 +18,9 @@ end desc "Build the documentation page" task :doc do source = 'documentation/index.html.erb' - Thread.new { `bin/coffee-script documentation/cs/*.cs -o documentation/js -w` } + child = fork { exec "bin/coffee-script documentation/cs/*.cs -o documentation/js -w" } + at_exit { Process.kill("INT", child) } + Signal.trap("INT") { exit } loop do mtime = File.stat(source).mtime if !@mtime || mtime > @mtime diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 105292dd5f..2c313bdf94 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -65,6 +65,7 @@ def watch_coffee_scripts sleep WATCH_INTERVAL end end + Signal.trap("INT") { watch_thread.kill } watch_thread.join end From 2f211196a2e2fd3da1a4a7545c268f37367b9f6e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 22 Dec 2009 10:11:41 -0500 Subject: [PATCH 045/303] moderate refactor of nodes.rb -- tests pass and examples compile without warnings --- lib/coffee_script/nodes.rb | 232 +++++++++++++++++++++---------------- 1 file changed, 134 insertions(+), 98 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index ec69cc84b4..0785cf77cf 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -25,17 +25,20 @@ def self.custom_assign end def write(code) - puts "#{self.class.to_s}:\n#{code}\n\n" if ENV['VERBOSE'] + puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE'] code end + def compile(o={}) + @options = o.dup + end + # Default implementations of the common node methods. def unwrap; self; end def line_ending; ';'; end def statement?; false; end def custom_return?; false; end def custom_assign?; false; end - def compile(indent='', scope=nil, opts={}); end end # A collection of nodes, each one representing an expression. @@ -65,30 +68,33 @@ def unwrap # If this is the top-level Expressions, wrap everything in a safety closure. def root_compile - "(function(){\n#{compile(TAB, Scope.new)}\n})();" + options = {:indent => TAB, :scope => Scope.new} + "(function(){\n#{compile(options)}\n})();" end # The extra fancy is to handle pushing down returns and assignments # recursively to the final lines of inner statements. - def compile(indent='', scope=nil, opts={}) - return root_compile unless scope - code = @expressions.map { |n| - if n == @expressions.last && (opts[:return] || opts[:assign]) - if opts[:return] - if n.statement? || n.custom_return? - "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" + def compile(options={}) + return root_compile unless options[:scope] + code = @expressions.map { |node| + o = super(options) + if node == @expressions.last && (o[:return] || o[:assign]) + if o[:return] + if node.statement? || node.custom_return? + "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" else - "#{indent}return #{n.compile(indent, scope, opts)}#{n.line_ending}" + "#{o[:indent]}return #{node.compile(o)}#{node.line_ending}" end - elsif opts[:assign] - if n.statement? || n.custom_assign? - "#{indent}#{n.compile(indent, scope, opts)}#{n.line_ending}" + elsif o[:assign] + if node.statement? || node.custom_assign? + "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" else - "#{indent}#{AssignNode.new(ValueNode.new(LiteralNode.new(opts[:assign])), n).compile(indent, scope, opts)};" + "#{o[:indent]}#{AssignNode.new(ValueNode.new(LiteralNode.new(o[:assign])), node).compile(o)};" end end else - "#{indent}#{n.compile(indent, scope)}#{n.line_ending}" + o.delete(:return) and o.delete(:assign) + "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" end }.join("\n") write(code) @@ -114,9 +120,9 @@ def line_ending @value.to_s[-1..-1] == ';' ? '' : ';' end - def compile(indent, scope, opts={}) - code = @value.to_s - write(code) + def compile(o={}) + o = super(o) + write(@value.to_s) end end @@ -135,9 +141,10 @@ def line_ending @expression.custom_return? ? '' : ';' end - def compile(indent, scope, opts={}) - return write(@expression.compile(indent, scope, opts.merge(:return => true))) if @expression.custom_return? - compiled = @expression.compile(indent, scope) + def compile(o={}) + o = super(o) + return write(@expression.compile(o.merge(:return => true))) if @expression.custom_return? + compiled = @expression.compile(o) write(@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}") end end @@ -162,15 +169,16 @@ def super? @variable == :super end - def compile(indent, scope, opts={}) - args = @arguments.map{|a| a.compile(indent, scope, :no_paren => true) }.join(', ') - return write(compile_super(args, indent, scope, opts)) if super? + def compile(o={}) + o = super(o) + args = @arguments.map{|a| a.compile(o.merge(:no_paren => true)) }.join(', ') + return write(compile_super(args, o)) if super? prefix = @new ? "new " : '' - write("#{prefix}#{@variable.compile(indent, scope)}(#{args})") + write("#{prefix}#{@variable.compile(o)}(#{args})") end - def compile_super(args, indent, scope, opts) - methname = opts[:last_assign].sub(LEADING_DOT, '') + def compile_super(args, o) + methname = o[:last_assign].sub(LEADING_DOT, '') "this.constructor.prototype.#{methname}.call(this, #{args})" end end @@ -183,8 +191,9 @@ def initialize(subclass, superclass) @subclass, @superclass = subclass, superclass end - def compile(indent, scope, opts={}) - "#{@subclass}.prototype = #{@superclass.compile(indent, scope, opts)}" + def compile(o={}) + o = super(o) + "#{@subclass}.prototype = #{@superclass.compile(o)}" end end @@ -218,9 +227,10 @@ def custom_return? @literal.is_a?(Node) && @literal.custom_return? && !properties? end - def compile(indent, scope, opts={}) - parts = [@literal, @properties].flatten.map do |v| - v.respond_to?(:compile) ? v.compile(indent, scope, opts) : v.to_s + def compile(o={}) + o = super(o) + parts = [@literal, @properties].flatten.map do |val| + val.respond_to?(:compile) ? val.compile(o) : val.to_s end @last = parts.last write(parts.join('')) @@ -235,7 +245,8 @@ def initialize(name) @name = name end - def compile(indent, scope, opts={}) + def compile(o={}) + o = super(o) write(".#{@name}") end end @@ -248,8 +259,9 @@ def initialize(index) @index = index end - def compile(indent, scope, opts={}) - write("[#{@index.compile(indent, scope)}]") + def compile(o={}) + o = super(o) + write("[#{@index.compile(o)}]") end end @@ -263,8 +275,9 @@ def initialize(from, to) @from, @to = from, to end - def compile(indent, scope, opts={}) - write(".slice(#{@from.compile(indent, scope, opts)}, #{@to.compile(indent, scope, opts)} + 1)") + def compile(o={}) + o = super(o) + write(".slice(#{@from.compile(o)}, #{@to.compile(o)} + 1)") end end @@ -285,18 +298,19 @@ def line_ending @value.custom_assign? ? '' : ';' end - def compile(indent, scope, opts={}) - name = @variable.respond_to?(:compile) ? @variable.compile(indent, scope) : @variable + def compile(o={}) + o = super(o) + name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s - opts = opts.merge({:assign => name, :last_assign => last}) - return write("#{@variable}: #{@value.compile(indent, scope, opts)}") if @context == :object - return write("#{name} = #{@value.compile(indent, scope, opts)}") if @variable.properties? - defined = scope.find(name) - postfix = !defined && opts[:return] ? ";\n#{indent}return #{name}" : '' - def_part = defined ? "" : "var #{name};\n#{indent}" - return write(def_part + @value.compile(indent, scope, opts)) if @value.custom_assign? + o = o.merge(:assign => name, :last_assign => last) + return write("#{@variable}: #{@value.compile(o)}") if @context == :object + return write("#{name} = #{@value.compile(o)}") if @variable.properties? + defined = o[:scope].find(name) + postfix = !defined && o[:return] ? ";\n#{o[:indent]}return #{name}" : '' + def_part = defined ? "" : "var #{name};\n#{o[:indent]}" + return write(def_part + @value.compile(o)) if @value.custom_assign? def_part = defined ? name : "var #{name}" - val_part = @value.compile(indent, scope, opts).sub(LEADING_VAR, '') + val_part = @value.compile(o).sub(LEADING_VAR, '') write("#{def_part} = #{val_part}#{postfix}") end end @@ -326,21 +340,22 @@ def unary? @second.nil? end - def compile(indent, scope, opts={}) - return write(compile_conditional(indent, scope)) if CONDITIONALS.include?(@operator) - return write(compile_unary(indent, scope)) if unary? - write("#{@first.compile(indent, scope)} #{@operator} #{@second.compile(indent, scope)}") + def compile(o={}) + o = super(o) + return write(compile_conditional(o)) if CONDITIONALS.include?(@operator) + return write(compile_unary(o)) if unary? + write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}") end - def compile_conditional(indent, scope) - first, second = @first.compile(indent, scope), @second.compile(indent, scope) + def compile_conditional(o) + first, second = @first.compile(o), @second.compile(o) sym = @operator[0..1] "#{first} = #{first} #{sym} #{second}" end - def compile_unary(indent, scope) + def compile_unary(o) space = @operator == 'delete' ? ' ' : '' - "#{@operator}#{space}#{@first.compile(indent, scope)}" + "#{@operator}#{space}#{@first.compile(o)}" end end @@ -353,12 +368,15 @@ def initialize(params, body) @body = body end - def compile(indent, scope, opts={}) - scope = Scope.new(scope) - @params.each {|id| scope.find(id.to_s) } - opts[:return] = true - opts.delete(:assign) - code = @body.compile(indent + TAB, scope, opts) + def compile(o={}) + o = super(o) + o[:scope] = Scope.new(o[:scope]) + o[:return] = true + indent = o[:indent] + o[:indent] += TAB + o.delete(:assign) + @params.each {|id| o[:scope].find(id.to_s) } + code = @body.compile(o) write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end end @@ -371,8 +389,11 @@ def initialize(properties = []) @properties = properties end - def compile(indent, scope, opts={}) - props = @properties.map {|p| indent + TAB + p.compile(indent + TAB, scope) }.join(",\n") + def compile(o={}) + o = super(o) + indent = o[:indent] + o[:indent] += TAB + props = @properties.map {|p| o[:indent] + p.compile(o) }.join(",\n") write("{\n#{props}\n#{indent}}") end end @@ -385,8 +406,9 @@ def initialize(objects=[]) @objects = objects end - def compile(indent, scope, opts={}) - objects = @objects.map {|o| o.compile(indent, scope) }.join(', ') + def compile(o={}) + o = super(o) + objects = @objects.map {|obj| obj.compile(o) }.join(', ') write("[#{objects}]") end end @@ -406,8 +428,11 @@ def line_ending '' end - def compile(indent, scope, opts={}) - write("while (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{@body.compile(indent + TAB, scope)}\n#{indent}}") + def compile(o={}) + o = super(o) + indent = o[:indent] + TAB + cond = @condition.compile(o.merge(:no_paren => true)) + write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}") end end @@ -430,7 +455,9 @@ def line_ending '' end - def compile(indent, scope, opts={}) + def compile(o={}) + o = super(o) + scope = o[:scope] name_found = scope.find(@name) index_found = @index && scope.find(@index) svar = scope.free_variable @@ -438,24 +465,24 @@ def compile(indent, scope, opts={}) lvar = scope.free_variable name_part = name_found ? @name : "var #{@name}" index_name = @index ? (index_found ? @index : "var #{@index}") : nil - source_part = "var #{svar} = #{@source.compile(indent, scope)};" + source_part = "var #{svar} = #{@source.compile(o)};" for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" - var_part = "\n#{indent + TAB}#{name_part} = #{svar}[#{ivar}];\n" - index_part = @index ? "#{indent + TAB}#{index_name} = #{ivar};\n" : '' + var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n" + index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : '' set_result = '' save_result = '' return_result = '' body = @body suffix = ';' - if opts[:return] || opts[:assign] + if o[:return] || o[:assign] rvar = scope.free_variable - set_result = "var #{rvar} = [];\n#{indent}" - save_result += "#{rvar}[#{ivar}] = " + set_result = "var #{rvar} = [];\n#{o[:indent]}" + save_result += "#{rvar}[#{ivar}] = " return_result = rvar - return_result = "#{opts[:assign]} = #{return_result};" if opts[:assign] - return_result = "return #{return_result};" if opts[:return] - return_result = "\n#{indent}#{return_result}" + return_result = "#{o[:assign]} = #{return_result};" if o[:assign] + return_result = "return #{return_result};" if o[:return] + return_result = "\n#{o[:indent]}#{return_result}" if @filter body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) body = IfNode.new(@filter, body, nil, :statement) @@ -466,8 +493,9 @@ def compile(indent, scope, opts={}) body = IfNode.new(@filter, @body) end - body = body.compile(indent + TAB, scope) - write("#{source_part}\n#{indent}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent + TAB}#{save_result}#{body}#{suffix}\n#{indent}}#{return_result}") + indent = o[:indent] + TAB + body = body.compile(o.merge(:indent => indent)) + write("#{source_part}\n#{o[:indent]}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}") end end @@ -485,10 +513,13 @@ def line_ending '' end - def compile(indent, scope, opts={}) - catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(indent + TAB, scope, opts)}\n#{indent}}" - finally_part = @finally && " finally {\n#{@finally.compile(indent + TAB, scope, opts)}\n#{indent}}" - write("try {\n#{@try.compile(indent + TAB, scope, opts)}\n#{indent}}#{catch_part}#{finally_part}") + def compile(o={}) + o = super(o) + indent = o[:indent] + o[:indent] += TAB + catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(o)}\n#{indent}}" + finally_part = @finally && " finally {\n#{@finally.compile(o)}\n#{indent}}" + write("try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}") end end @@ -502,8 +533,9 @@ def initialize(expression) @expression = expression end - def compile(indent, scope, opts={}) - write("throw #{@expression.compile(indent, scope)}") + def compile(o={}) + o = super(o) + write("throw #{@expression.compile(o)}") end end @@ -527,10 +559,11 @@ def custom_return? @expressions.custom_return? end - def compile(indent, scope, opts={}) - compiled = @expressions.compile(indent, scope, opts) + def compile(o={}) + o = super(o) + compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' - write(opts[:no_paren] || statement? ? compiled : "(#{compiled})") + write(o[:no_paren] || statement? ? compiled : "(#{compiled})") end end @@ -591,25 +624,28 @@ def line_ending statement? ? '' : ';' end - def compile(indent, scope, opts={}) - write(opts[:statement] || statement? ? compile_statement(indent, scope, opts) : compile_ternary(indent, scope)) + def compile(o={}) + o = super(o) + write(o[:statement] || statement? ? compile_statement(o) : compile_ternary(o)) end # Compile the IfNode as a regular if-else statement. Flattened chains # force sub-else bodies into statement form. - def compile_statement(indent, scope, opts) - if_part = "if (#{@condition.compile(indent, scope, :no_paren => true)}) {\n#{Expressions.wrap(@body).compile(indent + TAB, scope, opts)}\n#{indent}}" + def compile_statement(o) + indent = o[:indent] + o[:indent] += TAB + if_part = "if (#{@condition.compile(o.merge(:no_paren => true))}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" return if_part unless @else_body else_part = chain? ? - " else #{@else_body.compile(indent, scope, opts.merge(:statement => true))}" : - " else {\n#{Expressions.wrap(@else_body).compile(indent + TAB, scope, opts)}\n#{indent}}" + " else #{@else_body.compile(o.merge(:statement => true, :indent => indent))}" : + " else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{indent}}" if_part + else_part end # Compile the IfNode into a ternary operator. - def compile_ternary(indent, scope) - if_part = "#{@condition.compile(indent, scope)} ? #{@body.compile(indent, scope)}" - else_part = @else_body ? "#{@else_body.compile(indent, scope)}" : 'null' + def compile_ternary(o) + if_part = "#{@condition.compile(o)} ? #{@body.compile(o)}" + else_part = @else_body ? "#{@else_body.compile(o)}" : 'null' "#{if_part} : #{else_part}" end end From 9b8f01864617ef3e33e4c697aab773067c83ab5d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 22 Dec 2009 10:16:53 -0500 Subject: [PATCH 046/303] nice -- it's pushing down assignments properly (recursively) now --- examples/code.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index 6f533fcef3..cd4ad999e5 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -114,14 +114,17 @@ drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i). # Switch statements ("else" serves as a default). -switch day +activity: switch day case "Tuesday" then eat_breakfast() case "Sunday" then go_to_church() case "Saturday" then go_to_the_park() case "Wednesday" - eat_breakfast() - go_to_work() - eat_dinner() + if day is bingo_day + go_to_bingo() + else + eat_breakfast() + go_to_work() + eat_dinner(). else go_to_work(). # Semicolons can optionally be used instead of newlines. From d45643c52782f3c92c7f0cabfdaa3d33198e2242 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 22 Dec 2009 10:48:58 -0500 Subject: [PATCH 047/303] passing through comments as tags on Values, but not printing them out quite yet... --- TODO | 12 +----------- lib/coffee_script/lexer.rb | 11 ++++++++--- lib/coffee_script/value.rb | 6 +++--- test/unit/test_lexer.rb | 6 ++++++ test/unit/test_parser.rb | 6 ++++++ 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/TODO b/TODO index 7ec3b61ee4..14a7bfa828 100644 --- a/TODO +++ b/TODO @@ -1,19 +1,9 @@ TODO: -* Finish the examples. +* Finish the doc page. * Write a test suite that checks the JS evaluation. -* Figure out a generic way to transform statements into expressions, and - use it recursively for returns and assigns on whiles, fors, ifs, etc. - -* Create the documentation page. (amy, idle) - uv -c . -s coffeescript -t amy --no-lines examples/code.cs > code.html - -* Object comprehensions would be easy to add to array comprehensions -- if - we knew that the variable in question is, indeed, an object. Check for - length? Special syntax to tag it? - * Is it possible to close blocks (functions, ifs, trys) without an explicit block delimiter or significant whitespace? diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index fc363801d2..4293f44a57 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -32,6 +32,7 @@ class Lexer # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ MULTILINER = /[\r\n]/ + COMMENT_CLEANER = /^\s*#\s*/ # Tokens that always constitute the start of an expression. EXP_START = ['{', '(', '['] @@ -111,6 +112,8 @@ def regex_token # Matches and consumes comments. def remove_comment return false unless comment = @chunk[COMMENT, 1] + cleaned = comment.gsub(COMMENT_CLEANER, '') + @prev_comment ? @prev_comment << cleaned : @prev_comment = [cleaned] @i += comment.length end @@ -139,10 +142,12 @@ def literal_token @i += value.length end - # Add a token to the results, taking note of the line number for syntax - # errors later in the parse. + # Add a token to the results, taking note of the line number, and + # immediately-preceding comment. def token(tag, value) - @tokens << [tag, Value.new(value, @line)] + comment = @prev_comment + @prev_comment = nil + @tokens << [tag, Value.new(value, @line, comment)] end # Peek at the previous token. diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb index c724b5ca00..249190fa60 100644 --- a/lib/coffee_script/value.rb +++ b/lib/coffee_script/value.rb @@ -3,10 +3,10 @@ module CoffeeScript # Instead of producing raw Ruby objects, the Lexer produces values of this # class, wrapping native objects tagged with line number information. class Value - attr_reader :line + attr_reader :line, :comment - def initialize(value, line) - @value, @line = value, line + def initialize(value, line, comment=nil) + @value, @line, @comment = value, line, comment end def to_str diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index 8c29b89632..a16bf4a552 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -35,6 +35,12 @@ def test_lexing_if_statement [")", ")"], [:IF, "if"], [:IDENTIFIER, "happy"]] end + def test_lexing_comment + code = "a: 1\n # comment\n # on two lines\nb: 2" + token = @lex.tokenize(code).detect {|t| t[1].comment } + assert token[1].comment == ['comment', 'on two lines'] + end + def test_lexing tokens = @lex.tokenize(File.read('test/fixtures/each.cs')) assert tokens.inspect == File.read('test/fixtures/each.tokens') diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index d7c1f457ee..e2e0e58921 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -52,6 +52,12 @@ def test_parsing_array_comprehension assert nodes.first.source.literal.objects.last.value == "5" end + def test_parsing_comment + nodes = @par.parse("a: 1\n # comment\nb: 2").expressions + # Comments are being passed through to the raw values, + # but are not yet properly exposed within the nodes. + end + def test_parsing nodes = @par.parse(File.read('test/fixtures/each.cs')) assign = nodes.expressions.first From 65809d08f62eb6a5804bb438861b88bd3e0f4331 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 22 Dec 2009 11:27:19 -0500 Subject: [PATCH 048/303] first draft of parsing and printing along comments -- unfortunately, not yet working within objects and arrays --- lib/coffee_script/grammar.y | 7 +++++++ lib/coffee_script/lexer.rb | 20 +++++++++----------- lib/coffee_script/nodes.rb | 36 +++++++++++++++++++++++++++++++++--- lib/coffee_script/value.rb | 6 +++--- test/fixtures/each.js | 3 +++ test/fixtures/each.tokens | 2 +- test/unit/test_lexer.rb | 5 +++-- test/unit/test_parser.rb | 5 ++--- 8 files changed, 61 insertions(+), 23 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index a1d759dacd..4b88bbc70e 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -13,6 +13,7 @@ token SWITCH CASE token EXTENDS SUPER token DELETE token NEWLINE +token COMMENT token JS # Declare order of operations. @@ -81,6 +82,7 @@ rule | While | For | Switch + | Comment ; # All tokens that can terminate an expression. @@ -123,6 +125,11 @@ rule RETURN Expression { result = ReturnNode.new(val[1]) } ; + # A comment. + Comment: + COMMENT { result = CommentNode.new(val[0]) } + ; + # Arithmetic and logical operators # For Ruby's Operator precedence, see: # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 4293f44a57..8a832a59ae 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -24,15 +24,15 @@ class Lexer JS = /\A(`(.*?)`)/ OPERATOR = /\A([+\*&|\/\-%=<>]+)/ WHITESPACE = /\A([ \t\r]+)/ - NEWLINE = /\A([\r\n]+)/ - COMMENT = /\A(#[^\r\n]*)/ + NEWLINE = /\A(\n+)/ + COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ - MULTILINER = /[\r\n]/ - COMMENT_CLEANER = /^\s*#\s*/ + MULTILINER = /\n/ + COMMENT_CLEANER = /^\s*#/ # Tokens that always constitute the start of an expression. EXP_START = ['{', '(', '['] @@ -61,7 +61,7 @@ def extract_next_token return if string_token return if js_token return if regex_token - return if remove_comment + return if comment_token return if whitespace_token return literal_token end @@ -110,10 +110,10 @@ def regex_token end # Matches and consumes comments. - def remove_comment + def comment_token return false unless comment = @chunk[COMMENT, 1] - cleaned = comment.gsub(COMMENT_CLEANER, '') - @prev_comment ? @prev_comment << cleaned : @prev_comment = [cleaned] + token(:COMMENT, comment.gsub(COMMENT_CLEANER, '').split(MULTILINER)) + token("\n", "\n") @i += comment.length end @@ -145,9 +145,7 @@ def literal_token # Add a token to the results, taking note of the line number, and # immediately-preceding comment. def token(tag, value) - comment = @prev_comment - @prev_comment = nil - @tokens << [tag, Value.new(value, @line, comment)] + @tokens << [tag, Value.new(value, @line)] end # Peek at the previous token. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 0785cf77cf..6f25b44f8f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -46,6 +46,8 @@ class Expressions < Node statement attr_reader :expressions + STRIP_TRAILING_WHITESPACE = /\s+$/ + # Wrap up a node as an Expressions, unless it already is. def self.wrap(node) node.is_a?(Expressions) ? node : Expressions.new([node]) @@ -66,10 +68,17 @@ def unwrap @expressions.length == 1 ? @expressions.first : self end + # Is the node last in this block of expressions. + def last?(node) + @last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1 + node == @expressions[@last_index] + end + # If this is the top-level Expressions, wrap everything in a safety closure. def root_compile - options = {:indent => TAB, :scope => Scope.new} - "(function(){\n#{compile(options)}\n})();" + code = compile(:indent => TAB, :scope => Scope.new) + code.gsub!(STRIP_TRAILING_WHITESPACE, '') + "(function(){\n#{code}\n})();" end # The extra fancy is to handle pushing down returns and assignments @@ -78,7 +87,7 @@ def compile(options={}) return root_compile unless options[:scope] code = @expressions.map { |node| o = super(options) - if node == @expressions.last && (o[:return] || o[:assign]) + if last?(node) && (o[:return] || o[:assign]) if o[:return] if node.statement? || node.custom_return? "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" @@ -149,6 +158,27 @@ def compile(o={}) end end + # Pass through CoffeeScript comments into JavaScript comments at the + # same position. + class CommentNode < Node + statement + + def initialize(lines) + @lines = lines.value + end + + def line_ending + '' + end + + def compile(o={}) + delimiter = "\n#{o[:indent]}//" + comment = "#{delimiter}#{@lines.join(delimiter)}" + write(comment) + end + + end + # Node for a function invocation. Takes care of converting super() calls into # calls against the prototype's function of the same name. class CallNode < Node diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb index 249190fa60..532e1db815 100644 --- a/lib/coffee_script/value.rb +++ b/lib/coffee_script/value.rb @@ -3,10 +3,10 @@ module CoffeeScript # Instead of producing raw Ruby objects, the Lexer produces values of this # class, wrapping native objects tagged with line number information. class Value - attr_reader :line, :comment + attr_reader :value, :line - def initialize(value, line, comment=nil) - @value, @line, @comment = value, line, comment + def initialize(value, line) + @value, @line = value, line end def to_str diff --git a/test/fixtures/each.js b/test/fixtures/each.js index 56f0fabcd3..9ee1dcea7b 100644 --- a/test/fixtures/each.js +++ b/test/fixtures/each.js @@ -1,4 +1,7 @@ (function(){ + + // The cornerstone, an each implementation. + // Handles objects implementing forEach, arrays, and raw objects. _.each = function(obj, iterator, context) { var index = 0; try { diff --git a/test/fixtures/each.tokens b/test/fixtures/each.tokens index 5e50931f1f..360c5088cb 100644 --- a/test/fixtures/each.tokens +++ b/test/fixtures/each.tokens @@ -1 +1 @@ -[["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file +[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index a16bf4a552..0f3297b4ee 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -37,8 +37,9 @@ def test_lexing_if_statement def test_lexing_comment code = "a: 1\n # comment\n # on two lines\nb: 2" - token = @lex.tokenize(code).detect {|t| t[1].comment } - assert token[1].comment == ['comment', 'on two lines'] + assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"], [:NUMBER, "1"], + ["\n", "\n"], [:COMMENT, [" comment", " on two lines"]], ["\n", "\n"], + [:IDENTIFIER, "b"], [":", ":"], [:NUMBER, "2"]] end def test_lexing diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index e2e0e58921..4a906edc87 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -54,13 +54,12 @@ def test_parsing_array_comprehension def test_parsing_comment nodes = @par.parse("a: 1\n # comment\nb: 2").expressions - # Comments are being passed through to the raw values, - # but are not yet properly exposed within the nodes. + assert nodes[1].is_a? CommentNode end def test_parsing nodes = @par.parse(File.read('test/fixtures/each.cs')) - assign = nodes.expressions.first + assign = nodes.expressions[1] assert assign.is_a? AssignNode assert assign.variable.literal == '_' assert assign.value.is_a? CodeNode From ec58d6fda244ae05bce4793f613ff093bae1eb03 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 22 Dec 2009 11:50:43 -0500 Subject: [PATCH 049/303] got comments within object and array literals working out --- lib/coffee_script/grammar.y | 1 + lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 13 ++++++++++--- test/fixtures/inner_comments.cs | 15 +++++++++++++++ test/fixtures/inner_comments.js | 15 +++++++++++++++ test/unit/test_parser.rb | 5 +++++ 6 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/inner_comments.cs create mode 100644 test/fixtures/inner_comments.js diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 4b88bbc70e..7a81a84ad5 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -118,6 +118,7 @@ rule # Assignment within an object literal. AssignObj: IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) } + | Comment { result = val[0] } ; # A return statement. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 8a832a59ae..89a6eabfcb 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -32,7 +32,7 @@ class Lexer # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ MULTILINER = /\n/ - COMMENT_CLEANER = /^\s*#/ + COMMENT_CLEANER = /(^\s*#|\n\s*$)/ # Tokens that always constitute the start of an expression. EXP_START = ['{', '(', '['] diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 6f25b44f8f..ef86d4bc13 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -423,7 +423,10 @@ def compile(o={}) o = super(o) indent = o[:indent] o[:indent] += TAB - props = @properties.map {|p| o[:indent] + p.compile(o) }.join(",\n") + props = @properties.map { |prop| + joiner = prop == @properties.last ? '' : prop.is_a?(CommentNode) ? "\n" : ",\n" + o[:indent] + prop.compile(o) + joiner + }.join('') write("{\n#{props}\n#{indent}}") end end @@ -438,8 +441,12 @@ def initialize(objects=[]) def compile(o={}) o = super(o) - objects = @objects.map {|obj| obj.compile(o) }.join(', ') - write("[#{objects}]") + objects = @objects.map { |obj| + joiner = obj.is_a?(CommentNode) ? "\n#{o[:indent] + TAB}" : obj == @objects.last ? '' : ', ' + obj.compile(o.merge(:indent => o[:indent] + TAB)) + joiner + }.join('') + ending = objects.include?("\n") ? "\n#{o[:indent]}]" : ']' + write("[#{objects}#{ending}") end end diff --git a/test/fixtures/inner_comments.cs b/test/fixtures/inner_comments.cs new file mode 100644 index 0000000000..48121ffde6 --- /dev/null +++ b/test/fixtures/inner_comments.cs @@ -0,0 +1,15 @@ +object: { + a: 1 + # Comments between the elements. + b: 2 + # Like this. + c: 3 +} + +array: [ + 1 + # Comments between the elements. + 2 + # Like this. + 3 +] \ No newline at end of file diff --git a/test/fixtures/inner_comments.js b/test/fixtures/inner_comments.js new file mode 100644 index 0000000000..1b4386d209 --- /dev/null +++ b/test/fixtures/inner_comments.js @@ -0,0 +1,15 @@ +(function(){ + var object = { + a: 1, + // Comments between the elements. + b: 2, + // Like this. + c: 3 + }; + var array = [1, + // Comments between the elements. + 2, + // Like this. + 3 + ]; +})(); \ No newline at end of file diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 4a906edc87..45d6ff09b9 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -57,6 +57,11 @@ def test_parsing_comment assert nodes[1].is_a? CommentNode end + def test_parsing_inner_comments + nodes = @par.parse(File.read('test/fixtures/inner_comments.cs')) + assert nodes.compile == File.read('test/fixtures/inner_comments.js') + end + def test_parsing nodes = @par.parse(File.read('test/fixtures/each.cs')) assign = nodes.expressions[1] From e916d4648d0b6ba93b0a9d983e4ee1261dcb3685 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 22 Dec 2009 12:08:29 -0500 Subject: [PATCH 050/303] removed all traces of 'extends' -- it's not any shorter or more convenient than just setting the prototype --- documentation/cs/super.cs | 4 ++-- documentation/js/array_comprehensions.js | 3 +++ documentation/js/expressions_assignment.js | 3 --- documentation/js/intro.js | 2 ++ documentation/js/punctuation.js | 4 ++++ documentation/js/super.js | 1 - documentation/js/switch.js | 4 +++- examples/code.cs | 4 ++-- index.html | 11 +++++++---- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 10 ++-------- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 15 --------------- 13 files changed, 27 insertions(+), 38 deletions(-) delete mode 100644 documentation/js/expressions_assignment.js diff --git a/documentation/cs/super.cs b/documentation/cs/super.cs index 21c89a26d4..2c97fb9941 100644 --- a/documentation/cs/super.cs +++ b/documentation/cs/super.cs @@ -3,13 +3,13 @@ alert(this.name + " moved " + meters + "m."). Snake: name => this.name: name. -Snake extends new Animal() +Snake.prototype: new Animal() Snake.prototype.move: => alert("Slithering...") super(5). Horse: name => this.name: name. -Horse extends new Animal() +Horse.prototype: new Animal() Horse.prototype.move: => alert("Galloping...") super(45). diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index 75242df70f..3fcbd836fe 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -1,4 +1,6 @@ (function(){ + + // Eat lunch. var lunch; var a = ['toast', 'cheese', 'wine']; var d = []; @@ -7,6 +9,7 @@ d[b] = food.eat(); } lunch = d; + // Zebra-stripe a table. var e = table; for (var f=0, g=e.length; f this.name: name. -Snake extends Animal +Snake.prototype: Animal Snake.prototype.move: => alert('Slithering...') super(5). Horse: name => this.name: name. -Horse extends Animal +Horse.prototype: Animal Horse.prototype.move: => alert('Galloping...') super(45). diff --git a/index.html b/index.html index 1148000306..63b99d6a32 100644 --- a/index.html +++ b/index.html @@ -275,7 +275,9 @@

Table of Contents

# Zebra-stripe a table. highlight(row) for row, i in table if i % 2 is 0. -
var lunch;
+
+// Eat lunch.
+var lunch;
 var a = ['toast', 'cheese', 'wine'];
 var d = [];
 for (var b=0, c=a.length; b<c; b++) {
@@ -283,6 +285,7 @@ 

Table of Contents

d[b] = food.eat(); } lunch = d; +// Zebra-stripe a table. var e = table; for (var f=0, g=e.length; f<g; f++) { var row = e[f]; @@ -333,7 +336,6 @@

Table of Contents

sam.move() tom.move()
var Animal = function() {
-
 };
 Animal.prototype.move = function(meters) {
   return alert(this.name + " moved " + meters + "m.");
@@ -359,7 +361,6 @@ 

Table of Contents

sam.move(); tom.move();

+ +

Installation and Usage

+ +
+sudo gem install coffee-script
+ +

+ Installing the gem provides the coffee-script command, which can + be used to compile CoffeeScript .cs files into JavaScript, as + well as debug. By default, coffee-script writes out the + JavaScript as .js files in the same directory, but output + can be customized with the following options: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-o, --output [DIR] + Write out all compiled JavaScript files into the specified directory. +
-w, --watch + Watch the modification times of the named scripts, recompiling as + soon as a change occurs. +
-p, --print + Instead of writing out the JavaScript as a file, print it + directly to stdout. +
-l, --lint + If the jsl (JavaScript Lint) command is installed, use it + to check the compilation of a CoffeeScript file. +
-e, --eval + Compile and print a little snippet of CoffeeScript directly from the + command line. For example:
coffee-script -e "square: x => x * x." +
-t, --tokens + Instead of parsing the CoffeeScript, just lex it, and print out the + token stream: [:IDENTIFIER, "square"], [":", ":"], [:PARAM, "x"] ... +
-v, --verbose + As the JavaScript is being generated, print out every step of code + generation, including lexical scope and the node in the + AST. +
+ +

+ Examples: +

+ +
+coffee-script path/to/script.cs
+coffee-script --watch --lint experimental.cs
+coffee-script --print app/scripts/*.cs > concatenation.js
+ +

Language Reference

+

- In all of the following examples, the source CoffeeScript is provided on - the left, and the direct compilation into JavaScript is on the right. + + This document is structured so that it can be read from top to bottom, + if you like. Later sections use ideas and syntax previously introduced. + In all of the following examples, the source CoffeeScript is provided on + the left, and the direct compilation into JavaScript is on the right. + Familiarity with JavaScript is assumed, although it would certainly + be nice to have a tutorial that builds from the ground up in the future. +

-
- -

- Punctuation Primer - You don't need to use semicolons to (;) terminate expressions, ending - the line will do just as well. So newlines can matter, but whitespace is - not otherwise significant. Instead of using curly braces ({ }) - to delimit blocks of code, a period (.) marks the end of a - function, if statement, or try/catch. -

- - -

- Functions and Invocation - Let's start with the best part, shall we? Function literals are my - absolute favorite thing about CoffeeScript. -

-
square: x => x * x.
+    

+ Punctuation Primer + You don't need to use semicolons ; to terminate expressions, ending + the line will do just as well. All other whitespace is + not significant. Instead of using curly braces { } + to delimit a block of code, use a period . to mark the end of a + function, if-statement, switch, or try/catch. +

+ +

+ Functions and Invocation + Let's start with the best part, shall we? Functions are defined + by a list of parameters, an arrow, and the function body. The empty + function looks like this: =>. +

+
square: x => x * x.
 cube:   x => square(x) * x.
 
var square = function(x) {
   return x * x;
@@ -98,12 +267,28 @@ 

Table of Contents

}; ;alert(cube(5));'>run: cube(5)
-

- Objects and Arrays - Object and Array literals look very similar. When you spread out - each assignment on a separate line, the commas are optional. -

-
song: ["do", "re", "mi", "fa", "so"]
+    

+ Assignment + Use a colon : to assign, as in + JSON. Equal signs are only needed for + mathy things. +

+
greeting: "Hello CoffeeScript"
+difficulty: 0.5
+
var greeting = "Hello CoffeeScript";
+var difficulty = 0.5;
+

+ +

+ Objects and Arrays + Object and Array literals look very similar to their JavaScript cousins. + When you spread out each assignment on a separate line, the commas are + optional. In this way, assigning object properties looks the same as + assigning local variables. +

+
song: ["do", "re", "mi", "fa", "so"]
 ages: {
   max: 10
   ida: 9
@@ -122,31 +307,14 @@ 

Table of Contents

tim: 11 }; ;alert(song.join(","));'>run: song.join(",")
-

-

- -

- Assignment - All assignment in CoffeeScript, whether to a variable or to an object - property, uses a colon. Equal signs are only needed for mathy things. -

-
greeting: "Hello CoffeeScript"
-difficulty: 0.5
-
var greeting = "Hello CoffeeScript";
-var difficulty = 0.5;
-

-

-

- -

- Lexical Scoping and Variable Safety - The CoffeeScript compiler takes care to make sure that all of your variables - are properly defined within lexical scope — you never need to declare - var yourself. -

-
num: 1
+
+    

+ Lexical Scoping and Variable Safety + The CoffeeScript compiler takes care to make sure that all of your variables + are properly defined within lexical scope — you never need to declare + var yourself. +

+
num: 1
 change_numbers: =>
   num: 2
   new_num: 3.
@@ -166,17 +334,28 @@ 

Table of Contents

}; var new_num = change_numbers(); ;alert(new_num);'>run: new_num
-

- Notice how the variables are declared with var the first time - they appear. The second reference of num, within the function, - is not redeclared because num is still in scope. As opposed - to the second new_num, in the last line. -

- -

- Conditionals, Ternaries, and Conditional Assignment -

-
mood: greatly_improved if singing
+    

+ Notice how the variables are declared with var the first time + they appear. The second reference of num, within the function, + is not redeclared because num is still in scope. As opposed + to the second new_num, in the last line. +

+

+ Although suppressed within this documentation, all + CoffeeScript output is wrapped in an anonymous function: + (function(){ ... })(); This safety wrapper, combined with the + automatic generation of the var keyword, make it exceedingly difficult + to pollute the global namespace by accident. +

+ +

+ Conditionals, Ternaries, and Conditional Assignment + If/else statements can be written without the use of parenthesis and + curly brackets. As with functions and other block expressions, conditionals + are closed with periods. No period is necessary when using the single-line + postfix form, with the if at the end. +

+
mood: greatly_improved if singing
 
 if happy and knows_it
   claps_hands()
@@ -196,17 +375,23 @@ 

Table of Contents

var date = friday ? sue : jill; expensive = expensive || do_the_math();

-

-

- -

- Everything is an Expression - You might have noticed how even though we don't add return statements - to CoffeScript functions, they nonetheless return their final value. - The CoffeeScript compiler tries to make sure that every little language - construct can be used as an expression. -

-
grade: student =>
+    

+ CoffeeScript includes the conditional assignment operators: ||:, + which only assigns a value to a variable if the variable's current value + is falsy, and &&:, which will only replace the value of + truthy variables. +

+ +

+ Everything is an Expression (at least, as much as possible) + You might have noticed how even though we don't add return statements + to CoffeScript functions, they nonetheless return their final value. + The CoffeeScript compiler tries to make sure that all statements in the + language can be used as expressions. Watch how the return gets + pushed down into each possible branch of execution, in the function + below. +

+
grade: student =>
   if student.excellent_work
     "A+"
   else if student.okay_stuff
@@ -219,11 +404,7 @@ 

Table of Contents

if (student.excellent_work) { return "A+"; } else if (student.okay_stuff) { - return if (student.tried_hard) { - return "B"; - } else { - return "B-"; - }; + return student.tried_hard ? "B" : "B-"; } else { return "C"; } @@ -233,30 +414,23 @@

Table of Contents

if (student.excellent_work) { return "A+"; } else if (student.okay_stuff) { - return if (student.tried_hard) { - return "B"; - } else { - return "B-"; - }; + return student.tried_hard ? "B" : "B-"; } else { return "C"; } }; var eldest = 24 > 21 ? "Liz" : "Ike"; ;alert(eldest);'>run: eldest
-

- When compiling a function definition, CoffeeScript tries to push down - the return statement to each of the potential final lines of the function. - It uses the same mechanism to push down assignment statements. If statement - are compiled into ternary operators when possible, so that they can be used - as expressions. -

- -

- While Loops - The only low-level loop that CoffeeScript provides is the while loop. -

-
while demand > supply
+    

+ The same mechanism is used to push down assignment statements, switch + statements, and if-elses (although the ternary operator is preferred). +

+ +

+ While Loops + The only low-level loop that CoffeeScript provides is the while loop. +

+
while demand > supply
   sell()
   restock().
 
@@ -269,16 +443,23 @@ 

Table of Contents

buy(); }

+

+ Other JavaScript loops, such as for loops and do-while loops + can be mimicked by variations on while, but the hope is that you + won't need to do that with CoffeeScript, either because you're using + each (forEach) style iterators, or... +

-

- Array Comprehensions - Most of your looping needs should be handled by array comprehensions. - They replace (and compile into) for loops, handling - each/forEach style loops, as well as select/filter. - Unlike for loops, array comprehensions are expressions, and can be returned - and assigned. -

-
# Eat lunch.
+    

+ Array Comprehensions + For your looping needs, CoffeeScript provides array comprehensions + similar to Python's. They replace (and compile into) for loops, with + optional guard clauses and the value of the current array index. + Unlike for loops, array comprehensions are expressions, and can be returned + and assigned. They should be able to handle most places where you otherwise + would use a loop, each/forEach, map, or select/filter. +

+
# Eat lunch.
 lunch: food.eat() for food in ['toast', 'cheese', 'wine'].
 
 # Zebra-stripe a table.
@@ -302,13 +483,13 @@ 

Table of Contents

}

-

- Array Slice Literals - CoffeeScript includes a literal syntax for extracting slices of arrays. - The first argument is the index of the first element in the slice, and - the second is the index of the last one. -

-
nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+    

+ Array Slice Literals + CoffeeScript includes syntax for extracting slices of arrays. + The first argument is the index of the first element in the slice, and + the second is the index of the last one. +

+
nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 three_to_six: nums[3, 6]
 
var nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
 var three_to_six = nums.slice(3, 6 + 1);
@@ -316,13 +497,16 @@ 

Table of Contents

var three_to_six = nums.slice(3, 6 + 1); ;alert(three_to_six);'>run: three_to_six
-

- Inheritance, and Calling Super from a Subclass - JavaScript's prototypal inheritance has always been a bit of a - brain-bender, with a whole family tree of libraries (Base2, Prototype - ). -

-
Animal: => .
+    

+ Calling Super from a Subclass + JavaScript's prototypal inheritance has always been a bit of a + brain-bender, with a whole family tree of libraries that provide a cleaner + syntax for classical inheritance on top of JavaScript's prototypes: + Base2, + Prototype.js, + JS.Class, etc. +

+
Animal: => .
 Animal.prototype.move: meters =>
   alert(this.name + " moved " + meters + "m.").
 
@@ -395,12 +579,12 @@ 

Table of Contents

tom.move(); ;'>run
-

- Embedded JavaScript - If you ever need to interpolate literal JavaScript snippets, you can - use backticks to pass JavaScript straight through. -

-
js: => `alert("Hello JavaScript");`.
+    

+ Embedded JavaScript + If you ever need to interpolate literal JavaScript snippets, you can + use backticks to pass JavaScript straight through. +

+
js: => `alert("Hello JavaScript");`.
 
 js() if 10 > 9
 
var js = function() {
@@ -417,16 +601,32 @@ 

Table of Contents

} ;'>run
-

- Switch/Case/Else - Switch statements in JavaScript are fundamentally broken. You can only - do string comparisons, and need to break at the end of each case - statment to prevent falling through to the default case. CoffeeScript - compiles switch statements into if-else chains, allowing you to - compare any object (via ===), preventing fall-through, and resulting - in a returnable expression. -

-
switch day
+    

+ Aliases + Because the == operator frequently causes undesirable coercion, + is intransitive, and has a different meaning than in other languages, + CoffeeScript compiles == into ===, and != into + !==. +

+

+ is also compiles into ===, + and aint into !==. +

+

+ +

+ + +

+ Switch/Case/Else + Switch statements in JavaScript are fundamentally broken. You can only + do string comparisons, and need to break at the end of each case + statment to prevent falling through to the default case. CoffeeScript + compiles switch statements into if-else chains, allowing you to + compare any object (via ===), preventing fall-through, and resulting + in a returnable expression. +

+
switch day
 case "Tuesday"   then eat_breakfast()
 case "Wednesday" then go_to_the_park()
 case "Saturday"
@@ -438,9 +638,7 @@ 

Table of Contents

} else if (day === "Wednesday") { go_to_the_park(); } else if (day === "Saturday") { - if (day === bingo_day) { - go_to_bingo(); - }; + day === bingo_day ? go_to_bingo() : null; } else if (day === "Sunday") { go_to_church(); } else { @@ -448,16 +646,16 @@

Table of Contents

}

-

- Try/Catch/Finally - Try/catch statements just about the same as JavaScript (although - they work as expressions). No braces required. -

-
try
+    

+ Try/Catch/Finally + Try/catch statements just about the same as JavaScript (although + they work as expressions). No braces required. +

+
try
   all_hell_breaks_loose()
   cats_and_dogs_living_together()
 catch error
-  print( error )
+  print(error)
 finally
   clean_up().
 
try {
@@ -470,11 +668,11 @@ 

Table of Contents

}

-

- Multiline Strings - Multiline strings are allowed in CoffeeScript. -

-
moby_dick: "Call me Ishmael. Some years ago --
+    

+ Multiline Strings + Multiline strings are allowed in CoffeeScript. +

+
moby_dick: "Call me Ishmael. Some years ago --
 never mind how long precisely -- having little
 or no money in my purse, and nothing particular
 to interest me on shore, I thought I would sail
@@ -488,8 +686,6 @@ 

Table of Contents

world...";

-
-
diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 2c313bdf94..1172d7c2e8 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -41,12 +41,13 @@ def usage # Compiles (or partially compiles) the source CoffeeScript file, returning # the desired JS, tokens, or lint results. def compile_javascript(source) - return tokens(source) if @options[:tokens] - contents = compile(source) - return unless contents - return puts(contents) if @options[:print] - return lint(contents) if @options[:lint] - File.open(path_for(source), 'w+') {|f| f.write(contents) } + script = File.read(source) + return tokens(script) if @options[:tokens] + js = compile(script, source) + return unless js + return puts(js) if @options[:print] + return lint(js) if @options[:lint] + File.open(path_for(source), 'w+') {|f| f.write(js) } end # Spins up a watcher thread to keep track of the modification times of the @@ -92,18 +93,22 @@ def lint(js) # Eval a little piece of CoffeeScript directly from the command line. def eval_scriptlet - puts CoffeeScript.compile(@sources.join(' ')) + script = @sources.join(' ') + return tokens(script) if @options[:tokens] + js = compile(script) + return lint(js) if @options[:lint] + puts js end # Print the tokens that the lexer generates from a source script. - def tokens(source) - puts Lexer.new.tokenize(File.read(source)).inspect + def tokens(script) + puts Lexer.new.tokenize(script).inspect end # Compile a single source file to JavaScript. - def compile(source) + def compile(script, source='') begin - CoffeeScript.compile(File.open(source)) + CoffeeScript.compile(script) rescue CoffeeScript::ParseError => e STDERR.puts e.message(source) exit(1) unless @options[:watch] diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 47bb983546..ef00e4c8ec 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -26,12 +26,12 @@ prechigh left '<=' '<' '>' '>=' right '==' '!=' IS AINT left '&&' '||' AND OR - right '-=' '+=' '/=' '*=' '||=' '&&=' + right '-=' '+=' '/=' '*=' right DELETE left "." right THROW FOR IN WHILE NEW left UNLESS IF ELSE - left ":" + left ":" '||:' '&&:' right RETURN preclow @@ -173,8 +173,8 @@ rule | Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '||:' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '&&:' Expression { result = OpNode.new(val[1], val[0], val[2]) } | DELETE Expression { result = OpNode.new(val[0], val[1]) } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 9016ab392c..5cf50aa095 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -112,6 +112,7 @@ def regex_token # Matches and consumes comments. def comment_token return false unless comment = @chunk[COMMENT, 1] + @line += comment.scan(MULTILINER).length token(:COMMENT, comment.gsub(COMMENT_CLEANER, '').split(MULTILINER)) token("\n", "\n") @i += comment.length diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 1b5d2a89ba..7328e402b6 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -342,7 +342,7 @@ class OpNode < Node "aint" => "!==", 'not' => '!', } - CONDITIONALS = ['||=', '&&='] + CONDITIONALS = ['||:', '&&:'] attr_reader :operator, :first, :second From a4d014549b321a5c19067cb8d202e9de83ab703f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 23 Dec 2009 19:42:44 -0500 Subject: [PATCH 053/303] broken waypoint, but fixed line numbers with the new JS comments --- lib/coffee_script/lexer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 5cf50aa095..5ea078443f 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -22,7 +22,7 @@ class Lexer NUMBER = /\A\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b/i STRING = /\A(""|''|"(.*?)[^\\]"|'(.*?)[^\\]')/m JS = /\A(`(.*?)`)/ - OPERATOR = /\A([+\*&|\/\-%=<>]+)/ + OPERATOR = /\A([+\*&|\/\-%=<>:]+)/ WHITESPACE = /\A([ \t\r]+)/ NEWLINE = /\A(\n+)/ COMMENT = /\A((#[^\n]*\s*)+)/m From 64879cdc66837978442fbc4a1ba7faa90663cd45 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 23 Dec 2009 20:24:55 -0500 Subject: [PATCH 054/303] added yes, no, on and off as boolean aliases and a nice aliases section to the docs --- documentation/cs/aliases.cs | 5 ++ documentation/cs/embedded.cs | 5 +- documentation/index.html.erb | 32 +++++---- documentation/js/aliases.js | 12 ++++ documentation/js/embedded.js | 9 +-- index.html | 68 +++++++++++-------- .../Syntaxes/CoffeeScript.tmLanguage | 4 +- lib/coffee_script/grammar.y | 11 +-- lib/coffee_script/lexer.rb | 4 +- lib/coffee_script/nodes.rb | 4 +- 10 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 documentation/cs/aliases.cs create mode 100644 documentation/js/aliases.js diff --git a/documentation/cs/aliases.cs b/documentation/cs/aliases.cs new file mode 100644 index 0000000000..3bbbb490d7 --- /dev/null +++ b/documentation/cs/aliases.cs @@ -0,0 +1,5 @@ +launch() if ignition is on + +volume: 10 if band aint spinal_tap + +let_the_wild_rumpus_begin() unless answer is no diff --git a/documentation/cs/embedded.cs b/documentation/cs/embedded.cs index 5a4f0d2478..400c15b46e 100644 --- a/documentation/cs/embedded.cs +++ b/documentation/cs/embedded.cs @@ -1,3 +1,4 @@ -js: => `alert("Hello JavaScript");`. +hi: `function() { + return [document.title, "Hello JavaScript"].join(": "); +}` -js() if 10 > 9 \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index b2ea54868f..eba228f505 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -19,7 +19,7 @@ - CoffeeScript, briefly... + CoffeeScript @@ -33,7 +33,7 @@ CoffeeScript is a little language that compiles into JavaScript. Think of it as JavaScript's less ostentatious kid brother — the same genes, the same accent, but a different sense of style. Apart from a handful of - bonus goodies, statements in CoffeeScript correspond one-to-one with their + bonus goodies, statements in CoffeeScript correspond one-to-one with their equivalent in JavaScript, it's just another way of saying it.

@@ -232,7 +232,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

CoffeeScript includes the conditional assignment operators: ||:, which only assigns a value to a variable if the variable's current value - is falsy, and &&:, which will only replace the value of + is falsy, and &&:, which only replaces the value of truthy variables.

@@ -257,15 +257,15 @@ coffee-script --print app/scripts/*.cs > concatenation.js

<%= code_for('while') %>

- Other JavaScript loops, such as for loops and do-while loops - can be mimicked by variations on while, but the hope is that you - won't need to do that with CoffeeScript, either because you're using + Other JavaScript loops, such as for loops and do-while loops + can be mimicked by variations on while, but the hope is that you + won't need to do that with CoffeeScript, either because you're using each (forEach) style iterators, or...

Array Comprehensions - For your looping needs, CoffeeScript provides array comprehensions + For your looping needs, CoffeeScript provides array comprehensions similar to Python's. They replace (and compile into) for loops, with optional guard clauses and the value of the current array index. Unlike for loops, array comprehensions are expressions, and can be returned @@ -287,7 +287,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

JavaScript's prototypal inheritance has always been a bit of a brain-bender, with a whole family tree of libraries that provide a cleaner syntax for classical inheritance on top of JavaScript's prototypes: - Base2, + Base2, Prototype.js, JS.Class, etc.

@@ -298,24 +298,26 @@ coffee-script --print app/scripts/*.cs > concatenation.js
If you ever need to interpolate literal JavaScript snippets, you can use backticks to pass JavaScript straight through.

- <%= code_for('embedded', true) %> + <%= code_for('embedded', 'hi()') %>

Aliases - Because the == operator frequently causes undesirable coercion, + Because the == operator frequently causes undesirable coercion, is intransitive, and has a different meaning than in other languages, CoffeeScript compiles == into ===, and != into !==. + In addition, is compiles into ===, + and aint into !==.

- is also compiles into ===, - and aint into !==. + As in YAML, on and yes + are the same as boolean true, while off and no are boolean false.

- + For single-line statements, unless can be used as the inverse of if.

- - + <%= code_for('aliases') %> +

Switch/Case/Else Switch statements in JavaScript are fundamentally broken. You can only diff --git a/documentation/js/aliases.js b/documentation/js/aliases.js new file mode 100644 index 0000000000..f30e00ea67 --- /dev/null +++ b/documentation/js/aliases.js @@ -0,0 +1,12 @@ +(function(){ + if (ignition === true) { + launch(); + } + var volume; + if (band !== spinal_tap) { + volume = 10; + } + if (!(answer === false)) { + let_the_wild_rumpus_begin(); + } +})(); \ No newline at end of file diff --git a/documentation/js/embedded.js b/documentation/js/embedded.js index 4686b68813..3c16b1856d 100644 --- a/documentation/js/embedded.js +++ b/documentation/js/embedded.js @@ -1,8 +1,5 @@ (function(){ - var js = function() { - return alert("Hello JavaScript"); - }; - if (10 > 9) { - js(); - } + var hi = function() { + return [document.title, "Hello JavaScript"].join(": "); +}; })(); \ No newline at end of file diff --git a/index.html b/index.html index 4417434cd0..6f5e195c51 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - CoffeeScript, briefly... + CoffeeScript @@ -19,7 +19,7 @@

CoffeeScript

CoffeeScript is a little language that compiles into JavaScript. Think of it as JavaScript's less ostentatious kid brother — the same genes, the same accent, but a different sense of style. Apart from a handful of - bonus goodies, statements in CoffeeScript correspond one-to-one with their + bonus goodies, statements in CoffeeScript correspond one-to-one with their equivalent in JavaScript, it's just another way of saying it.

@@ -363,7 +363,7 @@

Language Reference

date: if friday then sue else jill. -expensive ||= do_the_math() +expensive ||: do_the_math()
var mood;
 if (singing) {
   mood = greatly_improved;
@@ -378,7 +378,7 @@ 

Language Reference

CoffeeScript includes the conditional assignment operators: ||:, which only assigns a value to a variable if the variable's current value - is falsy, and &&:, which will only replace the value of + is falsy, and &&:, which only replaces the value of truthy variables.

@@ -444,15 +444,15 @@

Language Reference

}

- Other JavaScript loops, such as for loops and do-while loops - can be mimicked by variations on while, but the hope is that you - won't need to do that with CoffeeScript, either because you're using + Other JavaScript loops, such as for loops and do-while loops + can be mimicked by variations on while, but the hope is that you + won't need to do that with CoffeeScript, either because you're using each (forEach) style iterators, or...

Array Comprehensions - For your looping needs, CoffeeScript provides array comprehensions + For your looping needs, CoffeeScript provides array comprehensions similar to Python's. They replace (and compile into) for loops, with optional guard clauses and the value of the current array index. Unlike for loops, array comprehensions are expressions, and can be returned @@ -502,7 +502,7 @@

Language Reference

JavaScript's prototypal inheritance has always been a bit of a brain-bender, with a whole family tree of libraries that provide a cleaner syntax for classical inheritance on top of JavaScript's prototypes: - Base2, + Base2, Prototype.js, JS.Class, etc.

@@ -584,39 +584,51 @@

Language Reference

If you ever need to interpolate literal JavaScript snippets, you can use backticks to pass JavaScript straight through.

-
js: => `alert("Hello JavaScript");`.
+    
hi: `function() {
+  return [document.title, "Hello JavaScript"].join(": ");
+}`
 
-js() if 10 > 9
-
var js = function() {
-  return alert("Hello JavaScript");
+
var hi = function() {
+return [document.title, "Hello JavaScript"].join(": ");
 };
-if (10 > 9) {
-  js();
-}
-

+;alert(hi());'>run: hi()

Aliases - Because the == operator frequently causes undesirable coercion, + Because the == operator frequently causes undesirable coercion, is intransitive, and has a different meaning than in other languages, CoffeeScript compiles == into ===, and != into !==. + In addition, is compiles into ===, + and aint into !==.

- is also compiles into ===, - and aint into !==. + As in YAML, on and yes + are the same as boolean true, while off and no are boolean false.

- + For single-line statements, unless can be used as the inverse of if.

- - +
launch() if ignition is on
+
+volume: 10 if band aint spinal_tap
+
+let_the_wild_rumpus_begin() unless answer is no
+
if (ignition === true) {
+  launch();
+}
+var volume;
+if (band !== spinal_tap) {
+  volume = 10;
+}
+if (!(answer === false)) {
+  let_the_wild_rumpus_begin();
+}
+

+

Switch/Case/Else Switch statements in JavaScript are fundamentally broken. You can only diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index b788c281ce..cb348dcc1d 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -211,13 +211,13 @@ match - \btrue\b + \b(true|on|yes)\b name constant.language.boolean.true.cs match - \bfalse\b + \b(false|off|no)\b name constant.language.boolean.false.cs diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index ef00e4c8ec..d07ad289e1 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -3,7 +3,7 @@ class Parser # Declare tokens produced by the lexer token IF ELSE THEN UNLESS token NUMBER STRING REGEX -token TRUE FALSE NULL +token TRUE FALSE YES NO ON OFF token IDENTIFIER PROPERTY_ACCESS token CODE PARAM NEW RETURN token TRY CATCH FINALLY THROW @@ -102,11 +102,14 @@ rule | STRING { result = LiteralNode.new(val[0]) } | JS { result = LiteralNode.new(val[0]) } | REGEX { result = LiteralNode.new(val[0]) } - | TRUE { result = LiteralNode.new(true) } - | FALSE { result = LiteralNode.new(false) } - | NULL { result = LiteralNode.new(nil) } | BREAK { result = LiteralNode.new(val[0]) } | CONTINUE { result = LiteralNode.new(val[0]) } + | TRUE { result = LiteralNode.new(true) } + | FALSE { result = LiteralNode.new(false) } + | YES { result = LiteralNode.new(true) } + | NO { result = LiteralNode.new(false) } + | ON { result = LiteralNode.new(true) } + | OFF { result = LiteralNode.new(false) } ; # Assignment to a variable. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 5ea078443f..b511d0a764 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -7,7 +7,7 @@ class Lexer # The list of keywords passed verbatim to the parser. KEYWORDS = ["if", "else", "then", "unless", - "true", "false", "null", + "true", "false", "yes", "no", "on", "off", "and", "or", "is", "aint", "not", "new", "return", "try", "catch", "finally", "throw", @@ -21,7 +21,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 - JS = /\A(`(.*?)`)/ + JS = /\A(``|`(.*?)[^\\]`)/m OPERATOR = /\A([+\*&|\/\-%=<>:]+)/ WHITESPACE = /\A([ \t\r]+)/ NEWLINE = /\A(\n+)/ diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7328e402b6..1b7484ff08 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -601,7 +601,7 @@ def initialize(condition, body, else_body=nil, tags={}) @body = body && body.unwrap @else_body = else_body && else_body.unwrap @tags = tags - @condition = OpNode.new("!", @condition) if @tags[:invert] + @condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert] end def <<(else_body) @@ -656,7 +656,7 @@ def compile(o={}) def compile_statement(o) indent = o[:indent] o[:indent] += TAB - if_part = "if (#{@condition.compile(o.merge(:no_paren => true))}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" + if_part = "if (#{@condition.compile(o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" return if_part unless @else_body else_part = chain? ? " else #{@else_body.compile(o.merge(:indent => indent))}" : From cdfb5091bee1fd4d139cd972da6e6831b2dd4c5e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 23 Dec 2009 20:48:55 -0500 Subject: [PATCH 055/303] more better super docs, better switch docs --- documentation/cs/switch.cs | 4 +++- documentation/index.html.erb | 18 +++++++++++++----- documentation/js/switch.js | 5 ++++- index.html | 27 ++++++++++++++++++++------- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/documentation/cs/switch.cs b/documentation/cs/switch.cs index 41bf90451d..41109413b1 100644 --- a/documentation/cs/switch.cs +++ b/documentation/cs/switch.cs @@ -2,6 +2,8 @@ case "Tuesday" then eat_breakfast() case "Wednesday" then go_to_the_park() case "Saturday" - if day is bingo_day then go_to_bingo(). + if day is bingo_day + go_to_bingo() + go_dancing(). case "Sunday" then go_to_church() else go_to_work(). \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index eba228f505..4fe61d1e92 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -290,6 +290,12 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Base2, Prototype.js, JS.Class, etc. + The libraries provide syntactic sugar, but the built-in inheritance would + be completely usable if it weren't for one small exception: + it's very awkward to call super, the prototype object's + implementation of the current function. CoffeeScript converts + super() calls into calls against the immediate ancestor's + method of the same name.

<%= code_for('super', true) %> @@ -320,12 +326,14 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Switch/Case/Else - Switch statements in JavaScript are fundamentally broken. You can only - do string comparisons, and need to break at the end of each case - statment to prevent falling through to the default case. CoffeeScript - compiles switch statements into if-else chains, allowing you to + Switch statements in JavaScript are rather broken. You can only + do string comparisons, and need to remember to break at the end of + every case statement to avoid accidentally falling through to + the default case. CoffeeScript + compiles switch statements into JavaScript if-else chains, allowing you to compare any object (via ===), preventing fall-through, and resulting - in a returnable expression. + in a returnable, assignable expression. To specify the default case, just + use else.

<%= code_for('switch') %> diff --git a/documentation/js/switch.js b/documentation/js/switch.js index 36a2a15e2e..53406faae9 100644 --- a/documentation/js/switch.js +++ b/documentation/js/switch.js @@ -4,7 +4,10 @@ } else if (day === "Wednesday") { go_to_the_park(); } else if (day === "Saturday") { - day === bingo_day ? go_to_bingo() : null; + if (day === bingo_day) { + go_to_bingo(); + go_dancing(); + } } else if (day === "Sunday") { go_to_church(); } else { diff --git a/index.html b/index.html index 6f5e195c51..bd610f4d93 100644 --- a/index.html +++ b/index.html @@ -505,6 +505,12 @@

Language Reference

Base2, Prototype.js, JS.Class, etc. + The libraries provide syntactic sugar, but the built-in inheritance would + be completely usable if it weren't for one small exception: + it's very awkward to call super, the prototype object's + implementation of the current function. CoffeeScript converts + super() calls into calls against the immediate ancestor's + method of the same name.

Animal: => .
 Animal.prototype.move: meters =>
@@ -631,18 +637,22 @@ 

Language Reference

Switch/Case/Else - Switch statements in JavaScript are fundamentally broken. You can only - do string comparisons, and need to break at the end of each case - statment to prevent falling through to the default case. CoffeeScript - compiles switch statements into if-else chains, allowing you to + Switch statements in JavaScript are rather broken. You can only + do string comparisons, and need to remember to break at the end of + every case statement to avoid accidentally falling through to + the default case. CoffeeScript + compiles switch statements into JavaScript if-else chains, allowing you to compare any object (via ===), preventing fall-through, and resulting - in a returnable expression. + in a returnable, assignable expression. To specify the default case, just + use else.

switch day
 case "Tuesday"   then eat_breakfast()
 case "Wednesday" then go_to_the_park()
 case "Saturday"
-  if day is bingo_day then go_to_bingo().
+  if day is bingo_day
+    go_to_bingo()
+    go_dancing().
 case "Sunday"    then go_to_church()
 else go_to_work().
 
if (day === "Tuesday") {
@@ -650,7 +660,10 @@ 

Language Reference

} else if (day === "Wednesday") { go_to_the_park(); } else if (day === "Saturday") { - day === bingo_day ? go_to_bingo() : null; + if (day === bingo_day) { + go_to_bingo(); + go_dancing(); + } } else if (day === "Sunday") { go_to_church(); } else { From 5f9a190683a4c95bb906af40b5473e76dbe3b119 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 23 Dec 2009 20:57:35 -0500 Subject: [PATCH 056/303] ... --- documentation/index.html.erb | 5 +++-- index.html | 5 +++-- lib/coffee_script/nodes.rb | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 4fe61d1e92..920cdc620f 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -41,12 +41,13 @@

Disclaimer: - CoffeeScript is just for fun and seriously alpha. There is no guarantee, + CoffeeScript is just for fun and seriously alpha. I'm sure that there are still + plenty of holes in the walls and leaks in the syntax. There is no guarantee, explicit or implied, of its suitability for any purpose. That said, it compiles into clean JavaScript (the good parts) that can use existing JavaScript libraries seamlessly, and can pass through JSLint without warnings. The compiled - scripts are quite readable — pretty-printed, with comments + output is quite readable — pretty-printed, with comments preserved intact.

diff --git a/index.html b/index.html index bd610f4d93..71df7c156a 100644 --- a/index.html +++ b/index.html @@ -27,12 +27,13 @@

CoffeeScript

Disclaimer: - CoffeeScript is just for fun and seriously alpha. There is no guarantee, + CoffeeScript is just for fun and seriously alpha. I'm sure that there are still + plenty of holes in the walls and leaks in the syntax. There is no guarantee, explicit or implied, of its suitability for any purpose. That said, it compiles into clean JavaScript (the good parts) that can use existing JavaScript libraries seamlessly, and can pass through JSLint without warnings. The compiled - scripts are quite readable — pretty-printed, with comments + output is quite readable — pretty-printed, with comments preserved intact.

diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 1b7484ff08..2367a03ac2 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -524,6 +524,8 @@ def compile(o={}) # A try/catch/finally block. class TryNode < Node statement + custom_return + custom_assign attr_reader :try, :error, :recovery, :finally @@ -540,7 +542,7 @@ def compile(o={}) indent = o[:indent] o[:indent] += TAB catch_part = @recovery && " catch (#{@error}) {\n#{@recovery.compile(o)}\n#{indent}}" - finally_part = @finally && " finally {\n#{@finally.compile(o)}\n#{indent}}" + finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:assign => nil, :return => nil))}\n#{indent}}" write("try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}") end end From eae53d47879ed59cf889cfded05dfcb4095feeb0 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 23 Dec 2009 21:00:04 -0500 Subject: [PATCH 057/303] added the ! sign as an allowed operator --- examples/underscore.cs | 2 +- lib/coffee_script/lexer.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/underscore.cs b/examples/underscore.cs index 20b1fafebf..56abbb5312 100644 --- a/examples/underscore.cs +++ b/examples/underscore.cs @@ -26,7 +26,7 @@ _: root._: obj => new wrapper(obj). # Export the Underscore object for CommonJS. -exports._: _ if typeof(exports) aint 'undefined' +if typeof(exports) != 'undefined' then exports._: _. # Create quick reference variables for speed access to core prototypes. slice: Array.prototype.slice diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index b511d0a764..a67e305021 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -22,7 +22,7 @@ class Lexer 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 - OPERATOR = /\A([+\*&|\/\-%=<>:]+)/ + OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t\r]+)/ NEWLINE = /\A(\n+)/ COMMENT = /\A((#[^\n]*\s*)+)/m From 2b94849429770a377dc9f80bc0ac84931c0a42f9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 23 Dec 2009 21:09:32 -0500 Subject: [PATCH 058/303] ported over a little more underscore --- documentation/index.html.erb | 3 +++ examples/code.cs | 2 +- examples/underscore.cs | 40 ++++++++++++++++-------------------- index.html | 3 +++ 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 920cdc620f..8916d842d7 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -316,6 +316,9 @@ coffee-script --print app/scripts/*.cs > concatenation.js
In addition, is compiles into ===, and aint into !==.

+

+ You can use not as an alias for !. +

As in YAML, on and yes are the same as boolean true, while off and no are boolean false. diff --git a/examples/code.cs b/examples/code.cs index f6d58537d3..ad520571b2 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -99,7 +99,7 @@ continue if continuing. # Unary operators. -!!true +!true # Lexical scoping. a: 5 diff --git a/examples/underscore.cs b/examples/underscore.cs index 56abbb5312..754d1842e4 100644 --- a/examples/underscore.cs +++ b/examples/underscore.cs @@ -78,28 +78,24 @@ _.each(reversed, reverser) memo. -# # Return the first value which passes a truth test. -# _.detect = function(obj, iterator, context) { -# var result; -# _.each(obj, function(value, index, list) { -# if (iterator.call(context, value, index, list)) { -# result = value; -# _.breakLoop(); -# } -# }); -# return result; -# }; -# -# # Return all the elements that pass a truth test. Use JavaScript 1.6's -# # filter(), if it exists. -# _.select = function(obj, iterator, context) { -# if (obj && _.isFunction(obj.filter)) return obj.filter(iterator, context); -# var results = []; -# _.each(obj, function(value, index, list) { -# iterator.call(context, value, index, list) && results.push(value); -# }); -# return results; -# }; + # Return the first value which passes a truth test. + _.detect: obj, iterator, context => + result: null + _.each(obj, (value, index, list => + if iterator.call(context, value, index, list) + result: value + _.breakLoop()..)) + result. + + # Return all the elements that pass a truth test. Use JavaScript 1.6's + # filter(), if it exists. + _.select: obj, iterator, context => + if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context). + results: [] + _.each(obj, (value, index, list => + iterator.call(context, value, index, list) and results.push(value).)) + results. + # # # Return all the elements for which a truth test fails. # _.reject = function(obj, iterator, context) { diff --git a/index.html b/index.html index 71df7c156a..f71b6addee 100644 --- a/index.html +++ b/index.html @@ -612,6 +612,9 @@

Language Reference

In addition, is compiles into ===, and aint into !==.

+

+ You can use not as an alias for !. +

As in YAML, on and yes are the same as boolean true, while off and no are boolean false. From b1f3ad24a2a3d0dd78f1e05ab2555f7c94086f0f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 00:37:33 -0500 Subject: [PATCH 059/303] added git st with the new operator regex --- examples/code.cs | 2 +- lib/coffee_script/grammar.y | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/code.cs b/examples/code.cs index ad520571b2..f6d58537d3 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -99,7 +99,7 @@ continue if continuing. # Unary operators. -!true +!!true # Lexical scoping. a: 5 diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index d07ad289e1..65941b3711 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -18,7 +18,7 @@ token JS # Declare order of operations. prechigh - nonassoc UMINUS NOT '!' '~' + nonassoc UMINUS NOT '!' '!!' '~' left '*' '/' '%' left '+' '-' left '<<' '>>' '>>>' @@ -138,6 +138,7 @@ rule # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html Operation: '!' Expression { result = OpNode.new(val[0], val[1]) } + | '!!' Expression { result = OpNode.new(val[0], val[1]) } | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } | NOT Expression { result = OpNode.new(val[0], val[1]) } | '~' Expression { result = OpNode.new(val[0], val[1]) } From 0a58eeef2b1234a9159096d637c66c30fa339f7e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 01:22:41 -0500 Subject: [PATCH 060/303] waypoint --- documentation/cs/strings.cs | 3 ++- documentation/cs/super.cs | 4 ++++ documentation/index.html.erb | 17 +++++++++------- documentation/js/strings.js | 10 ++++----- index.html | 34 ++++++++++++++++++++----------- lib/coffee_script/command_line.rb | 2 +- lib/coffee_script/lexer.rb | 2 +- 7 files changed, 45 insertions(+), 27 deletions(-) diff --git a/documentation/cs/strings.cs b/documentation/cs/strings.cs index 335d702bcc..0f41e4fadc 100644 --- a/documentation/cs/strings.cs +++ b/documentation/cs/strings.cs @@ -3,4 +3,5 @@ never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the -world..." \ No newline at end of file +world..." + diff --git a/documentation/cs/super.cs b/documentation/cs/super.cs index 2c97fb9941..baee074ac9 100644 --- a/documentation/cs/super.cs +++ b/documentation/cs/super.cs @@ -19,3 +19,7 @@ sam.move() tom.move() + + + + diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 8916d842d7..3df8a2965c 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -41,7 +41,7 @@

Disclaimer: - CoffeeScript is just for fun and seriously alpha. I'm sure that there are still + CoffeeScript is just for fun and seriously alpha. I'm sure that there are still plenty of holes in the walls and leaks in the syntax. There is no guarantee, explicit or implied, of its suitability for any purpose. That said, it compiles into clean JavaScript (the good parts) that can use existing @@ -173,12 +173,15 @@ coffee-script --print app/scripts/*.cs > concatenation.js

the line will do just as well. All other whitespace is not significant. Instead of using curly braces { } to delimit a block of code, use a period . to mark the end of a - function, if-statement, switch, or try/catch. + block for + functions, + if-statements, + switch, and try/catch.

Functions and Invocation - Let's start with the best part, shall we? Functions are defined + Let's start with the best part of CoffeeScript, shall we? Functions are defined by a list of parameters, an arrow, and the function body. The empty function looks like this: =>.

@@ -293,7 +296,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js
JS.Class, etc. The libraries provide syntactic sugar, but the built-in inheritance would be completely usable if it weren't for one small exception: - it's very awkward to call super, the prototype object's + it's very awkward to call super, the prototype object's implementation of the current function. CoffeeScript converts super() calls into calls against the immediate ancestor's method of the same name. @@ -331,8 +334,8 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Switch/Case/Else Switch statements in JavaScript are rather broken. You can only - do string comparisons, and need to remember to break at the end of - every case statement to avoid accidentally falling through to + do string comparisons, and need to remember to break at the end of + every case statement to avoid accidentally falling through to the default case. CoffeeScript compiles switch statements into JavaScript if-else chains, allowing you to compare any object (via ===), preventing fall-through, and resulting @@ -352,7 +355,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Multiline Strings Multiline strings are allowed in CoffeeScript.

- <%= code_for('strings') %> + <%= code_for('strings', 'moby_dick') %>
diff --git a/documentation/js/strings.js b/documentation/js/strings.js index 3787620821..b3407d7f70 100644 --- a/documentation/js/strings.js +++ b/documentation/js/strings.js @@ -1,8 +1,8 @@ (function(){ - var moby_dick = "Call me Ishmael. Some years ago --\ -never mind how long precisely -- having little\ -or no money in my purse, and nothing particular\ -to interest me on shore, I thought I would sail\ -about a little and see the watery part of the\ + var moby_dick = "Call me Ishmael. Some years ago -- \ +never mind how long precisely -- having little \ +or no money in my purse, and nothing particular \ +to interest me on shore, I thought I would sail \ +about a little and see the watery part of the \ world..."; })(); \ No newline at end of file diff --git a/index.html b/index.html index f71b6addee..1b6152f5e3 100644 --- a/index.html +++ b/index.html @@ -27,7 +27,7 @@

CoffeeScript

Disclaimer: - CoffeeScript is just for fun and seriously alpha. I'm sure that there are still + CoffeeScript is just for fun and seriously alpha. I'm sure that there are still plenty of holes in the walls and leaks in the syntax. There is no guarantee, explicit or implied, of its suitability for any purpose. That said, it compiles into clean JavaScript (the good parts) that can use existing @@ -243,12 +243,15 @@

Language Reference

the line will do just as well. All other whitespace is not significant. Instead of using curly braces { } to delimit a block of code, use a period . to mark the end of a - function, if-statement, switch, or try/catch. + block for + functions, + if-statements, + switch, and try/catch.

Functions and Invocation - Let's start with the best part, shall we? Functions are defined + Let's start with the best part of CoffeeScript, shall we? Functions are defined by a list of parameters, an arrow, and the function body. The empty function looks like this: =>.

@@ -508,7 +511,7 @@

Language Reference

JS.Class, etc. The libraries provide syntactic sugar, but the built-in inheritance would be completely usable if it weren't for one small exception: - it's very awkward to call super, the prototype object's + it's very awkward to call super, the prototype object's implementation of the current function. CoffeeScript converts super() calls into calls against the immediate ancestor's method of the same name. @@ -642,8 +645,8 @@

Language Reference

Switch/Case/Else Switch statements in JavaScript are rather broken. You can only - do string comparisons, and need to remember to break at the end of - every case statement to avoid accidentally falling through to + do string comparisons, and need to remember to break at the end of + every case statement to avoid accidentally falling through to the default case. CoffeeScript compiles switch statements into JavaScript if-else chains, allowing you to compare any object (via ===), preventing fall-through, and resulting @@ -707,13 +710,20 @@

Language Reference

to interest me on shore, I thought I would sail about a little and see the watery part of the world..." -
var moby_dick = "Call me Ishmael. Some years ago --\
-never mind how long precisely -- having little\
-or no money in my purse, and nothing particular\
-to interest me on shore, I thought I would sail\
-about a little and see the watery part of the\
+
+
var moby_dick = "Call me Ishmael. Some years ago -- \
+never mind how long precisely -- having little \
+or no money in my purse, and nothing particular \
+to interest me on shore, I thought I would sail \
+about a little and see the watery part of the \
 world...";
-

+
diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 1172d7c2e8..19ed27d03b 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -93,7 +93,7 @@ def lint(js) # Eval a little piece of CoffeeScript directly from the command line. def eval_scriptlet - script = @sources.join(' ') + script = STDIN.tty? ? @sources.join(' ') : STDIN.read return tokens(script) if @options[:tokens] js = compile(script) return lint(js) if @options[:lint] diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index a67e305021..d44c486db2 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -89,7 +89,7 @@ def string_token return false unless string = @chunk[STRING, 1] escaped = string.gsub(MULTILINER) do |match| @line += 1 - "\\\n" + " \\\n" end token(:STRING, escaped) @i += string.length From 705b2939825933f5b9f29b9d7d389cb803c1d71b Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 23 Dec 2009 23:01:39 -0800 Subject: [PATCH 061/303] first draft of docs are done --- documentation/css/docs.css | 2 +- documentation/index.html.erb | 91 ++++++++++++---------- index.html | 125 ++++++++++++++++-------------- lib/coffee_script/command_line.rb | 2 +- lib/coffee_script/grammar.y | 16 ++-- lib/coffee_script/nodes.rb | 3 +- 6 files changed, 128 insertions(+), 111 deletions(-) diff --git a/documentation/css/docs.css b/documentation/css/docs.css index dd2a4f86c7..d3688f315e 100644 --- a/documentation/css/docs.css +++ b/documentation/css/docs.css @@ -1,7 +1,7 @@ body { font-size: 14px; line-height: 20px; - background: #efeff9; + background: #f3f3f9; color: #191933; font-family: Arial, Helvetica, sans-serif; } diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 3df8a2965c..3f0392534c 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -42,10 +42,10 @@

Disclaimer: CoffeeScript is just for fun and seriously alpha. I'm sure that there are still - plenty of holes in the walls and leaks in the syntax. There is no guarantee, + plenty of holes in the lexer and leaks in the syntax. There is no guarantee, explicit or implied, of its suitability for any purpose. That said, it compiles into clean JavaScript (the good parts) that can use existing - JavaScript libraries seamlessly, and can pass through + JavaScript libraries seamlessly, and passes through JSLint without warnings. The compiled output is quite readable — pretty-printed, with comments preserved intact. @@ -63,12 +63,12 @@ Lexical Scoping and Variable Safety
Conditionals, Ternaries, and Conditional Assignment
Everything is an Expression
+ Aliases
While Loops
Array Comprehensions
Array Slice Literals
Calling Super from a Subclass
Embedded JavaScript
- Aliases
Switch/Case/Else
Try/Catch/Finally
Multiline Strings
@@ -88,7 +88,7 @@ sudo gem install coffee-script

Installing the gem provides the coffee-script command, which can be used to compile CoffeeScript .cs files into JavaScript, as - well as debug. By default, coffee-script writes out the + well as debug them. By default, coffee-script writes out the JavaScript as .js files in the same directory, but output can be customized with the following options:

@@ -103,7 +103,7 @@ sudo gem install coffee-script -w, --watch - Watch the modification times of the named scripts, recompiling as + Watch the modification times of the coffee-scripts, recompiling as soon as a change occurs. @@ -118,14 +118,15 @@ sudo gem install coffee-script -l, --lint If the jsl (JavaScript Lint) command is installed, use it - to check the compilation of a CoffeeScript file. + to check the compilation of a CoffeeScript file. (Handy in + conjunction with --watch) -e, --eval Compile and print a little snippet of CoffeeScript directly from the - command line. For example:
coffee-script -e "square: x => x * x." + command line (or from stdin). For example:
coffee-script -e "square: x => x * x." @@ -143,6 +144,12 @@ sudo gem install coffee-script AST. + + --install-bundle + + Install the TextMate bundle for CoffeeScript syntax highlighting. + +

@@ -160,10 +167,9 @@ coffee-script --print app/scripts/*.cs > concatenation.js This document is structured so that it can be read from top to bottom, if you like. Later sections use ideas and syntax previously introduced. + Familiarity with JavaScript is assumed. In all of the following examples, the source CoffeeScript is provided on the left, and the direct compilation into JavaScript is on the right. - Familiarity with JavaScript is assumed, although it would certainly - be nice to have a tutorial that builds from the ground up in the future.

@@ -173,7 +179,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js the line will do just as well. All other whitespace is not significant. Instead of using curly braces { } to delimit a block of code, use a period . to mark the end of a - block for + block, for functions, if-statements, switch, and try/catch. @@ -181,9 +187,8 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Functions and Invocation - Let's start with the best part of CoffeeScript, shall we? Functions are defined - by a list of parameters, an arrow, and the function body. The empty - function looks like this: =>. + Functions are defined by a list of parameters, an arrow, and the + function body. The empty function looks like this: =>.

<%= code_for('functions', 'cube(5)') %> @@ -207,18 +212,18 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Lexical Scoping and Variable Safety The CoffeeScript compiler takes care to make sure that all of your variables - are properly defined within lexical scope — you never need to declare - var yourself. + are properly declared within lexical scope — you never need to write + var yourself.

<%= code_for('scope', 'new_num') %>

Notice how the variables are declared with var the first time they appear. The second reference of num, within the function, is not redeclared because num is still in scope. As opposed - to the second new_num, in the last line. + to the second occurrence of new_num, in the last line.

- Although suppressed within this documentation, all + Although suppressed within this documentation for clarity, all CoffeeScript output is wrapped in an anonymous function: (function(){ ... })(); This safety wrapper, combined with the automatic generation of the var keyword, make it exceedingly difficult @@ -243,7 +248,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Everything is an Expression (at least, as much as possible) You might have noticed how even though we don't add return statements - to CoffeScript functions, they nonetheless return their final value. + to CoffeeScript functions, they nonetheless return their final value. The CoffeeScript compiler tries to make sure that all statements in the language can be used as expressions. Watch how the return gets pushed down into each possible branch of execution, in the function @@ -251,9 +256,30 @@ coffee-script --print app/scripts/*.cs > concatenation.js

<%= code_for('expressions', 'eldest') %>

- The same mechanism is used to push down assignment statements, switch - statements, and if-elses (although the ternary operator is preferred). + The same mechanism is used to push down assignment through switch + statements, and if-elses (although the ternary operator is preferred). +

+ +

+ Aliases + Because the == operator frequently causes undesirable coercion, + is intransitive, and has a different meaning than in other languages, + CoffeeScript compiles == into ===, and != into + !==. + In addition, is compiles into ===, + and aint into !==. +

+

+ You can use not as an alias for !. +

+

+ As in YAML, on and yes + are the same as boolean true, while off and no are boolean false.

+

+ For single-line statements, unless can be used as the inverse of if. +

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

While Loops @@ -310,30 +336,9 @@ coffee-script --print app/scripts/*.cs > concatenation.js

<%= code_for('embedded', 'hi()') %> -

- Aliases - Because the == operator frequently causes undesirable coercion, - is intransitive, and has a different meaning than in other languages, - CoffeeScript compiles == into ===, and != into - !==. - In addition, is compiles into ===, - and aint into !==. -

-

- You can use not as an alias for !. -

-

- As in YAML, on and yes - are the same as boolean true, while off and no are boolean false. -

-

- For single-line statements, unless can be used as the inverse of if. -

- <%= code_for('aliases') %> -

Switch/Case/Else - Switch statements in JavaScript are rather broken. You can only + Switch statements in JavaScript are rather broken. You can only do string comparisons, and need to remember to break at the end of every case statement to avoid accidentally falling through to the default case. CoffeeScript @@ -346,7 +351,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Try/Catch/Finally - Try/catch statements just about the same as JavaScript (although + Try/catch statements are just about the same as JavaScript (although they work as expressions). No braces required.

<%= code_for('try') %> diff --git a/index.html b/index.html index 1b6152f5e3..a34ac81d21 100644 --- a/index.html +++ b/index.html @@ -28,10 +28,10 @@

CoffeeScript

Disclaimer: CoffeeScript is just for fun and seriously alpha. I'm sure that there are still - plenty of holes in the walls and leaks in the syntax. There is no guarantee, + plenty of holes in the lexer and leaks in the syntax. There is no guarantee, explicit or implied, of its suitability for any purpose. That said, it compiles into clean JavaScript (the good parts) that can use existing - JavaScript libraries seamlessly, and can pass through + JavaScript libraries seamlessly, and passes through JSLint without warnings. The compiled output is quite readable — pretty-printed, with comments preserved intact. @@ -49,12 +49,12 @@

Table of Contents

Lexical Scoping and Variable Safety
Conditionals, Ternaries, and Conditional Assignment
Everything is an Expression
+ Aliases
While Loops
Array Comprehensions
Array Slice Literals
Calling Super from a Subclass
Embedded JavaScript
- Aliases
Switch/Case/Else
Try/Catch/Finally
Multiline Strings
@@ -158,7 +158,7 @@

Installation and Usage

Installing the gem provides the coffee-script command, which can be used to compile CoffeeScript .cs files into JavaScript, as - well as debug. By default, coffee-script writes out the + well as debug them. By default, coffee-script writes out the JavaScript as .js files in the same directory, but output can be customized with the following options:

@@ -173,7 +173,7 @@

Installation and Usage

-w, --watch - Watch the modification times of the named scripts, recompiling as + Watch the modification times of the coffee-scripts, recompiling as soon as a change occurs. @@ -188,14 +188,15 @@

Installation and Usage

-l, --lint If the jsl (JavaScript Lint) command is installed, use it - to check the compilation of a CoffeeScript file. + to check the compilation of a CoffeeScript file. (Handy in + conjunction with --watch) -e, --eval Compile and print a little snippet of CoffeeScript directly from the - command line. For example:
coffee-script -e "square: x => x * x." + command line (or from stdin). For example:
coffee-script -e "square: x => x * x." @@ -213,6 +214,12 @@

Installation and Usage

AST. + + --install-bundle + + Install the TextMate bundle for CoffeeScript syntax highlighting. + +

@@ -230,10 +237,9 @@

Language Reference

This document is structured so that it can be read from top to bottom, if you like. Later sections use ideas and syntax previously introduced. + Familiarity with JavaScript is assumed. In all of the following examples, the source CoffeeScript is provided on the left, and the direct compilation into JavaScript is on the right. - Familiarity with JavaScript is assumed, although it would certainly - be nice to have a tutorial that builds from the ground up in the future.

@@ -243,7 +249,7 @@

Language Reference

the line will do just as well. All other whitespace is not significant. Instead of using curly braces { } to delimit a block of code, use a period . to mark the end of a - block for + block, for functions, if-statements, switch, and try/catch. @@ -251,9 +257,8 @@

Language Reference

Functions and Invocation - Let's start with the best part of CoffeeScript, shall we? Functions are defined - by a list of parameters, an arrow, and the function body. The empty - function looks like this: =>. + Functions are defined by a list of parameters, an arrow, and the + function body. The empty function looks like this: =>.

square: x => x * x.
 cube:   x => square(x) * x.
@@ -315,8 +320,8 @@ 

Language Reference

Lexical Scoping and Variable Safety The CoffeeScript compiler takes care to make sure that all of your variables - are properly defined within lexical scope — you never need to declare - var yourself. + are properly declared within lexical scope — you never need to write + var yourself.

num: 1
 change_numbers: =>
@@ -342,10 +347,10 @@ 

Language Reference

Notice how the variables are declared with var the first time they appear. The second reference of num, within the function, is not redeclared because num is still in scope. As opposed - to the second new_num, in the last line. + to the second occurrence of new_num, in the last line.

- Although suppressed within this documentation, all + Although suppressed within this documentation for clarity, all CoffeeScript output is wrapped in an anonymous function: (function(){ ... })(); This safety wrapper, combined with the automatic generation of the var keyword, make it exceedingly difficult @@ -389,7 +394,7 @@

Language Reference

Everything is an Expression (at least, as much as possible) You might have noticed how even though we don't add return statements - to CoffeScript functions, they nonetheless return their final value. + to CoffeeScript functions, they nonetheless return their final value. The CoffeeScript compiler tries to make sure that all statements in the language can be used as expressions. Watch how the return gets pushed down into each possible branch of execution, in the function @@ -426,9 +431,45 @@

Language Reference

var eldest = 24 > 21 ? "Liz" : "Ike"; ;alert(eldest);'>run: eldest

- The same mechanism is used to push down assignment statements, switch - statements, and if-elses (although the ternary operator is preferred). + The same mechanism is used to push down assignment through switch + statements, and if-elses (although the ternary operator is preferred).

+ +

+ Aliases + Because the == operator frequently causes undesirable coercion, + is intransitive, and has a different meaning than in other languages, + CoffeeScript compiles == into ===, and != into + !==. + In addition, is compiles into ===, + and aint into !==. +

+

+ You can use not as an alias for !. +

+

+ As in YAML, on and yes + are the same as boolean true, while off and no are boolean false. +

+

+ For single-line statements, unless can be used as the inverse of if. +

+
launch() if ignition is on
+
+volume: 10 if band aint spinal_tap
+
+let_the_wild_rumpus_begin() unless answer is no
+
if (ignition === true) {
+  launch();
+}
+var volume;
+if (band !== spinal_tap) {
+  volume = 10;
+}
+if (!(answer === false)) {
+  let_the_wild_rumpus_begin();
+}
+

While Loops @@ -537,6 +578,10 @@

Language Reference

sam.move() tom.move() + + + +
var Animal = function() {
 };
 Animal.prototype.move = function(meters) {
@@ -606,45 +651,9 @@ 

Language Reference

}; ;alert(hi());'>run: hi()
-

- Aliases - Because the == operator frequently causes undesirable coercion, - is intransitive, and has a different meaning than in other languages, - CoffeeScript compiles == into ===, and != into - !==. - In addition, is compiles into ===, - and aint into !==. -

-

- You can use not as an alias for !. -

-

- As in YAML, on and yes - are the same as boolean true, while off and no are boolean false. -

-

- For single-line statements, unless can be used as the inverse of if. -

-
launch() if ignition is on
-
-volume: 10 if band aint spinal_tap
-
-let_the_wild_rumpus_begin() unless answer is no
-
if (ignition === true) {
-  launch();
-}
-var volume;
-if (band !== spinal_tap) {
-  volume = 10;
-}
-if (!(answer === false)) {
-  let_the_wild_rumpus_begin();
-}
-

-

Switch/Case/Else - Switch statements in JavaScript are rather broken. You can only + Switch statements in JavaScript are rather broken. You can only do string comparisons, and need to remember to break at the end of every case statement to avoid accidentally falling through to the default case. CoffeeScript @@ -680,7 +689,7 @@

Language Reference

Try/Catch/Finally - Try/catch statements just about the same as JavaScript (although + Try/catch statements are just about the same as JavaScript (although they work as expressions). No braces required.

try
diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb
index 19ed27d03b..2ab75295da 100644
--- a/lib/coffee_script/command_line.rb
+++ b/lib/coffee_script/command_line.rb
@@ -10,7 +10,7 @@ module CoffeeScript
   class CommandLine
 
     BANNER = <<-EOS
-coffee-script compiles CoffeeScript files into JavaScript.
+coffee-script compiles CoffeeScript source files into JavaScript.
 
 Usage:
   coffee-script path/to/script.cs
diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
index 65941b3711..d9ea226c69 100644
--- a/lib/coffee_script/grammar.y
+++ b/lib/coffee_script/grammar.y
@@ -273,13 +273,15 @@ rule
 
   # Try/catch/finally exception handling blocks.
   Try:
-    TRY Expressions CATCH IDENTIFIER
-      Expressions "."                 { result = TryNode.new(val[1], val[3], val[4]) }
-  | TRY Expressions FINALLY
-      Expressions "."                 { result = TryNode.new(val[1], nil, nil, val[3]) }
-  | TRY Expressions CATCH IDENTIFIER
-      Expressions
-      FINALLY Expressions "."         { result = TryNode.new(val[1], val[3], val[4], val[6]) }
+    TRY Expressions Catch "."         { result = TryNode.new(val[1], val[2][0], val[2][1]) }
+  | TRY Expressions Catch
+    FINALLY Then Expressions "."      { result = TryNode.new(val[1], val[2][0], val[2][1], val[5]) }
+  ;
+
+  # A catch clause.
+  Catch:
+    /* nothing */                     { result = [nil, nil] }
+  | CATCH IDENTIFIER Then Expressions { result = [val[1], val[3]] }
   ;
 
   # Throw an exception.
diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb
index 2367a03ac2..38fac8bc44 100644
--- a/lib/coffee_script/nodes.rb
+++ b/lib/coffee_script/nodes.rb
@@ -541,7 +541,8 @@ def compile(o={})
       o = super(o)
       indent = o[:indent]
       o[:indent] += TAB
-      catch_part = @recovery &&  " catch (#{@error}) {\n#{@recovery.compile(o)}\n#{indent}}"
+      error_part = @error ? " (#{@error}) " : ' '
+      catch_part = @recovery &&  " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}"
       finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:assign => nil, :return => nil))}\n#{indent}}"
       write("try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}")
     end

From 417753bd621f80a9bb86125e39baa2fdb8d7be75 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas 
Date: Wed, 23 Dec 2009 23:12:29 -0800
Subject: [PATCH 062/303] added readme

---
 README | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/README b/README
index e69de29bb2..fa170417b9 100644
--- a/README
+++ b/README
@@ -0,0 +1,38 @@
+=                              
+            {                   
+         }   }   {              
+        {   {  }  }             
+         }   }{  {               
+        {  }{  }  }                    _____       __  __           
+       ( }{ }{  { )                   / ____|     / _|/ _|          
+     .- { { }  { }} -.               | |     ___ | |_| |_ ___  ___ 
+    (  ( } { } { } }  )              | |    / _ \|  _|  _/ _ \/ _ \ 
+    |`-..________ ..-'|              | |___| (_) | | | ||  __/  __/ 
+    |                 |               \_____\___/|_| |_| \___|\___|
+    |                 ;--.           
+    |                (__  \            _____           _       _   
+    |                 | )  )          / ____|         (_)     | |  
+    |                 |/  /          | (___   ___ _ __ _ _ __ | |_ 
+    |                 (  /            \___ \ / __| '__| | '_ \| __|
+    |                 |/              ____) | (__| |  | | |_) | |_ 
+    |                 |              |_____/ \___|_|  |_| .__/ \__|
+     `-.._________..-'                                  | |        
+                                                        |_|
+                  
+                                                        
+  CoffeeScript is a little language that compiles into JavaScript.
+  
+  Install the compiler:
+  gem install coffee-script
+  
+  Compile a script:
+  coffee-script /path/to/script.cs
+  
+  For documentation, usage, and examples, see:
+  http://jashkenas.github.com/coffee-script/
+  
+  To suggest a feature or report a bug:
+  http://github.com/jashkenas/coffee-script/issues/
+
+  The source repository:
+  git://github.com/jashkenas/coffee-script.git
\ No newline at end of file

From b743e3219a219b60b420ebc2c4d7f6d1a614d329 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas 
Date: Thu, 24 Dec 2009 00:12:07 -0800
Subject: [PATCH 063/303] added some execution test

---
 README                                        |  2 +-
 TODO                                          | 13 ------------
 documentation/index.html.erb                  | 21 +++++++++++++++----
 index.html                                    | 21 +++++++++++++++----
 lib/coffee_script/nodes.rb                    |  2 +-
 .../fixtures/execution/array_comprehension.cs |  4 ++++
 .../fixtures/execution/array_comprehension.js | 21 +++++++++++++++++++
 .../fixtures/execution/assign_to_try_catch.cs |  6 ++++++
 .../fixtures/execution/assign_to_try_catch.js |  9 ++++++++
 test/fixtures/execution/fancy_if_statement.cs | 11 ++++++++++
 test/fixtures/execution/fancy_if_statement.js |  6 ++++++
 test/unit/test_execution.rb                   | 17 +++++++++++++++
 12 files changed, 110 insertions(+), 23 deletions(-)
 delete mode 100644 TODO
 create mode 100644 test/fixtures/execution/array_comprehension.cs
 create mode 100644 test/fixtures/execution/array_comprehension.js
 create mode 100644 test/fixtures/execution/assign_to_try_catch.cs
 create mode 100644 test/fixtures/execution/assign_to_try_catch.js
 create mode 100644 test/fixtures/execution/fancy_if_statement.cs
 create mode 100644 test/fixtures/execution/fancy_if_statement.js
 create mode 100644 test/unit/test_execution.rb

diff --git a/README b/README
index fa170417b9..244d6f82e0 100644
--- a/README
+++ b/README
@@ -35,4 +35,4 @@
   http://github.com/jashkenas/coffee-script/issues/
 
   The source repository:
-  git://github.com/jashkenas/coffee-script.git
\ No newline at end of file
+  git://github.com/jashkenas/coffee-script.git
diff --git a/TODO b/TODO
deleted file mode 100644
index 14a7bfa828..0000000000
--- a/TODO
+++ /dev/null
@@ -1,13 +0,0 @@
-TODO:
-
-* Finish the doc page.
-
-* Write a test suite that checks the JS evaluation.
-
-* Is it possible to close blocks (functions, ifs, trys) without an explicit
-  block delimiter or significant whitespace?
-  
-* Is it possible to pass comments through cleanly and have them show up on
-  the other end? This includes comments in the middle of array and object
-  literals, and argument lists.
-  
\ No newline at end of file
diff --git a/documentation/index.html.erb b/documentation/index.html.erb
index 3f0392534c..fabb13a0f6 100644
--- a/documentation/index.html.erb
+++ b/documentation/index.html.erb
@@ -32,7 +32,7 @@
     

CoffeeScript is a little language that compiles into JavaScript. Think of it as JavaScript's less ostentatious kid brother — the same genes, - the same accent, but a different sense of style. Apart from a handful of + roughly the same height, but a different sense of style. Apart from a handful of bonus goodies, statements in CoffeeScript correspond one-to-one with their equivalent in JavaScript, it's just another way of saying it.

@@ -72,6 +72,7 @@ Switch/Case/Else
Try/Catch/Finally
Multiline Strings
+ Change Log

Mini Overview

@@ -81,9 +82,14 @@ <%= code_for('overview', 'cubed_list') %>

Installation and Usage

+ +

+ The CoffeeScript compiler is written in pure Ruby, and is available + as a Ruby Gem. +

-sudo gem install coffee-script
+gem install coffee-script

Installing the gem provides the coffee-script command, which can @@ -339,7 +345,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Switch/Case/Else Switch statements in JavaScript are rather broken. You can only - do string comparisons, and need to remember to break at the end of + do comparisons based on string equality, and need to remember to break at the end of every case statement to avoid accidentally falling through to the default case. CoffeeScript compiles switch statements into JavaScript if-else chains, allowing you to @@ -352,7 +358,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Try/Catch/Finally Try/catch statements are just about the same as JavaScript (although - they work as expressions). No braces required. + they work as expressions).

<%= code_for('try') %> @@ -361,6 +367,13 @@ coffee-script --print app/scripts/*.cs > concatenation.js Multiline strings are allowed in CoffeeScript.

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

Change Log

+ +

+ 0.1.0 + Initial CoffeeScript release. +

diff --git a/index.html b/index.html index a34ac81d21..ff9087fe17 100644 --- a/index.html +++ b/index.html @@ -18,7 +18,7 @@

CoffeeScript

CoffeeScript is a little language that compiles into JavaScript. Think of it as JavaScript's less ostentatious kid brother — the same genes, - the same accent, but a different sense of style. Apart from a handful of + roughly the same height, but a different sense of style. Apart from a handful of bonus goodies, statements in CoffeeScript correspond one-to-one with their equivalent in JavaScript, it's just another way of saying it.

@@ -58,6 +58,7 @@

Table of Contents

Switch/Case/Else
Try/Catch/Finally
Multiline Strings
+ Change Log

Mini Overview

@@ -151,9 +152,14 @@

Mini Overview

;alert(cubed_list);'>run: cubed_list

Installation and Usage

+ +

+ The CoffeeScript compiler is written in pure Ruby, and is available + as a Ruby Gem. +

-sudo gem install coffee-script
+gem install coffee-script

Installing the gem provides the coffee-script command, which can @@ -654,7 +660,7 @@

Language Reference

Switch/Case/Else Switch statements in JavaScript are rather broken. You can only - do string comparisons, and need to remember to break at the end of + do comparisons based on string equality, and need to remember to break at the end of every case statement to avoid accidentally falling through to the default case. CoffeeScript compiles switch statements into JavaScript if-else chains, allowing you to @@ -690,7 +696,7 @@

Language Reference

Try/Catch/Finally Try/catch statements are just about the same as JavaScript (although - they work as expressions). No braces required. + they work as expressions).

try
   all_hell_breaks_loose()
@@ -733,6 +739,13 @@ 

Language Reference

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

Change Log

+ +

+ 0.1.0 + Initial CoffeeScript release. +

diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 38fac8bc44..07603d388d 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -507,7 +507,7 @@ def compile(o={}) return_result = "\n#{o[:indent]}#{return_result}" if @filter body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) - body = IfNode.new(@filter, body, nil, :statement) + body = IfNode.new(@filter, body, nil, :statement => true) save_result = '' suffix = '' end diff --git a/test/fixtures/execution/array_comprehension.cs b/test/fixtures/execution/array_comprehension.cs new file mode 100644 index 0000000000..378d8f7e31 --- /dev/null +++ b/test/fixtures/execution/array_comprehension.cs @@ -0,0 +1,4 @@ +nums: n * n for n in [1, 2, 3] if n % 2 aint 0. +result: n * 2 for n in nums. + +print(result.join(',') is '2,18') \ No newline at end of file diff --git a/test/fixtures/execution/array_comprehension.js b/test/fixtures/execution/array_comprehension.js new file mode 100644 index 0000000000..5bdaeee995 --- /dev/null +++ b/test/fixtures/execution/array_comprehension.js @@ -0,0 +1,21 @@ +(function(){ + var nums; + var a = [1, 2, 3]; + var d = []; + for (var b=0, c=a.length; b Date: Thu, 24 Dec 2009 00:41:12 -0800 Subject: [PATCH 064/303] with a more comprehensive execution test that uncovered some missing spots --- lib/coffee_script/grammar.y | 6 ++++- lib/coffee_script/nodes.rb | 16 ++++++------ test/fixtures/execution/test_everything.cs | 23 +++++++++++++++++ test/fixtures/execution/test_everything.js | 29 ++++++++++++++++++++++ test/unit/test_execution.rb | 1 + 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/execution/test_everything.cs create mode 100644 test/fixtures/execution/test_everything.js diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index d9ea226c69..6261125fb4 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -18,7 +18,7 @@ token JS # Declare order of operations. prechigh - nonassoc UMINUS NOT '!' '!!' '~' + nonassoc UMINUS NOT '!' '!!' '~' '++' '--' left '*' '/' '%' left '+' '-' left '<<' '>>' '>>>' @@ -142,6 +142,10 @@ rule | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } | NOT Expression { result = OpNode.new(val[0], val[1]) } | '~' Expression { result = OpNode.new(val[0], val[1]) } + | '--' Expression { result = OpNode.new(val[0], val[1]) } + | '++' Expression { result = OpNode.new(val[0], val[1]) } + | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) } + | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) } | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 07603d388d..38d3e57efc 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -318,11 +318,11 @@ def compile(o={}) name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s o = o.merge(:assign => name, :last_assign => last) + postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : '' return write("#{@variable}: #{@value.compile(o)}") if @context == :object - return write("#{name} = #{@value.compile(o)}") if @variable.properties? + return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign? defined = o[:scope].find(name) - postfix = !defined && o[:return] ? ";\n#{o[:indent]}return #{name}" : '' - def_part = defined ? "" : "var #{name};\n#{o[:indent]}" + def_part = defined || @variable.properties? ? "" : "var #{name};\n#{o[:indent]}" return write(def_part + @value.compile(o)) if @value.custom_assign? def_part = defined ? name : "var #{name}" val_part = @value.compile(o).sub(LEADING_VAR, '') @@ -346,8 +346,8 @@ class OpNode < Node attr_reader :operator, :first, :second - def initialize(operator, first, second=nil) - @first, @second = first, second + def initialize(operator, first, second=nil, flip=false) + @first, @second, @flip = first, second, flip @operator = CONVERSIONS[operator] || operator end @@ -369,8 +369,10 @@ def compile_conditional(o) end def compile_unary(o) - space = @operator == 'delete' ? ' ' : '' - "#{@operator}#{space}#{@first.compile(o)}" + space = @operator.to_s == 'delete' ? ' ' : '' + parts = [@operator.to_s, space, @first.compile(o)] + parts.reverse! if @flip + parts.join('') end end diff --git a/test/fixtures/execution/test_everything.cs b/test/fixtures/execution/test_everything.cs new file mode 100644 index 0000000000..62a43a85f2 --- /dev/null +++ b/test/fixtures/execution/test_everything.cs @@ -0,0 +1,23 @@ +func: => + a: 3 + b: [] + while a >= 0 + b.push('o') + a--. + + c: { + text: b + } + + c: 'error' unless 42 > 41 + + c.text: if false + 'error' + else + c.text + '---'. + + c.list: let for let in c.text.split('') if let is '-'. + + c.single: c.list[1, 1][0]. + +print(func() == '-') diff --git a/test/fixtures/execution/test_everything.js b/test/fixtures/execution/test_everything.js new file mode 100644 index 0000000000..59cee64e1d --- /dev/null +++ b/test/fixtures/execution/test_everything.js @@ -0,0 +1,29 @@ +(function(){ + var func = function() { + var a = 3; + var b = []; + while (a >= 0) { + b.push('o'); + a--; + } + var c = { + text: b + }; + if (!(42 > 41)) { + c = 'error'; + } + c.text = false ? 'error' : c.text + '---'; + var d = c.text.split(''); + var g = []; + for (var e=0, f=d.length; e Date: Thu, 24 Dec 2009 00:45:16 -0800 Subject: [PATCH 065/303] fixed the broken try/catch grammar --- lib/coffee_script/grammar.y | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 6261125fb4..b90de5c947 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -279,13 +279,13 @@ rule Try: TRY Expressions Catch "." { result = TryNode.new(val[1], val[2][0], val[2][1]) } | TRY Expressions Catch - FINALLY Then Expressions "." { result = TryNode.new(val[1], val[2][0], val[2][1], val[5]) } + FINALLY Expressions "." { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } ; # A catch clause. Catch: /* nothing */ { result = [nil, nil] } - | CATCH IDENTIFIER Then Expressions { result = [val[1], val[3]] } + | CATCH IDENTIFIER Expressions { result = [val[1], val[2]] } ; # Throw an exception. From 849f0e419297d36e02c7a2cef2e94bdfbf2d7c71 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 00:49:11 -0800 Subject: [PATCH 066/303] added comprehensive linting to the test suit --- test/unit/test_execution.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index fcef1de5af..6dca669a75 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -2,6 +2,8 @@ class ExecutionTest < Test::Unit::TestCase + NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/ + def test_execution_of_coffeescript `bin/coffee-script test/fixtures/execution/*.cs` sources = Dir['test/fixtures/execution/*.js'].map {|f| File.expand_path(f) } @@ -15,4 +17,14 @@ def test_execution_of_coffeescript Dir.chdir(starting_place) end + def test_lintless_coffeescript + lint_results = `bin/coffee-script -l test/fixtures/execution/*.cs` + assert lint_results.match(NO_WARNINGS) + end + + def test_lintless_examples + lint_results = `bin/coffee-script -l examples/*.cs` + assert lint_results.match(NO_WARNINGS) + end + end From 8d76f4bd3f3b3b5d460bd57829bcd6a0f7408b80 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 01:33:59 -0800 Subject: [PATCH 067/303] changing switch/case to switch/when -- it's a better word --- documentation/cs/aliases.cs | 2 ++ documentation/cs/switch.cs | 8 ++--- documentation/index.html.erb | 16 ++++++---- documentation/js/aliases.js | 1 + documentation/js/super.js | 2 ++ examples/code.cs | 8 ++--- index.html | 31 +++++++++++++------ .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 22 ++++++------- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 2 +- test/fixtures/execution/test_switch.cs | 11 +++++++ test/fixtures/execution/test_switch.js | 16 ++++++++++ 13 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 test/fixtures/execution/test_switch.cs create mode 100644 test/fixtures/execution/test_switch.js diff --git a/documentation/cs/aliases.cs b/documentation/cs/aliases.cs index 3bbbb490d7..efb592eb30 100644 --- a/documentation/cs/aliases.cs +++ b/documentation/cs/aliases.cs @@ -3,3 +3,5 @@ volume: 10 if band aint spinal_tap let_the_wild_rumpus_begin() unless answer is no + +if car.speed < speed_limit then accelerate(). diff --git a/documentation/cs/switch.cs b/documentation/cs/switch.cs index 41109413b1..955adef6db 100644 --- a/documentation/cs/switch.cs +++ b/documentation/cs/switch.cs @@ -1,9 +1,9 @@ switch day -case "Tuesday" then eat_breakfast() -case "Wednesday" then go_to_the_park() -case "Saturday" +when "Tuesday" then eat_breakfast() +when "Wednesday" then go_to_the_park() +when "Saturday" if day is bingo_day go_to_bingo() go_dancing(). -case "Sunday" then go_to_church() +when "Sunday" then go_to_church() else go_to_work(). \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index fabb13a0f6..b988444757 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -69,7 +69,7 @@ Array Slice Literals
Calling Super from a Subclass
Embedded JavaScript
- Switch/Case/Else
+ Switch/When/Else
Try/Catch/Finally
Multiline Strings
Change Log
@@ -278,6 +278,11 @@ coffee-script --print app/scripts/*.cs > concatenation.js

You can use not as an alias for !.

+

+ Instead of a newline or semicolon, then can be used to separate + conditions from expressions, in while, + if/else, and switch/when statements. +

As in YAML, on and yes are the same as boolean true, while off and no are boolean false. @@ -343,15 +348,14 @@ coffee-script --print app/scripts/*.cs > concatenation.js <%= code_for('embedded', 'hi()') %>

- Switch/Case/Else + Switch/When/Else Switch statements in JavaScript are rather broken. You can only do comparisons based on string equality, and need to remember to break at the end of every case statement to avoid accidentally falling through to - the default case. CoffeeScript - compiles switch statements into JavaScript if-else chains, allowing you to + the default case. CoffeeScript compiles switch statements into JavaScript if-else chains, allowing you to compare any object (via ===), preventing fall-through, and resulting - in a returnable, assignable expression. To specify the default case, just - use else. + in a returnable, assignable expression. The format is: switch condition, + when clauses, else the default case.

<%= code_for('switch') %> diff --git a/documentation/js/aliases.js b/documentation/js/aliases.js index f30e00ea67..804d56eadc 100644 --- a/documentation/js/aliases.js +++ b/documentation/js/aliases.js @@ -9,4 +9,5 @@ if (!(answer === false)) { let_the_wild_rumpus_begin(); } + car.speed < speed_limit ? accelerate() : null; })(); \ No newline at end of file diff --git a/documentation/js/super.js b/documentation/js/super.js index d4bd62614e..d9199affed 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -6,6 +6,7 @@ }; var Snake = function(name) { this.name = name; + return this.name; }; Snake.prototype = new Animal(); Snake.prototype.move = function() { @@ -14,6 +15,7 @@ }; var Horse = function(name) { this.name = name; + return this.name; }; Horse.prototype = new Animal(); Horse.prototype.move = function() { diff --git a/examples/code.cs b/examples/code.cs index f6d58537d3..919a991bf9 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -115,10 +115,10 @@ # Switch statements ("else" serves as a default). activity: switch day -case "Tuesday" then eat_breakfast() -case "Sunday" then go_to_church() -case "Saturday" then go_to_the_park() -case "Wednesday" +when "Tuesday" then eat_breakfast() +when "Sunday" then go_to_church() +when "Saturday" then go_to_the_park() +when "Wednesday" if day is bingo_day go_to_bingo() else diff --git a/index.html b/index.html index ff9087fe17..8777fdb75d 100644 --- a/index.html +++ b/index.html @@ -55,7 +55,7 @@

Table of Contents

Array Slice Literals
Calling Super from a Subclass
Embedded JavaScript
- Switch/Case/Else
+ Switch/When/Else
Try/Catch/Finally
Multiline Strings
Change Log
@@ -453,6 +453,11 @@

Language Reference

You can use not as an alias for !.

+

+ Instead of a newline or semicolon, then can be used to separate + conditions from expressions, in while, + if/else, and switch/when statements. +

As in YAML, on and yes are the same as boolean true, while off and no are boolean false. @@ -465,6 +470,8 @@

Language Reference

volume: 10 if band aint spinal_tap let_the_wild_rumpus_begin() unless answer is no + +if car.speed < speed_limit then accelerate().
if (ignition === true) {
   launch();
 }
@@ -475,6 +482,7 @@ 

Language Reference

if (!(answer === false)) { let_the_wild_rumpus_begin(); } +car.speed < speed_limit ? accelerate() : null;

@@ -595,6 +603,7 @@

Language Reference

}; var Snake = function(name) { this.name = name; + return this.name; }; Snake.prototype = new Animal(); Snake.prototype.move = function() { @@ -603,6 +612,7 @@

Language Reference

}; var Horse = function(name) { this.name = name; + return this.name; }; Horse.prototype = new Animal(); Horse.prototype.move = function() { @@ -620,6 +630,7 @@

Language Reference

}; var Snake = function(name) { this.name = name; + return this.name; }; Snake.prototype = new Animal(); Snake.prototype.move = function() { @@ -628,6 +639,7 @@

Language Reference

}; var Horse = function(name) { this.name = name; + return this.name; }; Horse.prototype = new Animal(); Horse.prototype.move = function() { @@ -658,24 +670,23 @@

Language Reference

;alert(hi());'>run: hi()

- Switch/Case/Else + Switch/When/Else Switch statements in JavaScript are rather broken. You can only do comparisons based on string equality, and need to remember to break at the end of every case statement to avoid accidentally falling through to - the default case. CoffeeScript - compiles switch statements into JavaScript if-else chains, allowing you to + the default case. CoffeeScript compiles switch statements into JavaScript if-else chains, allowing you to compare any object (via ===), preventing fall-through, and resulting - in a returnable, assignable expression. To specify the default case, just - use else. + in a returnable, assignable expression. The format is: switch condition, + when clauses, else the default case.

switch day
-case "Tuesday"   then eat_breakfast()
-case "Wednesday" then go_to_the_park()
-case "Saturday"
+when "Tuesday"   then eat_breakfast()
+when "Wednesday" then go_to_the_park()
+when "Saturday"
   if day is bingo_day
     go_to_bingo()
     go_dancing().
-case "Sunday"    then go_to_church()
+when "Sunday"    then go_to_church()
 else go_to_work().
 
if (day === "Tuesday") {
   eat_breakfast();
diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
index cb348dcc1d..af4540fd1a 100644
--- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
+++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
@@ -205,7 +205,7 @@
 		
 		
 			match
-			\b(break|case|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|while)\b
+			\b(break|when|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|while)\b
 			name
 			keyword.control.cs
 		
diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
index b90de5c947..31509e49f9 100644
--- a/lib/coffee_script/grammar.y
+++ b/lib/coffee_script/grammar.y
@@ -9,7 +9,7 @@ token CODE PARAM NEW RETURN
 token TRY CATCH FINALLY THROW
 token BREAK CONTINUE
 token FOR IN WHILE
-token SWITCH CASE
+token SWITCH WHEN
 token SUPER
 token DELETE
 token NEWLINE
@@ -320,23 +320,23 @@ rule
       IF Expression "."               { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) }
   ;
 
-  # Switch/Case blocks.
+  # Switch/When blocks.
   Switch:
     SWITCH Expression Then
-      Cases "."                       { result = val[3].rewrite_condition(val[1]) }
+      Whens "."                       { result = val[3].rewrite_condition(val[1]) }
   | SWITCH Expression Then
-      Cases ELSE Expressions "."      { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
+      Whens ELSE Expressions "."      { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
   ;
 
-  # The inner list of cases.
-  Cases:
-    Case                              { result = val[0] }
-  | Cases Case                        { result = val[0] << val[1] }
+  # The inner list of whens.
+  Whens:
+    When                              { result = val[0] }
+  | Whens When                        { result = val[0] << val[1] }
   ;
 
-  # An individual case.
-  Case:
-    CASE Expression Then Expressions  { result = IfNode.new(val[1], val[3]) }
+  # An individual when.
+  When:
+    WHEN Expression Then Expressions  { result = IfNode.new(val[1], val[3]) }
   ;
 
   # All of the following nutso if-else destructuring is to make the
diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb
index d44c486db2..f704ebe56f 100644
--- a/lib/coffee_script/lexer.rb
+++ b/lib/coffee_script/lexer.rb
@@ -13,7 +13,7 @@ class Lexer
                   "try", "catch", "finally", "throw",
                   "break", "continue",
                   "for", "in", "while",
-                  "switch", "case",
+                  "switch", "when",
                   "super",
                   "delete"]
 
diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb
index 38d3e57efc..e1b228d0f6 100644
--- a/lib/coffee_script/nodes.rb
+++ b/lib/coffee_script/nodes.rb
@@ -594,7 +594,7 @@ def compile(o={})
     end
   end
 
-  # If/else statements. Switch/cases get compiled into these. Acts as an
+  # If/else statements. Switch/whens get compiled into these. Acts as an
   # expression by pushing down requested returns to the expression bodies.
   # Single-expression IfNodes are compiled into ternary operators if possible,
   # because ternaries are first-class returnable assignable expressions.
diff --git a/test/fixtures/execution/test_switch.cs b/test/fixtures/execution/test_switch.cs
new file mode 100644
index 0000000000..bce30883cd
--- /dev/null
+++ b/test/fixtures/execution/test_switch.cs
@@ -0,0 +1,11 @@
+num: 10
+
+result: switch num
+when 5 then false
+when 'a'
+  false
+when 10 then true
+when 11 then false
+else false.
+
+print(result)
diff --git a/test/fixtures/execution/test_switch.js b/test/fixtures/execution/test_switch.js
new file mode 100644
index 0000000000..05874d4e04
--- /dev/null
+++ b/test/fixtures/execution/test_switch.js
@@ -0,0 +1,16 @@
+(function(){
+  var num = 10;
+  var result;
+  if (num === 5) {
+    result = false;
+  } else if (num === 'a') {
+    result = false;
+  } else if (num === 10) {
+    result = true;
+  } else if (num === 11) {
+    result = false;
+  } else {
+    result = false;
+  }
+  print(result);
+})();
\ No newline at end of file

From 726a9b229ef5933adb4356e067241b3462eaf606 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas 
Date: Thu, 24 Dec 2009 01:38:32 -0800
Subject: [PATCH 068/303] doc tweaks

---
 documentation/index.html.erb | 8 ++++++--
 index.html                   | 8 ++++++--
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/documentation/index.html.erb b/documentation/index.html.erb
index b988444757..ff4401aba0 100644
--- a/documentation/index.html.erb
+++ b/documentation/index.html.erb
@@ -238,14 +238,18 @@ coffee-script --print app/scripts/*.cs > concatenation.js

Conditionals, Ternaries, and Conditional Assignment - If/else statements can be written without the use of parenthesis and + If/else statements can be written without the use of parenthesis and curly brackets. As with functions and other block expressions, conditionals are closed with periods. No period is necessary when using the single-line postfix form, with the if at the end.

+

+ CoffeeScript will compile if statements using the ternary operator + when possible, to make it easier to use the result as an expression. +

<%= code_for('conditionals') %>

- CoffeeScript includes the conditional assignment operators: ||:, + The conditional assignment operators are available: ||:, which only assigns a value to a variable if the variable's current value is falsy, and &&:, which only replaces the value of truthy variables. diff --git a/index.html b/index.html index 8777fdb75d..1f67ec89a9 100644 --- a/index.html +++ b/index.html @@ -365,11 +365,15 @@

Language Reference

Conditionals, Ternaries, and Conditional Assignment - If/else statements can be written without the use of parenthesis and + If/else statements can be written without the use of parenthesis and curly brackets. As with functions and other block expressions, conditionals are closed with periods. No period is necessary when using the single-line postfix form, with the if at the end.

+

+ CoffeeScript will compile if statements using the ternary operator + when possible, to make it easier to use the result as an expression. +

mood: greatly_improved if singing
 
 if happy and knows_it
@@ -391,7 +395,7 @@ 

Language Reference

expensive = expensive || do_the_math();

- CoffeeScript includes the conditional assignment operators: ||:, + The conditional assignment operators are available: ||:, which only assigns a value to a variable if the variable's current value is falsy, and &&:, which only replaces the value of truthy variables. From c438123a3d47ab284d64bf2f65169bb9d9c36403 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 09:54:12 -0800 Subject: [PATCH 069/303] added a wish list to the docs --- coffee-script.gemspec | 4 ++-- documentation/css/docs.css | 3 +++ documentation/index.html.erb | 31 +++++++++++++++++++++++++++++++ index.html | 31 +++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index 6fcf2ea1d9..6bb4194869 100644 --- a/coffee-script.gemspec +++ b/coffee-script.gemspec @@ -1,12 +1,12 @@ Gem::Specification.new do |s| s.name = 'coffee-script' s.version = '0.1.0' # Keep version in sync with coffee-script.rb - s.date = '2009-12-16' + s.date = '2009-12-24' s.homepage = "http://jashkenas.github.com/coffee-script/" s.summary = "The CoffeeScript Compiler" s.description = <<-EOS - ... + CoffeeScript is a little language that compiles into JavaScript. EOS s.authors = ['Jeremy Ashkenas'] diff --git a/documentation/css/docs.css b/documentation/css/docs.css index d3688f315e..d1b5e4bfe3 100644 --- a/documentation/css/docs.css +++ b/documentation/css/docs.css @@ -30,6 +30,9 @@ b.header { margin: 40px 0 5px 0; font-size: 16px; } +li { + margin-bottom: 7px; +} table { margin: 16px 0 0 13px; padding: 0; width: 625px; diff --git a/documentation/index.html.erb b/documentation/index.html.erb index ff4401aba0..a0ddba4a17 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -72,6 +72,7 @@ Switch/When/Else
Try/Catch/Finally
Multiline Strings
+ Contributing
Change Log

@@ -376,6 +377,36 @@ coffee-script --print app/scripts/*.cs > concatenation.js

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

Contributing

+ +

+ Here's a wish list of things that would be wonderful to have in + CoffeeScript: +

+ +
    +
  • + A JavaScript version of the compiler, perhaps using Alessandro Warth's + OMeta. +
  • +
  • + Ideas for alternate syntax to end blocks of expressions — the periods + can look a little weird with deeply nested structure. +
  • +
  • + Test cases for any syntax errors you encounter that you think CoffeeScript + should be able to compile properly. +
  • +
  • + A tutorial that introduces CoffeeScript from the ground up for folks + without knowledge of JavaScript. +
  • +
  • + Integration with Processing.js's JavaScript API (this would depend on + having a JavaScript version of the compiler). +
  • +
+

Change Log

diff --git a/index.html b/index.html index 1f67ec89a9..d93c7de1b3 100644 --- a/index.html +++ b/index.html @@ -58,6 +58,7 @@

Table of Contents

Switch/When/Else
Try/Catch/Finally
Multiline Strings
+ Contributing
Change Log

@@ -755,6 +756,36 @@

Language Reference

world..."; ;alert(moby_dick);'>run: moby_dick
+

Contributing

+ +

+ Here's a wish list of things that would be wonderful to have in + CoffeeScript: +

+ +
    +
  • + A JavaScript version of the compiler, perhaps using Alessandro Warth's + OMeta. +
  • +
  • + Ideas for alternate syntax to end blocks of expressions — the periods + can look a little weird with deeply nested structure. +
  • +
  • + Test cases for any syntax errors you encounter that you think CoffeeScript + should be able to compile properly. +
  • +
  • + A tutorial that introduces CoffeeScript from the ground up for folks + without knowledge of JavaScript. +
  • +
  • + Integration with Processing.js's JavaScript API (this would depend on + having a JavaScript version of the compiler). +
  • +
+

Change Log

From 840e2b89fcd3e019e52dcc03b5bc27fb479a705f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 09:56:44 -0800 Subject: [PATCH 070/303] docs --- documentation/index.html.erb | 2 +- index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index a0ddba4a17..cdabf6cd7d 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -172,7 +172,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

- This document is structured so that it can be read from top to bottom, + This reference is structured so that it can be read from top to bottom, if you like. Later sections use ideas and syntax previously introduced. Familiarity with JavaScript is assumed. In all of the following examples, the source CoffeeScript is provided on diff --git a/index.html b/index.html index d93c7de1b3..209bd20f0f 100644 --- a/index.html +++ b/index.html @@ -242,7 +242,7 @@

Language Reference

- This document is structured so that it can be read from top to bottom, + This reference is structured so that it can be read from top to bottom, if you like. Later sections use ideas and syntax previously introduced. Familiarity with JavaScript is assumed. In all of the following examples, the source CoffeeScript is provided on From 2e8f03b4cc31d7fe0a3e2f6e4d18978f864acf48 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 10:31:44 -0800 Subject: [PATCH 071/303] another wish --- documentation/index.html.erb | 5 +++++ index.html | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index cdabf6cd7d..6423ebd521 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -405,6 +405,11 @@ coffee-script --print app/scripts/*.cs > concatenation.js Integration with Processing.js's JavaScript API (this would depend on having a JavaScript version of the compiler). +

  • + A lot of the code generation in nodes.rb gets into messy + string manipulation. Techniques for cleaning this up across the board + would be appreciated. +
  • Change Log

    diff --git a/index.html b/index.html index 209bd20f0f..0fa2770140 100644 --- a/index.html +++ b/index.html @@ -784,6 +784,11 @@

    Contributing

    Integration with Processing.js's JavaScript API (this would depend on having a JavaScript version of the compiler). +
  • + A lot of the code generation in nodes.rb gets into messy + string manipulation. Techniques for cleaning this up across the board + would be appreciated. +
  • Change Log

    From 5d1ec9d2a95b2807e5b404c51ed46d3c68f2ee97 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 11:46:51 -0800 Subject: [PATCH 072/303] added the instanceof operator to the grammar as an operation node --- lib/coffee_script/grammar.y | 5 +++-- lib/coffee_script/lexer.rb | 2 +- test/fixtures/execution/keyword_operators.cs | 10 ++++++++++ test/fixtures/execution/keyword_operators.js | 10 ++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/execution/keyword_operators.cs create mode 100644 test/fixtures/execution/keyword_operators.js diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 31509e49f9..3e28312684 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -10,8 +10,8 @@ token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE token SWITCH WHEN +token DELETE INSTANCEOF token SUPER -token DELETE token NEWLINE token COMMENT token JS @@ -27,7 +27,7 @@ prechigh right '==' '!=' IS AINT left '&&' '||' AND OR right '-=' '+=' '/=' '*=' - right DELETE + right DELETE INSTANCEOF left "." right THROW FOR IN WHILE NEW left UNLESS IF ELSE @@ -185,6 +185,7 @@ rule | Expression '&&:' Expression { result = OpNode.new(val[1], val[0], val[2]) } | DELETE Expression { result = OpNode.new(val[0], val[1]) } + | Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) } ; # Function definition. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index f704ebe56f..3d074d80c8 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -15,7 +15,7 @@ class Lexer "for", "in", "while", "switch", "when", "super", - "delete"] + "delete", "instanceof"] # Token matching regexes. IDENTIFIER = /\A([a-zA-Z$_]\w*)/ diff --git a/test/fixtures/execution/keyword_operators.cs b/test/fixtures/execution/keyword_operators.cs new file mode 100644 index 0000000000..66ceb4b74e --- /dev/null +++ b/test/fixtures/execution/keyword_operators.cs @@ -0,0 +1,10 @@ +a: 5 +atype: typeof(a) + +b: "hello" +btype: typeof(b) + +Klass: => . +k: new Klass() + +print(atype is 'number' and btype is 'string' and k instanceof(Klass)) \ No newline at end of file diff --git a/test/fixtures/execution/keyword_operators.js b/test/fixtures/execution/keyword_operators.js new file mode 100644 index 0000000000..21e06cad98 --- /dev/null +++ b/test/fixtures/execution/keyword_operators.js @@ -0,0 +1,10 @@ +(function(){ + var a = 5; + var atype = typeof(a); + var b = "hello"; + var btype = typeof(b); + var Klass = function() { + }; + var k = new Klass(); + print(atype === 'number' && btype === 'string' && k instanceof Klass); +})(); \ No newline at end of file From 7f502543d2f6d1e070206d6b6bdafe004aa7fa99 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 11:50:44 -0800 Subject: [PATCH 073/303] added the typeof operater as an OpNode --- lib/coffee_script/grammar.y | 5 +++-- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 5 +++-- test/fixtures/execution/keyword_operators.cs | 6 +++--- test/fixtures/execution/keyword_operators.js | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 3e28312684..c31d519690 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -10,7 +10,7 @@ token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE token SWITCH WHEN -token DELETE INSTANCEOF +token DELETE INSTANCEOF TYPEOF token SUPER token NEWLINE token COMMENT @@ -27,7 +27,7 @@ prechigh right '==' '!=' IS AINT left '&&' '||' AND OR right '-=' '+=' '/=' '*=' - right DELETE INSTANCEOF + right DELETE INSTANCEOF TYPEOF left "." right THROW FOR IN WHILE NEW left UNLESS IF ELSE @@ -185,6 +185,7 @@ rule | Expression '&&:' Expression { result = OpNode.new(val[1], val[0], val[2]) } | DELETE Expression { result = OpNode.new(val[0], val[1]) } + | TYPEOF Expression { result = OpNode.new(val[0], val[1]) } | Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 3d074d80c8..683de4726b 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -15,7 +15,7 @@ class Lexer "for", "in", "while", "switch", "when", "super", - "delete", "instanceof"] + "delete", "instanceof", "typeof"] # Token matching regexes. IDENTIFIER = /\A([a-zA-Z$_]\w*)/ diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index e1b228d0f6..41d94f63b6 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -342,7 +342,8 @@ class OpNode < Node "aint" => "!==", 'not' => '!', } - CONDITIONALS = ['||:', '&&:'] + CONDITIONALS = ['||:', '&&:'] + PREFIX_OPERATORS = ['typeof', 'delete'] attr_reader :operator, :first, :second @@ -369,7 +370,7 @@ def compile_conditional(o) end def compile_unary(o) - space = @operator.to_s == 'delete' ? ' ' : '' + space = PREFIX_OPERATORS.include?(@operator.to_s) ? ' ' : '' parts = [@operator.to_s, space, @first.compile(o)] parts.reverse! if @flip parts.join('') diff --git a/test/fixtures/execution/keyword_operators.cs b/test/fixtures/execution/keyword_operators.cs index 66ceb4b74e..573de104a6 100644 --- a/test/fixtures/execution/keyword_operators.cs +++ b/test/fixtures/execution/keyword_operators.cs @@ -1,10 +1,10 @@ a: 5 -atype: typeof(a) +atype: typeof a b: "hello" -btype: typeof(b) +btype: typeof b Klass: => . k: new Klass() -print(atype is 'number' and btype is 'string' and k instanceof(Klass)) \ No newline at end of file +print(atype is 'number' and btype is 'string' and k instanceof Klass) \ No newline at end of file diff --git a/test/fixtures/execution/keyword_operators.js b/test/fixtures/execution/keyword_operators.js index 21e06cad98..ffccad4c25 100644 --- a/test/fixtures/execution/keyword_operators.js +++ b/test/fixtures/execution/keyword_operators.js @@ -1,8 +1,8 @@ (function(){ var a = 5; - var atype = typeof(a); + var atype = typeof a; var b = "hello"; - var btype = typeof(b); + var btype = typeof b; var Klass = function() { }; var k = new Klass(); From bfb24c6b5db2bb583905658c8f075ee7c5f7c57f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 11:59:19 -0800 Subject: [PATCH 074/303] bumping to 0.1.1 --- coffee-script.gemspec | 9 +++++++-- lib/coffee-script.rb | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index 6bb4194869..c759114b44 100644 --- a/coffee-script.gemspec +++ b/coffee-script.gemspec @@ -1,12 +1,17 @@ Gem::Specification.new do |s| s.name = 'coffee-script' - s.version = '0.1.0' # Keep version in sync with coffee-script.rb + s.version = '0.1.1' # Keep version in sync with coffee-script.rb s.date = '2009-12-24' s.homepage = "http://jashkenas.github.com/coffee-script/" s.summary = "The CoffeeScript Compiler" s.description = <<-EOS - CoffeeScript is a little language that compiles into JavaScript. + CoffeeScript is a little language that compiles into JavaScript. Think + of it as JavaScript's less ostentatious kid brother -- the same genes, + roughly the same height, but a different sense of style. Apart from a + handful of bonus goodies, statements in CoffeeScript correspond + one-to-one with their equivalent in JavaScript, it's just another + way of saying it. EOS s.authors = ['Jeremy Ashkenas'] diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index ee77f129c7..5f31687666 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -9,7 +9,7 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.1.0' # Keep in sync with the gemspec. + VERSION = '0.1.1' # Keep in sync with the gemspec. # Compile a script (String or IO) to JavaScript. def self.compile(script) From 39ceca477d16d91dde3d9ccc6a56e9611811b770 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 12:02:28 -0800 Subject: [PATCH 075/303] docs for 0.1.1 --- documentation/index.html.erb | 5 +++++ index.html | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 6423ebd521..d567cf1fd2 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -414,6 +414,11 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    Change Log

    +

    + 0.1.1 + Added instanceof and typeof as operators. +

    +

    0.1.0 Initial CoffeeScript release. diff --git a/index.html b/index.html index 0fa2770140..7125f53a44 100644 --- a/index.html +++ b/index.html @@ -793,6 +793,11 @@

    Contributing

    Change Log

    +

    + 0.1.1 + Added instanceof and typeof as operators. +

    +

    0.1.0 Initial CoffeeScript release. From aafd3cd6ea72df868cfa9f8d05049e5e4a2a2521 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 13:48:46 -0800 Subject: [PATCH 076/303] first, totally broken branch of significant whitespace -- it can handle examples/whitespace.cs though --- examples/whitespace.cs | 12 +++++++++++ examples/whitespace.js | 8 +++++++ lib/coffee_script/grammar.y | 42 +++++++++++++++++++++---------------- lib/coffee_script/lexer.rb | 28 +++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 examples/whitespace.cs create mode 100644 examples/whitespace.js diff --git a/examples/whitespace.cs b/examples/whitespace.cs new file mode 100644 index 0000000000..94fa5e7eca --- /dev/null +++ b/examples/whitespace.cs @@ -0,0 +1,12 @@ +# square: x => x * x + +square: x => + x * x + +elements.each(el => + el.click(event => + el.show() + ) +) + +a: 5 \ No newline at end of file diff --git a/examples/whitespace.js b/examples/whitespace.js new file mode 100644 index 0000000000..3d0af9e5fb --- /dev/null +++ b/examples/whitespace.js @@ -0,0 +1,8 @@ +(function(){ + + // square: x => x * x + var square = function(x) { + return x * x; + }; + var a = 5; +})(); \ No newline at end of file diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index c31d519690..38132871a2 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -15,6 +15,7 @@ token SUPER token NEWLINE token COMMENT token JS +token INDENT OUTDENT # Declare order of operations. prechigh @@ -32,7 +33,8 @@ prechigh right THROW FOR IN WHILE NEW left UNLESS IF ELSE left ":" '||:' '&&:' - right RETURN + right RETURN INDENT + left OUTDENT preclow # We expect 4 shift/reduce errors for optional syntax. @@ -84,6 +86,11 @@ rule | Comment ; + Block: + Expression Terminator { result = Expressions.new([val[0]]) } + | INDENT Expressions OUTDENT { result = val[1] } + ; + # All tokens that can terminate an expression. Terminator: "\n" @@ -191,14 +198,14 @@ rule # Function definition. Code: - ParamList "=>" CodeBody "." { result = CodeNode.new(val[0], val[2]) } - | "=>" CodeBody "." { result = CodeNode.new([], val[1]) } + ParamList "=>" CodeBody { result = CodeNode.new(val[0], val[2]) } + | "=>" CodeBody { result = CodeNode.new([], val[1]) } ; # The body of a function. CodeBody: /* nothing */ { result = Expressions.new([]) } - | Expressions { result = val[0] } + | Block { result = val[0] } ; # The parameters to a function definition. @@ -279,15 +286,15 @@ rule # Try/catch/finally exception handling blocks. Try: - TRY Expressions Catch "." { result = TryNode.new(val[1], val[2][0], val[2][1]) } + TRY Expressions Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) } | TRY Expressions Catch - FINALLY Expressions "." { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } + FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } ; # A catch clause. Catch: /* nothing */ { result = [nil, nil] } - | CATCH IDENTIFIER Expressions { result = [val[1], val[2]] } + | CATCH IDENTIFIER Block { result = [val[1], val[2]] } ; # Throw an exception. @@ -302,32 +309,31 @@ rule # The while loop. (there is no do..while). While: - WHILE Expression Then - Expressions "." { result = WhileNode.new(val[1], val[3]) } + WHILE Expression Then Block { result = WhileNode.new(val[1], val[3]) } ; # Array comprehensions, including guard and current index. For: Expression FOR IDENTIFIER - IN PureExpression "." { result = ForNode.new(val[0], val[4], val[2], nil) } + IN PureExpression { result = ForNode.new(val[0], val[4], val[2], nil) } | Expression FOR IDENTIFIER "," IDENTIFIER - IN PureExpression "." { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) } + IN PureExpression { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) } | Expression FOR IDENTIFIER IN PureExpression - IF Expression "." { result = ForNode.new(val[0], val[4], val[2], val[6]) } + IF Expression { result = ForNode.new(val[0], val[4], val[2], val[6]) } | Expression FOR IDENTIFIER "," IDENTIFIER IN PureExpression - IF Expression "." { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) } + IF Expression { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) } ; # Switch/When blocks. Switch: SWITCH Expression Then - Whens "." { result = val[3].rewrite_condition(val[1]) } + Whens { result = val[3].rewrite_condition(val[1]) } | SWITCH Expression Then - Whens ELSE Expressions "." { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } + Whens ELSE Block { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } ; # The inner list of whens. @@ -338,7 +344,7 @@ rule # An individual when. When: - WHEN Expression Then Expressions { result = IfNode.new(val[1], val[3]) } + WHEN Expression Then Block { result = IfNode.new(val[1], val[3]) } ; # All of the following nutso if-else destructuring is to make the @@ -358,8 +364,8 @@ rule # Terminating else bodies are strictly optional. ElseBody - "." { result = nil } - | ELSE Expressions "." { result = val[1] } + /* nothing */ { result = nil } + | ELSE Block { result = val[1] } ; # All the alternatives for ending an if-else block. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 683de4726b..bb25ed070e 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -28,6 +28,7 @@ class Lexer COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ + INDENT = /\A\n( *)/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ @@ -45,6 +46,8 @@ def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @i = 0 # Current character position we're parsing @line = 1 # The current line. + @indent = 0 # The current indent level. + @indents = [] # The stack of all indent levels we are currently within. @tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value] while @i < @code.length @chunk = @code[@i..-1] @@ -62,6 +65,7 @@ def extract_next_token return if js_token return if regex_token return if comment_token + return if indent_token return if whitespace_token return literal_token end @@ -118,6 +122,23 @@ def comment_token @i += comment.length end + def indent_token + return false unless indent = @chunk[INDENT, 1] + size = indent.size + return literal_token if size == @indent + if size > @indent + tag = :INDENT + @indent = size + @indents << @indent + else + tag = :OUTDENT + @indents.pop + @indent = @indents.first || 0 + end + @i += (size + 1) + token(tag, size) + end + # Matches and consumes non-meaningful whitespace. def whitespace_token return false unless whitespace = @chunk[WHITESPACE, 1] @@ -182,6 +203,13 @@ def remove_leading_newlines @tokens.pop if last_value == "\n" end + # Close up all remaining open blocks. + def close_indentation + while indent = @indents.pop + token(:OUTDENT, @indents.first || 0) + end + end + end end \ No newline at end of file From da84906d1e07bbe34797969c77a51ff372777331 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 14:34:48 -0800 Subject: [PATCH 077/303] this might not work for ))) outdent cases --- examples/whitespace.cs | 2 +- lib/coffee_script/grammar.y | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/whitespace.cs b/examples/whitespace.cs index 94fa5e7eca..e2f427198a 100644 --- a/examples/whitespace.cs +++ b/examples/whitespace.cs @@ -1,4 +1,4 @@ -# square: x => x * x +square: x => x * x square: x => x * x diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 38132871a2..126d5f884d 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -87,7 +87,7 @@ rule ; Block: - Expression Terminator { result = Expressions.new([val[0]]) } + Expression { result = Expressions.new(val) } | INDENT Expressions OUTDENT { result = val[1] } ; From ed504ea6bee355efb24ba323f6a830117e998073 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 14:37:30 -0800 Subject: [PATCH 078/303] added and -> &&, or -> || to the docs (they were missing) --- documentation/index.html.erb | 4 ++++ index.html | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index d567cf1fd2..167f8c5c8c 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -283,6 +283,10 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    You can use not as an alias for !.

    +

    + For logic, and compiles to &&, and or + into ||. +

    Instead of a newline or semicolon, then can be used to separate conditions from expressions, in while, diff --git a/index.html b/index.html index 7125f53a44..cc4910f1b5 100644 --- a/index.html +++ b/index.html @@ -458,6 +458,10 @@

    Language Reference

    You can use not as an alias for !.

    +

    + For logic, and compiles to &&, and or + into ||. +

    Instead of a newline or semicolon, then can be used to separate conditions from expressions, in while, From 08248180f9fcc3b3794bd55b283eff9f85db7d2c Mon Sep 17 00:00:00 2001 From: tlrobinson Date: Thu, 24 Dec 2009 14:40:39 -0800 Subject: [PATCH 079/303] Read from stdin if source is "-" --- lib/coffee_script/command_line.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 2ab75295da..3d2327f440 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -41,11 +41,15 @@ def usage # Compiles (or partially compiles) the source CoffeeScript file, returning # the desired JS, tokens, or lint results. def compile_javascript(source) - script = File.read(source) + if source == "-" + script = STDIN.read + else + script = File.read(source) + end return tokens(script) if @options[:tokens] js = compile(script, source) return unless js - return puts(js) if @options[:print] + return puts(js) if @options[:print] or source == "-" return lint(js) if @options[:lint] File.open(path_for(source), 'w+') {|f| f.write(js) } end @@ -73,7 +77,7 @@ def watch_coffee_scripts # Ensure that all of the source files exist. def check_sources usage if @sources.empty? - missing = @sources.detect {|s| !File.exists?(s) } + missing = @sources.detect {|s| !File.exists?(s) and s != "-" } if missing STDERR.puts("File not found: '#{missing}'") exit(1) From dbccc1fa4f8e4463e96fff8aa9906693031af8e6 Mon Sep 17 00:00:00 2001 From: tlrobinson Date: Thu, 24 Dec 2009 14:41:35 -0800 Subject: [PATCH 080/303] Narwhal support for CoffeeScript --- lib-js/coffee-script.js | 91 ++++++++++++++++++++++++++++++++++ lib-js/coffee-script/loader.js | 23 +++++++++ package.json | 9 ++++ 3 files changed, 123 insertions(+) create mode 100644 lib-js/coffee-script.js create mode 100644 lib-js/coffee-script/loader.js create mode 100644 package.json diff --git a/lib-js/coffee-script.js b/lib-js/coffee-script.js new file mode 100644 index 0000000000..266f1309d9 --- /dev/null +++ b/lib-js/coffee-script.js @@ -0,0 +1,91 @@ +var FILE = require("file"); +var OS = require("os"); + +exports.run = function(args) { + // TODO: non-REPL + + args.shift(); + + if (args.length) { + require(FILE.absolute(args[0])); + return; + } + + while (true) + { + try { + system.stdout.write("cs> ").flush(); + + var result = exports.cs_eval(require("readline").readline()); + + if (result !== undefined) + print(result); + + } catch (e) { + print(e); + } + } +} + +// executes the coffee-script Ruby program to convert from CoffeeScript to Objective-J. +// eventually this will hopefully be replaced by a JavaScript program. +var coffeePath = FILE.path(module.path).dirname().dirname().join("bin", "coffee-script"); + +exports.compileFile = function(path) { + var coffee = OS.popen([coffeePath, "--print", path]); + + if (coffee.wait() !== 0) + throw new Error("coffee compiler error"); + + return coffee.stdout.read(); +} + +exports.compile = function(source) { + var coffee = OS.popen([coffeePath, "-"]); + + coffee.stdin.write(source).flush().close(); + + if (coffee.wait() !== 0) + throw new Error("coffee compiler error"); + + return coffee.stdout.read(); +} + +// these two functions are equivalent to objective-j's objj_eval/make_narwhal_factory. +// implemented as a call to coffee and objj_eval/make_narwhal_factory +exports.cs_eval = function(source) { + init(); + + var code = exports.compile(source); + + // strip the function wrapper, we add our own. + // TODO: this is very fragile + code = code.split("\n").slice(1,-2).join("\n"); + + return eval(code); +} + +exports.make_narwhal_factory = function(path) { + init(); + + var code = exports.compileFile(path); + + // strip the function wrapper, we add our own. + // TODO: this is very fragile + code = code.split("\n").slice(1,-2).join("\n"); + + var factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; + + if (system.engine === "rhino") + return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); + + // eval requires parenthesis, but parenthesis break compileFunction. + else + return eval("(" + factoryText + ")"); +} + + +var init = function() { + // make sure it's only done once + init = function(){} +} \ No newline at end of file diff --git a/lib-js/coffee-script/loader.js b/lib-js/coffee-script/loader.js new file mode 100644 index 0000000000..9dfba97105 --- /dev/null +++ b/lib-js/coffee-script/loader.js @@ -0,0 +1,23 @@ +var coffeescript = null; + +function CoffeeScriptLoader() { + var loader = {}; + var factories = {}; + + loader.reload = function(topId, path) { + if (!coffeescript) coffeescript = require("coffee-script"); + + //print("loading objective-j: " + topId + " (" + path + ")"); + factories[topId] = coffeescript.make_narwhal_factory(path); + } + + loader.load = function(topId, path) { + if (!factories.hasOwnProperty(topId)) + loader.reload(topId, path); + return factories[topId]; + } + + return loader; +}; + +require.loader.loaders.unshift([".cs", CoffeeScriptLoader()]); diff --git a/package.json b/package.json new file mode 100644 index 0000000000..ead29983f2 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "coffee-script", + "lib": "lib-js", + "preload": ["coffee-script/loader"], + "description": "Unfancy JavaScript", + "keywords": ["javascript", "language"], + "author": "Jeremy Ashkenas", + "version": "0.1.1" +} From 9f809984154c82a2798d83b8dcaa9db830f35c57 Mon Sep 17 00:00:00 2001 From: tlrobinson Date: Thu, 24 Dec 2009 14:42:57 -0800 Subject: [PATCH 081/303] Command line CoffeeScript --- bin/cs | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 bin/cs diff --git a/bin/cs b/bin/cs new file mode 100755 index 0000000000..b59bb2abf6 --- /dev/null +++ b/bin/cs @@ -0,0 +1,3 @@ +#!/usr/bin/env narwhal + +require("coffee-script").run(system.args); From 73a0777705cdf780a449f3dc4fa92938e279b75a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 14:51:53 -0800 Subject: [PATCH 082/303] outdent lexing is correct now, I think --- lib/coffee_script/lexer.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index bb25ed070e..aeb266d46c 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -122,6 +122,7 @@ def comment_token @i += comment.length end + # Record tokens for indentation differing from the previous line. def indent_token return false unless indent = @chunk[INDENT, 1] size = indent.size @@ -132,8 +133,8 @@ def indent_token @indents << @indent else tag = :OUTDENT - @indents.pop - @indent = @indents.first || 0 + @indents.pop while @indents.last && ((@indents.last || 0) > size) + @indent = @indents.last || 0 end @i += (size + 1) token(tag, size) From c3ff752c403727fe834a6c29bf71e706e9726ea6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 15:05:14 -0800 Subject: [PATCH 083/303] ... --- examples/whitespace.cs | 4 +--- lib/coffee_script/grammar.y | 7 ++++++- lib/coffee_script/lexer.rb | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/whitespace.cs b/examples/whitespace.cs index e2f427198a..0f26bd450c 100644 --- a/examples/whitespace.cs +++ b/examples/whitespace.cs @@ -5,8 +5,6 @@ elements.each(el => el.click(event => - el.show() - ) -) + el.show())) a: 5 \ No newline at end of file diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 126d5f884d..e1ec2fb9b8 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -88,7 +88,12 @@ rule Block: Expression { result = Expressions.new(val) } - | INDENT Expressions OUTDENT { result = val[1] } + | INDENT Expressions Outdent { result = val[1] } + ; + + Outdent: + /* nothing */ + | OUTDENT ; # All tokens that can terminate an expression. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index aeb266d46c..e72c3cb7a2 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -136,6 +136,7 @@ def indent_token @indents.pop while @indents.last && ((@indents.last || 0) > size) @indent = @indents.last || 0 end + @line += 1 @i += (size + 1) token(tag, size) end From e27756cee88cdf863914fa3e5fcc188125a4e4d2 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 15:31:00 -0800 Subject: [PATCH 084/303] with a working -n --no-wrap option to disable the top-level function safety wrapper --- lib/coffee-script.rb | 4 ++-- lib/coffee_script/command_line.rb | 5 ++++- lib/coffee_script/nodes.rb | 9 +++++---- test/fixtures/each_no_wrap.js | 29 +++++++++++++++++++++++++++++ test/unit/test_execution.rb | 5 +++-- test/unit/test_parser.rb | 5 +++++ 6 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/each_no_wrap.js diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 5f31687666..1459a83de1 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -12,9 +12,9 @@ module CoffeeScript VERSION = '0.1.1' # Keep in sync with the gemspec. # Compile a script (String or IO) to JavaScript. - def self.compile(script) + def self.compile(script, options={}) script = script.read if script.respond_to?(:read) - Parser.new.parse(script).compile + Parser.new.parse(script).compile(options) end end diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 3d2327f440..4cc01e5241 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -112,7 +112,7 @@ def tokens(script) # Compile a single source file to JavaScript. def compile(script, source='') begin - CoffeeScript.compile(script) + CoffeeScript.compile(script, :no_wrap => @options[:no_wrap]) rescue CoffeeScript::ParseError => e STDERR.puts e.message(source) exit(1) unless @options[:watch] @@ -160,6 +160,9 @@ def parse_options opts.on('-v', '--verbose', 'print at every step of code generation') do |v| ENV['VERBOSE'] = 'true' end + opts.on('-n', '--no-wrap', 'suppress the top-level safety function wrapper') do |n| + @options[:no_wrap] = true + end opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i| install_bundle exit diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 41d94f63b6..31b2f29571 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -75,16 +75,17 @@ def last?(node) end # If this is the top-level Expressions, wrap everything in a safety closure. - def root_compile - code = compile(:indent => TAB, :scope => Scope.new) + def root_compile(o={}) + indent = o[:no_wrap] ? '' : TAB + code = compile(:indent => indent, :scope => Scope.new) code.gsub!(STRIP_TRAILING_WHITESPACE, '') - "(function(){\n#{code}\n})();" + o[:no_wrap] ? code : "(function(){\n#{code}\n})();" end # The extra fancy is to handle pushing down returns and assignments # recursively to the final lines of inner statements. def compile(options={}) - return root_compile unless options[:scope] + return root_compile(options) unless options[:scope] code = @expressions.map { |node| o = super(options) if last?(node) && (o[:return] || o[:assign]) diff --git a/test/fixtures/each_no_wrap.js b/test/fixtures/each_no_wrap.js new file mode 100644 index 0000000000..6ae498b7fc --- /dev/null +++ b/test/fixtures/each_no_wrap.js @@ -0,0 +1,29 @@ + +// The cornerstone, an each implementation. +// Handles objects implementing forEach, arrays, and raw objects. +_.each = function(obj, iterator, context) { + var index = 0; + try { + if (obj.forEach) { + obj.forEach(iterator, context); + } else if (_.isArray(obj) || _.isArguments(obj)) { + var a = obj; + for (var b=0, c=a.length; b true) == File.read('test/fixtures/each_no_wrap.js') + end + end From 42ca566d26c83d3b330adac194517e7741380270 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 15:35:58 -0800 Subject: [PATCH 085/303] document that -e can read from stdin --- lib/coffee_script/command_line.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 4cc01e5241..8fef1943c7 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -151,7 +151,7 @@ def parse_options opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l| @options[:lint] = true end - opts.on('-e', '--eval', 'eval a little scriptlet directly from the cli') do |e| + opts.on('-e', '--eval', 'eval a little scriptlet or read from stdin') do |e| @options[:eval] = true end opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t| From a80b532a05e4d75c50aacf264aa7a5d7c34c05be Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 15:49:42 -0800 Subject: [PATCH 086/303] removing the special-case std-reading in favor of '--eval' --- lib-js/coffee-script.js | 24 ++++++++++++------------ lib/coffee_script/command_line.rb | 10 +++------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib-js/coffee-script.js b/lib-js/coffee-script.js index 266f1309d9..dc7c32a023 100644 --- a/lib-js/coffee-script.js +++ b/lib-js/coffee-script.js @@ -3,14 +3,14 @@ var OS = require("os"); exports.run = function(args) { // TODO: non-REPL - + args.shift(); - + if (args.length) { require(FILE.absolute(args[0])); return; } - + while (true) { try { @@ -20,7 +20,7 @@ exports.run = function(args) { if (result !== undefined) print(result); - + } catch (e) { print(e); } @@ -41,7 +41,7 @@ exports.compileFile = function(path) { } exports.compile = function(source) { - var coffee = OS.popen([coffeePath, "-"]); + var coffee = OS.popen([coffeePath, "-e"]); coffee.stdin.write(source).flush().close(); @@ -55,30 +55,30 @@ exports.compile = function(source) { // implemented as a call to coffee and objj_eval/make_narwhal_factory exports.cs_eval = function(source) { init(); - + var code = exports.compile(source); - + // strip the function wrapper, we add our own. // TODO: this is very fragile code = code.split("\n").slice(1,-2).join("\n"); - + return eval(code); } exports.make_narwhal_factory = function(path) { init(); - + var code = exports.compileFile(path); - + // strip the function wrapper, we add our own. // TODO: this is very fragile code = code.split("\n").slice(1,-2).join("\n"); - + var factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; if (system.engine === "rhino") return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); - + // eval requires parenthesis, but parenthesis break compileFunction. else return eval("(" + factoryText + ")"); diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 8fef1943c7..eb749d93dd 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -41,15 +41,11 @@ def usage # Compiles (or partially compiles) the source CoffeeScript file, returning # the desired JS, tokens, or lint results. def compile_javascript(source) - if source == "-" - script = STDIN.read - else - script = File.read(source) - end + script = File.read(source) return tokens(script) if @options[:tokens] js = compile(script, source) return unless js - return puts(js) if @options[:print] or source == "-" + return puts(js) if @options[:print] return lint(js) if @options[:lint] File.open(path_for(source), 'w+') {|f| f.write(js) } end @@ -77,7 +73,7 @@ def watch_coffee_scripts # Ensure that all of the source files exist. def check_sources usage if @sources.empty? - missing = @sources.detect {|s| !File.exists?(s) and s != "-" } + missing = @sources.detect {|s| !File.exists?(s) } if missing STDERR.puts("File not found: '#{missing}'") exit(1) From 47812d9ea6609a15d4fe73b720d4bcf24a4c116a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 16:23:23 -0800 Subject: [PATCH 087/303] fixing super() calls, thanks to tolmasky --- lib/coffee_script/nodes.rb | 11 ++++++---- test/fixtures/execution/calling_super.cs | 23 ++++++++++++++++++++ test/fixtures/execution/calling_super.js | 27 ++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/execution/calling_super.cs create mode 100644 test/fixtures/execution/calling_super.js diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 31b2f29571..bced26706d 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -210,7 +210,8 @@ def compile(o={}) def compile_super(args, o) methname = o[:last_assign].sub(LEADING_DOT, '') - "this.constructor.prototype.#{methname}.call(this, #{args})" + arg_part = args.empty? ? '' : ", #{args}" + "#{o[:proto_assign]}.prototype.__proto__.#{methname}.call(this#{arg_part})" end end @@ -299,7 +300,8 @@ def compile(o={}) # Setting the value of a local variable, or the value of an object property. class AssignNode < Node - LEADING_VAR = /\Avar\s+/ + LEADING_VAR = /\Avar\s+/ + PROTO_ASSIGN = /\A(\S+)\.prototype/ statement custom_return @@ -316,9 +318,10 @@ def line_ending def compile(o={}) o = super(o) - name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable + name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable.to_s last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s - o = o.merge(:assign => name, :last_assign => last) + proto = name[PROTO_ASSIGN, 1] + o = o.merge(:assign => name, :last_assign => last, :proto_assign => proto) postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : '' return write("#{@variable}: #{@value.compile(o)}") if @context == :object return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign? diff --git a/test/fixtures/execution/calling_super.cs b/test/fixtures/execution/calling_super.cs new file mode 100644 index 0000000000..e729db1e41 --- /dev/null +++ b/test/fixtures/execution/calling_super.cs @@ -0,0 +1,23 @@ +Base: => . +Base.prototype.func: string => + 'zero/' + string. + +FirstChild: => . +FirstChild.prototype.__proto__: new Base() +FirstChild.prototype.func: string => + super('one/') + string. + +SecondChild: => . +SecondChild.prototype.__proto__: new FirstChild() +SecondChild.prototype.func: string => + super('two/') + string. + +ThirdChild: => . +ThirdChild.prototype.__proto__: new SecondChild() +ThirdChild.prototype.func: string => + super('three/') + string. + +result: (new ThirdChild()).func('four') + +print(result is 'zero/one/two/three/four') + diff --git a/test/fixtures/execution/calling_super.js b/test/fixtures/execution/calling_super.js new file mode 100644 index 0000000000..dfd3d99ab8 --- /dev/null +++ b/test/fixtures/execution/calling_super.js @@ -0,0 +1,27 @@ +(function(){ + var Base = function() { + }; + Base.prototype.func = function(string) { + return 'zero/' + string; + }; + var FirstChild = function() { + }; + FirstChild.prototype.__proto__ = new Base(); + FirstChild.prototype.func = function(string) { + return FirstChild.prototype.__proto__.func.call(this, 'one/') + string; + }; + var SecondChild = function() { + }; + SecondChild.prototype.__proto__ = new FirstChild(); + SecondChild.prototype.func = function(string) { + return SecondChild.prototype.__proto__.func.call(this, 'two/') + string; + }; + var ThirdChild = function() { + }; + ThirdChild.prototype.__proto__ = new SecondChild(); + ThirdChild.prototype.func = function(string) { + return ThirdChild.prototype.__proto__.func.call(this, 'three/') + string; + }; + var result = (new ThirdChild()).func('four'); + print(result === 'zero/one/two/three/four'); +})(); \ No newline at end of file From 1c83e68292270ddfef3d87da77b4ff5861bfbe26 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 16:49:23 -0800 Subject: [PATCH 088/303] got extends back in the language -- use it together with super --- documentation/cs/super.cs | 4 +-- documentation/index.html.erb | 14 ++++---- documentation/js/super.js | 8 ++--- examples/code.cs | 4 +-- index.html | 34 ++++++++++--------- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 12 +++++-- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 14 ++++++++ test/fixtures/execution/calling_super.cs | 6 ++-- 10 files changed, 62 insertions(+), 38 deletions(-) diff --git a/documentation/cs/super.cs b/documentation/cs/super.cs index baee074ac9..07cb3b31e5 100644 --- a/documentation/cs/super.cs +++ b/documentation/cs/super.cs @@ -3,13 +3,13 @@ alert(this.name + " moved " + meters + "m."). Snake: name => this.name: name. -Snake.prototype: new Animal() +Snake extends new Animal() Snake.prototype.move: => alert("Slithering...") super(5). Horse: name => this.name: name. -Horse.prototype: new Animal() +Horse extends new Animal() Horse.prototype.move: => alert("Galloping...") super(45). diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 167f8c5c8c..b51687d265 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -67,7 +67,7 @@ While Loops
    Array Comprehensions
    Array Slice Literals
    - Calling Super from a Subclass
    + Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    Try/Catch/Finally
    @@ -332,8 +332,8 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    <%= code_for('slices', 'three_to_six') %> -

    - Calling Super from a Subclass +

    + Inheritance, and Calling Super from a Subclass JavaScript's prototypal inheritance has always been a bit of a brain-bender, with a whole family tree of libraries that provide a cleaner syntax for classical inheritance on top of JavaScript's prototypes: @@ -341,9 +341,11 @@ coffee-script --print app/scripts/*.cs > concatenation.js Prototype.js, JS.Class, etc. The libraries provide syntactic sugar, but the built-in inheritance would - be completely usable if it weren't for one small exception: - it's very awkward to call super, the prototype object's - implementation of the current function. CoffeeScript converts + 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. CoffeeScript provides extends + to help with prototype setup, and converts super() calls into calls against the immediate ancestor's method of the same name.

    diff --git a/documentation/js/super.js b/documentation/js/super.js index d9199affed..64c3c4f931 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -8,19 +8,19 @@ this.name = name; return this.name; }; - Snake.prototype = new Animal(); + Snake.prototype.__proto__ = new Animal(); Snake.prototype.move = function() { alert("Slithering..."); - return this.constructor.prototype.move.call(this, 5); + return Snake.prototype.__proto__.move.call(this, 5); }; var Horse = function(name) { this.name = name; return this.name; }; - Horse.prototype = new Animal(); + Horse.prototype.__proto__ = new Animal(); Horse.prototype.move = function() { alert("Galloping..."); - return this.constructor.prototype.move.call(this, 45); + return Horse.prototype.__proto__.move.call(this, 45); }; var sam = new Snake("Sammy the Python"); var tom = new Horse("Tommy the Palomino"); diff --git a/examples/code.cs b/examples/code.cs index 919a991bf9..4b135cdf4f 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -145,13 +145,13 @@ aliquam erat volutpat. Ut wisi enim ad." alert(this.name + " moved " + meters + "m."). Snake: name => this.name: name. -Snake.prototype: Animal +Snake extends new Animal() Snake.prototype.move: => alert('Slithering...') super(5). Horse: name => this.name: name. -Horse.prototype: Animal +Horse extends new Animal() Horse.prototype.move: => alert('Galloping...') super(45). diff --git a/index.html b/index.html index cc4910f1b5..ac8b3ecbc0 100644 --- a/index.html +++ b/index.html @@ -53,7 +53,7 @@

    Table of Contents

    While Loops
    Array Comprehensions
    Array Slice Literals
    - Calling Super from a Subclass
    + Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    Try/Catch/Finally
    @@ -565,8 +565,8 @@

    Language Reference

    var three_to_six = nums.slice(3, 6 + 1); ;alert(three_to_six);'>run: three_to_six
    -

    - Calling Super from a Subclass +

    + Inheritance, and Calling Super from a Subclass JavaScript's prototypal inheritance has always been a bit of a brain-bender, with a whole family tree of libraries that provide a cleaner syntax for classical inheritance on top of JavaScript's prototypes: @@ -574,9 +574,11 @@

    Language Reference

    Prototype.js, JS.Class, etc. The libraries provide syntactic sugar, but the built-in inheritance would - be completely usable if it weren't for one small exception: - it's very awkward to call super, the prototype object's - implementation of the current function. CoffeeScript converts + 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. CoffeeScript provides extends + to help with prototype setup, and converts super() calls into calls against the immediate ancestor's method of the same name.

    @@ -585,13 +587,13 @@

    Language Reference

    alert(this.name + " moved " + meters + "m."). Snake: name => this.name: name. -Snake.prototype: new Animal() +Snake extends new Animal() Snake.prototype.move: => alert("Slithering...") super(5). Horse: name => this.name: name. -Horse.prototype: new Animal() +Horse extends new Animal() Horse.prototype.move: => alert("Galloping...") super(45). @@ -614,19 +616,19 @@

    Language Reference

    this.name = name; return this.name; }; -Snake.prototype = new Animal(); +Snake.prototype.__proto__ = new Animal(); Snake.prototype.move = function() { alert("Slithering..."); - return this.constructor.prototype.move.call(this, 5); + return Snake.prototype.__proto__.move.call(this, 5); }; var Horse = function(name) { this.name = name; return this.name; }; -Horse.prototype = new Animal(); +Horse.prototype.__proto__ = new Animal(); Horse.prototype.move = function() { alert("Galloping..."); - return this.constructor.prototype.move.call(this, 45); + return Horse.prototype.__proto__.move.call(this, 45); }; var sam = new Snake("Sammy the Python"); var tom = new Horse("Tommy the Palomino"); @@ -641,19 +643,19 @@

    Language Reference

    this.name = name; return this.name; }; -Snake.prototype = new Animal(); +Snake.prototype.__proto__ = new Animal(); Snake.prototype.move = function() { alert("Slithering..."); - return this.constructor.prototype.move.call(this, 5); + return Snake.prototype.__proto__.move.call(this, 5); }; var Horse = function(name) { this.name = name; return this.name; }; -Horse.prototype = new Animal(); +Horse.prototype.__proto__ = new Animal(); Horse.prototype.move = function() { alert("Galloping..."); - return this.constructor.prototype.move.call(this, 45); + return Horse.prototype.__proto__.move.call(this, 45); }; var sam = new Snake("Sammy the Python"); var tom = new Horse("Tommy the Palomino"); diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index af4540fd1a..0ef633a16d 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -229,7 +229,7 @@ match - \b(super|this)\b + \b(super|this|extends)\b name variable.language.cs diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index c31d519690..857847e5c0 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -11,7 +11,7 @@ token BREAK CONTINUE token FOR IN WHILE token SWITCH WHEN token DELETE INSTANCEOF TYPEOF -token SUPER +token SUPER EXTENDS token NEWLINE token COMMENT token JS @@ -29,8 +29,8 @@ prechigh right '-=' '+=' '/=' '*=' right DELETE INSTANCEOF TYPEOF left "." - right THROW FOR IN WHILE NEW - left UNLESS IF ELSE + right THROW FOR IN WHILE NEW SUPER + left UNLESS IF ELSE EXTENDS left ":" '||:' '&&:' right RETURN preclow @@ -81,6 +81,7 @@ rule | While | For | Switch + | Extends | Comment ; @@ -254,6 +255,11 @@ rule | Super { result = val[0] } ; + # Extending an object's prototype. + Extends: + Value EXTENDS Expression { result = ExtendsNode.new(val[0], val[2]) } + ; + # A generic function invocation. Invocation: Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 683de4726b..50d6697619 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -14,7 +14,7 @@ class Lexer "break", "continue", "for", "in", "while", "switch", "when", - "super", + "super", "extends", "delete", "instanceof", "typeof"] # Token matching regexes. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index bced26706d..076559ff08 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -215,6 +215,20 @@ def compile_super(args, o) end end + # Node to extend an object's prototype with an ancestor object. + class ExtendsNode < Node + attr_reader :sub_object, :super_object + + def initialize(sub_object, super_object) + @sub_object, @super_object = sub_object, super_object + end + + def compile(o={}) + "#{@sub_object.compile(o)}.prototype.__proto__ = #{@super_object.compile(o)}" + end + + end + # A value, indexed or dotted into, or vanilla. class ValueNode < Node attr_reader :literal, :properties, :last diff --git a/test/fixtures/execution/calling_super.cs b/test/fixtures/execution/calling_super.cs index e729db1e41..691924498c 100644 --- a/test/fixtures/execution/calling_super.cs +++ b/test/fixtures/execution/calling_super.cs @@ -3,17 +3,17 @@ 'zero/' + string. FirstChild: => . -FirstChild.prototype.__proto__: new Base() +FirstChild extends new Base() FirstChild.prototype.func: string => super('one/') + string. SecondChild: => . -SecondChild.prototype.__proto__: new FirstChild() +SecondChild extends new FirstChild() SecondChild.prototype.func: string => super('two/') + string. ThirdChild: => . -ThirdChild.prototype.__proto__: new SecondChild() +ThirdChild extends new SecondChild() ThirdChild.prototype.func: string => super('three/') + string. From 65e9ba4c300b6a297f8174b9bad880da94b0c05f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 17:05:55 -0800 Subject: [PATCH 089/303] bumping to 0.1.2 to get the super()/extends fix out there --- coffee-script.gemspec | 2 +- documentation/index.html.erb | 16 ++++++++++++++-- index.html | 16 ++++++++++++++-- lib/coffee-script.rb | 2 +- package.json | 14 +++++++------- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index c759114b44..3acde9d7da 100644 --- a/coffee-script.gemspec +++ b/coffee-script.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'coffee-script' - s.version = '0.1.1' # Keep version in sync with coffee-script.rb + s.version = '0.1.2' # Keep version in sync with coffee-script.rb s.date = '2009-12-24' s.homepage = "http://jashkenas.github.com/coffee-script/" diff --git a/documentation/index.html.erb b/documentation/index.html.erb index b51687d265..ca721afdc9 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -421,12 +421,24 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    Change Log

    - 0.1.1 + 0.1.2 + Fixed a bug with calling super() through more than one level of + inheritance, with the re-addition of the extends keyword. + Added experimental Narwhal + support (as a Tusk package), contributed by + Tom Robinson, including + bin/cs as a CoffeeScript REPL and interpreter. + New --no-wrap option to suppress the safety function + wrapper. +

    + +

    + 0.1.1 Added instanceof and typeof as operators.

    - 0.1.0 + 0.1.0 Initial CoffeeScript release.

    diff --git a/index.html b/index.html index ac8b3ecbc0..4178211163 100644 --- a/index.html +++ b/index.html @@ -800,12 +800,24 @@

    Contributing

    Change Log

    - 0.1.1 + 0.1.2 + Fixed a bug with calling super() through more than one level of + inheritance, with the re-addition of the extends keyword. + Added experimental Narwhal + support (as a Tusk package), contributed by + Tom Robinson, including + bin/cs as a CoffeeScript REPL and interpreter. + New --no-wrap option to suppress the safety function + wrapper. +

    + +

    + 0.1.1 Added instanceof and typeof as operators.

    - 0.1.0 + 0.1.0 Initial CoffeeScript release.

    diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 1459a83de1..29deca23a2 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -9,7 +9,7 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.1.1' # Keep in sync with the gemspec. + VERSION = '0.1.2' # 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 ead29983f2..4847572d61 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "coffee-script", - "lib": "lib-js", - "preload": ["coffee-script/loader"], - "description": "Unfancy JavaScript", - "keywords": ["javascript", "language"], - "author": "Jeremy Ashkenas", - "version": "0.1.1" + "name": "coffee-script", + "lib": "lib-js", + "preload": ["coffee-script/loader"], + "description": "Unfancy JavaScript", + "keywords": ["javascript", "language"], + "author": "Jeremy Ashkenas", + "version": "0.1.2" } From 95bfb0b45c55aae5a9d6630a12fd3ba9be406ce8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 17:14:53 -0800 Subject: [PATCH 090/303] allowing quoted strings within object assignment, a in JS and JSON --- lib/coffee_script/grammar.y | 1 + test/fixtures/execution/test_everything.cs | 2 +- test/fixtures/execution/test_everything.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 857847e5c0..c2459287f7 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -121,6 +121,7 @@ rule # Assignment within an object literal. AssignObj: IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) } + | STRING ":" Expression { result = AssignNode.new(val[0], val[2], :object) } | Comment { result = val[0] } ; diff --git a/test/fixtures/execution/test_everything.cs b/test/fixtures/execution/test_everything.cs index 62a43a85f2..aed47da8d6 100644 --- a/test/fixtures/execution/test_everything.cs +++ b/test/fixtures/execution/test_everything.cs @@ -6,7 +6,7 @@ a--. c: { - text: b + "text": b } c: 'error' unless 42 > 41 diff --git a/test/fixtures/execution/test_everything.js b/test/fixtures/execution/test_everything.js index 59cee64e1d..72a9a585b2 100644 --- a/test/fixtures/execution/test_everything.js +++ b/test/fixtures/execution/test_everything.js @@ -7,7 +7,7 @@ a--; } var c = { - text: b + "text": b }; if (!(42 > 41)) { c = 'error'; From 985e0a080b14ea4d5a6456dbfbebe110cd3b9de0 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 17:21:20 -0800 Subject: [PATCH 091/303] trading aint for isnt -- let's be serious --- documentation/cs/aliases.cs | 2 +- documentation/index.html.erb | 2 +- examples/code.cs | 2 +- examples/underscore.cs | 2 +- index.html | 4 ++-- .../CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 4 ++-- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 2 +- test/fixtures/each.cs | 2 +- test/fixtures/each.tokens | 2 +- test/fixtures/execution/array_comprehension.cs | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/documentation/cs/aliases.cs b/documentation/cs/aliases.cs index efb592eb30..f1a29e0f07 100644 --- a/documentation/cs/aliases.cs +++ b/documentation/cs/aliases.cs @@ -1,6 +1,6 @@ launch() if ignition is on -volume: 10 if band aint spinal_tap +volume: 10 if band isnt spinal_tap let_the_wild_rumpus_begin() unless answer is no diff --git a/documentation/index.html.erb b/documentation/index.html.erb index ca721afdc9..b19a5df14f 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -278,7 +278,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js CoffeeScript compiles == into ===, and != into !==. In addition, is compiles into ===, - and aint into !==. + and isnt into !==.

    You can use not as an alias for !. diff --git a/examples/code.cs b/examples/code.cs index 4b135cdf4f..5630265b12 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -5,7 +5,7 @@ odd: x => x % 2 is 0. -even: x => x % 2 aint 0. +even: x => x % 2 isnt 0. run_loop: => fire_events( e => e.stopPropagation(). ) diff --git a/examples/underscore.cs b/examples/underscore.cs index 754d1842e4..3b709bb366 100644 --- a/examples/underscore.cs +++ b/examples/underscore.cs @@ -49,7 +49,7 @@ return iterator.call(context, item, i, obj) for item, i in obj. if _.isArray(obj) or _.isArguments(obj) iterator.call(context, obj[key], key, obj) for key in _.keys(obj). catch e - throw e if e aint breaker. + throw e if e isnt breaker. obj. # Return the results of applying the iterator to each element. Use JavaScript diff --git a/index.html b/index.html index 4178211163..d3185e472c 100644 --- a/index.html +++ b/index.html @@ -453,7 +453,7 @@

    Language Reference

    CoffeeScript compiles == into ===, and != into !==. In addition, is compiles into ===, - and aint into !==. + and isnt into !==.

    You can use not as an alias for !. @@ -476,7 +476,7 @@

    Language Reference

    launch() if ignition is on
     
    -volume: 10 if band aint spinal_tap
    +volume: 10 if band isnt spinal_tap
     
     let_the_wild_rumpus_begin() unless answer is no
     
    diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    index 0ef633a16d..6a35fb273f 100644
    --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    @@ -241,7 +241,7 @@
     		
     		
     			match
    -			!|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|aint|not)\b
    +			!|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b
     			name
     			keyword.operator.cs
     		
    diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
    index c2459287f7..81c0ef19c7 100644
    --- a/lib/coffee_script/grammar.y
    +++ b/lib/coffee_script/grammar.y
    @@ -24,7 +24,7 @@ prechigh
       left     '<<' '>>' '>>>'
       left     '&' '|' '^'
       left     '<=' '<' '>' '>='
    -  right    '==' '!=' IS AINT
    +  right    '==' '!=' IS ISNT
       left     '&&' '||' AND OR
       right    '-=' '+=' '/=' '*='
       right    DELETE INSTANCEOF TYPEOF
    @@ -172,7 +172,7 @@ rule
       | Expression '==' Expression        { result = OpNode.new(val[1], val[0], val[2]) }
       | Expression '!=' Expression        { result = OpNode.new(val[1], val[0], val[2]) }
       | Expression IS Expression          { result = OpNode.new(val[1], val[0], val[2]) }
    -  | Expression AINT Expression        { result = OpNode.new(val[1], val[0], val[2]) }
    +  | Expression ISNT Expression        { result = OpNode.new(val[1], val[0], val[2]) }
     
       | Expression '&&' Expression        { result = OpNode.new(val[1], val[0], val[2]) }
       | Expression '||' Expression        { result = OpNode.new(val[1], val[0], val[2]) }
    diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb
    index 50d6697619..324c4e579f 100644
    --- a/lib/coffee_script/lexer.rb
    +++ b/lib/coffee_script/lexer.rb
    @@ -8,7 +8,7 @@ class Lexer
         # The list of keywords passed verbatim to the parser.
         KEYWORDS   = ["if", "else", "then", "unless",
                       "true", "false", "yes", "no", "on", "off",
    -                  "and", "or", "is", "aint", "not",
    +                  "and", "or", "is", "isnt", "not",
                       "new", "return",
                       "try", "catch", "finally", "throw",
                       "break", "continue",
    diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb
    index 076559ff08..021cd8a3bd 100644
    --- a/lib/coffee_script/nodes.rb
    +++ b/lib/coffee_script/nodes.rb
    @@ -357,7 +357,7 @@ class OpNode < Node
           'and'   => '&&',
           'or'    => '||',
           'is'    => '===',
    -      "aint"  => "!==",
    +      "isnt"  => "!==",
           'not'   => '!',
         }
         CONDITIONALS     = ['||:', '&&:']
    diff --git a/test/fixtures/each.cs b/test/fixtures/each.cs
    index 5a345f12f5..f783302fe3 100644
    --- a/test/fixtures/each.cs
    +++ b/test/fixtures/each.cs
    @@ -10,5 +10,5 @@
         else
           iterator.call(context, obj[key], key, obj) for key in _.keys(obj)..
       catch e
    -    throw e if e aint breaker.
    +    throw e if e isnt breaker.
       obj.
    \ No newline at end of file
    diff --git a/test/fixtures/each.tokens b/test/fixtures/each.tokens
    index 360c5088cb..4304bef4e7 100644
    --- a/test/fixtures/each.tokens
    +++ b/test/fixtures/each.tokens
    @@ -1 +1 @@
    -[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:AINT, "aint"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]]
    \ No newline at end of file
    +[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:ISNT, "isnt"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]]
    \ No newline at end of file
    diff --git a/test/fixtures/execution/array_comprehension.cs b/test/fixtures/execution/array_comprehension.cs
    index 378d8f7e31..973a595a67 100644
    --- a/test/fixtures/execution/array_comprehension.cs
    +++ b/test/fixtures/execution/array_comprehension.cs
    @@ -1,4 +1,4 @@
    -nums: n * n for n in [1, 2, 3] if n % 2 aint 0.
    +nums: n * n for n in [1, 2, 3] if n % 2 isnt 0.
     result: n * 2 for n in nums.
     
     print(result.join(',') is '2,18')
    \ No newline at end of file
    
    From 93009e07f6c7abf392ba44eded9c123bce327714 Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Thu, 24 Dec 2009 17:22:46 -0800
    Subject: [PATCH 092/303] updating docs for isnt
    
    ---
     index.html | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/index.html b/index.html
    index d3185e472c..c2d577e39d 100644
    --- a/index.html
    +++ b/index.html
    @@ -476,7 +476,7 @@ 

    Language Reference

    launch() if ignition is on
     
    -volume: 10 if band isnt spinal_tap
    +volume: 10 if band isnt spinal_tap
     
     let_the_wild_rumpus_begin() unless answer is no
     
    
    From 9b2326492b7813d3d99a50c96e83b0250ff848e6 Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Thu, 24 Dec 2009 17:37:24 -0800
    Subject: [PATCH 093/303] the --no-wrap option now disables top-level var
     declarations
    
    ---
     documentation/index.html.erb      | 8 ++++++++
     index.html                        | 8 ++++++++
     lib/coffee_script/command_line.rb | 2 +-
     lib/coffee_script/nodes.rb        | 7 ++++---
     4 files changed, 21 insertions(+), 4 deletions(-)
    
    diff --git a/documentation/index.html.erb b/documentation/index.html.erb
    index b19a5df14f..0b39fd0a11 100644
    --- a/documentation/index.html.erb
    +++ b/documentation/index.html.erb
    @@ -151,6 +151,14 @@ gem install coffee-script
    AST. + + -n, --no-wrap + + Compile the JavaScript without the top-level function safety wrapper + or var declarations, for situations where you want to add every + variable to global scope. + + --install-bundle diff --git a/index.html b/index.html index c2d577e39d..bdcdf689c2 100644 --- a/index.html +++ b/index.html @@ -221,6 +221,14 @@

    Installation and Usage

    AST. + + -n, --no-wrap + + Compile the JavaScript without the top-level function safety wrapper + or var declarations, for situations where you want to add every + variable to global scope. + + --install-bundle diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index eb749d93dd..7499cf81de 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -156,7 +156,7 @@ def parse_options opts.on('-v', '--verbose', 'print at every step of code generation') do |v| ENV['VERBOSE'] = 'true' end - opts.on('-n', '--no-wrap', 'suppress the top-level safety function wrapper') do |n| + opts.on('-n', '--no-wrap', 'raw output, no safety wrapper or vars') do |n| @options[:no_wrap] = true end opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i| diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 021cd8a3bd..93369c474e 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -77,7 +77,7 @@ def last?(node) # If this is the top-level Expressions, wrap everything in a safety closure. def root_compile(o={}) indent = o[:no_wrap] ? '' : TAB - code = compile(:indent => indent, :scope => Scope.new) + code = compile(o.merge(:indent => indent, :scope => Scope.new)) code.gsub!(STRIP_TRAILING_WHITESPACE, '') o[:no_wrap] ? code : "(function(){\n#{code}\n})();" end @@ -340,9 +340,9 @@ def compile(o={}) return write("#{@variable}: #{@value.compile(o)}") if @context == :object return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign? defined = o[:scope].find(name) - def_part = defined || @variable.properties? ? "" : "var #{name};\n#{o[:indent]}" + def_part = defined || @variable.properties? || o[:no_wrap] ? "" : "var #{name};\n#{o[:indent]}" return write(def_part + @value.compile(o)) if @value.custom_assign? - def_part = defined ? name : "var #{name}" + def_part = defined || o[:no_wrap] ? name : "var #{name}" val_part = @value.compile(o).sub(LEADING_VAR, '') write("#{def_part} = #{val_part}#{postfix}") end @@ -411,6 +411,7 @@ def compile(o={}) indent = o[:indent] o[:indent] += TAB o.delete(:assign) + o.delete(:no_wrap) @params.each {|id| o[:scope].find(id.to_s) } code = @body.compile(o) write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}") From 378e156e11339dc15a360e3931a2da352ecd09d6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 17:45:23 -0800 Subject: [PATCH 094/303] fixed the bin/cs repl to save assignment between commands by using the new --no-wrap --- lib-js/coffee-script.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib-js/coffee-script.js b/lib-js/coffee-script.js index dc7c32a023..1c142e80f3 100644 --- a/lib-js/coffee-script.js +++ b/lib-js/coffee-script.js @@ -32,7 +32,7 @@ exports.run = function(args) { var coffeePath = FILE.path(module.path).dirname().dirname().join("bin", "coffee-script"); exports.compileFile = function(path) { - var coffee = OS.popen([coffeePath, "--print", path]); + var coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]); if (coffee.wait() !== 0) throw new Error("coffee compiler error"); @@ -41,7 +41,7 @@ exports.compileFile = function(path) { } exports.compile = function(source) { - var coffee = OS.popen([coffeePath, "-e"]); + var coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]); coffee.stdin.write(source).flush().close(); @@ -58,10 +58,6 @@ exports.cs_eval = function(source) { var code = exports.compile(source); - // strip the function wrapper, we add our own. - // TODO: this is very fragile - code = code.split("\n").slice(1,-2).join("\n"); - return eval(code); } @@ -70,10 +66,6 @@ exports.make_narwhal_factory = function(path) { var code = exports.compileFile(path); - // strip the function wrapper, we add our own. - // TODO: this is very fragile - code = code.split("\n").slice(1,-2).join("\n"); - var factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; if (system.engine === "rhino") From 2f35bba0830d0f9b6143d447fe0cca25097f899f Mon Sep 17 00:00:00 2001 From: tlrobinson Date: Thu, 24 Dec 2009 19:34:17 -0800 Subject: [PATCH 095/303] Print compiler errors to stderr --- lib-js/coffee-script.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib-js/coffee-script.js b/lib-js/coffee-script.js index 1c142e80f3..7a2f307b23 100644 --- a/lib-js/coffee-script.js +++ b/lib-js/coffee-script.js @@ -34,8 +34,10 @@ var coffeePath = FILE.path(module.path).dirname().dirname().join("bin", "coffee- exports.compileFile = function(path) { var coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]); - if (coffee.wait() !== 0) - throw new Error("coffee compiler error"); + if (coffee.wait() !== 0) { + system.stderr.print(coffee.stderr.read()); + throw new Error("coffee-script compile error"); + } return coffee.stdout.read(); } @@ -45,8 +47,10 @@ exports.compile = function(source) { coffee.stdin.write(source).flush().close(); - if (coffee.wait() !== 0) - throw new Error("coffee compiler error"); + if (coffee.wait() !== 0) { + system.stderr.print(coffee.stderr.read()); + throw new Error("coffee-script compile error"); + } return coffee.stdout.read(); } From 6865f5be921a2761d7a6152c070f616ac2aadfad Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 22:08:32 -0800 Subject: [PATCH 096/303] removed dependency on v8 in favor of bin/cs --- .../fixtures/execution/array_comprehension.js | 21 -------------- .../fixtures/execution/assign_to_try_catch.js | 9 ------ test/fixtures/execution/calling_super.js | 27 ----------------- test/fixtures/execution/fancy_if_statement.js | 6 ---- test/fixtures/execution/keyword_operators.js | 10 ------- test/fixtures/execution/test_everything.cs | 2 +- test/fixtures/execution/test_everything.js | 29 ------------------- test/fixtures/execution/test_switch.js | 16 ---------- test/unit/test_execution.rb | 10 ++----- 9 files changed, 3 insertions(+), 127 deletions(-) delete mode 100644 test/fixtures/execution/array_comprehension.js delete mode 100644 test/fixtures/execution/assign_to_try_catch.js delete mode 100644 test/fixtures/execution/calling_super.js delete mode 100644 test/fixtures/execution/fancy_if_statement.js delete mode 100644 test/fixtures/execution/keyword_operators.js delete mode 100644 test/fixtures/execution/test_everything.js delete mode 100644 test/fixtures/execution/test_switch.js diff --git a/test/fixtures/execution/array_comprehension.js b/test/fixtures/execution/array_comprehension.js deleted file mode 100644 index 5bdaeee995..0000000000 --- a/test/fixtures/execution/array_comprehension.js +++ /dev/null @@ -1,21 +0,0 @@ -(function(){ - var nums; - var a = [1, 2, 3]; - var d = []; - for (var b=0, c=a.length; b= 0) { - b.push('o'); - a--; - } - var c = { - "text": b - }; - if (!(42 > 41)) { - c = 'error'; - } - c.text = false ? 'error' : c.text + '---'; - var d = c.text.split(''); - var g = []; - for (var e=0, f=d.length; e Date: Thu, 24 Dec 2009 22:25:29 -0800 Subject: [PATCH 097/303] making all assignment-y operators use a colon -- now it's +: -: *: /:, and friends --- examples/poignant.cs | 4 ++-- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 11 ++++++----- lib/coffee_script/nodes.rb | 5 +++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/poignant.cs b/examples/poignant.cs index 7c9b05f5da..1a6c77ee83 100644 --- a/examples/poignant.cs +++ b/examples/poignant.cs @@ -81,9 +81,9 @@ hit: damage => p_up: Math.rand( this.charisma ) if p_up % 9 is 7 - this.life += p_up / 4 + this.life +: p_up / 4 puts( "[" + this.name + " magick powers up " + p_up + "!]" ). - this.life -= damage + this.life -: damage if this.life <= 0 then puts( "[" + this.name + " has died.]" ).. # This method takes one turn in a fight. diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 6a35fb273f..5864011363 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -241,7 +241,7 @@ match - !|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b + !|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*:|(?<!\()/=|%:|\+:|\-:|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b name keyword.operator.cs diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 81c0ef19c7..e1ff01967a 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -26,7 +26,7 @@ prechigh left '<=' '<' '>' '>=' right '==' '!=' IS ISNT left '&&' '||' AND OR - right '-=' '+=' '/=' '*=' + right '-:' '+:' '/:' '*:' '%:' right DELETE INSTANCEOF TYPEOF left "." right THROW FOR IN WHILE NEW SUPER @@ -179,10 +179,11 @@ rule | Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '-:' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '+:' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '/:' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '*:' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '%:' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '||:' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '&&:' Expression { result = OpNode.new(val[1], val[0], val[2]) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 93369c474e..16b20b758b 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -359,6 +359,11 @@ class OpNode < Node 'is' => '===', "isnt" => "!==", 'not' => '!', + '+:' => '+=', + '-:' => '-=', + '*:' => '*=', + '/:' => '/=', + '%:' => '%=' } CONDITIONALS = ['||:', '&&:'] PREFIX_OPERATORS = ['typeof', 'delete'] From d8ceb3b4bb0c941fbec9ea818e3c63aac9f1adfa Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 22:29:30 -0800 Subject: [PATCH 098/303] sped up the execution test a good deal by running it all in one pass --- documentation/index.html.erb | 40 ++++++++++++++++++------------------ test/unit/test_execution.rb | 8 +++----- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 0b39fd0a11..51256ce479 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -83,7 +83,7 @@ <%= code_for('overview', 'cubed_list') %>

    Installation and Usage

    - +

    The CoffeeScript compiler is written in pure Ruby, and is available as a Ruby Gem. @@ -125,7 +125,7 @@ gem install coffee-script

    -l, --lint If the jsl (JavaScript Lint) command is installed, use it - to check the compilation of a CoffeeScript file. (Handy in + to check the compilation of a CoffeeScript file. (Handy in conjunction with --watch) @@ -194,15 +194,15 @@ coffee-script --print app/scripts/*.cs > concatenation.js the line will do just as well. All other whitespace is not significant. Instead of using curly braces { } to delimit a block of code, use a period . to mark the end of a - block, for - functions, - if-statements, + block, for + functions, + if-statements, switch, and try/catch.

    Functions and Invocation - Functions are defined by a list of parameters, an arrow, and the + Functions are defined by a list of parameters, an arrow, and the function body. The empty function looks like this: =>.

    <%= code_for('functions', 'cube(5)') %> @@ -278,7 +278,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js The same mechanism is used to push down assignment through switch statements, and if-elses (although the ternary operator is preferred).

    - +

    Aliases Because the == operator frequently causes undesirable coercion, @@ -297,7 +297,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    Instead of a newline or semicolon, then can be used to separate - conditions from expressions, in while, + conditions from expressions, in while, if/else, and switch/when statements.

    @@ -390,14 +390,14 @@ coffee-script --print app/scripts/*.cs > concatenation.js Multiline strings are allowed in CoffeeScript.

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

    Contributing

    - +

    Here's a wish list of things that would be wonderful to have in CoffeeScript:

    - +
    • A JavaScript version of the compiler, perhaps using Alessandro Warth's @@ -412,7 +412,7 @@ coffee-script --print app/scripts/*.cs > concatenation.js should be able to compile properly.
    • - A tutorial that introduces CoffeeScript from the ground up for folks + A tutorial that introduces CoffeeScript from the ground up for folks without knowledge of JavaScript.
    • @@ -420,31 +420,31 @@ coffee-script --print app/scripts/*.cs > concatenation.js having a JavaScript version of the compiler).
    • - A lot of the code generation in nodes.rb gets into messy + A lot of the code generation in nodes.rb gets into messy string manipulation. Techniques for cleaning this up across the board would be appreciated.
    - +

    Change Log

    - +

    0.1.2 - Fixed a bug with calling super() through more than one level of + Fixed a bug with calling super() through more than one level of inheritance, with the re-addition of the extends keyword. - Added experimental Narwhal - support (as a Tusk package), contributed by + Added experimental Narwhal + support (as a Tusk package), contributed by Tom Robinson, including bin/cs as a CoffeeScript REPL and interpreter. New --no-wrap option to suppress the safety function wrapper.

    - +

    0.1.1 Added instanceof and typeof as operators.

    - +

    0.1.0 Initial CoffeeScript release. diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 3021a6b753..9ec4ab4db4 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -3,13 +3,11 @@ class ExecutionTest < Test::Unit::TestCase NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/ + ALLS_WELL = /\A\n?(true\n)+\Z/ def test_execution_of_coffeescript - Dir['test/fixtures/execution/*.cs'].each do |source| - suceeded = `bin/cs #{source}`.chomp.to_sym == :true - puts "failed: #{source}" unless suceeded - assert suceeded - end + sources = ['test/fixtures/execution/*.cs'].join(' ') + assert `bin/cs #{sources}`.match(ALLS_WELL) end def test_lintless_coffeescript From 2d57ee693bbdae0ce890378e514ebccd990b9888 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 23:09:24 -0800 Subject: [PATCH 099/303] the narwhal integration written in JavaScript has been replaced with CoffeeScript, and compiler-generated variable names now start with '__' --- Rakefile | 16 ++- lib-js/coffee-script.js | 113 ++++++++------------- lib-js/coffee-script/loader.js | 40 ++++---- lib/coffee_script/narwhal/coffee-script.cs | 63 ++++++++++++ lib/coffee_script/narwhal/loader.cs | 20 ++++ lib/coffee_script/nodes.rb | 1 + lib/coffee_script/scope.rb | 2 +- test/fixtures/each.js | 14 +-- test/fixtures/each_no_wrap.js | 14 +-- 9 files changed, 177 insertions(+), 106 deletions(-) create mode 100644 lib/coffee_script/narwhal/coffee-script.cs create mode 100644 lib/coffee_script/narwhal/loader.cs diff --git a/Rakefile b/Rakefile index 87c11beef3..553194a933 100644 --- a/Rakefile +++ b/Rakefile @@ -10,9 +10,19 @@ task :test do Dir['test/*/**/test_*.rb'].each {|test| require test } end -desc "Recompile the Racc parser (pass -v and -g for verbose debugging)" -task :build, :extra_args do |t, args| - sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" +namespace :build do + + desc "Recompile the Racc parser (pass -v and -g for verbose debugging)" + task :parser, :extra_args do |t, args| + sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" + end + + desc "Compile the Narwhal interface for bin/cs" + task :narwhal do + sh "bin/coffee-script lib/coffee_script/narwhal/coffee-script.cs --print > lib-js/coffee-script.js" + sh "bin/coffee-script lib/coffee_script/narwhal/loader.cs --print > lib-js/coffee-script/loader.js" + end + end desc "Build the documentation page" diff --git a/lib-js/coffee-script.js b/lib-js/coffee-script.js index 7a2f307b23..99889dd8f5 100644 --- a/lib-js/coffee-script.js +++ b/lib-js/coffee-script.js @@ -1,87 +1,64 @@ -var FILE = require("file"); -var OS = require("os"); - -exports.run = function(args) { - // TODO: non-REPL - +(function(){ + var File = require('file'); + var OS = require('os'); + exports.run = function(args) { args.shift(); - if (args.length) { - require(FILE.absolute(args[0])); - return; + return require(File.absolute(args[0])); } - - while (true) - { - try { - system.stdout.write("cs> ").flush(); - - var result = exports.cs_eval(require("readline").readline()); - - if (result !== undefined) - print(result); - - } catch (e) { - print(e); + while (true) { + try { + system.stdout.write('cs> ').flush(); + var result = exports.cs_eval(require('readline').readline()); + if (result !== undefined) { + print(result); } + } catch (e) { + print(e); + } } -} - -// executes the coffee-script Ruby program to convert from CoffeeScript to Objective-J. -// eventually this will hopefully be replaced by a JavaScript program. -var coffeePath = FILE.path(module.path).dirname().dirname().join("bin", "coffee-script"); - -exports.compileFile = function(path) { + }; + // executes the coffee-script Ruby program to convert from CoffeeScript to Objective-J. + // eventually this will hopefully be replaced by a JavaScript program. + var coffeePath = File.path(module.path).dirname().dirname().join('bin', 'coffee-script'); + exports.compileFile = function(path) { var coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]); - if (coffee.wait() !== 0) { - system.stderr.print(coffee.stderr.read()); - throw new Error("coffee-script compile error"); + system.stderr.print(coffee.stderr.read()); + throw new Error("coffee-script compile error"); } - return coffee.stdout.read(); -} - -exports.compile = function(source) { + }; + exports.compile = function(source) { var coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]); - coffee.stdin.write(source).flush().close(); - if (coffee.wait() !== 0) { - system.stderr.print(coffee.stderr.read()); - throw new Error("coffee-script compile error"); + system.stderr.print(coffee.stderr.read()); + throw new Error("coffee-script compile error"); } - return coffee.stdout.read(); -} - -// these two functions are equivalent to objective-j's objj_eval/make_narwhal_factory. -// implemented as a call to coffee and objj_eval/make_narwhal_factory -exports.cs_eval = function(source) { + }; + // these two functions are equivalent to objective-j's objj_eval/make_narwhal_factory. + // implemented as a call to coffee and objj_eval/make_narwhal_factory + exports.cs_eval = function(source) { init(); - - var code = exports.compile(source); - - return eval(code); -} - -exports.make_narwhal_factory = function(path) { + return eval(exports.compile(source)); + }; + exports.make_narwhal_factory = function(path) { init(); - var code = exports.compileFile(path); - var factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; - - if (system.engine === "rhino") - return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); - - // eval requires parenthesis, but parenthesis break compileFunction. - else - return eval("(" + factoryText + ")"); -} - - -var init = function() { + if (system.engine === "rhino") { + return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); + } else { + // eval requires parenthesis, but parenthesis break compileFunction. + return eval("(" + factoryText + ")"); + } + }; + var init = function() { // make sure it's only done once - init = function(){} -} \ No newline at end of file + init = function() { + }; + return init; + }; +})(); diff --git a/lib-js/coffee-script/loader.js b/lib-js/coffee-script/loader.js index 9dfba97105..e0ec9d9d90 100644 --- a/lib-js/coffee-script/loader.js +++ b/lib-js/coffee-script/loader.js @@ -1,23 +1,23 @@ -var coffeescript = null; - -function CoffeeScriptLoader() { - var loader = {}; - var factories = {}; - +(function(){ + var coffeescript = null; + var CoffeeScriptLoader = function() { + var loader = { + }; + var factories = { + }; loader.reload = function(topId, path) { - if (!coffeescript) coffeescript = require("coffee-script"); - - //print("loading objective-j: " + topId + " (" + path + ")"); - factories[topId] = coffeescript.make_narwhal_factory(path); - } - + coffeescript = coffeescript || require('coffee-script'); + // print("loading objective-j: " + topId + " (" + path + ")"); + factories[topId] = coffeescript.make_narwhal_factory(path); + return factories[topId]; + }; loader.load = function(topId, path) { - if (!factories.hasOwnProperty(topId)) - loader.reload(topId, path); - return factories[topId]; - } - + if (!(factories.hasOwnProperty(topId))) { + loader.reload(topId, path); + } + return factories[topId]; + }; return loader; -}; - -require.loader.loaders.unshift([".cs", CoffeeScriptLoader()]); + }; + require.loader.loaders.unshift([".cs", CoffeeScriptLoader()]); +})(); diff --git a/lib/coffee_script/narwhal/coffee-script.cs b/lib/coffee_script/narwhal/coffee-script.cs new file mode 100644 index 0000000000..ca868779c0 --- /dev/null +++ b/lib/coffee_script/narwhal/coffee-script.cs @@ -0,0 +1,63 @@ +# This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.cs + +File: require('file') +OS: require('os') + +exports.run: args => + args.shift() + return require(File.absolute(args[0])) if args.length + + while true + try + system.stdout.write('cs> ').flush() + result: exports.cs_eval(require('readline').readline()) + print(result) if result isnt undefined + catch e + print(e)... + +# executes the coffee-script Ruby program to convert from CoffeeScript to Objective-J. +# eventually this will hopefully be replaced by a JavaScript program. +coffeePath: File.path(module.path).dirname().dirname().join('bin', 'coffee-script') + +exports.compileFile: path => + coffee: OS.popen([coffeePath, "--print", "--no-wrap", path]) + + if coffee.wait() isnt 0 + system.stderr.print(coffee.stderr.read()) + throw new Error("coffee-script compile error"). + + coffee.stdout.read(). + +exports.compile: source => + coffee: OS.popen([coffeePath, "--eval", "--no-wrap"]) + + coffee.stdin.write(source).flush().close() + + if coffee.wait() isnt 0 + system.stderr.print(coffee.stderr.read()) + throw new Error("coffee-script compile error"). + + coffee.stdout.read(). + +# these two functions are equivalent to objective-j's objj_eval/make_narwhal_factory. +# implemented as a call to coffee and objj_eval/make_narwhal_factory +exports.cs_eval: source => + init() + eval(exports.compile(source)). + +exports.make_narwhal_factory: path => + init() + code: exports.compileFile(path) + + factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}" + + if system.engine is "rhino" + Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) + else + # eval requires parenthesis, but parenthesis break compileFunction. + eval("(" + factoryText + ")").. + + +init: => + # make sure it's only done once + init: => .. \ No newline at end of file diff --git a/lib/coffee_script/narwhal/loader.cs b/lib/coffee_script/narwhal/loader.cs new file mode 100644 index 0000000000..8928ed1c07 --- /dev/null +++ b/lib/coffee_script/narwhal/loader.cs @@ -0,0 +1,20 @@ +# This (javascript) file is generated from lib/coffee_script/narwhal/loader.cs + +coffeescript: null + +CoffeeScriptLoader: => + loader: {} + factories: {} + + loader.reload: topId, path => + coffeescript ||: require('coffee-script') + # print("loading objective-j: " + topId + " (" + path + ")"); + factories[topId]: coffeescript.make_narwhal_factory(path). + + loader.load: topId, path => + loader.reload(topId, path) unless factories.hasOwnProperty(topId) + factories[topId]. + + loader. + +require.loader.loaders.unshift([".cs", CoffeeScriptLoader()]) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 16b20b758b..dcfcd096b2 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -479,6 +479,7 @@ def line_ending def compile(o={}) o = super(o) + o.delete(:return) indent = o[:indent] + TAB cond = @condition.compile(o.merge(:no_paren => true)) write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}") diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index 743552ea6c..8c07785b21 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -11,7 +11,7 @@ class Scope def initialize(parent=nil) @parent = parent @variables = {} - @temp_variable = @parent ? @parent.temp_variable : 'a' + @temp_variable = @parent ? @parent.temp_variable : '__a' end # Look up a variable in lexical scope, or declare it if not found. diff --git a/test/fixtures/each.js b/test/fixtures/each.js index 9ee1dcea7b..5877863959 100644 --- a/test/fixtures/each.js +++ b/test/fixtures/each.js @@ -8,16 +8,16 @@ if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { - var a = obj; - for (var b=0, c=a.length; b Date: Thu, 24 Dec 2009 23:28:01 -0800 Subject: [PATCH 100/303] majorly cleaned up the CoffeeScript that defines the Narwhal integration --- lib-js/coffee-script.js | 51 +++++++++-------- lib-js/coffee-script/loader.js | 31 +++++----- lib/coffee_script/narwhal/coffee-script.cs | 66 ++++++++++------------ lib/coffee_script/narwhal/loader.cs | 21 ++++--- 4 files changed, 81 insertions(+), 88 deletions(-) diff --git a/lib-js/coffee-script.js b/lib-js/coffee-script.js index 99889dd8f5..0f1e2e22fd 100644 --- a/lib-js/coffee-script.js +++ b/lib-js/coffee-script.js @@ -1,6 +1,22 @@ (function(){ - var File = require('file'); + + // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.cs Executes the `coffee-script` Ruby program to convert from CoffeeScript + // to Javascript. Eventually this will hopefully happen entirely within JS. Require external dependencies. var OS = require('os'); + var File = require('file'); + var Readline = require('readline'); + // The path to the CoffeeScript Compiler. + var coffeePath = File.path(module.path).dirname().dirname().join('bin', 'coffee-script'); + // Our general-purpose error handler. + var checkForErrors = function(coffeeProcess) { + if (coffeeProcess.wait() === 0) { + return true; + } + system.stderr.print(coffeeProcess.stderr.read()); + throw new Error("coffee-script compile error"); + }; + // Run a simple REPL, round-tripping to the CoffeeScript compiler for every + // command. exports.run = function(args) { args.shift(); if (args.length) { @@ -9,7 +25,7 @@ while (true) { try { system.stdout.write('cs> ').flush(); - var result = exports.cs_eval(require('readline').readline()); + var result = exports.evalCS(Readline.readline()); if (result !== undefined) { print(result); } @@ -18,34 +34,25 @@ } } }; - // executes the coffee-script Ruby program to convert from CoffeeScript to Objective-J. - // eventually this will hopefully be replaced by a JavaScript program. - var coffeePath = File.path(module.path).dirname().dirname().join('bin', 'coffee-script'); + // Compile a given CoffeeScript file into JavaScript. exports.compileFile = function(path) { var coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]); - if (coffee.wait() !== 0) { - system.stderr.print(coffee.stderr.read()); - throw new Error("coffee-script compile error"); - } + checkForErrors(coffee); return coffee.stdout.read(); }; + // Compile a string of CoffeeScript into JavaScript. exports.compile = function(source) { var coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]); coffee.stdin.write(source).flush().close(); - if (coffee.wait() !== 0) { - system.stderr.print(coffee.stderr.read()); - throw new Error("coffee-script compile error"); - } + checkForErrors(coffee); return coffee.stdout.read(); }; - // these two functions are equivalent to objective-j's objj_eval/make_narwhal_factory. - // implemented as a call to coffee and objj_eval/make_narwhal_factory - exports.cs_eval = function(source) { - init(); + // Evaluating a string of CoffeeScript first compiles it externally. + exports.evalCS = function(source) { return eval(exports.compile(source)); }; - exports.make_narwhal_factory = function(path) { - init(); + // Make a factory for the CoffeeScript environment. + exports.makeNarwhalFactory = function(path) { var code = exports.compileFile(path); var factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; if (system.engine === "rhino") { @@ -55,10 +62,4 @@ return eval("(" + factoryText + ")"); } }; - var init = function() { - // make sure it's only done once - init = function() { - }; - return init; - }; })(); diff --git a/lib-js/coffee-script/loader.js b/lib-js/coffee-script/loader.js index e0ec9d9d90..6e08cd61fd 100644 --- a/lib-js/coffee-script/loader.js +++ b/lib-js/coffee-script/loader.js @@ -1,23 +1,20 @@ (function(){ + + // This (javascript) file is generated from lib/coffee_script/narwhal/loader.cs var coffeescript = null; - var CoffeeScriptLoader = function() { - var loader = { - }; - var factories = { - }; - loader.reload = function(topId, path) { + var factories = { + }; + var loader = { + // Reload the coffee-script environment from source. + reload: function(topId, path) { coffeescript = coffeescript || require('coffee-script'); - // print("loading objective-j: " + topId + " (" + path + ")"); - factories[topId] = coffeescript.make_narwhal_factory(path); - return factories[topId]; - }; - loader.load = function(topId, path) { - if (!(factories.hasOwnProperty(topId))) { - loader.reload(topId, path); - } + factories[topId] = coffeescript.makeNarwhalFactory(path); return factories[topId]; - }; - return loader; + }, + // Ensure that the coffee-script environment is loaded. + load: function(topId, path) { + return factories[topId] = factories[topId] || this.reload(topId, path); + } }; - require.loader.loaders.unshift([".cs", CoffeeScriptLoader()]); + require.loader.loaders.unshift([".cs", loader]); })(); diff --git a/lib/coffee_script/narwhal/coffee-script.cs b/lib/coffee_script/narwhal/coffee-script.cs index ca868779c0..0970631209 100644 --- a/lib/coffee_script/narwhal/coffee-script.cs +++ b/lib/coffee_script/narwhal/coffee-script.cs @@ -1,8 +1,24 @@ # This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.cs -File: require('file') -OS: require('os') +# Executes the `coffee-script` Ruby program to convert from CoffeeScript +# to Javascript. Eventually this will hopefully happen entirely within JS. +# Require external dependencies. +OS: require('os') +File: require('file') +Readline: require('readline') + +# The path to the CoffeeScript Compiler. +coffeePath: File.path(module.path).dirname().dirname().join('bin', 'coffee-script') + +# Our general-purpose error handler. +checkForErrors: coffeeProcess => + return true if coffeeProcess.wait() is 0 + system.stderr.print(coffeeProcess.stderr.read()) + throw new Error("coffee-script compile error"). + +# Run a simple REPL, round-tripping to the CoffeeScript compiler for every +# command. exports.run: args => args.shift() return require(File.absolute(args[0])) if args.length @@ -10,54 +26,34 @@ while true try system.stdout.write('cs> ').flush() - result: exports.cs_eval(require('readline').readline()) + result: exports.evalCS(Readline.readline()) print(result) if result isnt undefined catch e print(e)... -# executes the coffee-script Ruby program to convert from CoffeeScript to Objective-J. -# eventually this will hopefully be replaced by a JavaScript program. -coffeePath: File.path(module.path).dirname().dirname().join('bin', 'coffee-script') - +# Compile a given CoffeeScript file into JavaScript. exports.compileFile: path => coffee: OS.popen([coffeePath, "--print", "--no-wrap", path]) - - if coffee.wait() isnt 0 - system.stderr.print(coffee.stderr.read()) - throw new Error("coffee-script compile error"). - + checkForErrors(coffee) coffee.stdout.read(). +# Compile a string of CoffeeScript into JavaScript. exports.compile: source => - coffee: OS.popen([coffeePath, "--eval", "--no-wrap"]) - - coffee.stdin.write(source).flush().close() - - if coffee.wait() isnt 0 - system.stderr.print(coffee.stderr.read()) - throw new Error("coffee-script compile error"). - - coffee.stdout.read(). + coffee: OS.popen([coffeePath, "--eval", "--no-wrap"]) + coffee.stdin.write(source).flush().close() + checkForErrors(coffee) + coffee.stdout.read(). -# these two functions are equivalent to objective-j's objj_eval/make_narwhal_factory. -# implemented as a call to coffee and objj_eval/make_narwhal_factory -exports.cs_eval: source => - init() - eval(exports.compile(source)). +# Evaluating a string of CoffeeScript first compiles it externally. +exports.evalCS: source => + eval(exports.compile(source)). -exports.make_narwhal_factory: path => - init() +# Make a factory for the CoffeeScript environment. +exports.makeNarwhalFactory: path => code: exports.compileFile(path) - factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}" - if system.engine is "rhino" Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) else # eval requires parenthesis, but parenthesis break compileFunction. eval("(" + factoryText + ")").. - - -init: => - # make sure it's only done once - init: => .. \ No newline at end of file diff --git a/lib/coffee_script/narwhal/loader.cs b/lib/coffee_script/narwhal/loader.cs index 8928ed1c07..a89425767f 100644 --- a/lib/coffee_script/narwhal/loader.cs +++ b/lib/coffee_script/narwhal/loader.cs @@ -1,20 +1,19 @@ # This (javascript) file is generated from lib/coffee_script/narwhal/loader.cs coffeescript: null +factories: {} -CoffeeScriptLoader: => - loader: {} - factories: {} +loader: { - loader.reload: topId, path => + # Reload the coffee-script environment from source. + reload: topId, path => coffeescript ||: require('coffee-script') - # print("loading objective-j: " + topId + " (" + path + ")"); - factories[topId]: coffeescript.make_narwhal_factory(path). + factories[topId]: coffeescript.makeNarwhalFactory(path). - loader.load: topId, path => - loader.reload(topId, path) unless factories.hasOwnProperty(topId) - factories[topId]. + # Ensure that the coffee-script environment is loaded. + load: topId, path => + factories[topId] ||: this.reload(topId, path). - loader. +} -require.loader.loaders.unshift([".cs", CoffeeScriptLoader()]) +require.loader.loaders.unshift([".cs", loader]) From 73aaf127c84d2b7fbf95006af5477fd8e9e4cce7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 24 Dec 2009 23:57:27 -0800 Subject: [PATCH 101/303] removed bin/cs in favor of a more comprehensive coffee-script command ... now with --interactive and --run --- Rakefile | 5 ++--- bin/cs | 3 --- lib/coffee_script/command_line.rb | 21 ++++++++++++++++++- lib/coffee_script/narwhal/coffee-script.cs | 2 +- .../narwhal/js}/coffee-script.js | 4 ++-- lib/coffee_script/narwhal/js/launcher.js | 3 +++ .../coffee_script/narwhal/js}/loader.js | 2 +- lib/coffee_script/narwhal/launcher.cs | 1 + package.json | 4 ++-- test/unit/test_execution.rb | 2 +- 10 files changed, 33 insertions(+), 14 deletions(-) delete mode 100755 bin/cs rename {lib-js => lib/coffee_script/narwhal/js}/coffee-script.js (94%) create mode 100644 lib/coffee_script/narwhal/js/launcher.js rename {lib-js/coffee-script => lib/coffee_script/narwhal/js}/loader.js (99%) create mode 100644 lib/coffee_script/narwhal/launcher.cs diff --git a/Rakefile b/Rakefile index 553194a933..6f508bde93 100644 --- a/Rakefile +++ b/Rakefile @@ -17,10 +17,9 @@ namespace :build do sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" end - desc "Compile the Narwhal interface for bin/cs" + desc "Compile the Narwhal interface for --interactive and --run" task :narwhal do - sh "bin/coffee-script lib/coffee_script/narwhal/coffee-script.cs --print > lib-js/coffee-script.js" - sh "bin/coffee-script lib/coffee_script/narwhal/loader.cs --print > lib-js/coffee-script/loader.js" + sh "bin/coffee-script lib/coffee_script/narwhal/*.cs -o lib/coffee_script/narwhal/js" end end diff --git a/bin/cs b/bin/cs deleted file mode 100755 index b59bb2abf6..0000000000 --- a/bin/cs +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env narwhal - -require("coffee-script").run(system.args); diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 7499cf81de..f7bf6c94b6 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -23,8 +23,10 @@ class CommandLine def initialize @mtimes = {} parse_options + return launch_repl if @options[:interactive] return eval_scriptlet if @options[:eval] check_sources + return run_scripts if @options[:run] @sources.each {|source| compile_javascript(source) } watch_coffee_scripts if @options[:watch] end @@ -100,6 +102,17 @@ def eval_scriptlet puts js end + # Use Narwhal to run an interactive CoffeeScript session. + def launch_repl + exec "narwhal lib/coffee_script/narwhal/js/launcher.js" + end + + # Use Narwhal to compile and execute CoffeeScripts. + def run_scripts + sources = @sources.join(' ') + exec "narwhal lib/coffee_script/narwhal/js/launcher.js #{sources}" + end + # Print the tokens that the lexer generates from a source script. def tokens(script) puts Lexer.new.tokenize(script).inspect @@ -134,6 +147,12 @@ def install_bundle def parse_options @options = {} @option_parser = OptionParser.new do |opts| + opts.on('-i', '--interactive', 'run a CoffeeScript REPL (requires Narwhal)') do |i| + @options[:interactive] = true + end + opts.on('-r', '--run', 'compile and run a script (requires Narwhal)') do |r| + @options[:run] = true + end opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d| @options[:output] = d FileUtils.mkdir_p(d) unless File.exists?(d) @@ -147,7 +166,7 @@ def parse_options opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l| @options[:lint] = true end - opts.on('-e', '--eval', 'eval a little scriptlet or read from stdin') do |e| + opts.on('-e', '--eval', 'compile a cli scriptlet or read from stdin') do |e| @options[:eval] = true end opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t| diff --git a/lib/coffee_script/narwhal/coffee-script.cs b/lib/coffee_script/narwhal/coffee-script.cs index 0970631209..fe474a7f31 100644 --- a/lib/coffee_script/narwhal/coffee-script.cs +++ b/lib/coffee_script/narwhal/coffee-script.cs @@ -9,7 +9,7 @@ Readline: require('readline') # The path to the CoffeeScript Compiler. -coffeePath: File.path(module.path).dirname().dirname().join('bin', 'coffee-script') +coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee-script') # Our general-purpose error handler. checkForErrors: coffeeProcess => diff --git a/lib-js/coffee-script.js b/lib/coffee_script/narwhal/js/coffee-script.js similarity index 94% rename from lib-js/coffee-script.js rename to lib/coffee_script/narwhal/js/coffee-script.js index 0f1e2e22fd..34f16f6aca 100644 --- a/lib-js/coffee-script.js +++ b/lib/coffee_script/narwhal/js/coffee-script.js @@ -6,7 +6,7 @@ var File = require('file'); var Readline = require('readline'); // The path to the CoffeeScript Compiler. - var coffeePath = File.path(module.path).dirname().dirname().join('bin', 'coffee-script'); + var coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee-script'); // Our general-purpose error handler. var checkForErrors = function(coffeeProcess) { if (coffeeProcess.wait() === 0) { @@ -62,4 +62,4 @@ return eval("(" + factoryText + ")"); } }; -})(); +})(); \ No newline at end of file diff --git a/lib/coffee_script/narwhal/js/launcher.js b/lib/coffee_script/narwhal/js/launcher.js new file mode 100644 index 0000000000..953c677ed3 --- /dev/null +++ b/lib/coffee_script/narwhal/js/launcher.js @@ -0,0 +1,3 @@ +(function(){ + require("coffee-script").run(system.args); +})(); \ No newline at end of file diff --git a/lib-js/coffee-script/loader.js b/lib/coffee_script/narwhal/js/loader.js similarity index 99% rename from lib-js/coffee-script/loader.js rename to lib/coffee_script/narwhal/js/loader.js index 6e08cd61fd..6db0e7914c 100644 --- a/lib-js/coffee-script/loader.js +++ b/lib/coffee_script/narwhal/js/loader.js @@ -17,4 +17,4 @@ } }; require.loader.loaders.unshift([".cs", loader]); -})(); +})(); \ No newline at end of file diff --git a/lib/coffee_script/narwhal/launcher.cs b/lib/coffee_script/narwhal/launcher.cs new file mode 100644 index 0000000000..26fe7ac383 --- /dev/null +++ b/lib/coffee_script/narwhal/launcher.cs @@ -0,0 +1 @@ +require("coffee-script").run(system.args) \ No newline at end of file diff --git a/package.json b/package.json index 4847572d61..e81ae93df0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coffee-script", - "lib": "lib-js", - "preload": ["coffee-script/loader"], + "lib": "lib/coffee_script/narwhal/js", + "preload": ["loader"], "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 9ec4ab4db4..26e7975bd7 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -7,7 +7,7 @@ class ExecutionTest < Test::Unit::TestCase def test_execution_of_coffeescript sources = ['test/fixtures/execution/*.cs'].join(' ') - assert `bin/cs #{sources}`.match(ALLS_WELL) + assert `bin/coffee-script -r #{sources}`.match(ALLS_WELL) end def test_lintless_coffeescript From e7e8e243a2e7955a95c92c18367158a69e6d7800 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 00:02:27 -0800 Subject: [PATCH 102/303] better error warnings on the command line --- lib/coffee_script/command_line.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index f7bf6c94b6..33439f85b3 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -105,12 +105,18 @@ def eval_scriptlet # Use Narwhal to run an interactive CoffeeScript session. def launch_repl exec "narwhal lib/coffee_script/narwhal/js/launcher.js" + rescue Errno::ENOENT + puts "Error: Narwhal must be installed to use the interactive REPL." + exit(1) end # Use Narwhal to compile and execute CoffeeScripts. def run_scripts sources = @sources.join(' ') exec "narwhal lib/coffee_script/narwhal/js/launcher.js #{sources}" + rescue Errno::ENOENT + puts "Error: Narwhal must be installed in order to execute CoffeeScripts." + exit(1) end # Print the tokens that the lexer generates from a source script. From c4ba971d3902abd6e3850099396d30bfdbb432fd Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 00:16:56 -0800 Subject: [PATCH 103/303] bumping to 0.1.3 ... here we go --- coffee-script.gemspec | 4 +- documentation/index.html.erb | 39 +++++++- documentation/js/array_comprehensions.js | 20 ++-- documentation/js/overview.js | 12 +-- index.html | 121 ++++++++++++++--------- lib/coffee-script.rb | 2 +- package.json | 2 +- 7 files changed, 129 insertions(+), 71 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index 3acde9d7da..ab7b493f75 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.1.2' # Keep version in sync with coffee-script.rb - s.date = '2009-12-24' + s.version = '0.1.3' # Keep version in sync with coffee-script.rb + s.date = '2009-12-25' 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 51256ce479..74d0c45fca 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -37,8 +37,6 @@ equivalent in JavaScript, it's just another way of saying it.

    - -

    Disclaimer: CoffeeScript is just for fun and seriously alpha. I'm sure that there are still @@ -95,14 +93,31 @@ gem install coffee-script

    Installing the gem provides the coffee-script command, which can be used to compile CoffeeScript .cs files into JavaScript, as - well as debug them. By default, coffee-script writes out the - JavaScript as .js files in the same directory, but output + well as debug them. In conjunction with + Narwhal, the coffee-script + command also provides direct evaluation and an interactive REPL. + When compiling to JavaScript, coffee-script writes the output + as .js files in the same directory by default, but output can be customized with the following options:

    - + + + + + + + + + @@ -427,6 +442,20 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    Change Log

    + +

    + 0.1.3 + The coffee-script command now includes --interactive, + which launches an interactive CoffeeScript session, and --run, + which directly compiles and executes a script. Both options depend on a + working installation of Narwhal. + The aint keyword has been replaced by isnt, which goes + together a little smoother with is. + Quoted strings are now allowed as identifiers within object literals: eg. + {"5+5": 10}. + All assignment operators now use a colon: +:, -:, + *:, etc. +

    0.1.2 diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index 3fcbd836fe..e1835a2fdb 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -2,18 +2,18 @@ // Eat lunch. var lunch; - var a = ['toast', 'cheese', 'wine']; - var d = []; - for (var b=0, c=a.length; b CoffeeScript equivalent in JavaScript, it's just another way of saying it.

    - -

    Disclaimer: CoffeeScript is just for fun and seriously alpha. I'm sure that there are still @@ -112,13 +110,13 @@

    Mini Overview

    }; // Array comprehensions:var cubed_list; -var a = list; -var d = []; -for (var b=0, c=a.length; b<c; b++) { - var num = a[b]; - d[b] = math.cube(num); +var __a = list; +var __d = []; +for (var __b=0, __c=__a.length; __b<__c; __b++) { + var num = __a[__b]; + __d[__b] = math.cube(num); } -cubed_list = d; +cubed_list = __d;

    Installation and Usage

    - +

    The CoffeeScript compiler is written in pure Ruby, and is available as a Ruby Gem. @@ -165,14 +163,31 @@

    Installation and Usage

    Installing the gem provides the coffee-script command, which can be used to compile CoffeeScript .cs files into JavaScript, as - well as debug them. By default, coffee-script writes out the - JavaScript as .js files in the same directory, but output + well as debug them. In conjunction with + Narwhal, the coffee-script + command also provides direct evaluation and an interactive REPL. + When compiling to JavaScript, coffee-script writes the output + as .js files in the same directory by default, but output can be customized with the following options:

    -o, --output [DIR]-i, --interactive + Launch an interactive CoffeeScript session. + Requires Narwhal. +
    -r, --run + Compile and execute the CoffeeScripts without saving the intermediate + JavaScript. Requires Narwhal. +
    -o, --output [DIR] Write out all compiled JavaScript files into the specified directory.
    - + + + + + + + + + @@ -195,7 +210,7 @@

    Installation and Usage

    @@ -264,15 +279,15 @@

    Language Reference

    the line will do just as well. All other whitespace is not significant. Instead of using curly braces { } to delimit a block of code, use a period . to mark the end of a - block, for - functions, - if-statements, + block, for + functions, + if-statements, switch, and try/catch.

    Functions and Invocation - Functions are defined by a list of parameters, an arrow, and the + Functions are defined by a list of parameters, an arrow, and the function body. The empty function looks like this: =>.

    square: x => x * x.
    @@ -453,7 +468,7 @@ 

    Language Reference

    The same mechanism is used to push down assignment through switch statements, and if-elses (although the ternary operator is preferred).

    - +

    Aliases Because the == operator frequently causes undesirable coercion, @@ -472,7 +487,7 @@

    Language Reference

    Instead of a newline or semicolon, then can be used to separate - conditions from expressions, in while, + conditions from expressions, in while, if/else, and switch/when statements.

    @@ -543,18 +558,18 @@

    Language Reference

     // Eat lunch.
     var lunch;
    -var a = ['toast', 'cheese', 'wine'];
    -var d = [];
    -for (var b=0, c=a.length; b<c; b++) {
    -  var food = a[b];
    -  d[b] = food.eat();
    +var __a = ['toast', 'cheese', 'wine'];
    +var __d = [];
    +for (var __b=0, __c=__a.length; __b<__c; __b++) {
    +  var food = __a[__b];
    +  __d[__b] = food.eat();
     }
    -lunch = d;
    +lunch = __d;
     // Zebra-stripe a table.
    -var e = table;
    -for (var f=0, g=e.length; f<g; f++) {
    -  var row = e[f];
    -  var i = f;
    +var __e = table;
    +for (var __f=0, __g=__e.length; __f<__g; __f++) {
    +  var row = __e[__f];
    +  var i = __f;
       i % 2 === 0 ? highlight(row) : null;
     }
     

    @@ -769,14 +784,14 @@

    Language Reference

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

    Contributing

    - +

    Here's a wish list of things that would be wonderful to have in CoffeeScript:

    - +
    • A JavaScript version of the compiler, perhaps using Alessandro Warth's @@ -791,7 +806,7 @@

      Contributing

      should be able to compile properly.
    • - A tutorial that introduces CoffeeScript from the ground up for folks + A tutorial that introduces CoffeeScript from the ground up for folks without knowledge of JavaScript.
    • @@ -799,31 +814,45 @@

      Contributing

      having a JavaScript version of the compiler).
    • - A lot of the code generation in nodes.rb gets into messy + A lot of the code generation in nodes.rb gets into messy string manipulation. Techniques for cleaning this up across the board would be appreciated.
    - +

    Change Log

    +

    + 0.1.3 + The coffee-script command now includes --interactive, + which launches an interactive CoffeeScript session, and --run, + which directly compiles and executes a script. Both options depend on a + working installation of Narwhal. + The aint keyword has been replaced by isnt, which goes + together a little smoother with is. + Quoted strings are now allowed as identifiers within object literals: eg. + {"5+5": 10}. + All assignment operators now use a colon: +:, -:, + *:, etc. +

    +

    0.1.2 - Fixed a bug with calling super() through more than one level of + Fixed a bug with calling super() through more than one level of inheritance, with the re-addition of the extends keyword. - Added experimental Narwhal - support (as a Tusk package), contributed by + Added experimental Narwhal + support (as a Tusk package), contributed by Tom Robinson, including bin/cs as a CoffeeScript REPL and interpreter. New --no-wrap option to suppress the safety function wrapper.

    - +

    0.1.1 Added instanceof and typeof as operators.

    - +

    0.1.0 Initial CoffeeScript release. diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 29deca23a2..6ae826e617 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -9,7 +9,7 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.1.2' # Keep in sync with the gemspec. + VERSION = '0.1.3' # 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 e81ae93df0..32dfd32d9d 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,5 @@ "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", - "version": "0.1.2" + "version": "0.1.3" } From 851ec824950b576a833b5b94e5d81a8d7e4f4aec Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 07:08:57 -0800 Subject: [PATCH 104/303] allowing chained function calls, one right after another --- lib/coffee_script/grammar.y | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index e1ff01967a..03d9bc3c74 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -259,12 +259,13 @@ rule # Extending an object's prototype. Extends: - Value EXTENDS Expression { result = ExtendsNode.new(val[0], val[2]) } + Value EXTENDS Expression { result = ExtendsNode.new(val[0], val[2]) } ; # A generic function invocation. Invocation: Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } + | Invocation "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } ; # Calling super. From 968aeec5236a6d286987b72244602cd413052ced Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 07:16:59 -0800 Subject: [PATCH 105/303] added a test to make sure that chained calls work --- test/fixtures/execution/chained_calls.cs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 test/fixtures/execution/chained_calls.cs diff --git a/test/fixtures/execution/chained_calls.cs b/test/fixtures/execution/chained_calls.cs new file mode 100644 index 0000000000..ac0ef26914 --- /dev/null +++ b/test/fixtures/execution/chained_calls.cs @@ -0,0 +1,5 @@ +identity_wrap: x => => x.. + +result: identity_wrap(identity_wrap(true))()() + +print(result) \ No newline at end of file From 55bf0a2c61c42e0e6d11fc57c895114de15237cc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 07:31:51 -0800 Subject: [PATCH 106/303] going back to familiar operators +: is just too strange --- documentation/cs/conditionals.cs | 2 +- documentation/index.html.erb | 4 ++-- examples/code.cs | 4 ++-- examples/poignant.cs | 4 ++-- index.html | 6 +++--- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 18 +++++++++--------- lib/coffee_script/narwhal/loader.cs | 4 ++-- lib/coffee_script/nodes.rb | 9 ++------- 9 files changed, 24 insertions(+), 29 deletions(-) diff --git a/documentation/cs/conditionals.cs b/documentation/cs/conditionals.cs index 5d1f985e76..253d912c3b 100644 --- a/documentation/cs/conditionals.cs +++ b/documentation/cs/conditionals.cs @@ -6,4 +6,4 @@ date: if friday then sue else jill. -expensive ||: do_the_math() \ No newline at end of file +expensive ||= do_the_math() \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 74d0c45fca..0442d1c517 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -273,9 +273,9 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    <%= code_for('conditionals') %>

    - The conditional assignment operators are available: ||:, + The conditional assignment operators are available: ||=, which only assigns a value to a variable if the variable's current value - is falsy, and &&:, which only replaces the value of + is falsy, and &&=, which only replaces the value of truthy variables.

    diff --git a/examples/code.cs b/examples/code.cs index 5630265b12..eb65b8268c 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -62,8 +62,8 @@ race(). # Conditional assignment: -good ||: evil -wine &&: cheese +good ||= evil +wine &&= cheese # Nested property access and calls. ((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position['top'].offset('x') diff --git a/examples/poignant.cs b/examples/poignant.cs index 1a6c77ee83..7c9b05f5da 100644 --- a/examples/poignant.cs +++ b/examples/poignant.cs @@ -81,9 +81,9 @@ hit: damage => p_up: Math.rand( this.charisma ) if p_up % 9 is 7 - this.life +: p_up / 4 + this.life += p_up / 4 puts( "[" + this.name + " magick powers up " + p_up + "!]" ). - this.life -: damage + this.life -= damage if this.life <= 0 then puts( "[" + this.name + " has died.]" ).. # This method takes one turn in a fight. diff --git a/index.html b/index.html index 57d840b4c6..544054fbd9 100644 --- a/index.html +++ b/index.html @@ -406,7 +406,7 @@

    Language Reference

    date:if friday then sue else jill. -expensive ||: do_the_math() +expensive ||= do_the_math()
    var mood;
     if (singing) {
       mood = greatly_improved;
    @@ -419,9 +419,9 @@ 

    Language Reference

    expensive = expensive || do_the_math();

    - The conditional assignment operators are available: ||:, + The conditional assignment operators are available: ||=, which only assigns a value to a variable if the variable's current value - is falsy, and &&:, which only replaces the value of + is falsy, and &&=, which only replaces the value of truthy variables.

    diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 5864011363..6a35fb273f 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -241,7 +241,7 @@ match - !|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*:|(?<!\()/=|%:|\+:|\-:|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b + !|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b name keyword.operator.cs diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 03d9bc3c74..9ca0af2022 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -26,12 +26,12 @@ prechigh left '<=' '<' '>' '>=' right '==' '!=' IS ISNT left '&&' '||' AND OR - right '-:' '+:' '/:' '*:' '%:' + right '-=' '+=' '/=' '*=' '%=' right DELETE INSTANCEOF TYPEOF left "." right THROW FOR IN WHILE NEW SUPER left UNLESS IF ELSE EXTENDS - left ":" '||:' '&&:' + left ":" '||=' '&&=' right RETURN preclow @@ -179,13 +179,13 @@ rule | Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '-:' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '+:' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '/:' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '*:' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '%:' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '||:' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '&&:' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) } | DELETE Expression { result = OpNode.new(val[0], val[1]) } | TYPEOF Expression { result = OpNode.new(val[0], val[1]) } diff --git a/lib/coffee_script/narwhal/loader.cs b/lib/coffee_script/narwhal/loader.cs index a89425767f..2405267ef3 100644 --- a/lib/coffee_script/narwhal/loader.cs +++ b/lib/coffee_script/narwhal/loader.cs @@ -7,12 +7,12 @@ # Reload the coffee-script environment from source. reload: topId, path => - coffeescript ||: require('coffee-script') + coffeescript ||= require('coffee-script') factories[topId]: coffeescript.makeNarwhalFactory(path). # Ensure that the coffee-script environment is loaded. load: topId, path => - factories[topId] ||: this.reload(topId, path). + factories[topId] ||= this.reload(topId, path). } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index dcfcd096b2..94556e7648 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -358,14 +358,9 @@ class OpNode < Node 'or' => '||', 'is' => '===', "isnt" => "!==", - 'not' => '!', - '+:' => '+=', - '-:' => '-=', - '*:' => '*=', - '/:' => '/=', - '%:' => '%=' + 'not' => '!' } - CONDITIONALS = ['||:', '&&:'] + CONDITIONALS = ['||=', '&&='] PREFIX_OPERATORS = ['typeof', 'delete'] attr_reader :operator, :first, :second From bc6ec37272a20200f5ed86b50164f87300d1b3b7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 07:42:27 -0800 Subject: [PATCH 107/303] allowing = to assign --- lib/coffee_script/grammar.y | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 9ca0af2022..d0e82c259f 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -31,7 +31,7 @@ prechigh left "." right THROW FOR IN WHILE NEW SUPER left UNLESS IF ELSE EXTENDS - left ":" '||=' '&&=' + left ':' '=' '||=' '&&=' right RETURN preclow @@ -116,6 +116,7 @@ rule # Assignment to a variable. Assign: Value ":" Expression { result = AssignNode.new(val[0], val[2]) } + | Value "=" Expression { result = AssignNode.new(val[0], val[2]) } ; # Assignment within an object literal. From 4b5db1181c8f348ca945841ef3035332f9b1ee96 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 13:21:17 -0800 Subject: [PATCH 108/303] make equals signs full equals of colons -- you can use them inside of object literals now too --- Rakefile | 4 ++-- lib/coffee_script/grammar.y | 11 +++++------ lib/coffee_script/lexer.rb | 6 +++++- test/fixtures/each.tokens | 2 +- test/fixtures/execution/test_everything.cs | 6 +++++- test/unit/test_lexer.rb | 10 +++++----- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Rakefile b/Rakefile index 6f508bde93..82536e6aec 100644 --- a/Rakefile +++ b/Rakefile @@ -13,8 +13,8 @@ end namespace :build do desc "Recompile the Racc parser (pass -v and -g for verbose debugging)" - task :parser, :extra_args do |t, args| - sh "racc #{args[:extra_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" + task :parser, :racc_args do |t, args| + sh "racc #{args[:racc_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y" end desc "Compile the Narwhal interface for --interactive and --run" diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index d0e82c259f..f9c4c2df73 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -28,10 +28,10 @@ prechigh left '&&' '||' AND OR right '-=' '+=' '/=' '*=' '%=' right DELETE INSTANCEOF TYPEOF - left "." + left '.' right THROW FOR IN WHILE NEW SUPER left UNLESS IF ELSE EXTENDS - left ':' '=' '||=' '&&=' + left ASSIGN '||=' '&&=' right RETURN preclow @@ -115,14 +115,13 @@ rule # Assignment to a variable. Assign: - Value ":" Expression { result = AssignNode.new(val[0], val[2]) } - | Value "=" Expression { result = AssignNode.new(val[0], val[2]) } + Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) } ; # Assignment within an object literal. AssignObj: - IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) } - | STRING ":" Expression { result = AssignNode.new(val[0], val[2], :object) } + IDENTIFIER ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) } + | STRING ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) } | Comment { result = val[0] } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 324c4e579f..96a31877b3 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -40,6 +40,9 @@ class Lexer # Tokens that always constitute the end of an expression. EXP_END = ['}', ')', ']'] + # Assignment tokens. + ASSIGN = [':', '='] + # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @@ -139,7 +142,8 @@ def literal_token value ||= @chunk[0,1] skip_following_newlines if EXP_START.include?(value) remove_leading_newlines if EXP_END.include?(value) - token(value, value) + tag = ASSIGN.include?(value) ? :ASSIGN : value + token(tag, value) @i += value.length end diff --git a/test/fixtures/each.tokens b/test/fixtures/each.tokens index 4304bef4e7..149a3170c7 100644 --- a/test/fixtures/each.tokens +++ b/test/fixtures/each.tokens @@ -1 +1 @@ -[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [":", ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [":", ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:ISNT, "isnt"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file +[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [:ASSIGN, ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [:ASSIGN, ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:ISNT, "isnt"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file diff --git a/test/fixtures/execution/test_everything.cs b/test/fixtures/execution/test_everything.cs index 827c80e1ba..f0a866dccf 100644 --- a/test/fixtures/execution/test_everything.cs +++ b/test/fixtures/execution/test_everything.cs @@ -16,7 +16,11 @@ else c.text + '---'. - c.list: l for l in c.text.split('') if l is '-'. + d = { + text = c.text + } + + c.list: l for l in d.text.split('') if l is '-'. c.single: c.list[1, 1][0]. diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index 0f3297b4ee..b626e4f33d 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -12,14 +12,14 @@ def test_lexing_an_empty_string def test_lexing_basic_assignment code = "a: 'one'; b: [1, 2]" - assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"], - [:STRING, "'one'"], [";", ";"], [:IDENTIFIER, "b"], [":", ":"], + assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"], + [:STRING, "'one'"], [";", ";"], [:IDENTIFIER, "b"], [:ASSIGN, ":"], ["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"]] end def test_lexing_object_literal code = "{one : 1}" - assert @lex.tokenize(code) == [["{", "{"], [:IDENTIFIER, "one"], [":", ":"], + assert @lex.tokenize(code) == [["{", "{"], [:IDENTIFIER, "one"], [:ASSIGN, ":"], [:NUMBER, "1"], ["}", "}"]] end @@ -37,9 +37,9 @@ def test_lexing_if_statement def test_lexing_comment code = "a: 1\n # comment\n # on two lines\nb: 2" - assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [":", ":"], [:NUMBER, "1"], + assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"], [:NUMBER, "1"], ["\n", "\n"], [:COMMENT, [" comment", " on two lines"]], ["\n", "\n"], - [:IDENTIFIER, "b"], [":", ":"], [:NUMBER, "2"]] + [:IDENTIFIER, "b"], [:ASSIGN, ":"], [:NUMBER, "2"]] end def test_lexing From 763e04fec94ff9278252265841a51e244e11d388 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 13:39:33 -0800 Subject: [PATCH 109/303] cleaned up the for grammar and eliminated a shift/reduce conflict --- lib/coffee_script/grammar.y | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index f9c4c2df73..5dd42d3b85 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -35,9 +35,9 @@ prechigh right RETURN preclow -# We expect 4 shift/reduce errors for optional syntax. +# We expect 3 shift/reduce errors for optional syntax. # There used to be 252 -- greatly improved. -expect 4 +expect 3 rule @@ -317,18 +317,19 @@ rule # Array comprehensions, including guard and current index. For: - Expression FOR IDENTIFIER - IN PureExpression "." { result = ForNode.new(val[0], val[4], val[2], nil) } - | Expression FOR - IDENTIFIER "," IDENTIFIER - IN PureExpression "." { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) } - | Expression FOR IDENTIFIER - IN PureExpression - IF Expression "." { result = ForNode.new(val[0], val[4], val[2], val[6]) } - | Expression FOR - IDENTIFIER "," IDENTIFIER - IN PureExpression - IF Expression "." { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) } + Expression FOR + ForVariables ForBody { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } + ; + + ForVariables: + IDENTIFIER { result = val } + | IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] } + ; + + ForBody: + IN PureExpression "." { result = [val[1]] } + | IN PureExpression + IF Expression "." { result = [val[1], val[3]] } ; # Switch/When blocks. From f55b4cd8b68c5004f9027f73d25d85010ef74ff7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 13:40:57 -0800 Subject: [PATCH 110/303] ForBody is really the ForSource --- lib/coffee_script/grammar.y | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 5dd42d3b85..99034a2a31 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -318,15 +318,17 @@ rule # Array comprehensions, including guard and current index. For: Expression FOR - ForVariables ForBody { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } + ForVariables ForSource { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } ; + # An array comprehension has variables for the current element and index. ForVariables: IDENTIFIER { result = val } | IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] } ; - ForBody: + # The source of the array comprehension can optionally be filtered. + ForSource: IN PureExpression "." { result = [val[1]] } | IN PureExpression IF Expression "." { result = [val[1], val[3]] } From e494d520eafb1ce47bb48a297204351e62cc5e60 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 13:57:47 -0800 Subject: [PATCH 111/303] stopped using __proto__, instead, using a variant of goog.inherits for extends and super() --- documentation/cs/super.cs | 4 ++-- documentation/js/super.js | 12 ++++++---- examples/code.cs | 4 ++-- index.html | 28 +++++++++++++++--------- lib/coffee_script/grammar.y | 3 ++- lib/coffee_script/nodes.rb | 8 +++++-- test/fixtures/execution/calling_super.cs | 6 ++--- 7 files changed, 41 insertions(+), 24 deletions(-) diff --git a/documentation/cs/super.cs b/documentation/cs/super.cs index 07cb3b31e5..3e80e8045f 100644 --- a/documentation/cs/super.cs +++ b/documentation/cs/super.cs @@ -3,13 +3,13 @@ alert(this.name + " moved " + meters + "m."). Snake: name => this.name: name. -Snake extends new Animal() +Snake extends Animal Snake.prototype.move: => alert("Slithering...") super(5). Horse: name => this.name: name. -Horse extends new Animal() +Horse extends Animal Horse.prototype.move: => alert("Galloping...") super(45). diff --git a/documentation/js/super.js b/documentation/js/super.js index 64c3c4f931..cfb99e5c93 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -8,19 +8,23 @@ this.name = name; return this.name; }; - Snake.prototype.__proto__ = new Animal(); + Snake.__superClass__ = Animal.prototype; + Snake.prototype = new Animal(); + Snake.prototype.constructor = Snake; Snake.prototype.move = function() { alert("Slithering..."); - return Snake.prototype.__proto__.move.call(this, 5); + return Snake.__superClass__.move.call(this, 5); }; var Horse = function(name) { this.name = name; return this.name; }; - Horse.prototype.__proto__ = new Animal(); + Horse.__superClass__ = Animal.prototype; + Horse.prototype = new Animal(); + Horse.prototype.constructor = Horse; Horse.prototype.move = function() { alert("Galloping..."); - return Horse.prototype.__proto__.move.call(this, 45); + return Horse.__superClass__.move.call(this, 45); }; var sam = new Snake("Sammy the Python"); var tom = new Horse("Tommy the Palomino"); diff --git a/examples/code.cs b/examples/code.cs index eb65b8268c..27c6d17a5a 100644 --- a/examples/code.cs +++ b/examples/code.cs @@ -145,13 +145,13 @@ aliquam erat volutpat. Ut wisi enim ad." alert(this.name + " moved " + meters + "m."). Snake: name => this.name: name. -Snake extends new Animal() +Snake extends Animal Snake.prototype.move: => alert('Slithering...') super(5). Horse: name => this.name: name. -Horse extends new Animal() +Horse extends Animal Horse.prototype.move: => alert('Galloping...') super(45). diff --git a/index.html b/index.html index 544054fbd9..40230798a9 100644 --- a/index.html +++ b/index.html @@ -610,13 +610,13 @@

    Language Reference

    alert(this.name + " moved " + meters + "m."). Snake: name => this.name: name. -Snake extends new Animal() +Snake extends Animal Snake.prototype.move: => alert("Slithering...") super(5). Horse: name => this.name: name. -Horse extends new Animal() +Horse extends Animal Horse.prototype.move: => alert("Galloping...") super(45). @@ -639,19 +639,23 @@

    Language Reference

    this.name = name; return this.name; }; -Snake.prototype.__proto__ = new Animal(); +Snake.__superClass__ = Animal.prototype; +Snake.prototype = new Animal(); +Snake.prototype.constructor = Snake; Snake.prototype.move = function() { alert("Slithering..."); - return Snake.prototype.__proto__.move.call(this, 5); + return Snake.__superClass__.move.call(this, 5); }; var Horse = function(name) { this.name = name; return this.name; }; -Horse.prototype.__proto__ = new Animal(); +Horse.__superClass__ = Animal.prototype; +Horse.prototype = new Animal(); +Horse.prototype.constructor = Horse; Horse.prototype.move = function() { alert("Galloping..."); - return Horse.prototype.__proto__.move.call(this, 45); + return Horse.__superClass__.move.call(this, 45); }; var sam = new Snake("Sammy the Python"); var tom = new Horse("Tommy the Palomino"); @@ -666,19 +670,23 @@

    Language Reference

    this.name = name; return this.name; }; -Snake.prototype.__proto__ = new Animal(); +Snake.__superClass__ = Animal.prototype; +Snake.prototype = new Animal(); +Snake.prototype.constructor = Snake; Snake.prototype.move = function() { alert("Slithering..."); - return Snake.prototype.__proto__.move.call(this, 5); + return Snake.__superClass__.move.call(this, 5); }; var Horse = function(name) { this.name = name; return this.name; }; -Horse.prototype.__proto__ = new Animal(); +Horse.__superClass__ = Animal.prototype; +Horse.prototype = new Animal(); +Horse.prototype.constructor = Horse; Horse.prototype.move = function() { alert("Galloping..."); - return Horse.prototype.__proto__.move.call(this, 45); + return Horse.__superClass__.move.call(this, 45); }; var sam = new Snake("Sammy the Python"); var tom = new Horse("Tommy the Palomino"); diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 99034a2a31..fbdd9efc09 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -259,7 +259,7 @@ rule # Extending an object's prototype. Extends: - Value EXTENDS Expression { result = ExtendsNode.new(val[0], val[2]) } + Value EXTENDS Value { result = ExtendsNode.new(val[0], val[2]) } ; # A generic function invocation. @@ -316,6 +316,7 @@ rule ; # Array comprehensions, including guard and current index. + # Looks a little confusing, check nodes.rb for the arguments to ForNode. For: Expression FOR ForVariables ForSource { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 94556e7648..968037f258 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -211,11 +211,12 @@ def compile(o={}) def compile_super(args, o) methname = o[:last_assign].sub(LEADING_DOT, '') arg_part = args.empty? ? '' : ", #{args}" - "#{o[:proto_assign]}.prototype.__proto__.#{methname}.call(this#{arg_part})" + "#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})" end end # Node to extend an object's prototype with an ancestor object. + # After goog.inherits from the Closure Library. class ExtendsNode < Node attr_reader :sub_object, :super_object @@ -224,7 +225,10 @@ def initialize(sub_object, super_object) end def compile(o={}) - "#{@sub_object.compile(o)}.prototype.__proto__ = #{@super_object.compile(o)}" + sub, sup = @sub_object.compile(o), @super_object.compile(o) + "#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" + + "#{sub}.prototype = new #{sup}();\n#{o[:indent]}" + + "#{sub}.prototype.constructor = #{sub}" end end diff --git a/test/fixtures/execution/calling_super.cs b/test/fixtures/execution/calling_super.cs index 691924498c..10e52c49ef 100644 --- a/test/fixtures/execution/calling_super.cs +++ b/test/fixtures/execution/calling_super.cs @@ -3,17 +3,17 @@ 'zero/' + string. FirstChild: => . -FirstChild extends new Base() +FirstChild extends Base FirstChild.prototype.func: string => super('one/') + string. SecondChild: => . -SecondChild extends new FirstChild() +SecondChild extends FirstChild SecondChild.prototype.func: string => super('two/') + string. ThirdChild: => . -ThirdChild extends new SecondChild() +ThirdChild extends SecondChild ThirdChild.prototype.func: string => super('three/') + string. From d46bf8ee71fabc8d72b47df73a7fe278e10e26ca Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 14:18:05 -0800 Subject: [PATCH 112/303] moved the coffeescript extension over from .cs to .coffee -- let's leave C# in peace. Changed array comprehensions to always return their mapped result, even when unassigned --- README | 2 +- Rakefile | 4 +- .../{cs/aliases.cs => coffee/aliases.coffee} | 0 .../array_comprehensions.coffee} | 0 .../assignment.coffee} | 0 .../conditionals.coffee} | 0 .../embedded.cs => coffee/embedded.coffee} | 0 .../expressions.coffee} | 0 .../functions.cs => coffee/functions.coffee} | 0 .../objects_and_arrays.coffee} | 0 .../overview.cs => coffee/overview.coffee} | 0 .../{cs/scope.cs => coffee/scope.coffee} | 0 .../{cs/slices.cs => coffee/slices.coffee} | 0 .../{cs/strings.cs => coffee/strings.coffee} | 0 .../{cs/super.cs => coffee/super.coffee} | 0 .../{cs/switch.cs => coffee/switch.coffee} | 0 .../{cs/try.cs => coffee/try.coffee} | 0 .../{cs/while.cs => coffee/while.coffee} | 0 documentation/cs/intro.cs | 3 - documentation/cs/punctuation.cs | 11 --- documentation/index.html.erb | 26 +++--- examples/{code.cs => code.coffee} | 0 examples/{documents.cs => documents.coffee} | 0 examples/{poignant.cs => poignant.coffee} | 0 ...{syntax_errors.cs => syntax_errors.coffee} | 0 examples/{underscore.cs => underscore.coffee} | 0 index.html | 24 ++--- .../Preferences/CoffeeScript.tmPreferences | 2 +- .../Syntaxes/CoffeeScript.tmLanguage | 91 +++++++++---------- lib/coffee_script/command_line.rb | 2 +- ...{coffee-script.cs => coffee-script.coffee} | 2 +- lib/coffee_script/narwhal/js/coffee-script.js | 2 +- lib/coffee_script/narwhal/js/loader.js | 4 +- .../narwhal/{launcher.cs => launcher.coffee} | 0 .../narwhal/{loader.cs => loader.coffee} | 4 +- lib/coffee_script/nodes.rb | 47 +++++----- test/fixtures/{each.cs => each.coffee} | 0 test/fixtures/each.js | 14 ++- test/fixtures/each_no_wrap.js | 14 ++- ...ehension.cs => array_comprehension.coffee} | 0 ...ry_catch.cs => assign_to_try_catch.coffee} | 0 ...{calling_super.cs => calling_super.coffee} | 0 ...{chained_calls.cs => chained_calls.coffee} | 0 ...statement.cs => fancy_if_statement.coffee} | 0 test/fixtures/execution/keyword_operators.cs | 10 -- ...t_everything.cs => test_everything.coffee} | 0 .../{test_switch.cs => test_switch.coffee} | 0 ...nner_comments.cs => inner_comments.coffee} | 0 test/unit/test_execution.rb | 6 +- test/unit/test_lexer.rb | 2 +- test/unit/test_parser.rb | 6 +- 51 files changed, 128 insertions(+), 148 deletions(-) rename documentation/{cs/aliases.cs => coffee/aliases.coffee} (100%) rename documentation/{cs/array_comprehensions.cs => coffee/array_comprehensions.coffee} (100%) rename documentation/{cs/assignment.cs => coffee/assignment.coffee} (100%) rename documentation/{cs/conditionals.cs => coffee/conditionals.coffee} (100%) rename documentation/{cs/embedded.cs => coffee/embedded.coffee} (100%) rename documentation/{cs/expressions.cs => coffee/expressions.coffee} (100%) rename documentation/{cs/functions.cs => coffee/functions.coffee} (100%) rename documentation/{cs/objects_and_arrays.cs => coffee/objects_and_arrays.coffee} (100%) rename documentation/{cs/overview.cs => coffee/overview.coffee} (100%) rename documentation/{cs/scope.cs => coffee/scope.coffee} (100%) rename documentation/{cs/slices.cs => coffee/slices.coffee} (100%) rename documentation/{cs/strings.cs => coffee/strings.coffee} (100%) rename documentation/{cs/super.cs => coffee/super.coffee} (100%) rename documentation/{cs/switch.cs => coffee/switch.coffee} (100%) rename documentation/{cs/try.cs => coffee/try.coffee} (100%) rename documentation/{cs/while.cs => coffee/while.coffee} (100%) delete mode 100644 documentation/cs/intro.cs delete mode 100644 documentation/cs/punctuation.cs rename examples/{code.cs => code.coffee} (100%) rename examples/{documents.cs => documents.coffee} (100%) rename examples/{poignant.cs => poignant.coffee} (100%) rename examples/{syntax_errors.cs => syntax_errors.coffee} (100%) rename examples/{underscore.cs => underscore.coffee} (100%) rename lib/coffee_script/narwhal/{coffee-script.cs => coffee-script.coffee} (98%) rename lib/coffee_script/narwhal/{launcher.cs => launcher.coffee} (100%) rename lib/coffee_script/narwhal/{loader.cs => loader.coffee} (85%) rename test/fixtures/{each.cs => each.coffee} (100%) rename test/fixtures/execution/{array_comprehension.cs => array_comprehension.coffee} (100%) rename test/fixtures/execution/{assign_to_try_catch.cs => assign_to_try_catch.coffee} (100%) rename test/fixtures/execution/{calling_super.cs => calling_super.coffee} (100%) rename test/fixtures/execution/{chained_calls.cs => chained_calls.coffee} (100%) rename test/fixtures/execution/{fancy_if_statement.cs => fancy_if_statement.coffee} (100%) delete mode 100644 test/fixtures/execution/keyword_operators.cs rename test/fixtures/execution/{test_everything.cs => test_everything.coffee} (100%) rename test/fixtures/execution/{test_switch.cs => test_switch.coffee} (100%) rename test/fixtures/{inner_comments.cs => inner_comments.coffee} (100%) diff --git a/README b/README index 244d6f82e0..04fb649f2c 100644 --- a/README +++ b/README @@ -26,7 +26,7 @@ gem install coffee-script Compile a script: - coffee-script /path/to/script.cs + coffee-script /path/to/script.coffee For documentation, usage, and examples, see: http://jashkenas.github.com/coffee-script/ diff --git a/Rakefile b/Rakefile index 82536e6aec..1891f84f80 100644 --- a/Rakefile +++ b/Rakefile @@ -19,7 +19,7 @@ namespace :build do desc "Compile the Narwhal interface for --interactive and --run" task :narwhal do - sh "bin/coffee-script lib/coffee_script/narwhal/*.cs -o lib/coffee_script/narwhal/js" + sh "bin/coffee-script lib/coffee_script/narwhal/*.coffee -o lib/coffee_script/narwhal/js" end end @@ -27,7 +27,7 @@ end desc "Build the documentation page" task :doc do source = 'documentation/index.html.erb' - child = fork { exec "bin/coffee-script documentation/cs/*.cs -o documentation/js -w" } + child = fork { exec "bin/coffee-script documentation/coffee/*.coffee -o documentation/js -w" } at_exit { Process.kill("INT", child) } Signal.trap("INT") { exit } loop do diff --git a/documentation/cs/aliases.cs b/documentation/coffee/aliases.coffee similarity index 100% rename from documentation/cs/aliases.cs rename to documentation/coffee/aliases.coffee diff --git a/documentation/cs/array_comprehensions.cs b/documentation/coffee/array_comprehensions.coffee similarity index 100% rename from documentation/cs/array_comprehensions.cs rename to documentation/coffee/array_comprehensions.coffee diff --git a/documentation/cs/assignment.cs b/documentation/coffee/assignment.coffee similarity index 100% rename from documentation/cs/assignment.cs rename to documentation/coffee/assignment.coffee diff --git a/documentation/cs/conditionals.cs b/documentation/coffee/conditionals.coffee similarity index 100% rename from documentation/cs/conditionals.cs rename to documentation/coffee/conditionals.coffee diff --git a/documentation/cs/embedded.cs b/documentation/coffee/embedded.coffee similarity index 100% rename from documentation/cs/embedded.cs rename to documentation/coffee/embedded.coffee diff --git a/documentation/cs/expressions.cs b/documentation/coffee/expressions.coffee similarity index 100% rename from documentation/cs/expressions.cs rename to documentation/coffee/expressions.coffee diff --git a/documentation/cs/functions.cs b/documentation/coffee/functions.coffee similarity index 100% rename from documentation/cs/functions.cs rename to documentation/coffee/functions.coffee diff --git a/documentation/cs/objects_and_arrays.cs b/documentation/coffee/objects_and_arrays.coffee similarity index 100% rename from documentation/cs/objects_and_arrays.cs rename to documentation/coffee/objects_and_arrays.coffee diff --git a/documentation/cs/overview.cs b/documentation/coffee/overview.coffee similarity index 100% rename from documentation/cs/overview.cs rename to documentation/coffee/overview.coffee diff --git a/documentation/cs/scope.cs b/documentation/coffee/scope.coffee similarity index 100% rename from documentation/cs/scope.cs rename to documentation/coffee/scope.coffee diff --git a/documentation/cs/slices.cs b/documentation/coffee/slices.coffee similarity index 100% rename from documentation/cs/slices.cs rename to documentation/coffee/slices.coffee diff --git a/documentation/cs/strings.cs b/documentation/coffee/strings.coffee similarity index 100% rename from documentation/cs/strings.cs rename to documentation/coffee/strings.coffee diff --git a/documentation/cs/super.cs b/documentation/coffee/super.coffee similarity index 100% rename from documentation/cs/super.cs rename to documentation/coffee/super.coffee diff --git a/documentation/cs/switch.cs b/documentation/coffee/switch.coffee similarity index 100% rename from documentation/cs/switch.cs rename to documentation/coffee/switch.coffee diff --git a/documentation/cs/try.cs b/documentation/coffee/try.coffee similarity index 100% rename from documentation/cs/try.cs rename to documentation/coffee/try.coffee diff --git a/documentation/cs/while.cs b/documentation/coffee/while.coffee similarity index 100% rename from documentation/cs/while.cs rename to documentation/coffee/while.coffee diff --git a/documentation/cs/intro.cs b/documentation/cs/intro.cs deleted file mode 100644 index b0f13bb982..0000000000 --- a/documentation/cs/intro.cs +++ /dev/null @@ -1,3 +0,0 @@ -# CoffeeScript on the left, JS on the right. - -square: x => x * x. diff --git a/documentation/cs/punctuation.cs b/documentation/cs/punctuation.cs deleted file mode 100644 index 3ecf20736d..0000000000 --- a/documentation/cs/punctuation.cs +++ /dev/null @@ -1,11 +0,0 @@ -# Comments start with hash marks. - -# Periods mark the end of a block. -left_hand: if raining then umbrella else parasol. - -# To signal the beginning of the next expression, -# use "then", or a newline. -left_hand: if raining - umbrella -else - parasol. diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 0442d1c517..29c233b7cd 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -3,7 +3,7 @@ def code_for(file, executable=false) @stripper ||= /(\A\(function\(\)\{\n|\}\)\(\);\Z|^ )/ return '' unless File.exists?("documentation/js/#{file}.js") - cs = File.read("documentation/cs/#{file}.cs") + cs = File.read("documentation/coffee/#{file}.coffee") js = File.read("documentation/js/#{file}.js").gsub(@stripper, '') cshtml = Uv.parse(cs, 'xhtml', 'coffeescript', false, 'idle', false) jshtml = Uv.parse(js, 'xhtml', 'javascript', false, 'idle', false) @@ -92,11 +92,11 @@ gem install coffee-script

    Installing the gem provides the coffee-script command, which can - be used to compile CoffeeScript .cs files into JavaScript, as - well as debug them. In conjunction with + be used to compile CoffeeScript .coffee files into JavaScript, as + well as debug them. In conjunction with Narwhal, the coffee-script - command also provides direct evaluation and an interactive REPL. - When compiling to JavaScript, coffee-script writes the output + command also provides direct evaluation and an interactive REPL. + When compiling to JavaScript, coffee-script writes the output as .js files in the same directory by default, but output can be customized with the following options:

    @@ -105,7 +105,7 @@ gem install coffee-script
    @@ -187,9 +187,9 @@ gem install coffee-script

    -coffee-script path/to/script.cs
    -coffee-script --watch --lint experimental.cs
    -coffee-script --print app/scripts/*.cs > concatenation.js
    +coffee-script path/to/script.coffee +coffee-script --watch --lint experimental.coffee +coffee-script --print app/scripts/*.coffee > concatenation.js

    Language Reference

    @@ -442,18 +442,18 @@ coffee-script --print app/scripts/*.cs > concatenation.js

    Change Log

    - +

    0.1.3 - The coffee-script command now includes --interactive, + The coffee-script command now includes --interactive, which launches an interactive CoffeeScript session, and --run, which directly compiles and executes a script. Both options depend on a working installation of Narwhal. - The aint keyword has been replaced by isnt, which goes + The aint keyword has been replaced by isnt, which goes together a little smoother with is. Quoted strings are now allowed as identifiers within object literals: eg. {"5+5": 10}. - All assignment operators now use a colon: +:, -:, + All assignment operators now use a colon: +:, -:, *:, etc.

    diff --git a/examples/code.cs b/examples/code.coffee similarity index 100% rename from examples/code.cs rename to examples/code.coffee diff --git a/examples/documents.cs b/examples/documents.coffee similarity index 100% rename from examples/documents.cs rename to examples/documents.coffee diff --git a/examples/poignant.cs b/examples/poignant.coffee similarity index 100% rename from examples/poignant.cs rename to examples/poignant.coffee diff --git a/examples/syntax_errors.cs b/examples/syntax_errors.coffee similarity index 100% rename from examples/syntax_errors.cs rename to examples/syntax_errors.coffee diff --git a/examples/underscore.cs b/examples/underscore.coffee similarity index 100% rename from examples/underscore.cs rename to examples/underscore.coffee diff --git a/index.html b/index.html index 40230798a9..b4a27c584b 100644 --- a/index.html +++ b/index.html @@ -162,11 +162,11 @@

    Installation and Usage

    Installing the gem provides the coffee-script command, which can - be used to compile CoffeeScript .cs files into JavaScript, as - well as debug them. In conjunction with + be used to compile CoffeeScript .coffee files into JavaScript, as + well as debug them. In conjunction with Narwhal, the coffee-script - command also provides direct evaluation and an interactive REPL. - When compiling to JavaScript, coffee-script writes the output + command also provides direct evaluation and an interactive REPL. + When compiling to JavaScript, coffee-script writes the output as .js files in the same directory by default, but output can be customized with the following options:

    @@ -175,7 +175,7 @@

    Installation and Usage

    @@ -257,9 +257,9 @@

    Installation and Usage

    -coffee-script path/to/script.cs
    -coffee-script --watch --lint experimental.cs
    -coffee-script --print app/scripts/*.cs > concatenation.js
    +coffee-script path/to/script.coffee +coffee-script --watch --lint experimental.coffee +coffee-script --print app/scripts/*.coffee > concatenation.js

    Language Reference

    @@ -829,18 +829,18 @@

    Contributing

    Change Log

    - +

    0.1.3 - The coffee-script command now includes --interactive, + The coffee-script command now includes --interactive, which launches an interactive CoffeeScript session, and --run, which directly compiles and executes a script. Both options depend on a working installation of Narwhal. - The aint keyword has been replaced by isnt, which goes + The aint keyword has been replaced by isnt, which goes together a little smoother with is. Quoted strings are now allowed as identifiers within object literals: eg. {"5+5": 10}. - All assignment operators now use a colon: +:, -:, + All assignment operators now use a colon: +:, -:, *:, etc.

    diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences b/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences index 5847e50b39..bc80ac941f 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences +++ b/lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences @@ -5,7 +5,7 @@ namecommentsscope - source.cs + source.coffeesettings shellVariables diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 6a35fb273f..927e017d82 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -6,8 +6,7 @@ CoffeeScript Syntax: version 1 fileTypes - cs - coffeescript + coffee name CoffeeScript @@ -19,22 +18,22 @@ 1 name - entity.name.function.cs + entity.name.function.coffee 2 name - keyword.operator.cs + keyword.operator.coffee 3 name - variable.parameter.function.cs + variable.parameter.function.coffee 4 name - storage.type.function.cs + storage.type.function.coffee comment @@ -42,7 +41,7 @@ match([a-zA-Z_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)name - meta.function.cs + meta.function.coffee captures @@ -50,12 +49,12 @@ 1 name - variable.parameter.function.cs + variable.parameter.function.coffee 2 name - storage.type.function.cs + storage.type.function.coffee comment @@ -63,7 +62,7 @@ match([a-zA-Z_?., $]*)\s*(=>)name - meta.inline.function.cs + meta.inline.function.coffee captures @@ -71,12 +70,12 @@ 1 name - keyword.operator.new.cs + keyword.operator.new.coffee 2 name - entity.name.type.instance.cs + entity.name.type.instance.coffee match @@ -88,7 +87,7 @@ match\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\bname - constant.numeric.cs + constant.numeric.coffee begin @@ -98,7 +97,7 @@ 0 name - punctuation.definition.string.begin.cs + punctuation.definition.string.begin.coffee end @@ -108,18 +107,18 @@ 0 name - punctuation.definition.string.end.cs + punctuation.definition.string.end.coffee name - string.quoted.single.cs + string.quoted.single.coffeepatterns match \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.) name - constant.character.escape.cs + constant.character.escape.coffee @@ -131,7 +130,7 @@ 0 name - punctuation.definition.string.begin.cs + punctuation.definition.string.begin.coffee end @@ -141,18 +140,18 @@ 0 name - punctuation.definition.string.end.cs + punctuation.definition.string.end.coffee name - string.quoted.double.cs + string.quoted.double.coffeepatterns match \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.) name - constant.character.escape.cs + constant.character.escape.coffee @@ -164,7 +163,7 @@ 0 name - punctuation.definition.string.begin.cs + punctuation.definition.string.begin.coffee end @@ -174,18 +173,18 @@ 0 name - punctuation.definition.string.end.cs + punctuation.definition.string.end.coffee name - string.quoted.script.cs + string.quoted.script.coffeepatterns match \\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.) name - constant.character.escape.cs + constant.character.escape.coffee @@ -195,61 +194,61 @@ 1 name - punctuation.definition.comment.cs + punctuation.definition.comment.coffee match(#).*$\n?name - comment.line.cs + comment.line.coffee match \b(break|when|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|while)\b name - keyword.control.cs + keyword.control.coffee match \b(true|on|yes)\b name - constant.language.boolean.true.cs + constant.language.boolean.true.coffee match \b(false|off|no)\b name - constant.language.boolean.false.cs + constant.language.boolean.false.coffee match \bnull\b name - constant.language.null.cs + constant.language.null.coffee match \b(super|this|extends)\b name - variable.language.cs + variable.language.coffee match \b(debugger)\b name - keyword.other.cs + keyword.other.coffee match !|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b name - keyword.operator.cs + keyword.operator.coffee match \b(Infinity|NaN|undefined)\b name - constant.language.cs + constant.language.coffee begin @@ -259,7 +258,7 @@ 1 name - punctuation.definition.string.begin.cs + punctuation.definition.string.begin.coffee end @@ -269,18 +268,18 @@ 1 name - punctuation.definition.string.end.cs + punctuation.definition.string.end.coffee name - string.regexp.cs + string.regexp.coffeepatterns match \\. name - constant.character.escape.cs + constant.character.escape.coffee @@ -288,41 +287,41 @@ match\;name - punctuation.terminator.statement.cs + punctuation.terminator.statement.coffee match ,[ |\t]* name - meta.delimiter.object.comma.cs + meta.delimiter.object.comma.coffee match \. name - meta.delimiter.method.period.cs + meta.delimiter.method.period.coffee match \{|\} name - meta.brace.curly.cs + meta.brace.curly.coffee match \(|\) name - meta.brace.round.cs + meta.brace.round.coffee match \[|\] name - meta.brace.square.cs + meta.brace.square.coffee scopeName - source.cs + source.coffeeuuid5B520980-A7D5-4E10-8582-1A4C889A8DE5 diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 33439f85b3..be80a7d632 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -13,7 +13,7 @@ class CommandLine coffee-script compiles CoffeeScript source files into JavaScript. Usage: - coffee-script path/to/script.cs + coffee-script path/to/script.coffee EOS # Seconds to pause between checks for changed source files. diff --git a/lib/coffee_script/narwhal/coffee-script.cs b/lib/coffee_script/narwhal/coffee-script.coffee similarity index 98% rename from lib/coffee_script/narwhal/coffee-script.cs rename to lib/coffee_script/narwhal/coffee-script.coffee index fe474a7f31..7c99e87ea3 100644 --- a/lib/coffee_script/narwhal/coffee-script.cs +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -1,4 +1,4 @@ -# This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.cs +# This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee # Executes the `coffee-script` Ruby program to convert from CoffeeScript # to Javascript. Eventually this will hopefully happen entirely within JS. diff --git a/lib/coffee_script/narwhal/js/coffee-script.js b/lib/coffee_script/narwhal/js/coffee-script.js index 34f16f6aca..7703a25f29 100644 --- a/lib/coffee_script/narwhal/js/coffee-script.js +++ b/lib/coffee_script/narwhal/js/coffee-script.js @@ -1,6 +1,6 @@ (function(){ - // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.cs Executes the `coffee-script` Ruby program to convert from CoffeeScript + // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee Executes the `coffee-script` Ruby program to convert from CoffeeScript // to Javascript. Eventually this will hopefully happen entirely within JS. Require external dependencies. var OS = require('os'); var File = require('file'); diff --git a/lib/coffee_script/narwhal/js/loader.js b/lib/coffee_script/narwhal/js/loader.js index 6db0e7914c..676b4bd81c 100644 --- a/lib/coffee_script/narwhal/js/loader.js +++ b/lib/coffee_script/narwhal/js/loader.js @@ -1,6 +1,6 @@ (function(){ - // This (javascript) file is generated from lib/coffee_script/narwhal/loader.cs + // This (javascript) file is generated from lib/coffee_script/narwhal/loader.coffee var coffeescript = null; var factories = { }; @@ -16,5 +16,5 @@ return factories[topId] = factories[topId] || this.reload(topId, path); } }; - require.loader.loaders.unshift([".cs", loader]); + require.loader.loaders.unshift([".coffee", loader]); })(); \ No newline at end of file diff --git a/lib/coffee_script/narwhal/launcher.cs b/lib/coffee_script/narwhal/launcher.coffee similarity index 100% rename from lib/coffee_script/narwhal/launcher.cs rename to lib/coffee_script/narwhal/launcher.coffee diff --git a/lib/coffee_script/narwhal/loader.cs b/lib/coffee_script/narwhal/loader.coffee similarity index 85% rename from lib/coffee_script/narwhal/loader.cs rename to lib/coffee_script/narwhal/loader.coffee index 2405267ef3..509a533eb9 100644 --- a/lib/coffee_script/narwhal/loader.cs +++ b/lib/coffee_script/narwhal/loader.coffee @@ -1,4 +1,4 @@ -# This (javascript) file is generated from lib/coffee_script/narwhal/loader.cs +# This (javascript) file is generated from lib/coffee_script/narwhal/loader.coffee coffeescript: null factories: {} @@ -16,4 +16,4 @@ } -require.loader.loaders.unshift([".cs", loader]) +require.loader.loaders.unshift([".coffee", loader]) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 968037f258..a210f31f1f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -506,32 +506,28 @@ def line_ending def compile(o={}) o = super(o) - scope = o[:scope] - name_found = scope.find(@name) - index_found = @index && scope.find(@index) - svar = scope.free_variable - ivar = scope.free_variable - lvar = scope.free_variable - name_part = name_found ? @name : "var #{@name}" - index_name = @index ? (index_found ? @index : "var #{@index}") : nil - source_part = "var #{svar} = #{@source.compile(o)};" - for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" - var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n" - index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : '' - - set_result = '' - save_result = '' - return_result = '' - body = @body - suffix = ';' + scope = o[:scope] + name_found = scope.find(@name) + index_found = @index && scope.find(@index) + svar = scope.free_variable + ivar = scope.free_variable + lvar = scope.free_variable + rvar = scope.free_variable + name_part = name_found ? @name : "var #{@name}" + index_name = @index ? (index_found ? @index : "var #{@index}") : nil + source_part = "var #{svar} = #{@source.compile(o)};" + for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" + var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n" + index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : '' + body = @body + suffix = ';' + set_result = "var #{rvar} = [];\n#{o[:indent]}" + save_result = "#{rvar}[#{ivar}] = " + return_result = rvar + if o[:return] || o[:assign] - rvar = scope.free_variable - set_result = "var #{rvar} = [];\n#{o[:indent]}" - save_result += "#{rvar}[#{ivar}] = " - return_result = rvar - return_result = "#{o[:assign]} = #{return_result};" if o[:assign] - return_result = "return #{return_result};" if o[:return] - return_result = "\n#{o[:indent]}#{return_result}" + return_result = "#{o[:assign]} = #{return_result}" if o[:assign] + return_result = "return #{return_result}" if o[:return] if @filter body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) body = IfNode.new(@filter, body, nil, :statement => true) @@ -542,6 +538,7 @@ def compile(o={}) body = IfNode.new(@filter, @body) end + return_result = "\n#{o[:indent]}#{return_result};" indent = o[:indent] + TAB body = body.compile(o.merge(:indent => indent)) write("#{source_part}\n#{o[:indent]}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}") diff --git a/test/fixtures/each.cs b/test/fixtures/each.coffee similarity index 100% rename from test/fixtures/each.cs rename to test/fixtures/each.coffee diff --git a/test/fixtures/each.js b/test/fixtures/each.js index 5877863959..8536f46a47 100644 --- a/test/fixtures/each.js +++ b/test/fixtures/each.js @@ -9,17 +9,21 @@ obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { var __a = obj; + var __d = []; for (var __b=0, __c=__a.length; __b<__c; __b++) { var item = __a[__b]; var i = __b; - iterator.call(context, item, i, obj); + __d[__b] = iterator.call(context, item, i, obj); } + __d; } else { - var __d = _.keys(obj); - for (var __e=0, __f=__d.length; __e<__f; __e++) { - var key = __d[__e]; - iterator.call(context, obj[key], key, obj); + var __e = _.keys(obj); + var __h = []; + for (var __f=0, __g=__e.length; __f<__g; __f++) { + var key = __e[__f]; + __h[__f] = iterator.call(context, obj[key], key, obj); } + __h; } } catch (e) { if (e !== breaker) { diff --git a/test/fixtures/each_no_wrap.js b/test/fixtures/each_no_wrap.js index 0d2f647991..5cc80f37f3 100644 --- a/test/fixtures/each_no_wrap.js +++ b/test/fixtures/each_no_wrap.js @@ -8,17 +8,21 @@ _.each = function(obj, iterator, context) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { var __a = obj; + var __d = []; for (var __b=0, __c=__a.length; __b<__c; __b++) { var item = __a[__b]; var i = __b; - iterator.call(context, item, i, obj); + __d[__b] = iterator.call(context, item, i, obj); } + __d; } else { - var __d = _.keys(obj); - for (var __e=0, __f=__d.length; __e<__f; __e++) { - var key = __d[__e]; - iterator.call(context, obj[key], key, obj); + var __e = _.keys(obj); + var __h = []; + for (var __f=0, __g=__e.length; __f<__g; __f++) { + var key = __e[__f]; + __h[__f] = iterator.call(context, obj[key], key, obj); } + __h; } } catch (e) { if (e !== breaker) { diff --git a/test/fixtures/execution/array_comprehension.cs b/test/fixtures/execution/array_comprehension.coffee similarity index 100% rename from test/fixtures/execution/array_comprehension.cs rename to test/fixtures/execution/array_comprehension.coffee diff --git a/test/fixtures/execution/assign_to_try_catch.cs b/test/fixtures/execution/assign_to_try_catch.coffee similarity index 100% rename from test/fixtures/execution/assign_to_try_catch.cs rename to test/fixtures/execution/assign_to_try_catch.coffee diff --git a/test/fixtures/execution/calling_super.cs b/test/fixtures/execution/calling_super.coffee similarity index 100% rename from test/fixtures/execution/calling_super.cs rename to test/fixtures/execution/calling_super.coffee diff --git a/test/fixtures/execution/chained_calls.cs b/test/fixtures/execution/chained_calls.coffee similarity index 100% rename from test/fixtures/execution/chained_calls.cs rename to test/fixtures/execution/chained_calls.coffee diff --git a/test/fixtures/execution/fancy_if_statement.cs b/test/fixtures/execution/fancy_if_statement.coffee similarity index 100% rename from test/fixtures/execution/fancy_if_statement.cs rename to test/fixtures/execution/fancy_if_statement.coffee diff --git a/test/fixtures/execution/keyword_operators.cs b/test/fixtures/execution/keyword_operators.cs deleted file mode 100644 index 573de104a6..0000000000 --- a/test/fixtures/execution/keyword_operators.cs +++ /dev/null @@ -1,10 +0,0 @@ -a: 5 -atype: typeof a - -b: "hello" -btype: typeof b - -Klass: => . -k: new Klass() - -print(atype is 'number' and btype is 'string' and k instanceof Klass) \ No newline at end of file diff --git a/test/fixtures/execution/test_everything.cs b/test/fixtures/execution/test_everything.coffee similarity index 100% rename from test/fixtures/execution/test_everything.cs rename to test/fixtures/execution/test_everything.coffee diff --git a/test/fixtures/execution/test_switch.cs b/test/fixtures/execution/test_switch.coffee similarity index 100% rename from test/fixtures/execution/test_switch.cs rename to test/fixtures/execution/test_switch.coffee diff --git a/test/fixtures/inner_comments.cs b/test/fixtures/inner_comments.coffee similarity index 100% rename from test/fixtures/inner_comments.cs rename to test/fixtures/inner_comments.coffee diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 26e7975bd7..8e9d79e8be 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -6,17 +6,17 @@ class ExecutionTest < Test::Unit::TestCase ALLS_WELL = /\A\n?(true\n)+\Z/ def test_execution_of_coffeescript - sources = ['test/fixtures/execution/*.cs'].join(' ') + sources = ['test/fixtures/execution/*.coffee'].join(' ') assert `bin/coffee-script -r #{sources}`.match(ALLS_WELL) end def test_lintless_coffeescript - lint_results = `bin/coffee-script -l test/fixtures/execution/*.cs` + lint_results = `bin/coffee-script -l test/fixtures/execution/*.coffee` assert lint_results.match(NO_WARNINGS) end def test_lintless_examples - lint_results = `bin/coffee-script -l examples/*.cs` + lint_results = `bin/coffee-script -l examples/*.coffee` assert lint_results.match(NO_WARNINGS) end diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index b626e4f33d..09edfd8749 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -43,7 +43,7 @@ def test_lexing_comment end def test_lexing - tokens = @lex.tokenize(File.read('test/fixtures/each.cs')) + tokens = @lex.tokenize(File.read('test/fixtures/each.coffee')) assert tokens.inspect == File.read('test/fixtures/each.tokens') end diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 05943b73c0..d356dfa1f3 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -58,12 +58,12 @@ def test_parsing_comment end def test_parsing_inner_comments - nodes = @par.parse(File.read('test/fixtures/inner_comments.cs')) + nodes = @par.parse(File.read('test/fixtures/inner_comments.coffee')) assert nodes.compile == File.read('test/fixtures/inner_comments.js') end def test_parsing - nodes = @par.parse(File.read('test/fixtures/each.cs')) + nodes = @par.parse(File.read('test/fixtures/each.coffee')) assign = nodes.expressions[1] assert assign.is_a? AssignNode assert assign.variable.literal == '_' @@ -73,7 +73,7 @@ def test_parsing end def test_no_wrap - nodes = @par.parse(File.read('test/fixtures/each.cs')) + nodes = @par.parse(File.read('test/fixtures/each.coffee')) assert nodes.compile(:no_wrap => true) == File.read('test/fixtures/each_no_wrap.js') end From 62485c2b8cbe439853b830b51d76919f9ad2b47c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 14:43:24 -0800 Subject: [PATCH 113/303] CoffeeScript 0.1.4 --- coffee-script.gemspec | 2 +- documentation/index.html.erb | 13 +++++++++++++ documentation/js/array_comprehensions.js | 4 +++- index.html | 17 ++++++++++++++++- lib/coffee-script.rb | 2 +- package.json | 2 +- 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index ab7b493f75..b7daca48df 100644 --- a/coffee-script.gemspec +++ b/coffee-script.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'coffee-script' - s.version = '0.1.3' # Keep version in sync with coffee-script.rb + s.version = '0.1.4' # Keep version in sync with coffee-script.rb s.date = '2009-12-25' s.homepage = "http://jashkenas.github.com/coffee-script/" diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 29c233b7cd..3ffd8e28b5 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -188,6 +188,7 @@ gem install coffee-script
     coffee-script path/to/script.coffee
    +coffee-script --interactive
     coffee-script --watch --lint experimental.coffee
     coffee-script --print app/scripts/*.coffee > concatenation.js
    @@ -442,6 +443,18 @@ coffee-script --print app/scripts/*.coffee > concatenation.js

    Change Log

    + +

    + 0.1.4 + The official CoffeeScript extension is now .coffee instead of + .cs, which properly belongs to + C#. + Due to popular demand, you can now also use = to assign. Unlike + JavaScript, = can also be used within object literals, interchangeably + with :. Made a grammatical fix for chained function calls + like func(1)(2)(3)(4). Inheritance and super no longer use + __proto__, so they should be IE-compatible now. +

    0.1.3 diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index e1835a2fdb..790281c92e 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -11,9 +11,11 @@ lunch = __d; // Zebra-stripe a table. var __e = table; + var __h = []; for (var __f=0, __g=__e.length; __f<__g; __f++) { var row = __e[__f]; var i = __f; - i % 2 === 0 ? highlight(row) : null; + __h[__f] = i % 2 === 0 ? highlight(row) : null; } + __h; })(); \ No newline at end of file diff --git a/index.html b/index.html index b4a27c584b..bfe0380b55 100644 --- a/index.html +++ b/index.html @@ -258,6 +258,7 @@

    Installation and Usage

     coffee-script path/to/script.coffee
    +coffee-script --interactive
     coffee-script --watch --lint experimental.coffee
     coffee-script --print app/scripts/*.coffee > concatenation.js
    @@ -567,11 +568,13 @@

    Language Reference

    lunch = __d; // Zebra-stripe a table. var __e = table; +var __h = []; for (var __f=0, __g=__e.length; __f<__g; __f++) { var row = __e[__f]; var i = __f; - i % 2 === 0 ? highlight(row) : null; + __h[__f] = i % 2 === 0 ? highlight(row) : null; } +__h;

    @@ -829,6 +832,18 @@

    Contributing

    Change Log

    + +

    + 0.1.4 + The official CoffeeScript extension is now .coffee instead of + .cs, which properly belongs to + C#. + Due to popular demand, you can now also use = to assign. Unlike + JavaScript, = can also be used within object literals, interchangeably + with :. Made a grammatical fix for chained function calls + like func(1)(2)(3)(4). Inheritance and super no longer use + __proto__, so they should be IE-compatible now. +

    0.1.3 diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 6ae826e617..0ae253a2b5 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -9,7 +9,7 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.1.3' # Keep in sync with the gemspec. + VERSION = '0.1.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 32dfd32d9d..30bae056e0 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,5 @@ "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", - "version": "0.1.3" + "version": "0.1.4" } From 1ba7c771360e177504cb8171e5e8dcc77d2c1481 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 16:20:28 -0800 Subject: [PATCH 114/303] reorganizing test fixtures and adding range literals for array slices --- examples/code.coffee | 2 +- examples/poignant.coffee | 2 +- lib/coffee_script/grammar.y | 12 ++++++-- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 28 +++++++++++++++---- ...coffee => test_array_comprehension.coffee} | 0 ...coffee => test_assign_to_try_catch.coffee} | 0 ...super.coffee => test_calling_super.coffee} | 0 ...calls.coffee => test_chained_calls.coffee} | 0 .../fixtures/execution/test_everything.coffee | 2 +- ....coffee => test_fancy_if_statement.coffee} | 0 .../execution/test_ranges_and_slices.coffee | 0 test/fixtures/{ => generation}/each.coffee | 0 test/fixtures/{ => generation}/each.js | 0 test/fixtures/{ => generation}/each.tokens | 0 .../fixtures/{ => generation}/each_no_wrap.js | 0 .../{ => generation}/inner_comments.coffee | 0 .../{ => generation}/inner_comments.js | 0 test/unit/test_execution.rb | 2 +- test/unit/test_lexer.rb | 4 +-- test/unit/test_parser.rb | 14 +++++----- 21 files changed, 46 insertions(+), 22 deletions(-) rename test/fixtures/execution/{array_comprehension.coffee => test_array_comprehension.coffee} (100%) rename test/fixtures/execution/{assign_to_try_catch.coffee => test_assign_to_try_catch.coffee} (100%) rename test/fixtures/execution/{calling_super.coffee => test_calling_super.coffee} (100%) rename test/fixtures/execution/{chained_calls.coffee => test_chained_calls.coffee} (100%) rename test/fixtures/execution/{fancy_if_statement.coffee => test_fancy_if_statement.coffee} (100%) create mode 100644 test/fixtures/execution/test_ranges_and_slices.coffee rename test/fixtures/{ => generation}/each.coffee (100%) rename test/fixtures/{ => generation}/each.js (100%) rename test/fixtures/{ => generation}/each.tokens (100%) rename test/fixtures/{ => generation}/each_no_wrap.js (100%) rename test/fixtures/{ => generation}/inner_comments.coffee (100%) rename test/fixtures/{ => generation}/inner_comments.js (100%) diff --git a/examples/code.coffee b/examples/code.coffee index 27c6d17a5a..23458fd834 100644 --- a/examples/code.coffee +++ b/examples/code.coffee @@ -132,7 +132,7 @@ wednesday: => eat_breakfast(); go_to_work(); eat_dinner(); . # Array slice literals. zero_to_nine: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -three_to_six: zero_to_nine[3, 6] +three_to_six: zero_to_nine[3..6] # Multiline strings with inner quotes. story: "Lorem ipsum dolor \"sit\" amet, consectetuer adipiscing elit, diff --git a/examples/poignant.coffee b/examples/poignant.coffee index 7c9b05f5da..973248ce29 100644 --- a/examples/poignant.coffee +++ b/examples/poignant.coffee @@ -149,5 +149,5 @@ wipe_mutterings_from: sentence => while sentence.indexOf('(') >= 0 open: sentence.indexOf('(') - 1 close: sentence.indexOf(')') + 1 - sentence: sentence[0, open] + sentence[close, sentence.length]. + sentence: sentence[0..open] + sentence[close..sentence.length]. sentence. \ No newline at end of file diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index fbdd9efc09..eecd853052 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -64,8 +64,7 @@ rule # The parts that are natural JavaScript expressions. PureExpression: - Literal - | Value + Value | Call | Code | Operation @@ -213,6 +212,7 @@ rule # Expressions that can be treated as values. Value: IDENTIFIER { result = ValueNode.new(val[0]) } + | Literal { result = ValueNode.new(val[0]) } | Array { result = ValueNode.new(val[0]) } | Object { result = ValueNode.new(val[0]) } | Parenthetical { result = ValueNode.new(val[0]) } @@ -234,7 +234,7 @@ rule # Array slice literal. Slice: - "[" Expression "," Expression "]" { result = SliceNode.new(val[1], val[3]) } + "[" Range "]" { result = SliceNode.new(val[1]) } ; # An object literal. @@ -273,6 +273,12 @@ rule SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) } ; + # The range literal. + Range: + Value "." "." Value { result = RangeNode.new(val[0], val[3]) } + | Value "." "." "." Value { result = RangeNode.new(val[0], val[4], true) } + ; + # The array literal. Array: "[" ArgList "]" { result = ArrayNode.new(val[1]) } diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 96a31877b3..fcf2161825 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -75,7 +75,7 @@ def identifier_token # Keywords are special identifiers tagged with their own name, 'if' will result # in an [:IF, "if"] token tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER - @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' + @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.') token(tag, identifier) @i += identifier.length end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index a210f31f1f..29b35cc309 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -300,19 +300,37 @@ def compile(o={}) end end + # A range literal. Ranges can be used to extract portions (slices) of arrays, + # or to specify a range for array comprehensions. + class RangeNode + attr_reader :from, :to + + def initialize(from, to, exclusive=false) + @from, @to, @exclusive = from, to, exclusive + end + + def exclusive? + @exclusive + end + + end + # An array slice literal. Unlike JavaScript's Array#slice, the second parameter # specifies the index of the end of the slice (just like the first parameter) # is the index of the beginning. class SliceNode < Node - attr_reader :from, :to + attr_reader :range - def initialize(from, to) - @from, @to = from, to + def initialize(range) + @range = range end def compile(o={}) - o = super(o) - write(".slice(#{@from.compile(o)}, #{@to.compile(o)} + 1)") + o = super(o) + from = @range.from.compile(o) + to = @range.to.compile(o) + plus_part = @range.exclusive? ? '' : ' + 1' + write(".slice(#{from}, #{to}#{plus_part})") end end diff --git a/test/fixtures/execution/array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee similarity index 100% rename from test/fixtures/execution/array_comprehension.coffee rename to test/fixtures/execution/test_array_comprehension.coffee diff --git a/test/fixtures/execution/assign_to_try_catch.coffee b/test/fixtures/execution/test_assign_to_try_catch.coffee similarity index 100% rename from test/fixtures/execution/assign_to_try_catch.coffee rename to test/fixtures/execution/test_assign_to_try_catch.coffee diff --git a/test/fixtures/execution/calling_super.coffee b/test/fixtures/execution/test_calling_super.coffee similarity index 100% rename from test/fixtures/execution/calling_super.coffee rename to test/fixtures/execution/test_calling_super.coffee diff --git a/test/fixtures/execution/chained_calls.coffee b/test/fixtures/execution/test_chained_calls.coffee similarity index 100% rename from test/fixtures/execution/chained_calls.coffee rename to test/fixtures/execution/test_chained_calls.coffee diff --git a/test/fixtures/execution/test_everything.coffee b/test/fixtures/execution/test_everything.coffee index f0a866dccf..ca24c6400a 100644 --- a/test/fixtures/execution/test_everything.coffee +++ b/test/fixtures/execution/test_everything.coffee @@ -22,6 +22,6 @@ func: => c.list: l for l in d.text.split('') if l is '-'. - c.single: c.list[1, 1][0]. + c.single: c.list[1..1][0]. print(func() == '-') diff --git a/test/fixtures/execution/fancy_if_statement.coffee b/test/fixtures/execution/test_fancy_if_statement.coffee similarity index 100% rename from test/fixtures/execution/fancy_if_statement.coffee rename to test/fixtures/execution/test_fancy_if_statement.coffee diff --git a/test/fixtures/execution/test_ranges_and_slices.coffee b/test/fixtures/execution/test_ranges_and_slices.coffee new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/fixtures/each.coffee b/test/fixtures/generation/each.coffee similarity index 100% rename from test/fixtures/each.coffee rename to test/fixtures/generation/each.coffee diff --git a/test/fixtures/each.js b/test/fixtures/generation/each.js similarity index 100% rename from test/fixtures/each.js rename to test/fixtures/generation/each.js diff --git a/test/fixtures/each.tokens b/test/fixtures/generation/each.tokens similarity index 100% rename from test/fixtures/each.tokens rename to test/fixtures/generation/each.tokens diff --git a/test/fixtures/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js similarity index 100% rename from test/fixtures/each_no_wrap.js rename to test/fixtures/generation/each_no_wrap.js diff --git a/test/fixtures/inner_comments.coffee b/test/fixtures/generation/inner_comments.coffee similarity index 100% rename from test/fixtures/inner_comments.coffee rename to test/fixtures/generation/inner_comments.coffee diff --git a/test/fixtures/inner_comments.js b/test/fixtures/generation/inner_comments.js similarity index 100% rename from test/fixtures/inner_comments.js rename to test/fixtures/generation/inner_comments.js diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 8e9d79e8be..7910e861d2 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -3,7 +3,7 @@ class ExecutionTest < Test::Unit::TestCase NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/ - ALLS_WELL = /\A\n?(true\n)+\Z/ + ALLS_WELL = /\A\n?(true\n)+\Z/m def test_execution_of_coffeescript sources = ['test/fixtures/execution/*.coffee'].join(' ') diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index 09edfd8749..5f8d52d214 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -43,8 +43,8 @@ def test_lexing_comment end def test_lexing - tokens = @lex.tokenize(File.read('test/fixtures/each.coffee')) - assert tokens.inspect == File.read('test/fixtures/each.tokens') + tokens = @lex.tokenize(File.read('test/fixtures/generation/each.coffee')) + assert tokens.inspect == File.read('test/fixtures/generation/each.tokens') end end diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index d356dfa1f3..ef0b02a13d 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -49,7 +49,7 @@ def test_parsing_array_comprehension assert nodes.first.is_a? ForNode assert nodes.first.body.literal == 'i' assert nodes.first.filter.operator == '===' - assert nodes.first.source.literal.objects.last.value == "5" + assert nodes.first.source.literal.objects.last.literal.value == "5" end def test_parsing_comment @@ -58,23 +58,23 @@ def test_parsing_comment end def test_parsing_inner_comments - nodes = @par.parse(File.read('test/fixtures/inner_comments.coffee')) - assert nodes.compile == File.read('test/fixtures/inner_comments.js') + nodes = @par.parse(File.read('test/fixtures/generation/inner_comments.coffee')) + assert nodes.compile == File.read('test/fixtures/generation/inner_comments.js') end def test_parsing - nodes = @par.parse(File.read('test/fixtures/each.coffee')) + nodes = @par.parse(File.read('test/fixtures/generation/each.coffee')) assign = nodes.expressions[1] assert assign.is_a? AssignNode assert assign.variable.literal == '_' assert assign.value.is_a? CodeNode assert assign.value.params == ['obj', 'iterator', 'context'] - assert nodes.compile == File.read('test/fixtures/each.js') + assert nodes.compile == File.read('test/fixtures/generation/each.js') end def test_no_wrap - nodes = @par.parse(File.read('test/fixtures/each.coffee')) - assert nodes.compile(:no_wrap => true) == File.read('test/fixtures/each_no_wrap.js') + nodes = @par.parse(File.read('test/fixtures/generation/each.coffee')) + assert nodes.compile(:no_wrap => true) == File.read('test/fixtures/generation/each_no_wrap.js') end end From dd5317ebe901d1fde6e08519359f19759963338f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 16:35:57 -0800 Subject: [PATCH 115/303] documenting ranges and slices --- documentation/coffee/slices.coffee | 8 +++- documentation/index.html.erb | 25 ++++++----- documentation/js/slices.js | 5 ++- index.html | 45 +++++++++++-------- .../execution/test_ranges_and_slices.coffee | 8 ++++ 5 files changed, 58 insertions(+), 33 deletions(-) diff --git a/documentation/coffee/slices.coffee b/documentation/coffee/slices.coffee index ac34b0b26c..7136e9544a 100644 --- a/documentation/coffee/slices.coffee +++ b/documentation/coffee/slices.coffee @@ -1,2 +1,6 @@ -nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -three_to_six: nums[3, 6] \ No newline at end of file +numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +three_to_six: numbers[3..6] + +numbers_copy: numbers[0...numbers.length] + diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 3ffd8e28b5..c68e78b4bc 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -64,7 +64,7 @@ Aliases
    While Loops
    Array Comprehensions
    - Array Slice Literals
    + Slicing Arrays with Ranges
    Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    @@ -349,12 +349,15 @@ coffee-script --print app/scripts/*.coffee > concatenation.js <%= code_for('array_comprehensions') %>

    - Array Slice Literals - CoffeeScript includes syntax for extracting slices of arrays. - The first argument is the index of the first element in the slice, and - the second is the index of the last one. + Slicing Arrays with Ranges + CoffeeScript borrows Ruby's + range syntax + for extracting slices of arrays. With two dots (3..5), the range + is inclusive: the first argument is the index of the first element in + the slice, and the second is the index of the last one. Three dots signify + a range that excludes the end.

    - <%= code_for('slices', 'three_to_six') %> + <%= code_for('slices', 'numbers_copy') %>

    Inheritance, and Calling Super from a Subclass @@ -443,17 +446,17 @@ coffee-script --print app/scripts/*.coffee > concatenation.js

    Change Log

    - +

    0.1.4 The official CoffeeScript extension is now .coffee instead of - .cs, which properly belongs to + .cs, which properly belongs to C#. Due to popular demand, you can now also use = to assign. Unlike JavaScript, = can also be used within object literals, interchangeably - with :. Made a grammatical fix for chained function calls - like func(1)(2)(3)(4). Inheritance and super no longer use - __proto__, so they should be IE-compatible now. + with :. Made a grammatical fix for chained function calls + like func(1)(2)(3)(4). Inheritance and super no longer use + __proto__, so they should be IE-compatible now.

    diff --git a/documentation/js/slices.js b/documentation/js/slices.js index a82ecdbec8..5f59028339 100644 --- a/documentation/js/slices.js +++ b/documentation/js/slices.js @@ -1,4 +1,5 @@ (function(){ - var nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var three_to_six = nums.slice(3, 6 + 1); + var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + var three_to_six = numbers.slice(3, 6 + 1); + var numbers_copy = numbers.slice(0, numbers.length); })(); \ No newline at end of file diff --git a/index.html b/index.html index bfe0380b55..437fd44d24 100644 --- a/index.html +++ b/index.html @@ -50,7 +50,7 @@

    Table of Contents

    Aliases
    While Loops
    Array Comprehensions
    - Array Slice Literals
    + Slicing Arrays with Ranges
    Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    @@ -578,18 +578,27 @@

    Language Reference


    - Array Slice Literals - CoffeeScript includes syntax for extracting slices of arrays. - The first argument is the index of the first element in the slice, and - the second is the index of the last one. -

    -
    nums: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    -three_to_six: nums[3, 6]
    -
    var nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    -var three_to_six = nums.slice(3, 6 + 1);
    -

    + Slicing Arrays with Ranges + CoffeeScript borrows Ruby's + range syntax + for extracting slices of arrays. With two dots (3..5), the range + is inclusive: the first argument is the index of the first element in + the slice, and the second is the index of the last one. Three dots signify + a range that excludes the end. +

    +
    numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    +
    +three_to_six: numbers[3..6]
    +
    +numbers_copy: numbers[0...numbers.length]
    +
    +
    var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    +var three_to_six = numbers.slice(3, 6 + 1);
    +var numbers_copy = numbers.slice(0, numbers.length);
    +

    Inheritance, and Calling Super from a Subclass @@ -832,17 +841,17 @@

    Contributing

    Change Log

    - +

    0.1.4 The official CoffeeScript extension is now .coffee instead of - .cs, which properly belongs to + .cs, which properly belongs to C#. Due to popular demand, you can now also use = to assign. Unlike JavaScript, = can also be used within object literals, interchangeably - with :. Made a grammatical fix for chained function calls - like func(1)(2)(3)(4). Inheritance and super no longer use - __proto__, so they should be IE-compatible now. + with :. Made a grammatical fix for chained function calls + like func(1)(2)(3)(4). Inheritance and super no longer use + __proto__, so they should be IE-compatible now.

    diff --git a/test/fixtures/execution/test_ranges_and_slices.coffee b/test/fixtures/execution/test_ranges_and_slices.coffee index e69de29bb2..63165c5d07 100644 --- a/test/fixtures/execution/test_ranges_and_slices.coffee +++ b/test/fixtures/execution/test_ranges_and_slices.coffee @@ -0,0 +1,8 @@ +array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +a: array[7..9] +b: array[2...4] + +result: a.concat(b).join(' ') + +print(result is "7 8 9 2 3") \ No newline at end of file From 52700ca92233981f66d9d8d993b67159d5eb201f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 19:34:40 -0800 Subject: [PATCH 116/303] comment --- lib/coffee_script/nodes.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 29b35cc309..825aed64f3 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -302,6 +302,8 @@ def compile(o={}) # A range literal. Ranges can be used to extract portions (slices) of arrays, # or to specify a range for array comprehensions. + # Because there's no corresponding concept in JavaScript, RangeNodes are never + # compiled directly, just used by other nodes that accept ranges. class RangeNode attr_reader :from, :to From b58102c5149918c6b25b8acc9a8363c893c2c99a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 19:48:47 -0800 Subject: [PATCH 117/303] don't add the no_wrap key to the options hash unless we're going to use it --- lib/coffee_script/command_line.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index be80a7d632..75f3b44d21 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -127,7 +127,9 @@ def tokens(script) # Compile a single source file to JavaScript. def compile(script, source='') begin - CoffeeScript.compile(script, :no_wrap => @options[:no_wrap]) + options = {} + options[:no_wrap] = true if @options[:no_wrap] + CoffeeScript.compile(script, options) rescue CoffeeScript::ParseError => e STDERR.puts e.message(source) exit(1) unless @options[:watch] From 7c132d65dc91701c2fd39e0944cb834fadfccbff Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 20:36:22 -0800 Subject: [PATCH 118/303] started raising syntax errors for parens wrapped around expressions (they used to silently be ignored) --- lib/coffee_script/command_line.rb | 6 +++--- lib/coffee_script/nodes.rb | 9 ++++++--- lib/coffee_script/parse_error.rb | 4 ++-- test/unit/test_parser.rb | 6 ++++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 75f3b44d21..f09f400d6e 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -125,13 +125,13 @@ def tokens(script) end # Compile a single source file to JavaScript. - def compile(script, source='') + def compile(script, source='error') begin options = {} options[:no_wrap] = true if @options[:no_wrap] CoffeeScript.compile(script, options) - rescue CoffeeScript::ParseError => e - STDERR.puts e.message(source) + rescue CoffeeScript::ParseError, SyntaxError => e + STDERR.puts "#{source}: #{e.message}" exit(1) unless @options[:watch] nil end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 825aed64f3..ff361d191b 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -608,7 +608,9 @@ def compile(o={}) end end - # An extra set of parenthesis, supplied by the script source. + # An extra set of parentheses, supplied by the script source. + # You can't wrap parentheses around bits that get compiled into JS statements, + # unfortunately. class ParentheticalNode < Node attr_reader :expressions @@ -617,7 +619,7 @@ def initialize(expressions) end def statement? - @expressions.statement? + @expressions.unwrap.statement? end def custom_assign? @@ -629,10 +631,11 @@ def custom_return? end def compile(o={}) + raise SyntaxError, "parentheses can't be wrapped around a statement" if statement? o = super(o) compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' - write(o[:no_paren] || statement? ? compiled : "(#{compiled})") + write(o[:no_paren] ? compiled : "(#{compiled})") end end diff --git a/lib/coffee_script/parse_error.rb b/lib/coffee_script/parse_error.rb index 88e1e2d209..b9cc2e430b 100644 --- a/lib/coffee_script/parse_error.rb +++ b/lib/coffee_script/parse_error.rb @@ -9,9 +9,9 @@ def initialize(token_id, value, stack) @token_id, @value, @stack = token_id, value, stack end - def message(source_file=nil) + def message line = @value.respond_to?(:line) ? @value.line : "END" - line_part = source_file ? "#{source_file}:#{line}:" : "line #{line}:" + line_part = "line #{line}:" id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.downcase}" : "" "#{line_part} syntax error for '#{@value.to_s}'#{id_part}" end diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index ef0b02a13d..67bd93f3a4 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -77,4 +77,10 @@ def test_no_wrap assert nodes.compile(:no_wrap => true) == File.read('test/fixtures/generation/each_no_wrap.js') end + def test_no_wrapping_parens_around_statements + assert_raises(SyntaxError) do + @par.parse("(a: 1)").compile + end + end + end From adaae0ccae0d695ace540d648b5714ed39252099 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 22:57:33 -0800 Subject: [PATCH 119/303] major internal reworking -- all variable declarations have been pushed up to the first line of the block scope -- all assignment is now an inherent expression --- lib/coffee_script/grammar.y | 12 ++--- lib/coffee_script/nodes.rb | 51 +++++++++++----------- lib/coffee_script/scope.rb | 21 +++++++-- test/fixtures/generation/each.js | 21 ++++----- test/fixtures/generation/each_no_wrap.js | 21 ++++----- test/fixtures/generation/inner_comments.js | 5 ++- test/unit/test_parser.rb | 6 +-- 7 files changed, 77 insertions(+), 60 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index eecd853052..d3c7b86292 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -119,8 +119,8 @@ rule # Assignment within an object literal. AssignObj: - IDENTIFIER ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) } - | STRING ASSIGN Expression { result = AssignNode.new(val[0], val[2], :object) } + IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) } + | STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) } | Comment { result = val[0] } ; @@ -143,10 +143,10 @@ rule | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } | NOT Expression { result = OpNode.new(val[0], val[1]) } | '~' Expression { result = OpNode.new(val[0], val[1]) } - | '--' Expression { result = OpNode.new(val[0], val[1]) } - | '++' Expression { result = OpNode.new(val[0], val[1]) } - | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) } - | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) } + | '--' Expression { result = OpNode.new(val[0], val[1]) } + | '++' Expression { result = OpNode.new(val[0], val[1]) } + | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) } + | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) } | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) } | Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index ff361d191b..69b9f6e439 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -77,16 +77,18 @@ def last?(node) # If this is the top-level Expressions, wrap everything in a safety closure. def root_compile(o={}) indent = o[:no_wrap] ? '' : TAB - code = compile(o.merge(:indent => indent, :scope => Scope.new)) + code = compile(o.merge(:indent => indent, :scope => Scope.new), o[:no_wrap] ? nil : :code) code.gsub!(STRIP_TRAILING_WHITESPACE, '') o[:no_wrap] ? code : "(function(){\n#{code}\n})();" end # The extra fancy is to handle pushing down returns and assignments # recursively to the final lines of inner statements. - def compile(options={}) + # Variables first defined within the Expressions body have their + # declarations pushed up to the top scope. + def compile(options={}, parent=nil) return root_compile(options) unless options[:scope] - code = @expressions.map { |node| + compiled = @expressions.map do |node| o = super(options) if last?(node) && (o[:return] || o[:assign]) if o[:return] @@ -99,14 +101,17 @@ def compile(options={}) if node.statement? || node.custom_assign? "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" else - "#{o[:indent]}#{AssignNode.new(ValueNode.new(LiteralNode.new(o[:assign])), node).compile(o)};" + "#{o[:indent]}#{AssignNode.new(o[:assign], node).compile(o)};" end end else o.delete(:return) and o.delete(:assign) "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" end - }.join("\n") + end + scope = options[:scope] + declarations = scope.any_declared? && parent == :code ? "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" : '' + code = declarations + compiled.join("\n") write(code) end end @@ -338,10 +343,8 @@ def compile(o={}) # Setting the value of a local variable, or the value of an object property. class AssignNode < Node - LEADING_VAR = /\Avar\s+/ PROTO_ASSIGN = /\A(\S+)\.prototype/ - statement custom_return attr_reader :variable, :value, :context @@ -356,19 +359,16 @@ def line_ending def compile(o={}) o = super(o) - name = @variable.respond_to?(:compile) ? @variable.compile(o) : @variable.to_s - last = @variable.respond_to?(:last) ? @variable.last.to_s : name.to_s + name = @variable.compile(o) + last = @variable.last.to_s proto = name[PROTO_ASSIGN, 1] - o = o.merge(:assign => name, :last_assign => last, :proto_assign => proto) + o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto) postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : '' - return write("#{@variable}: #{@value.compile(o)}") if @context == :object + return write("#{name}: #{@value.compile(o)}") if @context == :object return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign? - defined = o[:scope].find(name) - def_part = defined || @variable.properties? || o[:no_wrap] ? "" : "var #{name};\n#{o[:indent]}" - return write(def_part + @value.compile(o)) if @value.custom_assign? - def_part = defined || o[:no_wrap] ? name : "var #{name}" - val_part = @value.compile(o).sub(LEADING_VAR, '') - write("#{def_part} = #{val_part}#{postfix}") + o[:scope].find(name) unless @variable.properties? + return write(@value.compile(o)) if @value.custom_assign? + write("#{name} = #{@value.compile(o)}#{postfix}") end end @@ -436,8 +436,8 @@ def compile(o={}) o[:indent] += TAB o.delete(:assign) o.delete(:no_wrap) - @params.each {|id| o[:scope].find(id.to_s) } - code = @body.compile(o) + @params.each {|id| o[:scope].parameter(id.to_s) } + code = @body.compile(o, :code) write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end end @@ -533,20 +533,19 @@ def compile(o={}) ivar = scope.free_variable lvar = scope.free_variable rvar = scope.free_variable - name_part = name_found ? @name : "var #{@name}" - index_name = @index ? (index_found ? @index : "var #{@index}") : nil - source_part = "var #{svar} = #{@source.compile(o)};" - for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" - var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n" + index_name = @index ? @index : nil + source_part = "#{svar} = #{@source.compile(o)};" + for_part = "#{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" + var_part = "\n#{o[:indent] + TAB}#{@name} = #{svar}[#{ivar}];\n" index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : '' body = @body suffix = ';' - set_result = "var #{rvar} = [];\n#{o[:indent]}" + set_result = "#{rvar} = [];\n#{o[:indent]}" save_result = "#{rvar}[#{ivar}] = " return_result = rvar if o[:return] || o[:assign] - return_result = "#{o[:assign]} = #{return_result}" if o[:assign] + return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign] return_result = "return #{return_result}" if o[:return] if @filter body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index 8c07785b21..5e510b12ca 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -5,7 +5,7 @@ module CoffeeScript # whether a variable has been seen before or if it needs to be declared. class Scope - attr_reader :parent, :temp_variable + attr_reader :parent, :variables, :temp_variable # Initialize a scope with its parent, for lookups up the chain. def initialize(parent=nil) @@ -18,10 +18,16 @@ def initialize(parent=nil) def find(name, remote=false) found = check(name, remote) return found if found || remote - @variables[name.to_sym] = true + @variables[name.to_sym] = :var found end + # Define a local variable as originating from a parameter in current scope + # -- no var required. + def parameter(name) + @variables[name.to_sym] = :param + end + # Just check to see if a variable has already been declared. def check(name, remote=false) return true if @variables[name.to_sym] @@ -36,10 +42,19 @@ def reset(name) # Find an available, short, name for a compiler-generated variable. def free_variable @temp_variable.succ! while check(@temp_variable) - @variables[@temp_variable.to_sym] = true + @variables[@temp_variable.to_sym] = :var @temp_variable.dup end + def any_declared? + !declared_variables.empty? + end + + # Return the list of variables first declared in current scope. + def declared_variables + @variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort + end + end end \ No newline at end of file diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index 8536f46a47..8787ea0642 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -3,24 +3,25 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function(obj, iterator, context) { - var index = 0; + var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; + index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { - var __a = obj; - var __d = []; - for (var __b=0, __c=__a.length; __b<__c; __b++) { - var item = __a[__b]; - var i = __b; + __a = obj; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + item = __a[__b]; + i = __b; __d[__b] = iterator.call(context, item, i, obj); } __d; } else { - var __e = _.keys(obj); - var __h = []; - for (var __f=0, __g=__e.length; __f<__g; __f++) { - var key = __e[__f]; + __e = _.keys(obj); + __h = []; + for (__f=0, __g=__e.length; __f<__g; __f++) { + key = __e[__f]; __h[__f] = iterator.call(context, obj[key], key, obj); } __h; diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index 5cc80f37f3..aa88e142c2 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -2,24 +2,25 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function(obj, iterator, context) { - var index = 0; + var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; + index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { - var __a = obj; - var __d = []; - for (var __b=0, __c=__a.length; __b<__c; __b++) { - var item = __a[__b]; - var i = __b; + __a = obj; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + item = __a[__b]; + i = __b; __d[__b] = iterator.call(context, item, i, obj); } __d; } else { - var __e = _.keys(obj); - var __h = []; - for (var __f=0, __g=__e.length; __f<__g; __f++) { - var key = __e[__f]; + __e = _.keys(obj); + __h = []; + for (__f=0, __g=__e.length; __f<__g; __f++) { + key = __e[__f]; __h[__f] = iterator.call(context, obj[key], key, obj); } __h; diff --git a/test/fixtures/generation/inner_comments.js b/test/fixtures/generation/inner_comments.js index 1b4386d209..123b8cea46 100644 --- a/test/fixtures/generation/inner_comments.js +++ b/test/fixtures/generation/inner_comments.js @@ -1,12 +1,13 @@ (function(){ - var object = { + var array, object; + object = { a: 1, // Comments between the elements. b: 2, // Like this. c: 3 }; - var array = [1, + array = [1, // Comments between the elements. 2, // Like this. diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 67bd93f3a4..fecb18235c 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -24,8 +24,8 @@ def test_parsing_an_object_literal nodes = @par.parse("{one : 1 \n two : 2}").expressions obj = nodes.first.literal assert obj.is_a? ObjectNode - assert obj.properties.first.variable == "one" - assert obj.properties.last.variable == "two" + assert obj.properties.first.variable.literal.value == "one" + assert obj.properties.last.variable.literal.value == "two" end def test_parsing_an_function_definition @@ -79,7 +79,7 @@ def test_no_wrap def test_no_wrapping_parens_around_statements assert_raises(SyntaxError) do - @par.parse("(a: 1)").compile + @par.parse("(try thing() catch error fail().)").compile end end From 834442148bae7dd6816868d8377bf4445c6b7102 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 25 Dec 2009 23:17:34 -0800 Subject: [PATCH 120/303] docs for assignment-as-expression --- documentation/coffee/embedded.coffee | 1 + .../coffee/expressions_assignment.coffee | 1 + documentation/coffee/strings.coffee | 1 + documentation/css/docs.css | 2 + documentation/index.html.erb | 9 + documentation/js/aliases.js | 2 +- documentation/js/array_comprehensions.js | 21 +- documentation/js/assignment.js | 5 +- documentation/js/conditionals.js | 4 +- documentation/js/embedded.js | 3 +- documentation/js/expressions.js | 5 +- documentation/js/expressions_assignment.js | 4 + documentation/js/functions.js | 5 +- documentation/js/objects_and_arrays.js | 5 +- documentation/js/overview.js | 21 +- documentation/js/scope.js | 10 +- documentation/js/slices.js | 7 +- documentation/js/strings.js | 3 +- documentation/js/super.js | 11 +- index.html | 197 ++++++++++-------- .../execution/test_lexical_scope.coffee | 3 + 21 files changed, 191 insertions(+), 129 deletions(-) create mode 100644 documentation/coffee/expressions_assignment.coffee create mode 100644 documentation/js/expressions_assignment.js create mode 100644 test/fixtures/execution/test_lexical_scope.coffee diff --git a/documentation/coffee/embedded.coffee b/documentation/coffee/embedded.coffee index 400c15b46e..1791018998 100644 --- a/documentation/coffee/embedded.coffee +++ b/documentation/coffee/embedded.coffee @@ -2,3 +2,4 @@ hi: `function() { return [document.title, "Hello JavaScript"].join(": "); }` + diff --git a/documentation/coffee/expressions_assignment.coffee b/documentation/coffee/expressions_assignment.coffee new file mode 100644 index 0000000000..b8ecf4e3a8 --- /dev/null +++ b/documentation/coffee/expressions_assignment.coffee @@ -0,0 +1 @@ +six: (one: 1) + (two: 2) + (three: 3) \ No newline at end of file diff --git a/documentation/coffee/strings.coffee b/documentation/coffee/strings.coffee index 0f41e4fadc..1ec91081b6 100644 --- a/documentation/coffee/strings.coffee +++ b/documentation/coffee/strings.coffee @@ -5,3 +5,4 @@ to interest me on shore, I thought I would sail about a little and see the watery part of the world..." + diff --git a/documentation/css/docs.css b/documentation/css/docs.css index d1b5e4bfe3..1a4361f3a1 100644 --- a/documentation/css/docs.css +++ b/documentation/css/docs.css @@ -48,6 +48,8 @@ code, pre, tt { font-size: 12px; line-height: 18px; color: #191955; + white-space: pre-wrap; + word-wrap: break-word; } tt { background: #f8f8ff; diff --git a/documentation/index.html.erb b/documentation/index.html.erb index c68e78b4bc..6bf5ece6de 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -230,6 +230,10 @@ coffee-script --print app/scripts/*.coffee > concatenation.js mathy things.

    <%= code_for('assignment', 'greeting') %> +

    + Declarations of new variables are pushed up to the top of the current scope, + so that assignments may always be used within expressions. +

    Objects and Arrays @@ -293,7 +297,12 @@ coffee-script --print app/scripts/*.coffee > concatenation.js

    The same mechanism is used to push down assignment through switch statements, and if-elses (although the ternary operator is preferred). + Another part of manipulating assignment statements is + CoffeeScript's declaration of new variables at the top of the + current scope. This allows assignment to be used as a piece of an + expression.

    + <%= code_for('expressions_assignment', 'six') %>

    Aliases diff --git a/documentation/js/aliases.js b/documentation/js/aliases.js index 804d56eadc..c033ee2ef8 100644 --- a/documentation/js/aliases.js +++ b/documentation/js/aliases.js @@ -1,8 +1,8 @@ (function(){ + var volume; if (ignition === true) { launch(); } - var volume; if (band !== spinal_tap) { volume = 10; } diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index 790281c92e..636ccb49db 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -1,20 +1,19 @@ (function(){ - + var __a, __b, __c, __d, __e, __f, __g, __h, food, i, lunch, row; // Eat lunch. - var lunch; - var __a = ['toast', 'cheese', 'wine']; - var __d = []; - for (var __b=0, __c=__a.length; __b<__c; __b++) { - var food = __a[__b]; + __a = ['toast', 'cheese', 'wine']; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + food = __a[__b]; __d[__b] = food.eat(); } lunch = __d; // Zebra-stripe a table. - var __e = table; - var __h = []; - for (var __f=0, __g=__e.length; __f<__g; __f++) { - var row = __e[__f]; - var i = __f; + __e = table; + __h = []; + for (__f=0, __g=__e.length; __f<__g; __f++) { + row = __e[__f]; + i = __f; __h[__f] = i % 2 === 0 ? highlight(row) : null; } __h; diff --git a/documentation/js/assignment.js b/documentation/js/assignment.js index 0a586871dc..69bd8eb2d8 100644 --- a/documentation/js/assignment.js +++ b/documentation/js/assignment.js @@ -1,4 +1,5 @@ (function(){ - var greeting = "Hello CoffeeScript"; - var difficulty = 0.5; + var difficulty, greeting; + greeting = "Hello CoffeeScript"; + difficulty = 0.5; })(); \ No newline at end of file diff --git a/documentation/js/conditionals.js b/documentation/js/conditionals.js index b501b7e73c..b5d12fe392 100644 --- a/documentation/js/conditionals.js +++ b/documentation/js/conditionals.js @@ -1,5 +1,5 @@ (function(){ - var mood; + var date, mood; if (singing) { mood = greatly_improved; } @@ -7,6 +7,6 @@ claps_hands(); cha_cha_cha(); } - var date = friday ? sue : jill; + date = friday ? sue : jill; expensive = expensive || do_the_math(); })(); \ No newline at end of file diff --git a/documentation/js/embedded.js b/documentation/js/embedded.js index 3c16b1856d..d097f2609d 100644 --- a/documentation/js/embedded.js +++ b/documentation/js/embedded.js @@ -1,5 +1,6 @@ (function(){ - var hi = function() { + var hi; + hi = function() { return [document.title, "Hello JavaScript"].join(": "); }; })(); \ No newline at end of file diff --git a/documentation/js/expressions.js b/documentation/js/expressions.js index 9301da9c52..78f9244cba 100644 --- a/documentation/js/expressions.js +++ b/documentation/js/expressions.js @@ -1,5 +1,6 @@ (function(){ - var grade = function(student) { + var eldest, grade; + grade = function(student) { if (student.excellent_work) { return "A+"; } else if (student.okay_stuff) { @@ -8,5 +9,5 @@ return "C"; } }; - var eldest = 24 > 21 ? "Liz" : "Ike"; + eldest = 24 > 21 ? "Liz" : "Ike"; })(); \ No newline at end of file diff --git a/documentation/js/expressions_assignment.js b/documentation/js/expressions_assignment.js new file mode 100644 index 0000000000..ec67227a37 --- /dev/null +++ b/documentation/js/expressions_assignment.js @@ -0,0 +1,4 @@ +(function(){ + var one, six, three, two; + six = (one = 1) + (two = 2) + (three = 3); +})(); \ No newline at end of file diff --git a/documentation/js/functions.js b/documentation/js/functions.js index 2fc0d2f82e..d77c2c2695 100644 --- a/documentation/js/functions.js +++ b/documentation/js/functions.js @@ -1,8 +1,9 @@ (function(){ - var square = function(x) { + var cube, square; + square = function(x) { return x * x; }; - var cube = function(x) { + cube = function(x) { return square(x) * x; }; })(); \ No newline at end of file diff --git a/documentation/js/objects_and_arrays.js b/documentation/js/objects_and_arrays.js index bd3276cc51..cfd1011ed6 100644 --- a/documentation/js/objects_and_arrays.js +++ b/documentation/js/objects_and_arrays.js @@ -1,6 +1,7 @@ (function(){ - var song = ["do", "re", "mi", "fa", "so"]; - var ages = { + var ages, song; + song = ["do", "re", "mi", "fa", "so"]; + ages = { max: 10, ida: 9, tim: 11 diff --git a/documentation/js/overview.js b/documentation/js/overview.js index 0ed094908b..32cc4b3413 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -1,20 +1,20 @@ (function(){ - + var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, square; // Assignment: - var number = 42; - var opposite_day = true; + number = 42; + opposite_day = true; // Conditions: if (opposite_day) { number = -42; } // Functions: - var square = function(x) { + square = function(x) { return x * x; }; // Arrays: - var list = [1, 2, 3, 4, 5]; + list = [1, 2, 3, 4, 5]; // Objects: - var math = { + math = { root: Math.sqrt, square: square, cube: function(x) { @@ -22,11 +22,10 @@ } }; // Array comprehensions: - var cubed_list; - var __a = list; - var __d = []; - for (var __b=0, __c=__a.length; __b<__c; __b++) { - var num = __a[__b]; + __a = list; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + num = __a[__b]; __d[__b] = math.cube(num); } cubed_list = __d; diff --git a/documentation/js/scope.js b/documentation/js/scope.js index 1f110baa82..c5735c0446 100644 --- a/documentation/js/scope.js +++ b/documentation/js/scope.js @@ -1,9 +1,11 @@ (function(){ - var num = 1; - var change_numbers = function() { + var change_numbers, new_num, num; + num = 1; + change_numbers = function() { + var new_num; num = 2; - var new_num = 3; + new_num = 3; return new_num; }; - var new_num = change_numbers(); + new_num = change_numbers(); })(); \ No newline at end of file diff --git a/documentation/js/slices.js b/documentation/js/slices.js index 5f59028339..d819c4aea7 100644 --- a/documentation/js/slices.js +++ b/documentation/js/slices.js @@ -1,5 +1,6 @@ (function(){ - var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - var three_to_six = numbers.slice(3, 6 + 1); - var numbers_copy = numbers.slice(0, numbers.length); + var numbers, numbers_copy, three_to_six; + numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + three_to_six = numbers.slice(3, 6 + 1); + numbers_copy = numbers.slice(0, numbers.length); })(); \ No newline at end of file diff --git a/documentation/js/strings.js b/documentation/js/strings.js index b3407d7f70..0b486548c5 100644 --- a/documentation/js/strings.js +++ b/documentation/js/strings.js @@ -1,5 +1,6 @@ (function(){ - var moby_dick = "Call me Ishmael. Some years ago -- \ + var moby_dick; + moby_dick = "Call me Ishmael. Some years ago -- \ never mind how long precisely -- having little \ or no money in my purse, and nothing particular \ to interest me on shore, I thought I would sail \ diff --git a/documentation/js/super.js b/documentation/js/super.js index cfb99e5c93..5f857d2fa7 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -1,10 +1,11 @@ (function(){ - var Animal = function() { + var Animal, Horse, Snake, sam, tom; + Animal = function() { }; Animal.prototype.move = function(meters) { return alert(this.name + " moved " + meters + "m."); }; - var Snake = function(name) { + Snake = function(name) { this.name = name; return this.name; }; @@ -15,7 +16,7 @@ alert("Slithering..."); return Snake.__superClass__.move.call(this, 5); }; - var Horse = function(name) { + Horse = function(name) { this.name = name; return this.name; }; @@ -26,8 +27,8 @@ alert("Galloping..."); return Horse.__superClass__.move.call(this, 45); }; - var sam = new Snake("Sammy the Python"); - var tom = new Horse("Tommy the Palomino"); + sam = new Snake("Sammy the Python"); + tom = new Horse("Tommy the Palomino"); sam.move(); tom.move(); })(); \ No newline at end of file diff --git a/index.html b/index.html index 437fd44d24..615fd9befd 100644 --- a/index.html +++ b/index.html @@ -86,22 +86,22 @@

    Mini Overview

    # Array comprehensions: cubed_list: math.cube(num) for num in list. -
    +
    var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, square;
     // Assignment:
    -var number = 42;
    -var opposite_day = true;
    +number = 42;
    +opposite_day = true;
     // Conditions:
     if (opposite_day) {
       number = -42;
     }
     // Functions:
    -var square = function(x) {
    +square = function(x) {
       return x * x;
     };
     // Arrays:
    -var list = [1, 2, 3, 4, 5];
    +list = [1, 2, 3, 4, 5];
     // Objects:
    -var math = {
    +math = {
       root: Math.sqrt,
       square: square,
       cube: function(x) {
    @@ -109,30 +109,29 @@ 

    Mini Overview

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

    @@ -315,11 +315,17 @@

    Language Reference

    greeting: "Hello CoffeeScript"
     difficulty: 0.5
    -
    var greeting = "Hello CoffeeScript";
    -var difficulty = 0.5;
    -

    +

    + Declarations of new variables are pushed up to the top of the current scope, + so that assignments may always be used within expressions. +

    Objects and Arrays @@ -334,14 +340,16 @@

    Language Reference

    ida: 9 tim: 11 } -
    var song = ["do", "re", "mi", "fa", "so"];
    -var ages = {
    +
    var ages, song;
    +song = ["do", "re", "mi", "fa", "so"];
    +ages = {
       max: 10,
       ida: 9,
       tim: 11
     };
    -

    Notice how the variables are declared with var the first time @@ -408,7 +420,7 @@

    Language Reference

    date: if friday then sue else jill. expensive ||= do_the_math() -
    var mood;
    +
    var date, mood;
     if (singing) {
       mood = greatly_improved;
     }
    @@ -416,7 +428,7 @@ 

    Language Reference

    claps_hands(); cha_cha_cha(); } -var date = friday ? sue : jill; +date = friday ? sue : jill; expensive = expensive || do_the_math();

    @@ -444,7 +456,8 @@

    Language Reference

    "C".. eldest: if 24 > 21 then "Liz" else "Ike". -
    var grade = function(student) {
    +
    var eldest, grade;
    +grade = function(student) {
       if (student.excellent_work) {
         return "A+";
       } else if (student.okay_stuff) {
    @@ -453,8 +466,9 @@ 

    Language Reference

    return "C"; } }; -var eldest = 24 > 21 ? "Liz" : "Ike"; -

    The same mechanism is used to push down assignment through switch statements, and if-elses (although the ternary operator is preferred). -

    + Another part of manipulating assignment statements is + CoffeeScript's declaration of new variables at the top of the + current scope. This allows assignment to be used as a piece of an + expression. +

    +
    six: (one: 1) + (two: 2) + (three: 3)
    +
    var one, six, three, two;
    +six = (one = 1) + (two = 2) + (three = 3);
    +

    Aliases @@ -505,10 +529,10 @@

    Language Reference

    let_the_wild_rumpus_begin() unless answer is no if car.speed < speed_limit then accelerate(). -
    if (ignition === true) {
    +
    var volume;
    +if (ignition === true) {
       launch();
     }
    -var volume;
     if (band !== spinal_tap) {
       volume = 10;
     }
    @@ -556,22 +580,21 @@ 

    Language Reference

    # Zebra-stripe a table. highlight(row) for row, i in table if i % 2 is 0. -
    +
    var __a, __b, __c, __d, __e, __f, __g, __h, food, i, lunch, row;
     // Eat lunch.
    -var lunch;
    -var __a = ['toast', 'cheese', 'wine'];
    -var __d = [];
    -for (var __b=0, __c=__a.length; __b<__c; __b++) {
    -  var food = __a[__b];
    +__a = ['toast', 'cheese', 'wine'];
    +__d = [];
    +for (__b=0, __c=__a.length; __b<__c; __b++) {
    +  food = __a[__b];
       __d[__b] = food.eat();
     }
     lunch = __d;
     // Zebra-stripe a table.
    -var __e = table;
    -var __h = [];
    -for (var __f=0, __g=__e.length; __f<__g; __f++) {
    -  var row = __e[__f];
    -  var i = __f;
    +__e = table;
    +__h = [];
    +for (__f=0, __g=__e.length; __f<__g; __f++) {
    +  row = __e[__f];
    +  i = __f;
       __h[__f] = i % 2 === 0 ? highlight(row) : null;
     }
     __h;
    @@ -592,12 +615,14 @@ 

    Language Reference

    numbers_copy: numbers[0...numbers.length] -
    var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    -var three_to_six = numbers.slice(3, 6 + 1);
    -var numbers_copy = numbers.slice(0, numbers.length);
    -

    @@ -642,12 +667,13 @@

    Language Reference

    -
    var Animal = function() {
    +
    var Animal, Horse, Snake, sam, tom;
    +Animal = function() {
     };
     Animal.prototype.move = function(meters) {
       return alert(this.name + " moved " + meters + "m.");
     };
    -var Snake = function(name) {
    +Snake = function(name) {
       this.name = name;
       return this.name;
     };
    @@ -658,7 +684,7 @@ 

    Language Reference

    alert("Slithering..."); return Snake.__superClass__.move.call(this, 5); }; -var Horse = function(name) { +Horse = function(name) { this.name = name; return this.name; }; @@ -669,16 +695,17 @@

    Language Reference

    alert("Galloping..."); return Horse.__superClass__.move.call(this, 45); }; -var sam = new Snake("Sammy the Python"); -var tom = new Horse("Tommy the Palomino"); +sam = new Snake("Sammy the Python"); +tom = new Horse("Tommy the Palomino"); sam.move(); tom.move(); -

    @@ -715,10 +742,13 @@

    Language Reference

    return [document.title, "Hello JavaScript"].join(": "); }` -
    var hi = function() {
    +
    +
    var hi;
    +hi = function() {
     return [document.title, "Hello JavaScript"].join(": ");
     };
    -

    @@ -791,13 +821,16 @@

    Language Reference

    about a little and see the watery part of the world..." -
    var moby_dick = "Call me Ishmael. Some years ago -- \
    +
    +
    var moby_dick;
    +moby_dick = "Call me Ishmael. Some years ago -- \
     never mind how long precisely -- having little \
     or no money in my purse, and nothing particular \
     to interest me on shore, I thought I would sail \
     about a little and see the watery part of the \
     world...";
    -
    @@ -187,10 +187,10 @@ gem install coffee-script

    -coffee-script path/to/script.coffee
    -coffee-script --interactive
    -coffee-script --watch --lint experimental.coffee
    -coffee-script --print app/scripts/*.coffee > concatenation.js
    +coffee path/to/script.coffee +coffee --interactive +coffee --watch --lint experimental.coffee +coffee --print app/scripts/*.coffee > concatenation.js

    Language Reference

    @@ -470,7 +470,7 @@ coffee-script --print app/scripts/*.coffee > concatenation.js

    0.1.3 - The coffee-script command now includes --interactive, + The coffee command now includes --interactive, which launches an interactive CoffeeScript session, and --run, which directly compiles and executes a script. Both options depend on a working installation of Narwhal. diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index 636ccb49db..8d13577e41 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, __h, food, i, lunch, row; + var __a, __b, __c, __d, __e, __f, __g, __h, __i, __j, __k, __l, food, i, lunch, row; // Eat lunch. __a = ['toast', 'cheese', 'wine']; __d = []; @@ -17,4 +17,10 @@ __h[__f] = i % 2 === 0 ? highlight(row) : null; } __h; + // Check the first one hundred combinations. + __k = []; + for (__l=0, i=1, __j=100; i<=__j; i++, __l++) { + __k[__l] = lockpick(combinations[i]); + } + __k; })(); \ No newline at end of file diff --git a/index.html b/index.html index 615fd9befd..dea5b729ee 100644 --- a/index.html +++ b/index.html @@ -159,12 +159,12 @@

    Installation and Usage

    gem install coffee-script

    - Installing the gem provides the coffee-script command, which can + Installing the gem provides the coffee command, which can be used to compile CoffeeScript .coffee files into JavaScript, as well as debug them. In conjunction with - Narwhal, the coffee-script + Narwhal, the coffee command also provides direct evaluation and an interactive REPL. - When compiling to JavaScript, coffee-script writes the output + When compiling to JavaScript, coffee writes the output as .js files in the same directory by default, but output can be customized with the following options:

    @@ -216,7 +216,7 @@

    Installation and Usage

    @@ -255,10 +255,10 @@

    Installation and Usage

    -coffee-script path/to/script.coffee
    -coffee-script --interactive
    -coffee-script --watch --lint experimental.coffee
    -coffee-script --print app/scripts/*.coffee > concatenation.js
    +coffee path/to/script.coffee +coffee --interactive +coffee --watch --lint experimental.coffee +coffee --print app/scripts/*.coffee > concatenation.js

    Language Reference

    @@ -580,7 +580,10 @@

    Language Reference

    # Zebra-stripe a table. highlight(row) for row, i in table if i %2is0. -
    var __a, __b, __c, __d, __e, __f, __g, __h, food, i, lunch, row;
    +
    +# Check the first one hundred combinations.
    +lockpick(combinations[i]) for i in [1..100].
    +
    var __a, __b, __c, __d, __e, __f, __g, __h, __i, __j, __k, __l, food, i, lunch, row;
     // Eat lunch.
     __a = ['toast', 'cheese', 'wine'];
     __d = [];
    @@ -598,6 +601,12 @@ 

    Language Reference

    __h[__f] = i % 2 === 0 ? highlight(row) : null; } __h; +// Check the first one hundred combinations. +__k = []; +for (__l=0, i=1, __j=100; i<=__j; i++, __l++) { + __k[__l] = lockpick(combinations[i]); +} +__k;

    @@ -889,7 +898,7 @@

    Change Log

    0.1.3 - The coffee-script command now includes --interactive, + The coffee command now includes --interactive, which launches an interactive CoffeeScript session, and --run, which directly compiles and executes a script. Both options depend on a working installation of Narwhal. diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index f09f400d6e..afa5fdabcd 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -5,15 +5,15 @@ module CoffeeScript - # The CommandLine handles all of the functionality of the `coffee-script` + # The CommandLine handles all of the functionality of the `coffee` # utility. class CommandLine BANNER = <<-EOS -coffee-script compiles CoffeeScript source files into JavaScript. +coffee compiles CoffeeScript source files into JavaScript. Usage: - coffee-script path/to/script.coffee + coffee path/to/script.coffee EOS # Seconds to pause between checks for changed source files. @@ -190,8 +190,8 @@ def parse_options install_bundle exit end - opts.on_tail('--version', 'display coffee-script version') do - puts "coffee-script version #{CoffeeScript::VERSION}" + opts.on_tail('--version', 'display CoffeeScript version') do + puts "CoffeeScript version #{CoffeeScript::VERSION}" exit end opts.on_tail('-h', '--help', 'display this help message') do diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 42bcc5ca31..5271b29d3f 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -1,6 +1,6 @@ # This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee -# Executes the `coffee-script` Ruby program to convert from CoffeeScript +# Executes the `coffee` Ruby program to convert from CoffeeScript # to Javascript. Eventually this will hopefully happen entirely within JS. # Require external dependencies. @@ -9,13 +9,13 @@ File: require('file') Readline: require('readline') # The path to the CoffeeScript Compiler. -coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee-script') +coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee') # Our general-purpose error handler. checkForErrors: coffeeProcess => return true if coffeeProcess.wait() is 0 system.stderr.print(coffeeProcess.stderr.read()) - throw new Error("coffee-script compile error"). + throw new Error("CoffeeScript compile error"). # Run a simple REPL, round-tripping to the CoffeeScript compiler for every # command. diff --git a/lib/coffee_script/narwhal/js/coffee-script.js b/lib/coffee_script/narwhal/js/coffee-script.js index d24c3812f0..4be0e13dec 100644 --- a/lib/coffee_script/narwhal/js/coffee-script.js +++ b/lib/coffee_script/narwhal/js/coffee-script.js @@ -1,19 +1,19 @@ (function(){ var File, OS, Readline, checkForErrors, coffeePath; - // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee Executes the `coffee-script` Ruby program to convert from CoffeeScript + // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee Executes the `coffee` Ruby program to convert from CoffeeScript // to Javascript. Eventually this will hopefully happen entirely within JS. Require external dependencies. OS = require('os'); File = require('file'); Readline = require('readline'); // The path to the CoffeeScript Compiler. - coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee-script'); + coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee'); // Our general-purpose error handler. checkForErrors = function(coffeeProcess) { if (coffeeProcess.wait() === 0) { return true; } system.stderr.print(coffeeProcess.stderr.read()); - throw new Error("coffee-script compile error"); + throw new Error("CoffeeScript compile error"); }; // Run a simple REPL, round-tripping to the CoffeeScript compiler for every // command. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7b73e80f62..f0f63dc2fe 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -320,6 +320,13 @@ def exclusive? @exclusive end + # TODO -- figure out if we can detect if a range runs negative. + def downward? + + end + + # TODO -- figure out if we can expand ranges into the corresponding array, + # as an expression, reliably. def compile(o={}) write() end diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index f5335e5796..0737fdd3ae 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -7,21 +7,21 @@ class ExecutionTest < Test::Unit::TestCase def test_execution_of_coffeescript sources = ['test/fixtures/execution/*.coffee'].join(' ') - assert `bin/coffee-script -r #{sources}`.match(ALLS_WELL) + assert `bin/coffee -r #{sources}`.match(ALLS_WELL) end def test_lintless_coffeescript - lint_results = `bin/coffee-script -l test/fixtures/execution/*.coffee` + lint_results = `bin/coffee -l test/fixtures/execution/*.coffee` assert lint_results.match(NO_WARNINGS) end def test_lintless_examples - lint_results = `bin/coffee-script -l examples/*.coffee` + lint_results = `bin/coffee -l examples/*.coffee` assert lint_results.match(NO_WARNINGS) end def test_lintless_documentation - lint_results = `bin/coffee-script -l documentation/coffee/*.coffee` + lint_results = `bin/coffee -l documentation/coffee/*.coffee` assert lint_results.match(NO_WARNINGS) end From 2a1fc4b1b7dc146ed2b14e864d7bbf009ff8db0d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 09:05:57 -0800 Subject: [PATCH 125/303] docs --- documentation/index.html.erb | 13 +++++++++++-- index.html | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index f6961e9cec..b7fad1dda7 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -70,10 +70,11 @@ Switch/When/Else
    Try/Catch/Finally
    Multiline Strings
    + Resources
    Contributing
    Change Log

    - +

    Mini Overview

    CoffeeScript on the left, compiled JavaScript output on the right.

    @@ -418,6 +419,13 @@ coffee --print app/scripts/*.coffee > concatenation.js Multiline strings are allowed in CoffeeScript.

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

    Resources

    + +

    + Source Code
    + Bugs and Feature Requests
    +

    Contributing

    @@ -433,7 +441,8 @@ coffee --print app/scripts/*.coffee > concatenation.js
  • Ideas for alternate syntax to end blocks of expressions — the periods - can look a little weird with deeply nested structure. + can look a little weird with deeply nested structure. (There's now a + 'whitespace' branch — help add significant whitespace over there.)
  • Test cases for any syntax errors you encounter that you think CoffeeScript diff --git a/index.html b/index.html index dea5b729ee..218452c1ea 100644 --- a/index.html +++ b/index.html @@ -56,10 +56,11 @@

    Table of Contents

    Switch/When/Else
    Try/Catch/Finally
    Multiline Strings
    + Resources
    Contributing
    Change Log

    - +

    Mini Overview

    CoffeeScript on the left, compiled JavaScript output on the right.

    @@ -846,6 +847,13 @@

    Language Reference

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

    Resources

    + +

    + Source Code
    + Bugs and Feature Requests
    +

    Contributing

    @@ -861,7 +869,8 @@

    Contributing

  • Ideas for alternate syntax to end blocks of expressions — the periods - can look a little weird with deeply nested structure. + can look a little weird with deeply nested structure. (There's now a + 'whitespace' branch — help add significant whitespace over there.)
  • Test cases for any syntax errors you encounter that you think CoffeeScript From 08dddb27a0d3895943503ee1cc4c806b037e6f87 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 09:29:03 -0800 Subject: [PATCH 126/303] adding newline escaping, with tests --- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/lexer.rb | 4 +++- test/fixtures/execution/test_newline_escaping.coffee | 6 ++++++ test/unit/test_lexer.rb | 10 ++++++++-- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/execution/test_newline_escaping.coffee diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 927e017d82..990a236823 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -234,7 +234,7 @@ match - \b(debugger)\b + \b(debugger|\\)\b name keyword.other.coffee diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index fcf2161825..ed959f8478 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -130,11 +130,13 @@ def whitespace_token # We treat all other single characters as a token. Eg.: ( ) , . ! # Multi-character operators are also literal tokens, so that Racc can assign # the proper order of operations. Multiple newlines get merged together. + # Use a trailing \ to escape newlines. def literal_token value = @chunk[NEWLINE, 1] if value @line += value.length - token("\n", "\n") unless last_value == "\n" + token("\n", "\n") unless ["\n", "\\"].include?(last_value) + @tokens.pop if last_value == "\\" return @i += value.length end value = @chunk[OPERATOR, 1] diff --git a/test/fixtures/execution/test_newline_escaping.coffee b/test/fixtures/execution/test_newline_escaping.coffee new file mode 100644 index 0000000000..2f52e9327a --- /dev/null +++ b/test/fixtures/execution/test_newline_escaping.coffee @@ -0,0 +1,6 @@ +six: \ + 1 + \ + 2 + \ + 3 + +print(six is 6) \ No newline at end of file diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index 5f8d52d214..0d2ec1b598 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -38,8 +38,14 @@ def test_lexing_if_statement def test_lexing_comment code = "a: 1\n # comment\n # on two lines\nb: 2" assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"], [:NUMBER, "1"], - ["\n", "\n"], [:COMMENT, [" comment", " on two lines"]], ["\n", "\n"], - [:IDENTIFIER, "b"], [:ASSIGN, ":"], [:NUMBER, "2"]] + ["\n", "\n"], [:COMMENT, [" comment", " on two lines"]], ["\n", "\n"], + [:IDENTIFIER, "b"], [:ASSIGN, ":"], [:NUMBER, "2"]] + end + + def test_lexing_newline_escaper + code = "two: 1 + \\\n\n 1" + assert @lex.tokenize(code) == [[:IDENTIFIER, "two"], [:ASSIGN, ":"], + [:NUMBER, "1"], ["+", "+"], [:NUMBER, "1"]] end def test_lexing From 556f8cb68a968c71d2e6499facd65b6ceaf6c4b7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 10:49:11 -0800 Subject: [PATCH 127/303] little more progress on whitespace --- .../Syntaxes/CoffeeScript.tmLanguage | 4 +-- lib/coffee_script/grammar.y | 6 ++--- lib/coffee_script/lexer.rb | 25 ++++++++++--------- test/fixtures/generation/whitespace.coffee | 6 +++++ 4 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/generation/whitespace.coffee diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 990a236823..df92adbfa3 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -39,7 +39,7 @@ comment match stuff like: funcName: => … match - ([a-zA-Z_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) + ([a-zA-Z0-9_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) name meta.function.coffee @@ -60,7 +60,7 @@ comment match stuff like: a => … match - ([a-zA-Z_?., $]*)\s*(=>) + ([a-zA-Z0-9_?., $]*)\s*(=>) name meta.inline.function.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 6fd9df1258..b8089cdb6c 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -33,7 +33,7 @@ prechigh right THROW FOR IN WHILE NEW SUPER left UNLESS IF ELSE EXTENDS left ASSIGN '||=' '&&=' - right RETURN + right RETURN INDENT left OUTDENT preclow @@ -88,8 +88,8 @@ rule ; Block: - Expression { result = Expressions.new(val) } - | INDENT Expressions Outdent { result = val[1] } + # Expression { result = Expressions.new(val) } + INDENT Expressions Outdent { result = val[1] } ; Outdent: diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index b0fb531d40..abd45592c4 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -131,17 +131,16 @@ def indent_token size = indent.size return literal_token if size == @indent if size > @indent - tag = :INDENT + token(:INDENT, size - @indent) @indent = size @indents << @indent else - tag = :OUTDENT + token(:OUTDENT, @indent - size) @indents.pop while @indents.last && ((@indents.last || 0) > size) @indent = @indents.last || 0 end @line += 1 @i += (size + 1) - token(tag, size) end # Matches and consumes non-meaningful whitespace. @@ -150,18 +149,20 @@ def whitespace_token @i += whitespace.length end + # Multiple newlines get merged together. + # Use a trailing \ to escape newlines. + def newline_token(newlines) + return false unless newlines = @chunk[NEWLINE, 1] + @line += newlines.length + token("\n", "\n") unless ["\n", "\\"].include?(last_value) + @tokens.pop if last_value == "\\" + @i += newlines.length + end + # We treat all other single characters as a token. Eg.: ( ) , . ! # Multi-character operators are also literal tokens, so that Racc can assign - # the proper order of operations. Multiple newlines get merged together. - # Use a trailing \ to escape newlines. + # the proper order of operations. def literal_token - value = @chunk[NEWLINE, 1] - if value - @line += value.length - token("\n", "\n") unless ["\n", "\\"].include?(last_value) - @tokens.pop if last_value == "\\" - return @i += value.length - end value = @chunk[OPERATOR, 1] tag_parameters if value && value.match(CODE) value ||= @chunk[0,1] diff --git a/test/fixtures/generation/whitespace.coffee b/test/fixtures/generation/whitespace.coffee new file mode 100644 index 0000000000..7a5bfaf793 --- /dev/null +++ b/test/fixtures/generation/whitespace.coffee @@ -0,0 +1,6 @@ +# test +f1: x => + x * x + f2: y => + y * x + From 191875a85b70f3a3833267d8ad55abbbeb1aeea7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 10:59:47 -0800 Subject: [PATCH 128/303] got a lexer working along the lines of what kamatsu proposes --- lib/coffee_script/lexer.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index abd45592c4..3890d8a288 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -132,17 +132,24 @@ def indent_token return literal_token if size == @indent if size > @indent token(:INDENT, size - @indent) + @indents << size - @indent @indent = size - @indents << @indent else - token(:OUTDENT, @indent - size) - @indents.pop while @indents.last && ((@indents.last || 0) > size) - @indent = @indents.last || 0 + outdent_token(@indent - size) end @line += 1 @i += (size + 1) end + def outdent_token(move_out) + while move_out > 0 + last_indent = @indents.pop + token(:OUTDENT, last_indent) + move_out -= last_indent + end + @indent = @indents.last || 0 + end + # Matches and consumes non-meaningful whitespace. def whitespace_token return false unless whitespace = @chunk[WHITESPACE, 1] @@ -214,9 +221,7 @@ def remove_leading_newlines # Close up all remaining open blocks. def close_indentation - while indent = @indents.pop - token(:OUTDENT, @indents.first || 0) - end + outdent_token(@indent) end end From fde985209014dcf9a5ada323f7d6bceb6d89fd50 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 11:10:59 -0800 Subject: [PATCH 129/303] removing no_paren -- can cause order of operations errors --- lib/coffee_script/nodes.rb | 6 +++--- test/fixtures/generation/whitespace.coffee | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index f0f63dc2fe..04064641fa 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -207,7 +207,7 @@ def super? def compile(o={}) o = super(o) - args = @arguments.map{|a| a.compile(o.merge(:no_paren => true)) }.join(', ') + args = @arguments.map{|a| a.compile(o) }.join(', ') return write(compile_super(args, o)) if super? prefix = @new ? "new " : '' write("#{prefix}#{@variable.compile(o)}(#{args})") @@ -511,7 +511,7 @@ def compile(o={}) o = super(o) o.delete(:return) indent = o[:indent] + TAB - cond = @condition.compile(o.merge(:no_paren => true)) + cond = @condition.compile(o) write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}") end end @@ -656,7 +656,7 @@ def compile(o={}) o = super(o) compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' - write(o[:no_paren] ? compiled : "(#{compiled})") + write("(#{compiled})") end end diff --git a/test/fixtures/generation/whitespace.coffee b/test/fixtures/generation/whitespace.coffee index 7a5bfaf793..99874119c3 100644 --- a/test/fixtures/generation/whitespace.coffee +++ b/test/fixtures/generation/whitespace.coffee @@ -4,3 +4,6 @@ f1: x => f2: y => y * x +elements.each(el => + el.click(event => + el.show() if event.active)) \ No newline at end of file From 694833dbd05ad17a6678a714fb71d94fe7559fe0 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 11:55:34 -0800 Subject: [PATCH 130/303] removing no_paren -- it was optimizing away order of operations --- 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 f0f63dc2fe..04064641fa 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -207,7 +207,7 @@ def super? def compile(o={}) o = super(o) - args = @arguments.map{|a| a.compile(o.merge(:no_paren => true)) }.join(', ') + args = @arguments.map{|a| a.compile(o) }.join(', ') return write(compile_super(args, o)) if super? prefix = @new ? "new " : '' write("#{prefix}#{@variable.compile(o)}(#{args})") @@ -511,7 +511,7 @@ def compile(o={}) o = super(o) o.delete(:return) indent = o[:indent] + TAB - cond = @condition.compile(o.merge(:no_paren => true)) + cond = @condition.compile(o) write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}") end end @@ -656,7 +656,7 @@ def compile(o={}) o = super(o) compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' - write(o[:no_paren] ? compiled : "(#{compiled})") + write("(#{compiled})") end end From cc0c92d20d09e76d46b07d1860212ce71c32bf78 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 13:22:53 -0800 Subject: [PATCH 131/303] smarter but uglier lexer -- now handles most significant whitespace cases. Clean it up though... (newlines after outdents) --- lib/coffee_script/lexer.rb | 48 ++++++++++++++++++++-- test/fixtures/generation/whitespace.coffee | 15 +++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 3890d8a288..f55d3ec8cb 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -24,11 +24,11 @@ class Lexer JS = /\A(``|`(.*?)[^\\]`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t\r]+)/ - NEWLINE = /\A(\n+)/ + NEWLINE = /\A(\n+)(?![ \t\r]+)/ COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - INDENT = /\A\n( *)/ + INDENT = /\A\n([ \t\r]*)/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ @@ -56,6 +56,9 @@ def tokenize(code) @chunk = @code[@i..-1] extract_next_token end + close_indentation + remove_empty_outdents + rewrite_closing_parens @tokens end @@ -129,7 +132,7 @@ def comment_token def indent_token return false unless indent = @chunk[INDENT, 1] size = indent.size - return literal_token if size == @indent + return newline_token(indent) if size == @indent if size > @indent token(:INDENT, size - @indent) @indents << size - @indent @@ -224,6 +227,45 @@ def close_indentation outdent_token(@indent) end + # Rewrite the token stream, looking one token ahead and behind. + def rewrite_tokens + i = 0 + while i < @tokens.length - 1 + yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i) + i += 1 + end + end + + # You should be able to put blank lines within indented expressions. + # To that end, remove redundant outdent/indents from the token stream. + def remove_empty_outdents + rewrite_tokens do |prev, token, post, i| + match = (prev[0] == :OUTDENT && token[1] == "\n" && post[0] == :INDENT) + match = match && prev[1] == post[1] + next unless match + @tokens.delete_at(i + 1) + @tokens.delete_at(i - 1) + end + end + + # We'd like to support syntax like this: + # el.click(event => + # el.hide()) + # In order to accomplish this, move outdents that follow closing parens + # inwards, safely. + def rewrite_closing_parens + rewrite_tokens do |prev, token, post, i| + next(i += 1) unless token[1] == ')' + before_outdent = post && post[0] == :OUTDENT + after_outdent = prev && prev[0] == :OUTDENT + if before_outdent && !after_outdent + insert_index = i + insert_index -= 1 while @tokens[insert_index][1] == ')' && (@tokens[insert_index - 1][0] != :OUTDENT) + @tokens.insert(insert_index + 1, @tokens.delete_at(i + 1)) + end + end + end + end end \ No newline at end of file diff --git a/test/fixtures/generation/whitespace.coffee b/test/fixtures/generation/whitespace.coffee index 99874119c3..1a16ad3cd5 100644 --- a/test/fixtures/generation/whitespace.coffee +++ b/test/fixtures/generation/whitespace.coffee @@ -4,6 +4,21 @@ f1: x => f2: y => y * x + + + + f3: 3 + +# Parens can close on the proper level. +elements.each(el => + el.click(event => + el.reset() + el.show() if event.active + ) +) + +# Or, parens can close blocks early. elements.each(el => el.click(event => + el.reset() el.show() if event.active)) \ No newline at end of file From b4250c0f021f8fc5749f2f80e69dc7d0cbd1c9ab Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 19:29:59 -0800 Subject: [PATCH 132/303] that's it for now for significant whitespace -- I really just can't make flexible enough --- lib/coffee_script/grammar.y | 23 ++++++++-------------- lib/coffee_script/lexer.rb | 1 + test/fixtures/generation/whitespace.coffee | 4 ---- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index b8089cdb6c..8fbda92269 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -88,13 +88,8 @@ rule ; Block: - # Expression { result = Expressions.new(val) } - INDENT Expressions Outdent { result = val[1] } - ; - - Outdent: - /* nothing */ - | OUTDENT + Then Expression { result = Expressions.new([val[1]]) } + | INDENT Expressions OUTDENT { result = val[1] } ; # All tokens that can terminate an expression. @@ -302,8 +297,8 @@ rule # Try/catch/finally exception handling blocks. Try: - TRY Expressions Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) } - | TRY Expressions Catch + TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) } + | TRY Block Catch FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } ; @@ -320,12 +315,12 @@ rule # Parenthetical expressions. Parenthetical: - "(" Expressions ")" { result = ParentheticalNode.new(val[1]) } + "(" Expression ")" { result = ParentheticalNode.new(val[1]) } ; # The while loop. (there is no do..while). While: - WHILE Expression Then Block { result = WhileNode.new(val[1], val[3]) } + WHILE Expression Block { result = WhileNode.new(val[1], val[3]) } ; # Array comprehensions, including guard and current index. @@ -372,8 +367,7 @@ rule # An elsif portion of an if-else block. ElsIf: - ELSE IF Expression - Then Expressions { result = IfNode.new(val[2], val[4]) } + ELSE IF Expression Block { result = IfNode.new(val[2], val[4]) } ; # Multiple elsifs can be chained together. @@ -396,8 +390,7 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: - IF Expression - Then Expressions IfEnd { result = IfNode.new(val[1], val[3], val[4]) } + IF Expression Block IfEnd { result = IfNode.new(val[1], val[3], val[4]) } | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index f55d3ec8cb..b18109de87 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -150,6 +150,7 @@ def outdent_token(move_out) token(:OUTDENT, last_indent) move_out -= last_indent end + token("\n", "\n") @indent = @indents.last || 0 end diff --git a/test/fixtures/generation/whitespace.coffee b/test/fixtures/generation/whitespace.coffee index 1a16ad3cd5..450fd9e5b0 100644 --- a/test/fixtures/generation/whitespace.coffee +++ b/test/fixtures/generation/whitespace.coffee @@ -3,10 +3,6 @@ f1: x => x * x f2: y => y * x - - - - f3: 3 # Parens can close on the proper level. From eeef8d3612dbecb76ac1cae174f3aca317f426ea Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 20:35:43 -0800 Subject: [PATCH 133/303] got negative ranges working with (much, much) uglier compiled code --- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 24 ++++++++++--------- .../execution/test_range_comprehension.coffee | 7 ++++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index ed959f8478..2e6d95ec9b 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -19,7 +19,7 @@ class Lexer # Token matching regexes. 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 + 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 OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 04064641fa..46c2ad745b 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -307,8 +307,6 @@ def compile(o={}) # A range literal. Ranges can be used to extract portions (slices) of arrays, # or to specify a range for array comprehensions. - # RangeNodes get expanded into the equivalent array, if not used to index - # a slice or define an array comprehension. class RangeNode attr_reader :from, :to @@ -320,15 +318,20 @@ def exclusive? @exclusive end - # TODO -- figure out if we can detect if a range runs negative. - def downward? + def less_operator + @exclusive ? '<' : '<=' + end + def greater_operator + @exclusive ? '>' : '>=' end - # TODO -- figure out if we can expand ranges into the corresponding array, - # as an expression, reliably. - def compile(o={}) - write() + def compile(o, fv, tv) + fvv, tvv = @from.compile(o), @to.compile(o) + vars = "#{fv}=#{fvv}, #{tv}=#{tvv}" + compare = "(#{fvv} <= #{tvv} ? #{fv} #{less_operator} #{tv} : #{fv} #{greater_operator} #{tv})" + incr = "(#{fvv} <= #{tvv} ? #{fv} += 1 : #{fv} -= 1)" + "#{vars}; #{compare}; #{incr}" end end @@ -548,11 +551,10 @@ def compile(o={}) index_name = @index ? @index : nil if range source_part = '' - operator = @source.exclusive? ? '<' : '<=' - index_var = scope.free_variable - for_part = "#{index_var}=0, #{ivar}=#{@source.from.compile(o)}, #{lvar}=#{@source.to.compile(o)}; #{ivar}#{operator}#{lvar}; #{ivar}++, #{index_var}++" var_part = '' index_part = '' + index_var = scope.free_variable + for_part = "#{index_var}=0, #{@source.compile(o, ivar, lvar)}, #{index_var}++" else index_var = nil source_part = "#{svar} = #{@source.compile(o)};\n#{o[:indent]}" diff --git a/test/fixtures/execution/test_range_comprehension.coffee b/test/fixtures/execution/test_range_comprehension.coffee index 66ddefb2ba..8ae0b3d803 100644 --- a/test/fixtures/execution/test_range_comprehension.coffee +++ b/test/fixtures/execution/test_range_comprehension.coffee @@ -1,5 +1,8 @@ nums: i * 3 for i in [1..3]. -result: nums.join(', ') +negs: x for x in [-20..-10]. +negs: negs[0..2] -print(result is '3, 6, 9') \ No newline at end of file +result: nums.concat(negs).join(', ') + +print(result is '3, 6, 9, -20, -19, -18') \ No newline at end of file From 983dac84a23cc4a5324c9ad7b10454ec1e75daa7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 20:46:31 -0800 Subject: [PATCH 134/303] docs for range comprehensiosn --- documentation/coffee/array_comprehensions.coffee | 5 +---- documentation/index.html.erb | 5 +++++ documentation/js/array_comprehensions.js | 8 +------- index.html | 16 ++++++---------- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/documentation/coffee/array_comprehensions.coffee b/documentation/coffee/array_comprehensions.coffee index fb4934bfbe..ddda9319e8 100644 --- a/documentation/coffee/array_comprehensions.coffee +++ b/documentation/coffee/array_comprehensions.coffee @@ -2,7 +2,4 @@ lunch: food.eat() for food in ['toast', 'cheese', 'wine']. # Zebra-stripe a table. -highlight(row) for row, i in table if i % 2 is 0. - -# Check the first one hundred combinations. -lockpick(combinations[i]) for i in [1..100]. \ No newline at end of file +highlight(row) for row, i in table if i % 2 is 0. \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index b7fad1dda7..4524331f24 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -357,6 +357,11 @@ coffee --print app/scripts/*.coffee > concatenation.js would use a loop, each/forEach, map, or select/filter.

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

    + If you're not iterating over an actual array, you can use a range to + specify the start and end of an array comprehension: + coundown(i) for i in [10..1]. +

    Slicing Arrays with Ranges diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index 8d13577e41..636ccb49db 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, __h, __i, __j, __k, __l, food, i, lunch, row; + var __a, __b, __c, __d, __e, __f, __g, __h, food, i, lunch, row; // Eat lunch. __a = ['toast', 'cheese', 'wine']; __d = []; @@ -17,10 +17,4 @@ __h[__f] = i % 2 === 0 ? highlight(row) : null; } __h; - // Check the first one hundred combinations. - __k = []; - for (__l=0, i=1, __j=100; i<=__j; i++, __l++) { - __k[__l] = lockpick(combinations[i]); - } - __k; })(); \ No newline at end of file diff --git a/index.html b/index.html index 218452c1ea..b9ac15f4df 100644 --- a/index.html +++ b/index.html @@ -581,10 +581,7 @@

    Language Reference

    # Zebra-stripe a table. highlight(row) for row, i in table if i % 2 is 0. - -# Check the first one hundred combinations. -lockpick(combinations[i]) for i in [1..100]. -
    var __a, __b, __c, __d, __e, __f, __g, __h, __i, __j, __k, __l, food, i, lunch, row;
    +
    var __a, __b, __c, __d, __e, __f, __g, __h, food, i, lunch, row;
     // Eat lunch.
     __a = ['toast', 'cheese', 'wine'];
     __d = [];
    @@ -602,13 +599,12 @@ 

    Language Reference

    __h[__f] = i % 2 === 0 ? highlight(row) : null; } __h; -// Check the first one hundred combinations. -__k = []; -for (__l=0, i=1, __j=100; i<=__j; i++, __l++) { - __k[__l] = lockpick(combinations[i]); -} -__k;

    +

    + If you're not iterating over an actual array, you can use a range to + specify the start and end of an array comprehension: + coundown(i) for i in [10..1]. +

    Slicing Arrays with Ranges From 097bede5c87b15a5de86f02b6697884ea72976c6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 21:25:37 -0800 Subject: [PATCH 135/303] coffeescript 0.1.5, just for kicks --- coffee-script.gemspec | 4 ++-- documentation/index.html.erb | 9 +++++++++ index.html | 9 +++++++++ lib/coffee-script.rb | 2 +- package.json | 2 +- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index 4377b76eb0..a62e479fa0 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.1.4' # Keep version in sync with coffee-script.rb - s.date = '2009-12-25' + s.version = '0.1.5' # Keep version in sync with coffee-script.rb + s.date = '2009-12-26' 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 4524331f24..8eeb4512f1 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -469,6 +469,15 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Change Log

    + +

    + 0.1.5 + Array slice literals and array comprehensions can now both take Ruby-style + ranges to specify the start and end. JavaScript variable declaration is + now pushed up to the top of the scope, making all assignment statements into + expressions. You can use \ to escape newlines. + The coffee-script command is now called coffee. +

    0.1.4 diff --git a/index.html b/index.html index b9ac15f4df..aaca41a795 100644 --- a/index.html +++ b/index.html @@ -888,6 +888,15 @@

    Contributing

    Change Log

    + +

    + 0.1.5 + Array slice literals and array comprehensions can now both take Ruby-style + ranges to specify the start and end. JavaScript variable declaration is + now pushed up to the top of the scope, making all assignment statements into + expressions. You can use \ to escape newlines. + The coffee-script command is now called coffee. +

    0.1.4 diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 0ae253a2b5..6dffc582a6 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -9,7 +9,7 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.1.4' # Keep in sync with the gemspec. + VERSION = '0.1.5' # 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 30bae056e0..8b271c05a9 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,5 @@ "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", - "version": "0.1.4" + "version": "0.1.5" } From d7dd18b4761a9d607c1e60465e4c5cf5ea0c7688 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 21:55:56 -0800 Subject: [PATCH 136/303] more underscore, and removing custom_assign and return from conditional compilation --- examples/underscore.coffee | 41 +++++++++++++++++--------------------- lib/coffee_script/nodes.rb | 10 ++++++---- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 3b709bb366..078074bb36 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -93,31 +93,26 @@ _.reduceRight: obj, memo, iterator, context => if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context). results: [] _.each(obj, (value, index, list => - iterator.call(context, value, index, list) and results.push(value).)) + results.push(value) if iterator.call(context, value, index, list).)) results. -# -# # Return all the elements for which a truth test fails. -# _.reject = function(obj, iterator, context) { -# var results = []; -# _.each(obj, function(value, index, list) { -# !iterator.call(context, value, index, list) && results.push(value); -# }); -# return results; -# }; -# -# # Determine whether all of the elements match a truth test. Delegate to -# # JavaScript 1.6's every(), if it is present. -# _.all = function(obj, iterator, context) { -# iterator = iterator || _.identity; -# if (obj && _.isFunction(obj.every)) return obj.every(iterator, context); -# var result = true; -# _.each(obj, function(value, index, list) { -# if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop(); -# }); -# return result; -# }; -# + # 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).)) + results. + + # Determine whether all of the elements match a truth test. Delegate to + # JavaScript 1.6's every(), if it is present. + _.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).)) + result. + # # Determine if at least one element in the object matches a truth test. Use # # JavaScript 1.6's some(), if it exists. # _.any = function(obj, iterator, context) { diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 46c2ad745b..bfe1aead04 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -377,12 +377,11 @@ def compile(o={}) last = @variable.last.to_s proto = name[PROTO_ASSIGN, 1] o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto) - postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : '' return write("#{name}: #{@value.compile(o)}") if @context == :object - return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign? o[:scope].find(name) unless @variable.properties? return write(@value.compile(o)) if @value.custom_assign? - write("#{name} = #{@value.compile(o)}#{postfix}") + val = "#{name} = #{@value.compile(o)}" + write(o[:return] && !@value.custom_return? ? "return (#{val})" : val) end end @@ -728,8 +727,11 @@ def compile(o={}) # force sub-else bodies into statement form. def compile_statement(o) indent = o[:indent] + cond_o = o.dup + cond_o.delete(:assign) + cond_o.delete(:return) o[:indent] += TAB - if_part = "if (#{@condition.compile(o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" + if_part = "if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" return if_part unless @else_body else_part = chain? ? " else #{@else_body.compile(o.merge(:indent => indent))}" : From 3ee4e98cccc2dd4f6208a8fad81dc4672ce1a64f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 26 Dec 2009 22:24:21 -0800 Subject: [PATCH 137/303] more underscore and bugfix edits to code generation --- examples/underscore.coffee | 83 +++++++++++++++++--------------------- lib/coffee_script/nodes.rb | 1 + 2 files changed, 38 insertions(+), 46 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 078074bb36..6a55da636b 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -113,52 +113,43 @@ _.reduceRight: obj, memo, iterator, context => _.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 -# # JavaScript 1.6's some(), if it exists. -# _.any = function(obj, iterator, context) { -# iterator = iterator || _.identity; -# if (obj && _.isFunction(obj.some)) return obj.some(iterator, context); -# var result = false; -# _.each(obj, function(value, index, list) { -# if (result = iterator.call(context, value, index, list)) _.breakLoop(); -# }); -# return result; -# }; -# -# # Determine if a given value is included in the array or object, -# # based on '==='. -# _.include = function(obj, target) { -# if (_.isArray(obj)) return _.indexOf(obj, target) != -1; -# var found = false; -# _.each(obj, function(value) { -# if (found = value === target) _.breakLoop(); -# }); -# return found; -# }; -# -# # Invoke a method with arguments on every item in a collection. -# _.invoke = function(obj, method) { -# var args = _.rest(arguments, 2); -# return _.map(obj, function(value) { -# return (method ? value[method] : value).apply(value, args); -# }); -# }; -# -# # Convenience version of a common use case of map: fetching a property. -# _.pluck = function(obj, key) { -# return _.map(obj, function(value){ return value[key]; }); -# }; -# -# # Return the maximum item or (item-based computation). -# _.max = function(obj, iterator, context) { -# if (!iterator && _.isArray(obj)) return Math.max.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; -# }; + # Determine if at least one element in the object matches a truth test. Use + # JavaScript 1.6's some(), if it exists. + _.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)).)) + result. + + # Determine if a given value is included in the array or object, + # based on '==='. + _.include: obj, target => + return _.indexOf(obj, target) isnt -1 if _.isArray(obj) + found: false + _.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).)). + + # Convenience version of a common use case of map: fetching a property. + _.pluck: obj, key => + _.map(obj, (value => value[key].)). + + # Return the maximum item or (item-based computation). + _.max: obj, iterator, context => + return Math.max.apply(Math, obj) if !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. # # # Return the minimum element (or element-based computation). # _.min = function(obj, iterator, context) { diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index bfe1aead04..7730ed7444 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -95,6 +95,7 @@ def compile(options={}, parent=nil) if node.statement? || node.custom_return? "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" else + o.delete(:return) "#{o[:indent]}return #{node.compile(o)}#{node.line_ending}" end elsif o[:assign] From 55e736cb27c17cbff8e98de9b254381025bdc68a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 27 Dec 2009 11:01:19 -0800 Subject: [PATCH 138/303] more underscore examples --- examples/underscore.coffee | 269 ++++++++++++++++--------------------- 1 file changed, 119 insertions(+), 150 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 6a55da636b..8c463fcd32 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -306,18 +306,15 @@ _.reduceRight: obj, memo, iterator, context => # range[idx++] = i; # } # }; -# -# /* ----------------------- Function Functions: -----------------------------*/ -# -# # Create a function bound to a given object (assigning 'this', and arguments, -# # optionally). Binding with arguments is also known as 'curry'. -# _.bind = function(func, obj) { -# var args = _.rest(arguments, 2); -# return function() { -# return func.apply(obj || root, args.concat(_.toArray(arguments))); -# }; -# }; -# + + # ----------------------- Function Functions: ----------------------------- + + # Create a function bound to a given object (assigning 'this', and arguments, + # optionally). Binding with arguments is also known as 'curry'. + _.bind: func, obj => + args: _.rest(arguments, 2) + => func.apply(obj or root, args.concat(_.toArray(arguments))).. + # # Bind all of an object's methods to that object. Useful for ensuring that # # all callbacks defined on an object belong to it. # _.bindAll = function(obj) { @@ -333,36 +330,27 @@ _.reduceRight: obj, memo, iterator, context => # var args = _.rest(arguments, 2); # return setTimeout(function(){ return func.apply(func, args); }, wait); # }; -# -# # Defers a function, scheduling it to run after the current call stack has -# # cleared. -# _.defer = function(func) { -# return _.delay.apply(_, [func, 1].concat(_.rest(arguments))); -# }; -# -# # Returns the first function passed as an argument to the second, -# # allowing you to adjust arguments, run code before and after, and -# # conditionally execute the original function. -# _.wrap = function(func, wrapper) { -# return function() { -# var args = [func].concat(_.toArray(arguments)); -# return wrapper.apply(wrapper, args); -# }; -# }; -# -# # Returns a function that is the composition of a list of functions, each -# # consuming the return value of the function that follows. -# _.compose = function() { -# var funcs = _.toArray(arguments); -# return function() { -# var args = _.toArray(arguments); -# for (var i=funcs.length-1; i >= 0; i--) { -# args = [funcs[i].apply(this, args)]; -# } -# return args[0]; -# }; -# }; -# + + # Defers a function, scheduling it to run after the current call stack has + # cleared. + _.defer: func => + _.delay.apply(_, [func, 1].concat(_.rest(arguments))). + + # Returns the first function passed as an argument to the second, + # allowing you to adjust arguments, run code before and after, and + # conditionally execute the original function. + _.wrap: func, wrapper => + => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))).. + + # Returns a function that is the composition of a list of functions, each + # consuming the return value of the function that follows. + _.compose: => + funcs: _.toArray(arguments) + => + args: _.toArray(arguments) + args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0]. + args[0].. + # /* ------------------------- Object Functions: ---------------------------- */ # # # Retrieve the names of an object's properties. @@ -394,81 +382,67 @@ _.reduceRight: obj, memo, iterator, context => # if (_.isArray(obj)) return obj.slice(0); # return _.extend({}, obj); # }; -# -# # Perform a deep comparison to check if two objects are equal. -# _.isEqual = function(a, b) { -# # Check object identity. -# if (a === b) return true; -# # Different types? -# var atype = typeof(a), btype = typeof(b); -# if (atype != btype) return false; -# # Basic equality test (watch out for coercions). -# if (a == b) return true; -# # One is falsy and the other truthy. -# if ((!a && b) || (a && !b)) return false; -# # One of them implements an isEqual()? -# if (a.isEqual) return a.isEqual(b); -# # Check dates' integer values. -# if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime(); -# # Both are NaN? -# if (_.isNaN(a) && _.isNaN(b)) return true; -# # Compare regular expressions. -# if (_.isRegExp(a) && _.isRegExp(b)) -# return a.source === b.source && -# a.global === b.global && -# a.ignoreCase === b.ignoreCase && -# a.multiline === b.multiline; -# # If a is not an object by this point, we can't handle it. -# if (atype !== 'object') return false; -# # Check for different array lengths before comparing contents. -# if (a.length && (a.length !== b.length)) return false; -# # Nothing else worked, deep compare the contents. -# var aKeys = _.keys(a), bKeys = _.keys(b); -# # Different object sizes? -# if (aKeys.length != bKeys.length) return false; -# # Recursive comparison of contents. -# for (var key in a) if (!_.isEqual(a[key], b[key])) return false; -# return true; -# }; -# -# # Is a given array or object empty? -# _.isEmpty = function(obj) { -# return _.keys(obj).length == 0; -# }; -# -# # Is a given value a DOM element? -# _.isElement = function(obj) { -# return !!(obj && obj.nodeType == 1); -# }; -# -# # Is a given variable an arguments object? -# _.isArguments = function(obj) { -# return obj && _.isNumber(obj.length) && !_.isArray(obj) && !propertyIsEnumerable.call(obj, 'length'); -# }; -# -# # 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 = function(obj) { -# return _.isNumber(obj) && isNaN(obj); -# }; -# -# # Is a given value equal to null? -# _.isNull = function(obj) { -# return obj === null; -# }; -# -# # Is a given variable undefined? -# _.isUndefined = function(obj) { -# return typeof obj == '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 = function(obj, interceptor) { -# interceptor(obj); -# return obj; -# } -# + + # Perform a deep comparison to check if two objects are equal. + _.isEqual: a, b => + # Check object identity. + return true if a is b + # Different types? + atype: typeof(a); btype: typeof(b) + return false if atype isnt btype + # Basic equality test (watch out for coercions). + return true if `a == b` + # One is falsy and the other truthy. + return false if (!a and b) or (a and !b) + # One of them implements an isEqual()? + return a.isEqual(b) if a.isEqual + # Check dates' integer values. + return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b) + # Both are NaN? + return true if _.isNaN(a) and _.isNaN(b) + # Compare regular expressions. + if _.isRegExp(a) and _.isRegExp(b) + return a.source is b.source and \ + a.global is b.global and \ + a.ignoreCase is b.ignoreCase and \ + a.multiline is b.multiline. + # If a is not an object by this point, we can't handle it. + return false if atype isnt 'object' + # Check for different array lengths before comparing contents. + return false if a.length and (a.length isnt b.length) + # Nothing else worked, deep compare the contents. + aKeys: _.keys(a); bKeys: _.keys(b) + # Different object sizes? + return false if aKeys.length isnt bKeys.length + # Recursive comparison of contents. + # for (var key in a) if (!_.isEqual(a[key], b[key])) return false; + return true. + + # Is a given array or object empty? + _.isEmpty: obj => _.keys(obj).length is 0. + + # Is a given value a DOM element? + _.isElement: obj => !!(obj and obj.nodeType is 1). + + # Is a given variable an arguments object? + _.isArguments: obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length'). + + # 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). + + # Is a given value equal to 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. + # # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString # # functions based on their toString identifiers. # var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String']; @@ -478,26 +452,21 @@ _.reduceRight: obj, memo, iterator, context => # _['is' + types[i]] = function(obj) { return toString.call(obj) == identifier; }; # })(); # } -# -# /* -------------------------- Utility Functions: -------------------------- */ -# -# # Run Underscore.js in noConflict mode, returning the '_' variable to its -# # previous owner. Returns a reference to the Underscore object. -# _.noConflict = function() { -# root._ = previousUnderscore; -# return this; -# }; -# -# # Keep the identity function around for default iterators. -# _.identity = function(value) { -# return value; -# }; -# -# # Break out of the middle of an iteration. -# _.breakLoop = function() { -# throw breaker; -# }; -# + + # -------------------------- Utility Functions: -------------------------- + + # Run Underscore.js in noConflict mode, returning the '_' variable to its + # previous owner. Returns a reference to the Underscore object. + _.noConflict: => + root._: previousUnderscore + this. + + # Keep the identity function around for default iterators. + _.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; @@ -523,19 +492,19 @@ _.reduceRight: obj, memo, iterator, context => # + "');}return p.join('');"); # return data ? fn(data) : fn; # }; -# -# /*------------------------------- Aliases ----------------------------------*/ -# -# _.forEach = _.each; -# _.foldl = _.inject = _.reduce; -# _.foldr = _.reduceRight; -# _.filter = _.select; -# _.every = _.all; -# _.some = _.any; -# _.head = _.first; -# _.tail = _.rest; -# _.methods = _.functions; -# + + # ------------------------------- Aliases ---------------------------------- + + _.forEach: _.each + _.foldl: _.inject: _.reduce + _.foldr: _.reduceRight + _.filter: _.select + _.every: _.all + _.some: _.any + _.head: _.first + _.tail: _.rest + _.methods: _.functions + # /*------------------------ Setup the OOP Wrapper: --------------------------*/ # # # Helper function to continue chaining intermediate results. From 7ae8687a3eb42b36fa54aa4fd63815c9c91cbda8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 27 Dec 2009 12:43:05 -0800 Subject: [PATCH 139/303] fixing paths for running coffee compiles CoffeeScript source files into JavaScript. Usage: coffee path/to/script.coffee -i, --interactive run a CoffeeScript REPL (requires Narwhal) -r, --run compile and run a script (requires Narwhal) -o, --output [DIR] set the directory for compiled JavaScript -w, --watch watch scripts for changes, and recompile -p, --print print the compiled JavaScript to stdout -l, --lint pipe the compiled JavaScript through JSLint -e, --eval compile a cli scriptlet or read from stdin -t, --tokens print the tokens that the lexer produces -v, --verbose print at every step of code generation -n, --no-wrap raw output, no safety wrapper or vars --install-bundle install the CoffeeScript TextMate bundle --version display CoffeeScript version -h, --help display this help message outside of the coffee-script directory --- lib/coffee_script/command_line.rb | 7 +++++-- lib/coffee_script/narwhal/coffee-script.coffee | 6 ++++-- lib/coffee_script/narwhal/js/coffee-script.js | 13 ++++++++++--- lib/coffee_script/narwhal/js/launcher.js | 2 +- lib/coffee_script/narwhal/js/loader.js | 5 ++--- lib/coffee_script/narwhal/launcher.coffee | 2 +- lib/coffee_script/narwhal/loader.coffee | 2 +- 7 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index afa5fdabcd..1074d91176 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -19,6 +19,9 @@ class CommandLine # Seconds to pause between checks for changed source files. WATCH_INTERVAL = 0.5 + # Path to the Narwhal Launcher: + LAUNCHER = File.expand_path(File.dirname(__FILE__)) + '/narwhal/js/launcher.js' + # Run the CommandLine off the contents of ARGV. def initialize @mtimes = {} @@ -104,7 +107,7 @@ def eval_scriptlet # Use Narwhal to run an interactive CoffeeScript session. def launch_repl - exec "narwhal lib/coffee_script/narwhal/js/launcher.js" + exec "narwhal #{LAUNCHER}" rescue Errno::ENOENT puts "Error: Narwhal must be installed to use the interactive REPL." exit(1) @@ -113,7 +116,7 @@ def launch_repl # Use Narwhal to compile and execute CoffeeScripts. def run_scripts sources = @sources.join(' ') - exec "narwhal lib/coffee_script/narwhal/js/launcher.js #{sources}" + exec "narwhal #{LAUNCHER} #{sources}" rescue Errno::ENOENT puts "Error: Narwhal must be installed in order to execute CoffeeScripts." exit(1) diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 5271b29d3f..2a4d8f58f7 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -21,7 +21,9 @@ checkForErrors: coffeeProcess => # command. exports.run: args => args.shift() - return require(File.absolute(args[0])) if args.length + if args.length + exports.evalCS(File.read(path)) for path in args. + return true. while true try @@ -51,7 +53,7 @@ exports.evalCS: source => # Make a factory for the CoffeeScript environment. exports.makeNarwhalFactory: path => code: exports.compileFile(path) - factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}" + factoryText: "function(require,exports,module,system,print){ 1 + 1 /**/\n}" if system.engine is "rhino" Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) else diff --git a/lib/coffee_script/narwhal/js/coffee-script.js b/lib/coffee_script/narwhal/js/coffee-script.js index 4be0e13dec..dc283606e3 100644 --- a/lib/coffee_script/narwhal/js/coffee-script.js +++ b/lib/coffee_script/narwhal/js/coffee-script.js @@ -18,10 +18,17 @@ // Run a simple REPL, round-tripping to the CoffeeScript compiler for every // command. exports.run = function(args) { - var result; + var __a, __b, __c, __d, path, result; args.shift(); if (args.length) { - return require(File.absolute(args[0])); + __a = args; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + path = __a[__b]; + __d[__b] = exports.evalCS(File.read(path)); + } + __d; + return true; } while (true) { try { @@ -58,7 +65,7 @@ exports.makeNarwhalFactory = function(path) { var code, factoryText; code = exports.compileFile(path); - factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; + factoryText = "function(require,exports,module,system,print){ 1 + 1 /**/\n}"; if (system.engine === "rhino") { return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); } else { diff --git a/lib/coffee_script/narwhal/js/launcher.js b/lib/coffee_script/narwhal/js/launcher.js index 953c677ed3..c8442a18d9 100644 --- a/lib/coffee_script/narwhal/js/launcher.js +++ b/lib/coffee_script/narwhal/js/launcher.js @@ -1,3 +1,3 @@ (function(){ - require("coffee-script").run(system.args); + require("./coffee-script").run(system.args); })(); \ No newline at end of file diff --git a/lib/coffee_script/narwhal/js/loader.js b/lib/coffee_script/narwhal/js/loader.js index 35b522743a..cf4c1ed43a 100644 --- a/lib/coffee_script/narwhal/js/loader.js +++ b/lib/coffee_script/narwhal/js/loader.js @@ -7,9 +7,8 @@ loader = { // Reload the coffee-script environment from source. reload: function(topId, path) { - coffeescript = coffeescript || require('coffee-script'); - factories[topId] = coffeescript.makeNarwhalFactory(path); - return factories[topId]; + coffeescript = coffeescript || require('./coffee-script'); + return (factories[topId] = coffeescript.makeNarwhalFactory(path)); }, // Ensure that the coffee-script environment is loaded. load: function(topId, path) { diff --git a/lib/coffee_script/narwhal/launcher.coffee b/lib/coffee_script/narwhal/launcher.coffee index 26fe7ac383..ec83edef9b 100644 --- a/lib/coffee_script/narwhal/launcher.coffee +++ b/lib/coffee_script/narwhal/launcher.coffee @@ -1 +1 @@ -require("coffee-script").run(system.args) \ No newline at end of file +require("./coffee-script").run(system.args) \ No newline at end of file diff --git a/lib/coffee_script/narwhal/loader.coffee b/lib/coffee_script/narwhal/loader.coffee index 509a533eb9..9797408afd 100644 --- a/lib/coffee_script/narwhal/loader.coffee +++ b/lib/coffee_script/narwhal/loader.coffee @@ -7,7 +7,7 @@ loader: { # Reload the coffee-script environment from source. reload: topId, path => - coffeescript ||= require('coffee-script') + coffeescript ||= require('./coffee-script') factories[topId]: coffeescript.makeNarwhalFactory(path). # Ensure that the coffee-script environment is loaded. From ac00a195148fe7d07b1b8c2e3015350d87599d8b Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 27 Dec 2009 12:49:11 -0800 Subject: [PATCH 140/303] CoffeeScript 0.1.6 -- bugfixes --- coffee-script.gemspec | 4 ++-- documentation/index.html.erb | 7 +++++++ documentation/js/scope.js | 3 +-- documentation/js/super.js | 6 ++---- index.html | 25 +++++++++++++------------ lib/coffee-script.rb | 2 +- package.json | 2 +- 7 files changed, 27 insertions(+), 22 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index a62e479fa0..ef684aa0a7 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.1.5' # Keep version in sync with coffee-script.rb - s.date = '2009-12-26' + s.version = '0.1.6' # Keep version in sync with coffee-script.rb + s.date = '2009-12-27' 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 8eeb4512f1..8d756774c0 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -470,6 +470,13 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Change Log

    +

    + 0.1.6 + Bugfix for running coffee --interactive and --run + from outside of the CoffeeScript directory. Bugfix for nested + function/if-statements. +

    +

    0.1.5 Array slice literals and array comprehensions can now both take Ruby-style diff --git a/documentation/js/scope.js b/documentation/js/scope.js index c5735c0446..f4f1b4d289 100644 --- a/documentation/js/scope.js +++ b/documentation/js/scope.js @@ -4,8 +4,7 @@ change_numbers = function() { var new_num; num = 2; - new_num = 3; - return new_num; + return (new_num = 3); }; new_num = change_numbers(); })(); \ No newline at end of file diff --git a/documentation/js/super.js b/documentation/js/super.js index 5f857d2fa7..252d06127f 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -6,8 +6,7 @@ return alert(this.name + " moved " + meters + "m."); }; Snake = function(name) { - this.name = name; - return this.name; + return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -17,8 +16,7 @@ return Snake.__superClass__.move.call(this, 5); }; Horse = function(name) { - this.name = name; - return this.name; + return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); diff --git a/index.html b/index.html index aaca41a795..ce203fdfbb 100644 --- a/index.html +++ b/index.html @@ -373,8 +373,7 @@

    Language Reference

    change_numbers = function() { var new_num; num = 2; - new_num = 3; - return new_num; + return (new_num = 3); }; new_num = change_numbers();
    @@ -680,8 +678,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function(name) { - this.name = name; - return this.name; + return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -691,8 +688,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function(name) { - this.name = name; - return this.name; + return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); @@ -712,8 +708,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function(name) { - this.name = name; - return this.name; + return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -723,8 +718,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function(name) { - this.name = name; - return this.name; + return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); @@ -889,6 +883,13 @@

    Contributing

    Change Log

    +

    + 0.1.6 + Bugfix for running coffee --interactive and --run + from outside of the CoffeeScript directory. Bugfix for nested + function/if-statements. +

    +

    0.1.5 Array slice literals and array comprehensions can now both take Ruby-style diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 6dffc582a6..afae2b500d 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -9,7 +9,7 @@ # Namespace for all CoffeeScript internal classes. module CoffeeScript - VERSION = '0.1.5' # Keep in sync with the gemspec. + VERSION = '0.1.6' # 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 8b271c05a9..7d83b675e0 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,5 @@ "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", - "version": "0.1.5" + "version": "0.1.6" } From aa45456d7db7d35a9e98cae48ea8d05d03acb1cb Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 27 Dec 2009 21:50:02 -0800 Subject: [PATCH 141/303] part of the way to supporting multiline array comprehensions -- the grammar and parsing is there -- the code generation is tricky --- lib/coffee_script/grammar.y | 8 +++++--- lib/coffee_script/nodes.rb | 2 ++ test/fixtures/execution/test_array_comprehension.coffee | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index a0946f65bc..5c574201bd 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -321,7 +321,9 @@ rule # Looks a little confusing, check nodes.rb for the arguments to ForNode. For: Expression FOR - ForVariables ForSource { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } + ForVariables ForSource "." { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } + | FOR ForVariables ForSource + Terminator Expressions "." { result = ForNode.new(val[4], val[2][0], val[1][0], val[2][1], val[1][1]) } ; # An array comprehension has variables for the current element and index. @@ -332,9 +334,9 @@ rule # The source of the array comprehension can optionally be filtered. ForSource: - IN PureExpression "." { result = [val[1]] } + IN PureExpression { result = [val[1]] } | IN PureExpression - IF Expression "." { result = [val[1], val[3]] } + IF Expression { result = [val[1], val[3]] } ; # Switch/When blocks. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7730ed7444..188055e9c7 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -571,6 +571,8 @@ def compile(o={}) if o[:return] || o[:assign] return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign] return_result = "return #{return_result}" if o[:return] + o.delete(:assign) + o.delete(:return) if @filter body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) body = IfNode.new(@filter, body, nil, :statement => true) diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 973a595a67..7df031421d 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -1,4 +1,8 @@ nums: n * n for n in [1, 2, 3] if n % 2 isnt 0. -result: n * 2 for n in nums. +results: n * 2 for n in nums. -print(result.join(',') is '2,18') \ No newline at end of file +# next: for n in [1, 2, 3] if n % 2 isnt 0 +# print('hi') if false +# n * n * 2. + +print(results.join(',') is '2,18') \ No newline at end of file From 55616cf3b8f93e6181c6781a5933ca57968f2414 Mon Sep 17 00:00:00 2001 From: tlrobinson Date: Mon, 28 Dec 2009 01:16:57 -0800 Subject: [PATCH 142/303] Fixed Narwhal integration. Cleaned up module organization, etc. --- Rakefile | 3 ++- lib/coffee_script/command_line.rb | 8 ++++---- lib/coffee_script/narwhal/coffee-script.coffee | 2 +- lib/coffee_script/narwhal/js/launcher.js | 3 --- lib/coffee_script/narwhal/launcher.coffee | 1 - lib/coffee_script/narwhal/{js => lib}/coffee-script.js | 2 +- .../narwhal/{js => lib/coffee-script}/loader.js | 2 +- lib/coffee_script/narwhal/loader.coffee | 2 +- package.json | 4 ++-- 9 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 lib/coffee_script/narwhal/js/launcher.js delete mode 100644 lib/coffee_script/narwhal/launcher.coffee rename lib/coffee_script/narwhal/{js => lib}/coffee-script.js (99%) rename lib/coffee_script/narwhal/{js => lib/coffee-script}/loader.js (90%) diff --git a/Rakefile b/Rakefile index 51f7b2a3f0..c776851760 100644 --- a/Rakefile +++ b/Rakefile @@ -19,7 +19,8 @@ namespace :build do desc "Compile the Narwhal interface for --interactive and --run" task :narwhal do - sh "bin/coffee lib/coffee_script/narwhal/*.coffee -o lib/coffee_script/narwhal/js" + sh "bin/coffee lib/coffee_script/narwhal/*.coffee -o lib/coffee_script/narwhal/lib/coffee-script" + sh "mv lib/coffee_script/narwhal/lib/coffee-script/coffee-script.js lib/coffee_script/narwhal/lib/coffee-script.js" end end diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index 1074d91176..fda98af6ee 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -19,8 +19,8 @@ class CommandLine # Seconds to pause between checks for changed source files. WATCH_INTERVAL = 0.5 - # Path to the Narwhal Launcher: - LAUNCHER = File.expand_path(File.dirname(__FILE__)) + '/narwhal/js/launcher.js' + # Command to execute in Narwhal + LAUNCHER = "narwhal -e 'require(\"coffee-script\").run(system.args);'" # Run the CommandLine off the contents of ARGV. def initialize @@ -107,7 +107,7 @@ def eval_scriptlet # Use Narwhal to run an interactive CoffeeScript session. def launch_repl - exec "narwhal #{LAUNCHER}" + exec "#{LAUNCHER}" rescue Errno::ENOENT puts "Error: Narwhal must be installed to use the interactive REPL." exit(1) @@ -116,7 +116,7 @@ def launch_repl # Use Narwhal to compile and execute CoffeeScripts. def run_scripts sources = @sources.join(' ') - exec "narwhal #{LAUNCHER} #{sources}" + exec "#{LAUNCHER} #{sources}" rescue Errno::ENOENT puts "Error: Narwhal must be installed in order to execute CoffeeScripts." exit(1) diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 2a4d8f58f7..854687ccc2 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -53,7 +53,7 @@ exports.evalCS: source => # Make a factory for the CoffeeScript environment. exports.makeNarwhalFactory: path => code: exports.compileFile(path) - factoryText: "function(require,exports,module,system,print){ 1 + 1 /**/\n}" + factoryText: "function(require,exports,module,system,print){ "+code+" /**/\n}" if system.engine is "rhino" Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) else diff --git a/lib/coffee_script/narwhal/js/launcher.js b/lib/coffee_script/narwhal/js/launcher.js deleted file mode 100644 index c8442a18d9..0000000000 --- a/lib/coffee_script/narwhal/js/launcher.js +++ /dev/null @@ -1,3 +0,0 @@ -(function(){ - require("./coffee-script").run(system.args); -})(); \ No newline at end of file diff --git a/lib/coffee_script/narwhal/launcher.coffee b/lib/coffee_script/narwhal/launcher.coffee deleted file mode 100644 index ec83edef9b..0000000000 --- a/lib/coffee_script/narwhal/launcher.coffee +++ /dev/null @@ -1 +0,0 @@ -require("./coffee-script").run(system.args) \ No newline at end of file diff --git a/lib/coffee_script/narwhal/js/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js similarity index 99% rename from lib/coffee_script/narwhal/js/coffee-script.js rename to lib/coffee_script/narwhal/lib/coffee-script.js index dc283606e3..082047632b 100644 --- a/lib/coffee_script/narwhal/js/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -65,7 +65,7 @@ exports.makeNarwhalFactory = function(path) { var code, factoryText; code = exports.compileFile(path); - factoryText = "function(require,exports,module,system,print){ 1 + 1 /**/\n}"; + factoryText = "function(require,exports,module,system,print){ " + code + " /**/\n}"; if (system.engine === "rhino") { return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); } else { diff --git a/lib/coffee_script/narwhal/js/loader.js b/lib/coffee_script/narwhal/lib/coffee-script/loader.js similarity index 90% rename from lib/coffee_script/narwhal/js/loader.js rename to lib/coffee_script/narwhal/lib/coffee-script/loader.js index cf4c1ed43a..1a79dbbbac 100644 --- a/lib/coffee_script/narwhal/js/loader.js +++ b/lib/coffee_script/narwhal/lib/coffee-script/loader.js @@ -7,7 +7,7 @@ loader = { // Reload the coffee-script environment from source. reload: function(topId, path) { - coffeescript = coffeescript || require('./coffee-script'); + coffeescript = coffeescript || require('coffee-script'); return (factories[topId] = coffeescript.makeNarwhalFactory(path)); }, // Ensure that the coffee-script environment is loaded. diff --git a/lib/coffee_script/narwhal/loader.coffee b/lib/coffee_script/narwhal/loader.coffee index 9797408afd..509a533eb9 100644 --- a/lib/coffee_script/narwhal/loader.coffee +++ b/lib/coffee_script/narwhal/loader.coffee @@ -7,7 +7,7 @@ loader: { # Reload the coffee-script environment from source. reload: topId, path => - coffeescript ||= require('./coffee-script') + coffeescript ||= require('coffee-script') factories[topId]: coffeescript.makeNarwhalFactory(path). # Ensure that the coffee-script environment is loaded. diff --git a/package.json b/package.json index 7d83b675e0..d3811285cc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coffee-script", - "lib": "lib/coffee_script/narwhal/js", - "preload": ["loader"], + "lib": "lib/coffee_script/narwhal/lib", + "preload": ["coffee-script/loader"], "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", From a722b42eed3148c47c5d9c65114c01817041a408 Mon Sep 17 00:00:00 2001 From: tlrobinson Date: Mon, 28 Dec 2009 01:49:07 -0800 Subject: [PATCH 143/303] Add package on command line in case it's not installed in a Narwhal packages path. --- lib/coffee_script/command_line.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index fda98af6ee..fe5898d25b 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -20,7 +20,8 @@ class CommandLine WATCH_INTERVAL = 0.5 # Command to execute in Narwhal - LAUNCHER = "narwhal -e 'require(\"coffee-script\").run(system.args);'" + PACKAGE = File.dirname(File.dirname(File.dirname(__FILE__))) + LAUNCHER = "narwhal -p #{PACKAGE} -e 'require(\"coffee-script\").run(system.args);'" # Run the CommandLine off the contents of ARGV. def initialize From 845c8cba3b72d164fccdf4cc201a4c20249eb744 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 09:02:55 -0800 Subject: [PATCH 144/303] removing broken accidental commit --- lib/coffee_script/narwhal/coffee-script.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 2a4d8f58f7..00b854738b 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -53,7 +53,7 @@ exports.evalCS: source => # Make a factory for the CoffeeScript environment. exports.makeNarwhalFactory: path => code: exports.compileFile(path) - factoryText: "function(require,exports,module,system,print){ 1 + 1 /**/\n}" + factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}" if system.engine is "rhino" Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) else From 3762f4c680a3cbb4e69b7ac425d66ce77b2a3bfe Mon Sep 17 00:00:00 2001 From: tlrobinson Date: Mon, 28 Dec 2009 12:45:47 -0800 Subject: [PATCH 145/303] Add package.json to gemspec files so Narwhal integrations works when installed as a gem. --- coffee-script.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index ef684aa0a7..ec801ec371 100644 --- a/coffee-script.gemspec +++ b/coffee-script.gemspec @@ -22,5 +22,5 @@ Gem::Specification.new do |s| s.require_paths = ['lib'] s.executables = ['coffee'] - s.files = Dir['bin/*', 'examples/*', 'lib/**/*', 'coffee-script.gemspec', 'LICENSE', 'README'] + s.files = Dir['bin/*', 'examples/*', 'lib/**/*', 'coffee-script.gemspec', 'LICENSE', 'README', 'package.json'] end \ No newline at end of file From 41f3d54f4a1b8f7ffb76bb94c7caf3b7c6a217c5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 16:10:56 -0500 Subject: [PATCH 146/303] rebuilding narwhal libs --- lib/coffee_script/narwhal/lib/coffee-script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index 082047632b..d9266ca141 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -65,7 +65,7 @@ exports.makeNarwhalFactory = function(path) { var code, factoryText; code = exports.compileFile(path); - factoryText = "function(require,exports,module,system,print){ " + code + " /**/\n}"; + factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; if (system.engine === "rhino") { return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); } else { From a186e47e2cbfd20d60fe39328d7f10a8752091cc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 16:20:11 -0500 Subject: [PATCH 147/303] stop shifting args --- lib/coffee_script/narwhal/coffee-script.coffee | 1 - lib/coffee_script/narwhal/lib/coffee-script.js | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 00b854738b..645b083ad6 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -20,7 +20,6 @@ checkForErrors: coffeeProcess => # Run a simple REPL, round-tripping to the CoffeeScript compiler for every # command. exports.run: args => - args.shift() if args.length exports.evalCS(File.read(path)) for path in args. return true. diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index d9266ca141..3879f04ab7 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -19,7 +19,6 @@ // command. exports.run = function(args) { var __a, __b, __c, __d, path, result; - args.shift(); if (args.length) { __a = args; __d = []; From c84bb0c8759010a5bede887ed1b329327b1f6037 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 16:23:48 -0500 Subject: [PATCH 148/303] fixing relative path for execution tests --- lib/coffee_script/command_line.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index fe5898d25b..bf7481b051 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -20,7 +20,7 @@ class CommandLine WATCH_INTERVAL = 0.5 # Command to execute in Narwhal - PACKAGE = File.dirname(File.dirname(File.dirname(__FILE__))) + PACKAGE = File.expand_path(File.dirname(__FILE__) + '/../..') LAUNCHER = "narwhal -p #{PACKAGE} -e 'require(\"coffee-script\").run(system.args);'" # Run the CommandLine off the contents of ARGV. From 7b5b2617433987cc2436c2204a586f036b7acd5c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 20:05:14 -0500 Subject: [PATCH 149/303] removed unused example --- documentation/coffee/array_comprehensions.coffee | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/documentation/coffee/array_comprehensions.coffee b/documentation/coffee/array_comprehensions.coffee index fb4934bfbe..ddda9319e8 100644 --- a/documentation/coffee/array_comprehensions.coffee +++ b/documentation/coffee/array_comprehensions.coffee @@ -2,7 +2,4 @@ lunch: food.eat() for food in ['toast', 'cheese', 'wine']. # Zebra-stripe a table. -highlight(row) for row, i in table if i % 2 is 0. - -# Check the first one hundred combinations. -lockpick(combinations[i]) for i in [1..100]. \ No newline at end of file +highlight(row) for row, i in table if i % 2 is 0. \ No newline at end of file From 50bdd2668c53e2ba33276e2810439ede2a1bb891 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 21:02:40 -0500 Subject: [PATCH 150/303] got lexer balancing parens, indent/outdents, brackets, and curlies --- .gitignore | 1 + documentation/index.html.erb | 2 +- index.html | 2 +- lib/coffee_script/grammar.y | 2 +- lib/coffee_script/lexer.rb | 45 +++++++++++++------ .../narwhal/coffee-script.coffee | 2 +- .../narwhal/lib/coffee-script.js | 2 +- 7 files changed, 38 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 2eb797553a..4cc33381e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +test.coffee parser.output *.gem \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 8d756774c0..cd594909a6 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -268,7 +268,7 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Conditionals, Ternaries, and Conditional Assignment - If/else statements can be written without the use of parenthesis and + If/else statements can be written without the use of parentheses and curly brackets. As with functions and other block expressions, conditionals are closed with periods. No period is necessary when using the single-line postfix form, with the if at the end. diff --git a/index.html b/index.html index ce203fdfbb..2c572a7beb 100644 --- a/index.html +++ b/index.html @@ -401,7 +401,7 @@

    Language Reference

    Conditionals, Ternaries, and Conditional Assignment - If/else statements can be written without the use of parenthesis and + If/else statements can be written without the use of parentheses and curly brackets. As with functions and other block expressions, conditionals are closed with periods. No period is necessary when using the single-line postfix form, with the if at the end. diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 64d7d67dcc..5eaf94e72d 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -392,7 +392,7 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: - IF Expression Block IfEnd { result = IfNode.new(val[1], val[3], val[4]) } + IF Expression Block IfEnd { result = IfNode.new(val[1], val[2], val[3]) } | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index c1038f8ebe..f44b4d2f15 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -44,6 +44,9 @@ class Lexer # Assignment tokens. ASSIGN = [':', '='] + # Tokens that must be balanced. + BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]] + # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @@ -58,6 +61,7 @@ def tokenize(code) end close_indentation remove_empty_outdents + ensure_balance(*BALANCED_PAIRS) rewrite_closing_parens @tokens end @@ -150,7 +154,7 @@ def outdent_token(move_out) token(:OUTDENT, last_indent) move_out -= last_indent end - token("\n", "\n") + # token("\n", "\n") @indent = @indents.last || 0 end @@ -229,9 +233,9 @@ def close_indentation end # Rewrite the token stream, looking one token ahead and behind. - def rewrite_tokens + def scan_tokens i = 0 - while i < @tokens.length - 1 + while i < @tokens.length yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i) i += 1 end @@ -240,7 +244,7 @@ def rewrite_tokens # You should be able to put blank lines within indented expressions. # To that end, remove redundant outdent/indents from the token stream. def remove_empty_outdents - rewrite_tokens do |prev, token, post, i| + scan_tokens do |prev, token, post, i| match = (prev[0] == :OUTDENT && token[1] == "\n" && post[0] == :INDENT) match = match && prev[1] == post[1] next unless match @@ -253,18 +257,33 @@ def remove_empty_outdents # el.click(event => # el.hide()) # In order to accomplish this, move outdents that follow closing parens - # inwards, safely. + # inwards, safely. The steps to accomplish this are: + # + # 1. Check that parentheses are balanced and in order. + # 2. Check that indent/outdents are balanced and in order. + # 3. Rewrite the stream with a stack: if you see an '(' or INDENT, add it + # to the stack. If you see an ')' or OUTDENT, pop the stack and replace + # it with the inverse of what we've just popped. + # def rewrite_closing_parens - rewrite_tokens do |prev, token, post, i| - next(i += 1) unless token[1] == ')' - before_outdent = post && post[0] == :OUTDENT - after_outdent = prev && prev[0] == :OUTDENT - if before_outdent && !after_outdent - insert_index = i - insert_index -= 1 while @tokens[insert_index][1] == ')' && (@tokens[insert_index - 1][0] != :OUTDENT) - @tokens.insert(insert_index + 1, @tokens.delete_at(i + 1)) + stack = [] + scan_tokens do |prev, token, post, i| + + end + end + + def ensure_balance(*pairs) + levels = Hash.new(0) + scan_tokens do |prev, token, post, i| + pairs.each do |pair| + open, close = *pair + levels[open] += 1 if token[0] == open + levels[open] -= 1 if token[0] == close + raise ParseError.new(token[0], token[1], nil) if levels[open] < 0 end end + unclosed = levels.detect {|k, v| v > 0 } + raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed end end diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 645b083ad6..86ace83102 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -56,5 +56,5 @@ exports.makeNarwhalFactory: path => if system.engine is "rhino" Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) else - # eval requires parenthesis, but parenthesis break compileFunction. + # eval requires parentheses, but parentheses break compileFunction. eval("(" + factoryText + ")").. diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index 3879f04ab7..fc75287861 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -68,7 +68,7 @@ if (system.engine === "rhino") { return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); } else { - // eval requires parenthesis, but parenthesis break compileFunction. + // eval requires parentheses, but parentheses break compileFunction. return eval("(" + factoryText + ")"); } }; From ddd52a1845e77279f4061b18283b24213b0da377 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 21:07:47 -0500 Subject: [PATCH 151/303] first draft of kamatsu's rewrite rules -- finally got whitespace to be flexible enough, I think. --- lib/coffee_script/lexer.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index f44b4d2f15..51401ccc35 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -268,7 +268,15 @@ def remove_empty_outdents def rewrite_closing_parens stack = [] scan_tokens do |prev, token, post, i| - + stack.push(token) if [:INDENT, '('].include?(token[0]) + if [:OUTDENT, ')'].include?(token[0]) + reciprocal = stack.pop + if reciprocal[0] == :INDENT + @tokens[i] = [:OUTDENT, Value.new(reciprocal[1], token[1].line)] + else + @tokens[i] = [')', Value.new(')', token[1].line)] + end + end end end From cea417de0207a9b5b8a6249c32ef4d0dd6a82083 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 28 Dec 2009 23:08:02 -0500 Subject: [PATCH 152/303] moving along with whitespace --- lib/coffee_script/grammar.y | 155 ++++++++---------- .../execution/test_array_comprehension.coffee | 2 +- .../execution/test_assign_to_try_catch.coffee | 2 +- .../execution/test_calling_super.coffee | 16 +- .../execution/test_chained_calls.coffee | 2 +- .../fixtures/execution/test_everything.coffee | 8 +- .../execution/test_fancy_if_statement.coffee | 5 +- .../execution/test_newline_escaping.coffee | 6 +- test/fixtures/execution/test_switch.coffee | 14 +- 9 files changed, 97 insertions(+), 113 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 5eaf94e72d..467852dbd8 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -30,11 +30,12 @@ prechigh right '-=' '+=' '/=' '*=' '%=' right DELETE INSTANCEOF TYPEOF left '.' + right INDENT + left OUTDENT right THROW FOR IN WHILE NEW SUPER left UNLESS IF ELSE EXTENDS left ASSIGN '||=' '&&=' - right RETURN INDENT - left OUTDENT + right RETURN preclow # We expect 3 shift/reduce errors for optional syntax. @@ -88,8 +89,7 @@ rule ; Block: - Then Expression { result = Expressions.new([val[1]]) } - | INDENT Expressions OUTDENT { result = val[1] } + INDENT Expressions OUTDENT { result = val[1] } ; # All tokens that can terminate an expression. @@ -98,12 +98,6 @@ rule | ";" ; - # All tokens that can serve to begin the second block of a multi-part expression. - Then: - THEN - | Terminator - ; - # All hard-coded values. Literal: NUMBER { result = LiteralNode.new(val[0]) } @@ -122,13 +116,13 @@ rule # Assignment to a variable. Assign: - Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) } + Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) } ; # Assignment within an object literal. AssignObj: - IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) } - | STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) } + IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) } + | STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) } | Comment { result = val[0] } ; @@ -146,69 +140,45 @@ rule # For Ruby's Operator precedence, see: # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html Operation: - '!' Expression { result = OpNode.new(val[0], val[1]) } - | '!!' Expression { result = OpNode.new(val[0], val[1]) } - | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } - | NOT Expression { result = OpNode.new(val[0], val[1]) } - | '~' Expression { result = OpNode.new(val[0], val[1]) } - | '--' Expression { result = OpNode.new(val[0], val[1]) } - | '++' Expression { result = OpNode.new(val[0], val[1]) } - | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) } - | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) } - - | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - | Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) } - - | DELETE Expression { result = OpNode.new(val[0], val[1]) } - | TYPEOF Expression { result = OpNode.new(val[0], val[1]) } - | Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) } + PrefixOperation + | InfixOperation + | PostfixOperation ; - # Function definition. - Code: - ParamList "=>" CodeBody { result = CodeNode.new(val[0], val[2]) } - | "=>" CodeBody { result = CodeNode.new([], val[1]) } + PrefixOperation: + PrefixSymbol PureExpression { result = OpNode.new(val[0], val[1]) } ; - # The body of a function. - CodeBody: - /* nothing */ { result = Expressions.new([]) } - | Block { result = val[0] } + PostfixOperation: + PureExpression PostfixSymbol { result = OpNode.new(val[1], val[0], nil, true) } + ; + + InfixOperation: + PureExpression + InfixSymbol PureExpression { result = OpNode.new(val[1], val[0], val[2]) } + ; + + PrefixSymbol: + '!' | '!!' | '-' = UMINUS | NOT | '~' | '--' | '++' | DELETE | TYPEOF + ; + + PostfixSymbol: + '--' | '++' + ; + + InfixSymbol: + '*' | '/' | '%' | '+' | '-' | '<<' | '>>' | '>>>' | '&' | '|' | '^' + | '<=' | '<' | '>' | '>=' | '==' | '!=' | IS | ISNT | '&&' | '||' + | AND | OR | '-=' | '+=' | '/=' | '*=' | '%=' | '||=' | '&&=' | INSTANCEOF + ; + + # Function definition. + Code: + ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) } + | ParamList "=>" Expression { result = CodeNode.new(val[0], Expressions.new([val[2]])) } + | "=>" Block { result = CodeNode.new([], val[1]) } + | "=>" Expression { result = CodeNode.new([], Expressions.new([val[1]])) } + | "=>" { result = CodeNode.new([], nil) } ; # The parameters to a function definition. @@ -299,7 +269,7 @@ rule Try: TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) } | TRY Block Catch - FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } + FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } ; # A catch clause. @@ -310,7 +280,7 @@ rule # Throw an exception. Throw: - THROW Expression { result = ThrowNode.new(val[1]) } + THROW PureExpression { result = ThrowNode.new(val[1]) } ; # Parenthetical expressions. @@ -320,7 +290,7 @@ rule # The while loop. (there is no do..while). While: - WHILE Expression Block { result = WhileNode.new(val[1], val[3]) } + WHILE PureExpression Block { result = WhileNode.new(val[1], val[3]) } ; # Array comprehensions, including guard and current index. @@ -328,8 +298,7 @@ rule For: Expression FOR ForVariables ForSource "." { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } - | FOR ForVariables ForSource - Terminator Expressions "." { result = ForNode.new(val[4], val[2][0], val[1][0], val[2][1], val[1][1]) } + | FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2][0], val[1][0], val[2][1], val[1][1]) } ; # An array comprehension has variables for the current element and index. @@ -347,10 +316,12 @@ rule # Switch/When blocks. Switch: - SWITCH Expression Then - Whens { result = val[3].rewrite_condition(val[1]) } - | SWITCH Expression Then - Whens ELSE Block { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } + SWITCH Expression INDENT + Whens OUTDENT { result = val[3].rewrite_condition(val[1]) } + | SWITCH Expression INDENT + Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } + | SWITCH Expression INDENT + Whens ELSE Expression OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } ; # The inner list of whens. @@ -361,15 +332,27 @@ rule # An individual when. When: - WHEN Expression Then Block { result = IfNode.new(val[1], val[3]) } + WHEN PureExpression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } + | WHEN PureExpression + THEN Expression Terminator { result = IfNode.new(val[1], val[3], nil, {:statement => true}) } ; # All of the following nutso if-else destructuring is to make the # grammar expand unambiguously. + # The condition itself. + IfClause: + IF PureExpression { result = val[1] } + ; + + IfBlock: + IfClause Block { result = IfNode.new(val[0], val[1]) } + | IfClause THEN Expression { result = IfNode.new(val[0], val[2]) } + ; + # An elsif portion of an if-else block. ElsIf: - ELSE IF Expression Block { result = IfNode.new(val[2], val[4]) } + ELSE IfBlock { result = val[1] } ; # Multiple elsifs can be chained together. @@ -392,9 +375,9 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: - IF Expression Block IfEnd { result = IfNode.new(val[1], val[2], val[3]) } - | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } - | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } + IfBlock IfEnd { result = val[0].add_else(val[1]) } + | Block IfClause { result = IfNode.new(val[1], Expressions.new([val[0]]), nil, {:statement => true}) } + | Block UNLESS PureExpression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } ; end diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 7df031421d..34e2b1e264 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -3,6 +3,6 @@ results: n * 2 for n in nums. # next: for n in [1, 2, 3] if n % 2 isnt 0 # print('hi') if false -# n * n * 2. +# n * n * 2 print(results.join(',') is '2,18') \ No newline at end of file diff --git a/test/fixtures/execution/test_assign_to_try_catch.coffee b/test/fixtures/execution/test_assign_to_try_catch.coffee index a8397fd05d..4db2d263f5 100644 --- a/test/fixtures/execution/test_assign_to_try_catch.coffee +++ b/test/fixtures/execution/test_assign_to_try_catch.coffee @@ -1,6 +1,6 @@ result: try nonexistent * missing catch error - true. + true print(result) \ No newline at end of file diff --git a/test/fixtures/execution/test_calling_super.coffee b/test/fixtures/execution/test_calling_super.coffee index 10e52c49ef..d2d6ed0d4c 100644 --- a/test/fixtures/execution/test_calling_super.coffee +++ b/test/fixtures/execution/test_calling_super.coffee @@ -1,21 +1,21 @@ -Base: => . +Base: => Base.prototype.func: string => - 'zero/' + string. + 'zero/' + string -FirstChild: => . +FirstChild: => FirstChild extends Base FirstChild.prototype.func: string => - super('one/') + string. + super('one/') + string -SecondChild: => . +SecondChild: => SecondChild extends FirstChild SecondChild.prototype.func: string => - super('two/') + string. + super('two/') + string -ThirdChild: => . +ThirdChild: => ThirdChild extends SecondChild ThirdChild.prototype.func: string => - super('three/') + string. + super('three/') + string result: (new ThirdChild()).func('four') diff --git a/test/fixtures/execution/test_chained_calls.coffee b/test/fixtures/execution/test_chained_calls.coffee index ac0ef26914..4cf76ec3a9 100644 --- a/test/fixtures/execution/test_chained_calls.coffee +++ b/test/fixtures/execution/test_chained_calls.coffee @@ -1,4 +1,4 @@ -identity_wrap: x => => x.. +identity_wrap: x => => x result: identity_wrap(identity_wrap(true))()() diff --git a/test/fixtures/execution/test_everything.coffee b/test/fixtures/execution/test_everything.coffee index ca24c6400a..1ecba88af2 100644 --- a/test/fixtures/execution/test_everything.coffee +++ b/test/fixtures/execution/test_everything.coffee @@ -3,7 +3,7 @@ func: => b: [] while a >= 0 b.push('o') - a--. + a-- c: { "text": b @@ -14,14 +14,14 @@ func: => c.text: if false 'error' else - c.text + '---'. + c.text + '---' d = { text = c.text } - c.list: l for l in d.text.split('') if l is '-'. + c.list: l for l in d.text.split('') if l is '-' - c.single: c.list[1..1][0]. + c.single: c.list[1..1][0] print(func() == '-') diff --git a/test/fixtures/execution/test_fancy_if_statement.coffee b/test/fixtures/execution/test_fancy_if_statement.coffee index 9e14947bf6..1e7546a0a1 100644 --- a/test/fixtures/execution/test_fancy_if_statement.coffee +++ b/test/fixtures/execution/test_fancy_if_statement.coffee @@ -3,9 +3,8 @@ c: false result: if a if b - if c then false - else + if c then false else if d - true.... + true print(result) \ No newline at end of file diff --git a/test/fixtures/execution/test_newline_escaping.coffee b/test/fixtures/execution/test_newline_escaping.coffee index 2f52e9327a..972e2f795c 100644 --- a/test/fixtures/execution/test_newline_escaping.coffee +++ b/test/fixtures/execution/test_newline_escaping.coffee @@ -1,6 +1,6 @@ six: \ - 1 + \ - 2 + \ - 3 +1 + \ +2 + \ +3 print(six is 6) \ No newline at end of file diff --git a/test/fixtures/execution/test_switch.coffee b/test/fixtures/execution/test_switch.coffee index bce30883cd..2fc7da813d 100644 --- a/test/fixtures/execution/test_switch.coffee +++ b/test/fixtures/execution/test_switch.coffee @@ -1,11 +1,13 @@ num: 10 result: switch num -when 5 then false -when 'a' - false -when 10 then true -when 11 then false -else false. + when 5 then false + when 'a' + true + true + false + when 10 then true + when 11 then false + else false print(result) From 3fbb870d01fa910d7ee28a2b40e54800bd6c1945 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 08:52:26 -0500 Subject: [PATCH 153/303] using 'where' for array comprehension filtering, after kamatsu's suggestion -- execution tests pass now with significant whitespace --- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 16 ++++++++-------- lib/coffee_script/lexer.rb | 18 +++++++++++------- .../execution/test_array_comprehension.coffee | 4 ++-- test/fixtures/execution/test_everything.coffee | 3 ++- .../execution/test_range_comprehension.coffee | 4 ++-- 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index df92adbfa3..29fa2ec45e 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -204,7 +204,7 @@ match - \b(break|when|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|while)\b + \b(break|when|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|where|while)\b name keyword.control.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 467852dbd8..2c64a861a6 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 WHILE +token FOR IN WHERE WHILE token SWITCH WHEN token DELETE INSTANCEOF TYPEOF token SUPER EXTENDS @@ -32,7 +32,7 @@ prechigh left '.' right INDENT left OUTDENT - right THROW FOR IN WHILE NEW SUPER + right THROW FOR IN WHERE WHILE NEW SUPER left UNLESS IF ELSE EXTENDS left ASSIGN '||=' '&&=' right RETURN @@ -178,7 +178,7 @@ rule | ParamList "=>" Expression { result = CodeNode.new(val[0], Expressions.new([val[2]])) } | "=>" Block { result = CodeNode.new([], val[1]) } | "=>" Expression { result = CodeNode.new([], Expressions.new([val[1]])) } - | "=>" { result = CodeNode.new([], nil) } + | "=>" { result = CodeNode.new([], Expressions.new([])) } ; # The parameters to a function definition. @@ -290,14 +290,14 @@ rule # The while loop. (there is no do..while). While: - WHILE PureExpression Block { result = WhileNode.new(val[1], val[3]) } + WHILE PureExpression Block { result = WhileNode.new(val[1], val[2]) } ; # Array comprehensions, including guard and current index. # Looks a little confusing, check nodes.rb for the arguments to ForNode. For: Expression FOR - ForVariables ForSource "." { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } + ForVariables ForSource { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } | FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2][0], val[1][0], val[2][1], val[1][1]) } ; @@ -311,7 +311,7 @@ rule ForSource: IN PureExpression { result = [val[1]] } | IN PureExpression - IF Expression { result = [val[1], val[3]] } + WHERE Expression { result = [val[1], val[3]] } ; # Switch/When blocks. @@ -376,8 +376,8 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: IfBlock IfEnd { result = val[0].add_else(val[1]) } - | Block IfClause { result = IfNode.new(val[1], Expressions.new([val[0]]), nil, {:statement => true}) } - | Block UNLESS PureExpression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } + | Expression IfClause { result = IfNode.new(val[1], Expressions.new([val[0]]), nil, {:statement => true}) } + | Expression UNLESS PureExpression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } ; end diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 51401ccc35..60a206f0e3 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -12,7 +12,7 @@ class Lexer "new", "return", "try", "catch", "finally", "throw", "break", "continue", - "for", "in", "while", + "for", "in", "where", "while", "switch", "when", "super", "extends", "delete", "instanceof", "typeof"] @@ -24,11 +24,11 @@ class Lexer JS = /\A(``|`(.*?)[^\\]`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t\r]+)/ - NEWLINE = /\A(\n+)(?![ \t\r]+)/ COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ INDENT = /\A\n([ \t\r]*)/ + NEWLINE = /\A(\n+)([ \t\r]*)/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ @@ -245,11 +245,15 @@ def scan_tokens # To that end, remove redundant outdent/indents from the token stream. def remove_empty_outdents scan_tokens do |prev, token, post, i| - match = (prev[0] == :OUTDENT && token[1] == "\n" && post[0] == :INDENT) - match = match && prev[1] == post[1] - next unless match - @tokens.delete_at(i + 1) - @tokens.delete_at(i - 1) + if prev[0] == :OUTDENT && token[1] == "\n" && post[0] == :INDENT && prev[1] == post[1] + @tokens.delete_at(i + 1) + @tokens.delete_at(i - 1) + end + if prev[0] == :OUTDENT && token[0] == :INDENT && prev[1] == token[1] + @tokens.delete_at(i) + @tokens.delete_at(i - 1) + @tokens.insert(i - 1, ["\n", Value.new("\n", prev[1].line)]) + end end end diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 34e2b1e264..4d3477735f 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -1,5 +1,5 @@ -nums: n * n for n in [1, 2, 3] if n % 2 isnt 0. -results: n * 2 for n in nums. +nums: n * n for n in [1, 2, 3] where n % 2 isnt 0 +results: n * 2 for n in nums # next: for n in [1, 2, 3] if n % 2 isnt 0 # print('hi') if false diff --git a/test/fixtures/execution/test_everything.coffee b/test/fixtures/execution/test_everything.coffee index 1ecba88af2..22a2972cdb 100644 --- a/test/fixtures/execution/test_everything.coffee +++ b/test/fixtures/execution/test_everything.coffee @@ -1,6 +1,7 @@ func: => a: 3 b: [] + while a >= 0 b.push('o') a-- @@ -20,7 +21,7 @@ func: => text = c.text } - c.list: l for l in d.text.split('') if l is '-' + c.list: l for l in d.text.split('') where l is '-' c.single: c.list[1..1][0] diff --git a/test/fixtures/execution/test_range_comprehension.coffee b/test/fixtures/execution/test_range_comprehension.coffee index 8ae0b3d803..7c29d96241 100644 --- a/test/fixtures/execution/test_range_comprehension.coffee +++ b/test/fixtures/execution/test_range_comprehension.coffee @@ -1,6 +1,6 @@ -nums: i * 3 for i in [1..3]. +nums: i * 3 for i in [1..3] -negs: x for x in [-20..-10]. +negs: x for x in [-20..-10] negs: negs[0..2] result: nums.concat(negs).join(', ') From f44443b58ad043f4d9ec12cf6da0365f8d45b1c3 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 09:18:41 -0500 Subject: [PATCH 154/303] still some kinks to work out -- mid-expression blocks --- lib/coffee_script/lexer.rb | 8 +++++--- test/fixtures/generation/each.coffee | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 60a206f0e3..f1a99e5f2d 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -139,11 +139,11 @@ def indent_token return newline_token(indent) if size == @indent if size > @indent token(:INDENT, size - @indent) - @indents << size - @indent - @indent = size + @indents << (size - @indent) else outdent_token(@indent - size) end + @indent = size @line += 1 @i += (size + 1) end @@ -154,6 +154,8 @@ def outdent_token(move_out) token(:OUTDENT, last_indent) move_out -= last_indent end + # TODO: Figure out what to do about blocks that close, ending the expression + # versus blocks that occur mid-expression. # token("\n", "\n") @indent = @indents.last || 0 end @@ -245,7 +247,7 @@ def scan_tokens # To that end, remove redundant outdent/indents from the token stream. def remove_empty_outdents scan_tokens do |prev, token, post, i| - if prev[0] == :OUTDENT && token[1] == "\n" && post[0] == :INDENT && prev[1] == post[1] + if prev && post && prev[0] == :OUTDENT && token[1] == "\n" && post[0] == :INDENT && prev[1] == post[1] @tokens.delete_at(i + 1) @tokens.delete_at(i - 1) end diff --git a/test/fixtures/generation/each.coffee b/test/fixtures/generation/each.coffee index f783302fe3..89bade6171 100644 --- a/test/fixtures/generation/each.coffee +++ b/test/fixtures/generation/each.coffee @@ -6,9 +6,9 @@ _.each: obj, iterator, context => if obj.forEach obj.forEach(iterator, context) else if _.isArray(obj) or _.isArguments(obj) - iterator.call(context, item, i, obj) for item, i in obj. + iterator.call(context, item, i, obj) for item, i in obj else - iterator.call(context, obj[key], key, obj) for key in _.keys(obj).. + iterator.call(context, obj[key], key, obj) for key in _.keys(obj) catch e - throw e if e isnt breaker. - obj. \ No newline at end of file + throw e if e isnt breaker + obj \ No newline at end of file From a3129e05ae8054b50d6f9da48cc6af509d5c3252 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 09:25:56 -0500 Subject: [PATCH 155/303] allowing indentation in object and array literals --- lib/coffee_script/grammar.y | 4 +++- lib/coffee_script/lexer.rb | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 2c64a861a6..f89d6a9b04 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -217,10 +217,11 @@ rule # Assignment within an object literal (comma or newline separated). AssignList: - /* nothing */ { result = []} + /* nothing */ { result = [] } | AssignObj { result = val } | AssignList "," AssignObj { result = val[0] << val[2] } | AssignList Terminator AssignObj { result = val[0] << val[2] } + | INDENT AssignList OUTDENT { result = val[1] } ; # All flavors of function call (instantiation, super, and regular). @@ -263,6 +264,7 @@ rule | Expression { result = val } | ArgList "," Expression { result = val[0] << val[2] } | ArgList Terminator Expression { result = val[0] << val[2] } + | INDENT ArgList OUTDENT { result = val[1] } ; # Try/catch/finally exception handling blocks. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index f1a99e5f2d..dc7c3eb42e 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -36,10 +36,10 @@ class Lexer COMMENT_CLEANER = /(^\s*#|\n\s*$)/ # Tokens that always constitute the start of an expression. - EXP_START = ['{', '(', '['] + # EXP_START = ['{', '(', '['] # Tokens that always constitute the end of an expression. - EXP_END = ['}', ')', ']'] + # EXP_END = ['}', ')', ']'] # Assignment tokens. ASSIGN = [':', '='] @@ -183,8 +183,8 @@ def literal_token value = @chunk[OPERATOR, 1] tag_parameters if value && value.match(CODE) value ||= @chunk[0,1] - skip_following_newlines if EXP_START.include?(value) - remove_leading_newlines if EXP_END.include?(value) + # skip_following_newlines if EXP_START.include?(value) + # remove_leading_newlines if EXP_END.include?(value) tag = ASSIGN.include?(value) ? :ASSIGN : value token(tag, value) @i += value.length @@ -216,18 +216,18 @@ def tag_parameters end # Consume and ignore newlines immediately after this point. - def skip_following_newlines - newlines = @code[(@i+1)..-1][NEWLINE, 1] - if newlines - @line += newlines.length - @i += newlines.length - end - end + # def skip_following_newlines + # newlines = @code[(@i+1)..-1][NEWLINE, 1] + # if newlines + # @line += newlines.length + # @i += newlines.length + # end + # end # Discard newlines immediately before this point. - def remove_leading_newlines - @tokens.pop if last_value == "\n" - end + # def remove_leading_newlines + # @tokens.pop if last_value == "\n" + # end # Close up all remaining open blocks. def close_indentation From ae112d6087d3e907ceb0d2d472b5d2c41dc352b4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 09:55:37 -0500 Subject: [PATCH 156/303] changing array comprehension filters from 'where' to 'when' to mirror case/when --- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 6 +-- lib/coffee_script/lexer.rb | 41 ++++++++----------- .../execution/test_array_comprehension.coffee | 2 +- .../fixtures/execution/test_everything.coffee | 2 +- 5 files changed, 22 insertions(+), 31 deletions(-) diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 29fa2ec45e..88bb3bffc5 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -204,7 +204,7 @@ match - \b(break|when|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|where|while)\b + \b(break|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|when|while)\b name keyword.control.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index f89d6a9b04..c4635b0d34 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 WHERE WHILE +token FOR IN WHILE token SWITCH WHEN token DELETE INSTANCEOF TYPEOF token SUPER EXTENDS @@ -32,7 +32,7 @@ prechigh left '.' right INDENT left OUTDENT - right THROW FOR IN WHERE WHILE NEW SUPER + right THROW FOR IN WHILE WHEN NEW SUPER left UNLESS IF ELSE EXTENDS left ASSIGN '||=' '&&=' right RETURN @@ -313,7 +313,7 @@ rule ForSource: IN PureExpression { result = [val[1]] } | IN PureExpression - WHERE Expression { result = [val[1], val[3]] } + WHEN Expression { result = [val[1], val[3]] } ; # Switch/When blocks. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index dc7c3eb42e..67c6c1a9e0 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -35,18 +35,16 @@ class Lexer MULTILINER = /\n/ COMMENT_CLEANER = /(^\s*#|\n\s*$)/ - # Tokens that always constitute the start of an expression. - # EXP_START = ['{', '(', '['] - - # Tokens that always constitute the end of an expression. - # EXP_END = ['}', ')', ']'] - # Assignment tokens. ASSIGN = [':', '='] # Tokens that must be balanced. BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]] + # Outdents that come before these tokens don't signify the end of the + # expression. TODO: Is this safe? + EXPRESSION_TAIL = [:CATCH, :WHEN, :ELSE, ')', ']', '}'] + # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @@ -61,6 +59,7 @@ def tokenize(code) end close_indentation remove_empty_outdents + remove_mid_expression_newlines ensure_balance(*BALANCED_PAIRS) rewrite_closing_parens @tokens @@ -154,9 +153,7 @@ def outdent_token(move_out) token(:OUTDENT, last_indent) move_out -= last_indent end - # TODO: Figure out what to do about blocks that close, ending the expression - # versus blocks that occur mid-expression. - # token("\n", "\n") + token("\n", "\n") @indent = @indents.last || 0 end @@ -183,8 +180,6 @@ def literal_token value = @chunk[OPERATOR, 1] tag_parameters if value && value.match(CODE) value ||= @chunk[0,1] - # skip_following_newlines if EXP_START.include?(value) - # remove_leading_newlines if EXP_END.include?(value) tag = ASSIGN.include?(value) ? :ASSIGN : value token(tag, value) @i += value.length @@ -215,20 +210,6 @@ def tag_parameters end end - # Consume and ignore newlines immediately after this point. - # def skip_following_newlines - # newlines = @code[(@i+1)..-1][NEWLINE, 1] - # if newlines - # @line += newlines.length - # @i += newlines.length - # end - # end - - # Discard newlines immediately before this point. - # def remove_leading_newlines - # @tokens.pop if last_value == "\n" - # end - # Close up all remaining open blocks. def close_indentation outdent_token(@indent) @@ -259,6 +240,14 @@ def remove_empty_outdents end end + # Some blocks occur in the middle of expressions -- when we're expecting + # this, remove their trailing newlines. + def remove_mid_expression_newlines + scan_tokens do |prev, token, post, i| + @tokens.delete_at(i) if post && EXPRESSION_TAIL.include?(post[0]) && token[0] == "\n" && prev[0] == :OUTDENT + end + end + # We'd like to support syntax like this: # el.click(event => # el.hide()) @@ -286,6 +275,8 @@ def rewrite_closing_parens end end + # Ensure that all listed pairs of tokens are correctly balanced throughout + # the course of the token stream. def ensure_balance(*pairs) levels = Hash.new(0) scan_tokens do |prev, token, post, i| diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 4d3477735f..8ce1e4f6fa 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -1,4 +1,4 @@ -nums: n * n for n in [1, 2, 3] where n % 2 isnt 0 +nums: n * n for n in [1, 2, 3] when n % 2 isnt 0 results: n * 2 for n in nums # next: for n in [1, 2, 3] if n % 2 isnt 0 diff --git a/test/fixtures/execution/test_everything.coffee b/test/fixtures/execution/test_everything.coffee index 22a2972cdb..b3fe5f70e7 100644 --- a/test/fixtures/execution/test_everything.coffee +++ b/test/fixtures/execution/test_everything.coffee @@ -21,7 +21,7 @@ func: => text = c.text } - c.list: l for l in d.text.split('') where l is '-' + c.list: l for l in d.text.split('') when l is '-' c.single: c.list[1..1][0] From 96803d0129343f4f6b4d7a3dab3e640e6ff5cf2c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 10:02:19 -0500 Subject: [PATCH 157/303] killing some newlines in the execution tests, to test the lexer's newline suppression --- test/fixtures/execution/test_assign_to_try_catch.coffee | 1 - test/fixtures/execution/test_everything.coffee | 1 - 2 files changed, 2 deletions(-) diff --git a/test/fixtures/execution/test_assign_to_try_catch.coffee b/test/fixtures/execution/test_assign_to_try_catch.coffee index 4db2d263f5..c635bbf0e5 100644 --- a/test/fixtures/execution/test_assign_to_try_catch.coffee +++ b/test/fixtures/execution/test_assign_to_try_catch.coffee @@ -2,5 +2,4 @@ result: try nonexistent * missing catch error true - print(result) \ No newline at end of file diff --git a/test/fixtures/execution/test_everything.coffee b/test/fixtures/execution/test_everything.coffee index b3fe5f70e7..512f219bd3 100644 --- a/test/fixtures/execution/test_everything.coffee +++ b/test/fixtures/execution/test_everything.coffee @@ -16,7 +16,6 @@ func: => 'error' else c.text + '---' - d = { text = c.text } From f5aafc6591822c83a6bd8395e38fcc07527677e8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 10:20:18 -0500 Subject: [PATCH 158/303] don't break trailing commas with rewrite_closing_parens --- lib/coffee_script/grammar.y | 3 +++ lib/coffee_script/lexer.rb | 47 ++++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index c4635b0d34..c263e930d7 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -221,6 +221,8 @@ rule | AssignObj { result = val } | AssignList "," AssignObj { result = val[0] << val[2] } | AssignList Terminator AssignObj { result = val[0] << val[2] } + | AssignList "," + Terminator AssignObj { result = val[0] << val[3] } | INDENT AssignList OUTDENT { result = val[1] } ; @@ -264,6 +266,7 @@ rule | Expression { result = val } | ArgList "," Expression { result = val[0] << val[2] } | ArgList Terminator Expression { result = val[0] << val[2] } + | ArgList "," Terminator Expression { result = val[0] << val[3] } | INDENT ArgList OUTDENT { result = val[1] } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 67c6c1a9e0..a1bba31cb3 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -248,20 +248,39 @@ def remove_mid_expression_newlines end end + # Ensure that all listed pairs of tokens are correctly balanced throughout + # the course of the token stream. + def ensure_balance(*pairs) + levels = Hash.new(0) + scan_tokens do |prev, token, post, i| + pairs.each do |pair| + open, close = *pair + levels[open] += 1 if token[0] == open + levels[open] -= 1 if token[0] == close + raise ParseError.new(token[0], token[1], nil) if levels[open] < 0 + end + end + unclosed = levels.detect {|k, v| v > 0 } + raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed + end + # We'd like to support syntax like this: # el.click(event => # el.hide()) # In order to accomplish this, move outdents that follow closing parens # inwards, safely. The steps to accomplish this are: # - # 1. Check that parentheses are balanced and in order. - # 2. Check that indent/outdents are balanced and in order. - # 3. Rewrite the stream with a stack: if you see an '(' or INDENT, add it + # 1. Check that all paired tokens are balanced and in order. + # 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it # to the stack. If you see an ')' or OUTDENT, pop the stack and replace # it with the inverse of what we've just popped. + # 3. Keep track of "debt" for tokens that we fake, to make sure we end + # up balanced in the end. + # 4. Make sure that we don't accidentally break trailing commas, which + # should be before OUTDENTS, but after parens. # def rewrite_closing_parens - stack = [] + stack, debt = [], [] scan_tokens do |prev, token, post, i| stack.push(token) if [:INDENT, '('].include?(token[0]) if [:OUTDENT, ')'].include?(token[0]) @@ -269,28 +288,14 @@ def rewrite_closing_parens if reciprocal[0] == :INDENT @tokens[i] = [:OUTDENT, Value.new(reciprocal[1], token[1].line)] else - @tokens[i] = [')', Value.new(')', token[1].line)] + index = prev[0] == ',' ? i - 1 : i + @tokens.delete_at(i) + @tokens.insert(index, [')', Value.new(')', token[1].line)]) end end end end - # Ensure that all listed pairs of tokens are correctly balanced throughout - # the course of the token stream. - def ensure_balance(*pairs) - levels = Hash.new(0) - scan_tokens do |prev, token, post, i| - pairs.each do |pair| - open, close = *pair - levels[open] += 1 if token[0] == open - levels[open] -= 1 if token[0] == close - raise ParseError.new(token[0], token[1], nil) if levels[open] < 0 - end - end - unclosed = levels.detect {|k, v| v > 0 } - raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed - end - end end \ No newline at end of file From fd357871f27456f9eaee46151a12dd0fda734d53 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 20:39:51 -0500 Subject: [PATCH 159/303] put the commas on the outside of expression closers --- lib/coffee_script/lexer.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index a1bba31cb3..5c6ad55a49 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -148,7 +148,7 @@ def indent_token end def outdent_token(move_out) - while move_out > 0 + while move_out > 0 && !@indents.empty? last_indent = @indents.pop token(:OUTDENT, last_indent) move_out -= last_indent @@ -276,8 +276,8 @@ def ensure_balance(*pairs) # it with the inverse of what we've just popped. # 3. Keep track of "debt" for tokens that we fake, to make sure we end # up balanced in the end. - # 4. Make sure that we don't accidentally break trailing commas, which - # should be before OUTDENTS, but after parens. + # 4. Make sure that we don't accidentally break trailing commas, which need + # to go on the outside of expression closers. # def rewrite_closing_parens stack, debt = [], [] @@ -285,13 +285,11 @@ def rewrite_closing_parens stack.push(token) if [:INDENT, '('].include?(token[0]) if [:OUTDENT, ')'].include?(token[0]) reciprocal = stack.pop - if reciprocal[0] == :INDENT - @tokens[i] = [:OUTDENT, Value.new(reciprocal[1], token[1].line)] - else - index = prev[0] == ',' ? i - 1 : i - @tokens.delete_at(i) - @tokens.insert(index, [')', Value.new(')', token[1].line)]) - end + tok = reciprocal[0] == :INDENT ? [:OUTDENT, Value.new(reciprocal[1], token[1].line)] : + [')', Value.new(')', token[1].line)] + index = prev[0] == ',' ? i - 1 : i + @tokens.delete_at(i) + @tokens.insert(index, tok) end end end From 541291e2f350e9932255b8f6f15a5cb0dd84d019 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 21:46:15 -0500 Subject: [PATCH 160/303] whitespace parser down to 4 shift/reduce errors -- good enough for me --- lib/coffee_script/grammar.y | 128 +++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index c263e930d7..552331cd77 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -32,15 +32,15 @@ prechigh left '.' right INDENT left OUTDENT - right THROW FOR IN WHILE WHEN NEW SUPER - left UNLESS IF ELSE EXTENDS + right THROW FOR IN WHILE WHEN NEW SUPER IF THEN ELSE + left UNLESS EXTENDS left ASSIGN '||=' '&&=' - right RETURN + right RETURN '=>' preclow -# We expect 3 shift/reduce errors for optional syntax. +# We expect 4 shift/reduce errors for optional syntax. # There used to be 252 -- greatly improved. -expect 3 +expect 4 rule @@ -61,22 +61,12 @@ rule # All types of expressions in our language. Expression: - PureExpression - | Statement - ; - - # The parts that are natural JavaScript expressions. - PureExpression: Value | Call | Code | Operation | Range - ; - - # We have to take extra care to convert these statements into expressions. - Statement: - Assign + | Assign | If | Try | Throw @@ -140,36 +130,57 @@ rule # For Ruby's Operator precedence, see: # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html Operation: - PrefixOperation - | InfixOperation - | PostfixOperation - ; - - PrefixOperation: - PrefixSymbol PureExpression { result = OpNode.new(val[0], val[1]) } - ; - - PostfixOperation: - PureExpression PostfixSymbol { result = OpNode.new(val[1], val[0], nil, true) } - ; - - InfixOperation: - PureExpression - InfixSymbol PureExpression { result = OpNode.new(val[1], val[0], val[2]) } - ; - - PrefixSymbol: - '!' | '!!' | '-' = UMINUS | NOT | '~' | '--' | '++' | DELETE | TYPEOF - ; - - PostfixSymbol: - '--' | '++' - ; - - InfixSymbol: - '*' | '/' | '%' | '+' | '-' | '<<' | '>>' | '>>>' | '&' | '|' | '^' - | '<=' | '<' | '>' | '>=' | '==' | '!=' | IS | ISNT | '&&' | '||' - | AND | OR | '-=' | '+=' | '/=' | '*=' | '%=' | '||=' | '&&=' | INSTANCEOF + '!' Expression { result = OpNode.new(val[0], val[1]) } + | '!!' Expression { result = OpNode.new(val[0], val[1]) } + | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) } + | NOT Expression { result = OpNode.new(val[0], val[1]) } + | '~' Expression { result = OpNode.new(val[0], val[1]) } + | '--' Expression { result = OpNode.new(val[0], val[1]) } + | '++' Expression { result = OpNode.new(val[0], val[1]) } + | DELETE Expression { result = OpNode.new(val[0], val[1]) } + | TYPEOF Expression { result = OpNode.new(val[0], val[1]) } + | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) } + | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) } + + | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + | Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) } + + | Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) } ; # Function definition. @@ -285,7 +296,7 @@ rule # Throw an exception. Throw: - THROW PureExpression { result = ThrowNode.new(val[1]) } + THROW Expression { result = ThrowNode.new(val[1]) } ; # Parenthetical expressions. @@ -295,7 +306,7 @@ rule # The while loop. (there is no do..while). While: - WHILE PureExpression Block { result = WhileNode.new(val[1], val[2]) } + WHILE Expression Block { result = WhileNode.new(val[1], val[2]) } ; # Array comprehensions, including guard and current index. @@ -314,8 +325,8 @@ rule # The source of the array comprehension can optionally be filtered. ForSource: - IN PureExpression { result = [val[1]] } - | IN PureExpression + IN Expression { result = [val[1]] } + | IN Expression WHEN Expression { result = [val[1], val[3]] } ; @@ -337,22 +348,17 @@ rule # An individual when. When: - WHEN PureExpression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } - | WHEN PureExpression + WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } + | WHEN Expression THEN Expression Terminator { result = IfNode.new(val[1], val[3], nil, {:statement => true}) } ; # All of the following nutso if-else destructuring is to make the # grammar expand unambiguously. - # The condition itself. - IfClause: - IF PureExpression { result = val[1] } - ; - IfBlock: - IfClause Block { result = IfNode.new(val[0], val[1]) } - | IfClause THEN Expression { result = IfNode.new(val[0], val[2]) } + IF Expression Block { result = IfNode.new(val[1], val[2]) } + | IF Expression THEN Expression { result = IfNode.new(val[1], val[3]) } ; # An elsif portion of an if-else block. @@ -381,8 +387,8 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: IfBlock IfEnd { result = val[0].add_else(val[1]) } - | Expression IfClause { result = IfNode.new(val[1], Expressions.new([val[0]]), nil, {:statement => true}) } - | Expression UNLESS PureExpression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } + | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } + | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } ; end From d3ab60df78ce65450afb4951809cb1a067c8de75 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 22:24:52 -0500 Subject: [PATCH 161/303] implementing kamatsu's debt-based lexer for closing delimiters --- lib/coffee_script/lexer.rb | 44 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 5c6ad55a49..b1d1b46fa7 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -45,6 +45,9 @@ class Lexer # expression. TODO: Is this safe? EXPRESSION_TAIL = [:CATCH, :WHEN, :ELSE, ')', ']', '}'] + # The inverse mappings of token pairs we're trying to fix up. + INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} + # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @@ -60,6 +63,7 @@ def tokenize(code) close_indentation remove_empty_outdents remove_mid_expression_newlines + move_commas_outside_outdents ensure_balance(*BALANCED_PAIRS) rewrite_closing_parens @tokens @@ -248,6 +252,16 @@ def remove_mid_expression_newlines end end + # Make sure that we don't accidentally break trailing commas, which need + # to go on the outside of expression closers. + def move_commas_outside_outdents + scan_tokens do |prev, token, post, i| + next unless token[0] == :OUTDENT && prev[0] == ',' + @tokens.delete_at(i) + @tokens.insert(i - 1, token) + end + end + # Ensure that all listed pairs of tokens are correctly balanced throughout # the course of the token stream. def ensure_balance(*pairs) @@ -276,20 +290,28 @@ def ensure_balance(*pairs) # it with the inverse of what we've just popped. # 3. Keep track of "debt" for tokens that we fake, to make sure we end # up balanced in the end. - # 4. Make sure that we don't accidentally break trailing commas, which need - # to go on the outside of expression closers. # def rewrite_closing_parens - stack, debt = [], [] + stack, debt = [], Hash.new(0) scan_tokens do |prev, token, post, i| - stack.push(token) if [:INDENT, '('].include?(token[0]) - if [:OUTDENT, ')'].include?(token[0]) - reciprocal = stack.pop - tok = reciprocal[0] == :INDENT ? [:OUTDENT, Value.new(reciprocal[1], token[1].line)] : - [')', Value.new(')', token[1].line)] - index = prev[0] == ',' ? i - 1 : i - @tokens.delete_at(i) - @tokens.insert(index, tok) + tag, inv = token[0], INVERSES[token[0]] + stack.push(token) if [:INDENT, '('].include?(tag) + if [:OUTDENT, ')'].include?(tag) + # If the tag is already in our debt, swallow it. + if debt[inv] > 0 + debt[inv] -= 1 + @tokens.delete_at(i) + next + end + # Pop the stack of open delimiters. + match = stack.pop + mtag = match[0] + # Continue onwards if it's the expected tag. + next if tag == INVERSES[mtag] + # Unexpected close, insert correct close, adding to the debt. + debt[mtag] += 1 + val = mtag == :INDENT ? match[1] : ')' + @tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)]) end end end From dd3c9abe152e16c773f04c158ae382e6106f86ff Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 29 Dec 2009 23:01:08 -0500 Subject: [PATCH 162/303] touch-ups cleanups to the lexer and rebuilding the narwhal libs from whitespace'd versions --- lib/coffee_script/lexer.rb | 38 +++++-------------- .../narwhal/coffee-script.coffee | 16 ++++---- lib/coffee_script/narwhal/loader.coffee | 4 +- 3 files changed, 20 insertions(+), 38 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index b1d1b46fa7..9dc9ec97c2 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -23,12 +23,12 @@ class Lexer STRING = /\A(""|''|"(.*?)[^\\]"|'(.*?)[^\\]')/m JS = /\A(``|`(.*?)[^\\]`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ - WHITESPACE = /\A([ \t\r]+)/ + WHITESPACE = /\A([ \t]+)/ COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - INDENT = /\A\n([ \t\r]*)/ - NEWLINE = /\A(\n+)([ \t\r]*)/ + MULTI_DENT = /\A((\n+[ \t]*)+)/ + LAST_DENT = /\n+([ \t]*)\Z/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ @@ -61,7 +61,6 @@ def tokenize(code) extract_next_token end close_indentation - remove_empty_outdents remove_mid_expression_newlines move_commas_outside_outdents ensure_balance(*BALANCED_PAIRS) @@ -137,8 +136,10 @@ def comment_token # Record tokens for indentation differing from the previous line. def indent_token - return false unless indent = @chunk[INDENT, 1] - size = indent.size + return false unless indent = @chunk[MULTI_DENT, 1] + @line += indent.scan(MULTILINER).size + @i += indent.size + size = indent[LAST_DENT, 1].length return newline_token(indent) if size == @indent if size > @indent token(:INDENT, size - @indent) @@ -147,8 +148,6 @@ def indent_token outdent_token(@indent - size) end @indent = size - @line += 1 - @i += (size + 1) end def outdent_token(move_out) @@ -158,7 +157,6 @@ def outdent_token(move_out) move_out -= last_indent end token("\n", "\n") - @indent = @indents.last || 0 end # Matches and consumes non-meaningful whitespace. @@ -170,11 +168,11 @@ def whitespace_token # Multiple newlines get merged together. # Use a trailing \ to escape newlines. def newline_token(newlines) - return false unless newlines = @chunk[NEWLINE, 1] - @line += newlines.length + lines = newlines.scan(MULTILINER).length + @line += lines token("\n", "\n") unless ["\n", "\\"].include?(last_value) @tokens.pop if last_value == "\\" - @i += newlines.length + true end # We treat all other single characters as a token. Eg.: ( ) , . ! @@ -228,22 +226,6 @@ def scan_tokens end end - # You should be able to put blank lines within indented expressions. - # To that end, remove redundant outdent/indents from the token stream. - def remove_empty_outdents - scan_tokens do |prev, token, post, i| - if prev && post && prev[0] == :OUTDENT && token[1] == "\n" && post[0] == :INDENT && prev[1] == post[1] - @tokens.delete_at(i + 1) - @tokens.delete_at(i - 1) - end - if prev[0] == :OUTDENT && token[0] == :INDENT && prev[1] == token[1] - @tokens.delete_at(i) - @tokens.delete_at(i - 1) - @tokens.insert(i - 1, ["\n", Value.new("\n", prev[1].line)]) - end - end - end - # Some blocks occur in the middle of expressions -- when we're expecting # this, remove their trailing newlines. def remove_mid_expression_newlines diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 86ace83102..3bb9772a1a 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -15,14 +15,14 @@ coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().dirna checkForErrors: coffeeProcess => return true if coffeeProcess.wait() is 0 system.stderr.print(coffeeProcess.stderr.read()) - throw new Error("CoffeeScript compile error"). + throw new Error("CoffeeScript compile error") # Run a simple REPL, round-tripping to the CoffeeScript compiler for every # command. exports.run: args => if args.length - exports.evalCS(File.read(path)) for path in args. - return true. + exports.evalCS(File.read(path)) for path in args + return true while true try @@ -30,24 +30,24 @@ exports.run: args => result: exports.evalCS(Readline.readline()) print(result) if result isnt undefined catch e - print(e)... + print(e) # Compile a given CoffeeScript file into JavaScript. exports.compileFile: path => coffee: OS.popen([coffeePath, "--print", "--no-wrap", path]) checkForErrors(coffee) - coffee.stdout.read(). + coffee.stdout.read() # Compile a string of CoffeeScript into JavaScript. exports.compile: source => coffee: OS.popen([coffeePath, "--eval", "--no-wrap"]) coffee.stdin.write(source).flush().close() checkForErrors(coffee) - coffee.stdout.read(). + coffee.stdout.read() # Evaluating a string of CoffeeScript first compiles it externally. exports.evalCS: source => - eval(exports.compile(source)). + eval(exports.compile(source)) # Make a factory for the CoffeeScript environment. exports.makeNarwhalFactory: path => @@ -57,4 +57,4 @@ exports.makeNarwhalFactory: path => Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) else # eval requires parentheses, but parentheses break compileFunction. - eval("(" + factoryText + ")").. + eval("(" + factoryText + ")") diff --git a/lib/coffee_script/narwhal/loader.coffee b/lib/coffee_script/narwhal/loader.coffee index 509a533eb9..ccdcacbf20 100644 --- a/lib/coffee_script/narwhal/loader.coffee +++ b/lib/coffee_script/narwhal/loader.coffee @@ -8,11 +8,11 @@ loader: { # Reload the coffee-script environment from source. reload: topId, path => coffeescript ||= require('coffee-script') - factories[topId]: coffeescript.makeNarwhalFactory(path). + factories[topId]: coffeescript.makeNarwhalFactory(path) # Ensure that the coffee-script environment is loaded. load: topId, path => - factories[topId] ||= this.reload(topId, path). + factories[topId] ||= this.reload(topId, path) } From 90051e9096da1fef57041ad6ff72e7eb0e713671 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 00:08:49 -0500 Subject: [PATCH 163/303] clean up a couple of test errors for whitespace --- test/unit/test_parser.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index fecb18235c..e8318501aa 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -21,7 +21,7 @@ def test_parsing_a_basic_assignment end def test_parsing_an_object_literal - nodes = @par.parse("{one : 1 \n two : 2}").expressions + nodes = @par.parse("{one : 1\ntwo : 2}").expressions obj = nodes.first.literal assert obj.is_a? ObjectNode assert obj.properties.first.variable.literal.value == "one" @@ -29,7 +29,7 @@ def test_parsing_an_object_literal end def test_parsing_an_function_definition - code = @par.parse("x, y => x * y.").expressions.first + code = @par.parse("x, y => x * y").expressions.first assert code.params == ['x', 'y'] body = code.body.expressions.first assert body.is_a? OpNode @@ -45,7 +45,7 @@ def test_parsing_if_statement end def test_parsing_array_comprehension - nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] if i % 2 is 0.").expressions + nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] if i % 2 is 0").expressions assert nodes.first.is_a? ForNode assert nodes.first.body.literal == 'i' assert nodes.first.filter.operator == '===' @@ -53,7 +53,7 @@ def test_parsing_array_comprehension end def test_parsing_comment - nodes = @par.parse("a: 1\n # comment\nb: 2").expressions + nodes = @par.parse("a: 1\n# comment\nb: 2").expressions assert nodes[1].is_a? CommentNode end @@ -79,7 +79,7 @@ def test_no_wrap def test_no_wrapping_parens_around_statements assert_raises(SyntaxError) do - @par.parse("(try thing() catch error fail().)").compile + @par.parse("(try thing() catch error fail())").compile end end From 6ddd808f7478d3f7b861e52f3ef856d8a1e2a152 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 00:22:27 -0500 Subject: [PATCH 164/303] removing dots from whitespace examples --- documentation/coffee/aliases.coffee | 2 +- documentation/coffee/array_comprehensions.coffee | 4 ++-- documentation/coffee/conditionals.coffee | 4 ++-- documentation/coffee/expressions.coffee | 6 +++--- documentation/coffee/functions.coffee | 4 ++-- documentation/coffee/overview.coffee | 6 +++--- documentation/coffee/scope.coffee | 2 +- documentation/coffee/super.coffee | 12 ++++++------ documentation/coffee/switch.coffee | 4 ++-- documentation/coffee/try.coffee | 2 +- documentation/coffee/while.coffee | 4 ++-- lib/coffee_script/grammar.y | 8 ++++---- test/unit/test_parser.rb | 3 ++- 13 files changed, 31 insertions(+), 30 deletions(-) diff --git a/documentation/coffee/aliases.coffee b/documentation/coffee/aliases.coffee index f1a29e0f07..720c2081ec 100644 --- a/documentation/coffee/aliases.coffee +++ b/documentation/coffee/aliases.coffee @@ -4,4 +4,4 @@ volume: 10 if band isnt spinal_tap let_the_wild_rumpus_begin() unless answer is no -if car.speed < speed_limit then accelerate(). +if car.speed < speed_limit then accelerate() diff --git a/documentation/coffee/array_comprehensions.coffee b/documentation/coffee/array_comprehensions.coffee index ddda9319e8..a04ab395f9 100644 --- a/documentation/coffee/array_comprehensions.coffee +++ b/documentation/coffee/array_comprehensions.coffee @@ -1,5 +1,5 @@ # Eat lunch. -lunch: food.eat() for food in ['toast', 'cheese', 'wine']. +lunch: food.eat() for food in ['toast', 'cheese', 'wine'] # Zebra-stripe a table. -highlight(row) for row, i in table if i % 2 is 0. \ No newline at end of file +highlight(row) for row, i in table when i % 2 is 0 \ No newline at end of file diff --git a/documentation/coffee/conditionals.coffee b/documentation/coffee/conditionals.coffee index 253d912c3b..50c18c43e1 100644 --- a/documentation/coffee/conditionals.coffee +++ b/documentation/coffee/conditionals.coffee @@ -2,8 +2,8 @@ mood: greatly_improved if singing if happy and knows_it claps_hands() - cha_cha_cha(). + cha_cha_cha() -date: if friday then sue else jill. +date: if friday then sue else jill expensive ||= do_the_math() \ No newline at end of file diff --git a/documentation/coffee/expressions.coffee b/documentation/coffee/expressions.coffee index 0e8da6d5e9..fa0c1e3af4 100644 --- a/documentation/coffee/expressions.coffee +++ b/documentation/coffee/expressions.coffee @@ -2,8 +2,8 @@ grade: student => if student.excellent_work "A+" else if student.okay_stuff - if student.tried_hard then "B" else "B-". + if student.tried_hard then "B" else "B-" else - "C".. + "C" -eldest: if 24 > 21 then "Liz" else "Ike". \ No newline at end of file +eldest: if 24 > 21 then "Liz" else "Ike" \ No newline at end of file diff --git a/documentation/coffee/functions.coffee b/documentation/coffee/functions.coffee index 35f4415aa1..eb2b9b9bc3 100644 --- a/documentation/coffee/functions.coffee +++ b/documentation/coffee/functions.coffee @@ -1,2 +1,2 @@ -square: x => x * x. -cube: x => square(x) * x. \ No newline at end of file +square: x => x * x +cube: x => square(x) * x \ No newline at end of file diff --git a/documentation/coffee/overview.coffee b/documentation/coffee/overview.coffee index 8e44f8aab8..44e4ec9592 100644 --- a/documentation/coffee/overview.coffee +++ b/documentation/coffee/overview.coffee @@ -6,7 +6,7 @@ opposite_day: true number: -42 if opposite_day # Functions: -square: x => x * x. +square: x => x * x # Arrays: list: [1, 2, 3, 4, 5] @@ -15,8 +15,8 @@ list: [1, 2, 3, 4, 5] math: { root: Math.sqrt square: square - cube: x => x * square(x). + cube: x => x * square(x) } # Array comprehensions: -cubed_list: math.cube(num) for num in list. +cubed_list: math.cube(num) for num in list diff --git a/documentation/coffee/scope.coffee b/documentation/coffee/scope.coffee index f15b3eff12..691072ab5b 100644 --- a/documentation/coffee/scope.coffee +++ b/documentation/coffee/scope.coffee @@ -1,5 +1,5 @@ num: 1 change_numbers: => num: 2 - new_num: 3. + new_num: 3 new_num: change_numbers() \ No newline at end of file diff --git a/documentation/coffee/super.coffee b/documentation/coffee/super.coffee index 3e80e8045f..cc28c84d90 100644 --- a/documentation/coffee/super.coffee +++ b/documentation/coffee/super.coffee @@ -1,18 +1,18 @@ -Animal: => . +Animal: => Animal.prototype.move: meters => - alert(this.name + " moved " + meters + "m."). + alert(this.name + " moved " + meters + "m.") -Snake: name => this.name: name. +Snake: name => this.name: name Snake extends Animal Snake.prototype.move: => alert("Slithering...") - super(5). + super(5) -Horse: name => this.name: name. +Horse: name => this.name: name Horse extends Animal Horse.prototype.move: => alert("Galloping...") - super(45). + super(45) sam: new Snake("Sammy the Python") tom: new Horse("Tommy the Palomino") diff --git a/documentation/coffee/switch.coffee b/documentation/coffee/switch.coffee index 955adef6db..ee6963a386 100644 --- a/documentation/coffee/switch.coffee +++ b/documentation/coffee/switch.coffee @@ -4,6 +4,6 @@ when "Wednesday" then go_to_the_park() when "Saturday" if day is bingo_day go_to_bingo() - go_dancing(). + go_dancing() when "Sunday" then go_to_church() -else go_to_work(). \ No newline at end of file +else go_to_work() \ No newline at end of file diff --git a/documentation/coffee/try.coffee b/documentation/coffee/try.coffee index 8a2c627553..169df020af 100644 --- a/documentation/coffee/try.coffee +++ b/documentation/coffee/try.coffee @@ -4,4 +4,4 @@ try catch error print(error) finally - clean_up(). \ No newline at end of file + clean_up() \ No newline at end of file diff --git a/documentation/coffee/while.coffee b/documentation/coffee/while.coffee index ab0a850a0a..3ca4a9845b 100644 --- a/documentation/coffee/while.coffee +++ b/documentation/coffee/while.coffee @@ -1,5 +1,5 @@ while demand > supply sell() - restock(). + restock() -while supply > demand then buy(). \ No newline at end of file +while supply > demand then buy() \ No newline at end of file diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 552331cd77..1bbdde76dc 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -32,8 +32,8 @@ prechigh left '.' right INDENT left OUTDENT - right THROW FOR IN WHILE WHEN NEW SUPER IF THEN ELSE - left UNLESS EXTENDS + right THROW FOR IN WHILE WHEN NEW SUPER THEN ELSE + left UNLESS EXTENDS IF left ASSIGN '||=' '&&=' right RETURN '=>' preclow @@ -284,14 +284,14 @@ rule # Try/catch/finally exception handling blocks. Try: TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) } + | TRY Block FINALLY Block { result = TryNode.new(val[1], nil, nil, val[3]) } | TRY Block Catch FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) } ; # A catch clause. Catch: - /* nothing */ { result = [nil, nil] } - | CATCH IDENTIFIER Block { result = [val[1], val[2]] } + CATCH IDENTIFIER Block { result = [val[1], val[2]] } ; # Throw an exception. diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index e8318501aa..88305e733d 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -45,7 +45,7 @@ def test_parsing_if_statement end def test_parsing_array_comprehension - nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] if i % 2 is 0").expressions + nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] when i % 2 is 0").expressions assert nodes.first.is_a? ForNode assert nodes.first.body.literal == 'i' assert nodes.first.filter.operator == '===' @@ -78,6 +78,7 @@ def test_no_wrap end def test_no_wrapping_parens_around_statements + @par.parse("try thing() catch error fail()").compile assert_raises(SyntaxError) do @par.parse("(try thing() catch error fail())").compile end From 7c97f32dd3ad00b6b776038f1642ea2e421db70c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 12:59:05 -0500 Subject: [PATCH 165/303] ignoring test.coffee --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2eb797553a..4cc33381e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +test.coffee parser.output *.gem \ No newline at end of file From ce0e71f7966d81beed53f53d9729fc6a37a20e0a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 13:34:25 -0500 Subject: [PATCH 166/303] fixing the double-printing bug with coffee -r --- .../narwhal/coffee-script.coffee | 4 +--- .../narwhal/lib/coffee-script.js | 9 +------ lib/coffee_script/nodes.rb | 24 +++++++++---------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 645b083ad6..118d527b00 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -20,9 +20,7 @@ checkForErrors: coffeeProcess => # Run a simple REPL, round-tripping to the CoffeeScript compiler for every # command. exports.run: args => - if args.length - exports.evalCS(File.read(path)) for path in args. - return true. + return true if args.length while true try diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index 3879f04ab7..4dc8f35c49 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -18,15 +18,8 @@ // Run a simple REPL, round-tripping to the CoffeeScript compiler for every // command. exports.run = function(args) { - var __a, __b, __c, __d, path, result; + var result; if (args.length) { - __a = args; - __d = []; - for (__b=0, __c=__a.length; __b<__c; __b++) { - path = __a[__b]; - __d[__b] = exports.evalCS(File.read(path)); - } - __d; return true; } while (true) { diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 188055e9c7..e82e9f1a19 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -390,22 +390,22 @@ def compile(o={}) # CoffeeScript operations into their JavaScript equivalents. class OpNode < Node CONVERSIONS = { - "==" => "===", - "!=" => "!==", - 'and' => '&&', - 'or' => '||', - 'is' => '===', - "isnt" => "!==", - 'not' => '!' + :== => "===", + :'!=' => "!==", + :and => '&&', + :or => '||', + :is => '===', + :isnt => "!==", + :not => '!' } - CONDITIONALS = ['||=', '&&='] - PREFIX_OPERATORS = ['typeof', 'delete'] + CONDITIONALS = [:'||=', :'&&='] + PREFIX_OPERATORS = [:typeof, :delete] attr_reader :operator, :first, :second def initialize(operator, first, second=nil, flip=false) @first, @second, @flip = first, second, flip - @operator = CONVERSIONS[operator] || operator + @operator = CONVERSIONS[operator.to_sym] || operator end def unary? @@ -414,7 +414,7 @@ def unary? def compile(o={}) o = super(o) - return write(compile_conditional(o)) if CONDITIONALS.include?(@operator) + return write(compile_conditional(o)) if CONDITIONALS.include?(@operator.to_sym) return write(compile_unary(o)) if unary? write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}") end @@ -426,7 +426,7 @@ def compile_conditional(o) end def compile_unary(o) - space = PREFIX_OPERATORS.include?(@operator.to_s) ? ' ' : '' + space = PREFIX_OPERATORS.include?(@operator.to_sym) ? ' ' : '' parts = [@operator.to_s, space, @first.compile(o)] parts.reverse! if @flip parts.join('') From ea58be283810eea6233f5b99a271fab2c8d6caaa Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 13:58:00 -0500 Subject: [PATCH 167/303] patched up lexer to add indentation to single-line flavors of statements -- let's expand this idea --- lib/coffee_script/lexer.rb | 20 ++++++++++++++++++++ lib/coffee_script/nodes.rb | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 9dc9ec97c2..5f5fcbf88e 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -63,6 +63,7 @@ def tokenize(code) close_indentation remove_mid_expression_newlines move_commas_outside_outdents + add_implicit_indentation ensure_balance(*BALANCED_PAIRS) rewrite_closing_parens @tokens @@ -243,6 +244,25 @@ def move_commas_outside_outdents @tokens.insert(i - 1, token) end end + + # Because our grammar is LALR(1), it can't handle some single-line + # expressions that lack ending delimiters. Use the lexer to add the implicit + # blocks, so it doesn't need to. + def add_implicit_indentation + scan_tokens do |prev, token, post, i| + if token[0] == :ELSE && post[0] != :INDENT + line = token[1].line + @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) + loop do + i += 1 + if !@tokens[i] || ["\n", ")", :OUTDENT].include?(@tokens[i][0]) + @tokens.insert(i, [:OUTDENT, Value.new(2, line)]) + break + end + end + end + end + end # Ensure that all listed pairs of tokens are correctly balanced throughout # the course of the token stream. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index e82e9f1a19..5aa5d277d8 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -694,7 +694,7 @@ def rewrite_condition(expression) # Rewrite a chain of IfNodes to add a default case as the final else. def add_else(expressions) - chain? ? @else_body.add_else(expressions) : @else_body = expressions + chain? ? @else_body.add_else(expressions) : @else_body = expressions.unwrap self end From f93e552cb3c2c4440eacd1b87561122b43e19393 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 14:32:59 -0500 Subject: [PATCH 168/303] fixin up narwhal factory and adding more implicit blocks to the lexer --- lib/coffee_script/grammar.y | 16 ++++++-------- lib/coffee_script/lexer.rb | 8 +++++-- .../narwhal/coffee-script.coffee | 14 +++---------- .../narwhal/lib/coffee-script.js | 21 +++++++------------ .../narwhal/lib/coffee-script/loader.js | 10 +++++---- lib/coffee_script/narwhal/loader.coffee | 8 +++---- lib/coffee_script/nodes.rb | 4 ++-- 7 files changed, 35 insertions(+), 46 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 1bbdde76dc..1a14e4c783 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -80,6 +80,7 @@ rule Block: INDENT Expressions OUTDENT { result = val[1] } + | INDENT OUTDENT { result = Expressions.new([]) } ; # All tokens that can terminate an expression. @@ -186,10 +187,7 @@ rule # Function definition. Code: ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) } - | ParamList "=>" Expression { result = CodeNode.new(val[0], Expressions.new([val[2]])) } | "=>" Block { result = CodeNode.new([], val[1]) } - | "=>" Expression { result = CodeNode.new([], Expressions.new([val[1]])) } - | "=>" { result = CodeNode.new([], Expressions.new([])) } ; # The parameters to a function definition. @@ -306,7 +304,7 @@ rule # The while loop. (there is no do..while). While: - WHILE Expression Block { result = WhileNode.new(val[1], val[2]) } + WHILE Expression Block { result = WhileNode.new(val[1], val[2]) } ; # Array comprehensions, including guard and current index. @@ -325,9 +323,9 @@ rule # The source of the array comprehension can optionally be filtered. ForSource: - IN Expression { result = [val[1]] } + IN Expression { result = [val[1]] } | IN Expression - WHEN Expression { result = [val[1], val[3]] } + WHEN Expression { result = [val[1], val[3]] } ; # Switch/When blocks. @@ -336,8 +334,6 @@ rule Whens OUTDENT { result = val[3].rewrite_condition(val[1]) } | SWITCH Expression INDENT Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } - | SWITCH Expression INDENT - Whens ELSE Expression OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) } ; # The inner list of whens. @@ -387,8 +383,8 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: IfBlock IfEnd { result = val[0].add_else(val[1]) } - | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } - | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } + | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } + | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } ; end diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 5f5fcbf88e..20f227ca20 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -44,6 +44,10 @@ class Lexer # Outdents that come before these tokens don't signify the end of the # expression. TODO: Is this safe? EXPRESSION_TAIL = [:CATCH, :WHEN, :ELSE, ')', ']', '}'] + + # Single-line flavors of block expressions that have unclosed endings. + # The grammar can't disambiguate them, so we insert the implicit indentation. + SINGLE_LINERS = [:ELSE, "=>"] # The inverse mappings of token pairs we're trying to fix up. INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} @@ -250,12 +254,12 @@ def move_commas_outside_outdents # blocks, so it doesn't need to. def add_implicit_indentation scan_tokens do |prev, token, post, i| - if token[0] == :ELSE && post[0] != :INDENT + if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT line = token[1].line @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) loop do i += 1 - if !@tokens[i] || ["\n", ")", :OUTDENT].include?(@tokens[i][0]) + if !@tokens[i] || @tokens[i][0] == "\n" @tokens.insert(i, [:OUTDENT, Value.new(2, line)]) break end diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 95846c94ec..0d57256e02 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -20,7 +20,9 @@ checkForErrors: coffeeProcess => # Run a simple REPL, round-tripping to the CoffeeScript compiler for every # command. exports.run: args => - return true if args.length + if args.length + exports.evalCS(File.read(path)) for path in args + return true while true try @@ -46,13 +48,3 @@ exports.compile: source => # Evaluating a string of CoffeeScript first compiles it externally. exports.evalCS: source => eval(exports.compile(source)) - -# Make a factory for the CoffeeScript environment. -exports.makeNarwhalFactory: path => - code: exports.compileFile(path) - factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}" - if system.engine is "rhino" - Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) - else - # eval requires parentheses, but parentheses break compileFunction. - eval("(" + factoryText + ")") diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index 9677a324fd..0b63db0ed8 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -18,8 +18,15 @@ // Run a simple REPL, round-tripping to the CoffeeScript compiler for every // command. exports.run = function(args) { - var result; + var __a, __b, __c, __d, path, result; if (args.length) { + __a = args; + __d = []; + for (__b=0, __c=__a.length; __b<__c; __b++) { + path = __a[__b]; + __d[__b] = exports.evalCS(File.read(path)); + } + __d; return true; } while (true) { @@ -53,16 +60,4 @@ exports.evalCS = function(source) { return eval(exports.compile(source)); }; - // Make a factory for the CoffeeScript environment. - exports.makeNarwhalFactory = function(path) { - var code, factoryText; - code = exports.compileFile(path); - factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; - if (system.engine === "rhino") { - return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); - } else { - // eval requires parentheses, but parentheses break compileFunction. - return eval("(" + factoryText + ")"); - } - }; })(); \ No newline at end of file diff --git a/lib/coffee_script/narwhal/lib/coffee-script/loader.js b/lib/coffee_script/narwhal/lib/coffee-script/loader.js index 1a79dbbbac..609385a7dc 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script/loader.js +++ b/lib/coffee_script/narwhal/lib/coffee-script/loader.js @@ -6,13 +6,15 @@ }; loader = { // Reload the coffee-script environment from source. - reload: function(topId, path) { + reload: function(topId) { coffeescript = coffeescript || require('coffee-script'); - return (factories[topId] = coffeescript.makeNarwhalFactory(path)); + return (factories[topId] = function() { + return coffeescript; + }); }, // Ensure that the coffee-script environment is loaded. - load: function(topId, path) { - return factories[topId] = factories[topId] || this.reload(topId, path); + load: function(topId) { + return factories[topId] = factories[topId] || this.reload(topId); } }; require.loader.loaders.unshift([".coffee", loader]); diff --git a/lib/coffee_script/narwhal/loader.coffee b/lib/coffee_script/narwhal/loader.coffee index ccdcacbf20..e55f7ba742 100644 --- a/lib/coffee_script/narwhal/loader.coffee +++ b/lib/coffee_script/narwhal/loader.coffee @@ -6,13 +6,13 @@ factories: {} loader: { # Reload the coffee-script environment from source. - reload: topId, path => + reload: topId => coffeescript ||= require('coffee-script') - factories[topId]: coffeescript.makeNarwhalFactory(path) + factories[topId]: => coffeescript # Ensure that the coffee-script environment is loaded. - load: topId, path => - factories[topId] ||= this.reload(topId, path) + load: topId => + factories[topId] ||= this.reload(topId) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 5aa5d277d8..82077906a9 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -693,8 +693,8 @@ def rewrite_condition(expression) end # Rewrite a chain of IfNodes to add a default case as the final else. - def add_else(expressions) - chain? ? @else_body.add_else(expressions) : @else_body = expressions.unwrap + def add_else(exprs) + chain? ? @else_body.add_else(exprs) : @else_body = (exprs && exprs.unwrap) self end From 9e3ef42c63b91a2f9aae0c43c4f9f73c90b3bc2d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 15:05:05 -0500 Subject: [PATCH 169/303] fixing up narwhal integration (again) --- .../narwhal/coffee-script.coffee | 11 ++++++++++ .../narwhal/lib/coffee-script.js | 22 ++++++++++++++++++- .../narwhal/lib/coffee-script/loader.js | 8 +++---- lib/coffee_script/narwhal/loader.coffee | 8 +++---- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 0d57256e02..289e58b4a4 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -22,6 +22,7 @@ checkForErrors: coffeeProcess => exports.run: args => if args.length exports.evalCS(File.read(path)) for path in args + delete args[i] for path, i in args return true while true @@ -48,3 +49,13 @@ exports.compile: source => # Evaluating a string of CoffeeScript first compiles it externally. exports.evalCS: source => eval(exports.compile(source)) + +# Make a factory for the CoffeeScript environment. +exports.makeNarwhalFactory: path => + code: exports.compileFile(path) + factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}" + if system.engine is "rhino" + Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null) + else + # eval requires parentheses, but parentheses break compileFunction. + eval("(" + factoryText + ")") diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index 0b63db0ed8..12ec02b1be 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -18,7 +18,7 @@ // Run a simple REPL, round-tripping to the CoffeeScript compiler for every // command. exports.run = function(args) { - var __a, __b, __c, __d, path, result; + var __a, __b, __c, __d, __e, __f, __g, __h, i, path, result; if (args.length) { __a = args; __d = []; @@ -27,6 +27,14 @@ __d[__b] = exports.evalCS(File.read(path)); } __d; + __e = args; + __h = []; + for (__f=0, __g=__e.length; __f<__g; __f++) { + path = __e[__f]; + i = __f; + __h[__f] = delete args[i]; + } + __h; return true; } while (true) { @@ -60,4 +68,16 @@ exports.evalCS = function(source) { return eval(exports.compile(source)); }; + // Make a factory for the CoffeeScript environment. + exports.makeNarwhalFactory = function(path) { + var code, factoryText; + code = exports.compileFile(path); + factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; + if (system.engine === "rhino") { + return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null); + } else { + // eval requires parentheses, but parentheses break compileFunction. + return eval("(" + factoryText + ")"); + } + }; })(); \ No newline at end of file diff --git a/lib/coffee_script/narwhal/lib/coffee-script/loader.js b/lib/coffee_script/narwhal/lib/coffee-script/loader.js index 609385a7dc..b668033239 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script/loader.js +++ b/lib/coffee_script/narwhal/lib/coffee-script/loader.js @@ -6,15 +6,15 @@ }; loader = { // Reload the coffee-script environment from source. - reload: function(topId) { + reload: function(topId, path) { coffeescript = coffeescript || require('coffee-script'); return (factories[topId] = function() { - return coffeescript; + return coffeescript.makeNarwhalFactory(path); }); }, // Ensure that the coffee-script environment is loaded. - load: function(topId) { - return factories[topId] = factories[topId] || this.reload(topId); + load: function(topId, path) { + return factories[topId] = factories[topId] || this.reload(topId, path); } }; require.loader.loaders.unshift([".coffee", loader]); diff --git a/lib/coffee_script/narwhal/loader.coffee b/lib/coffee_script/narwhal/loader.coffee index e55f7ba742..ea6e2a0edc 100644 --- a/lib/coffee_script/narwhal/loader.coffee +++ b/lib/coffee_script/narwhal/loader.coffee @@ -6,13 +6,13 @@ factories: {} loader: { # Reload the coffee-script environment from source. - reload: topId => + reload: topId, path => coffeescript ||= require('coffee-script') - factories[topId]: => coffeescript + factories[topId]: => coffeescript.makeNarwhalFactory(path) # Ensure that the coffee-script environment is loaded. - load: topId => - factories[topId] ||= this.reload(topId) + load: topId, path => + factories[topId] ||= this.reload(topId, path) } From c822f961452290e81961a802ae3b0b557779208a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 15:10:47 -0500 Subject: [PATCH 170/303] patching up the lexer and adding a test with trailing whitespace (it was too string for trailing whitespace before) --- lib/coffee_script/lexer.rb | 2 +- test/fixtures/execution/test_calling_super.coffee | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 20f227ca20..27f0795e03 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -27,7 +27,7 @@ class Lexer COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - MULTI_DENT = /\A((\n+[ \t]*)+)/ + MULTI_DENT = /\A((\n+([ \t]*(?=\S)))+)/ LAST_DENT = /\n+([ \t]*)\Z/ # Token cleaning regexes. diff --git a/test/fixtures/execution/test_calling_super.coffee b/test/fixtures/execution/test_calling_super.coffee index d2d6ed0d4c..8a9d4bc1ff 100644 --- a/test/fixtures/execution/test_calling_super.coffee +++ b/test/fixtures/execution/test_calling_super.coffee @@ -6,12 +6,12 @@ FirstChild: => FirstChild extends Base FirstChild.prototype.func: string => super('one/') + string - + SecondChild: => SecondChild extends FirstChild SecondChild.prototype.func: string => super('two/') + string - + ThirdChild: => ThirdChild extends SecondChild ThirdChild.prototype.func: string => From 9a946d2ac573c6c2480afd7fa38edd7d06e01714 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 15:52:07 -0500 Subject: [PATCH 171/303] execution tests still pass -- more lexer block insertion and 2 shift/reduces in the grammar now --- lib/coffee_script/grammar.y | 15 +++++++------- lib/coffee_script/lexer.rb | 13 +++++++----- .../execution/test_assign_to_try_catch.coffee | 5 ++++- test/fixtures/execution/test_switch.coffee | 2 +- test/unit/test_parser.rb | 20 +++++++++---------- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 1a14e4c783..fdbf88c501 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -1,7 +1,7 @@ class Parser # Declare tokens produced by the lexer -token IF ELSE THEN UNLESS +token IF ELSE UNLESS token NUMBER STRING REGEX token TRUE FALSE YES NO ON OFF token IDENTIFIER PROPERTY_ACCESS @@ -32,15 +32,15 @@ prechigh left '.' right INDENT left OUTDENT - right THROW FOR IN WHILE WHEN NEW SUPER THEN ELSE + right THROW FOR IN WHILE WHEN NEW SUPER ELSE left UNLESS EXTENDS IF left ASSIGN '||=' '&&=' right RETURN '=>' preclow -# We expect 4 shift/reduce errors for optional syntax. +# We expect 2 shift/reduce errors for optional syntax. # There used to be 252 -- greatly improved. -expect 4 +expect 2 rule @@ -344,9 +344,9 @@ rule # An individual when. When: - WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } - | WHEN Expression - THEN Expression Terminator { result = IfNode.new(val[1], val[3], nil, {:statement => true}) } + WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } + | WHEN Expression Block Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } + | Comment ; # All of the following nutso if-else destructuring is to make the @@ -354,7 +354,6 @@ rule IfBlock: IF Expression Block { result = IfNode.new(val[1], val[2]) } - | IF Expression THEN Expression { result = IfNode.new(val[1], val[3]) } ; # An elsif portion of an if-else block. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 27f0795e03..e7ac718bbb 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -27,7 +27,7 @@ class Lexer COMMENT = /\A((#[^\n]*\s*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - MULTI_DENT = /\A((\n+([ \t]*(?=\S)))+)/ + MULTI_DENT = /\A((\n+([ \t]*(?=\S))?)+)/ LAST_DENT = /\n+([ \t]*)\Z/ # Token cleaning regexes. @@ -47,7 +47,8 @@ class Lexer # Single-line flavors of block expressions that have unclosed endings. # The grammar can't disambiguate them, so we insert the implicit indentation. - SINGLE_LINERS = [:ELSE, "=>"] + SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] + SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE] # The inverse mappings of token pairs we're trying to fix up. INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} @@ -257,13 +258,15 @@ def add_implicit_indentation if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT line = token[1].line @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) + idx = i + 1 loop do - i += 1 - if !@tokens[i] || @tokens[i][0] == "\n" - @tokens.insert(i, [:OUTDENT, Value.new(2, line)]) + idx += 1 + if !@tokens[idx] || SINGLE_CLOSERS.include?(@tokens[idx][0]) + @tokens.insert(idx, [:OUTDENT, Value.new(2, line)]) break end end + @tokens.delete_at(i) if token[0] == :THEN end end end diff --git a/test/fixtures/execution/test_assign_to_try_catch.coffee b/test/fixtures/execution/test_assign_to_try_catch.coffee index c635bbf0e5..9de8178f02 100644 --- a/test/fixtures/execution/test_assign_to_try_catch.coffee +++ b/test/fixtures/execution/test_assign_to_try_catch.coffee @@ -2,4 +2,7 @@ result: try nonexistent * missing catch error true -print(result) \ No newline at end of file + +result2: try nonexistent * missing catch error then true + +print(result is true and result2 is true) \ No newline at end of file diff --git a/test/fixtures/execution/test_switch.coffee b/test/fixtures/execution/test_switch.coffee index 2fc7da813d..dad4335896 100644 --- a/test/fixtures/execution/test_switch.coffee +++ b/test/fixtures/execution/test_switch.coffee @@ -9,5 +9,5 @@ result: switch num when 10 then true when 11 then false else false - + print(result) diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 88305e733d..83d6ac645e 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -8,7 +8,7 @@ def setup def test_parsing_an_empty_string nodes = @par.parse("") - assert nodes.is_a? Expressions + assert nodes.is_a?(Expressions) assert nodes.expressions.empty? end @@ -16,14 +16,14 @@ def test_parsing_a_basic_assignment nodes = @par.parse("a: 'one'").expressions assert nodes.length == 1 assign = nodes.first - assert assign.is_a? AssignNode + assert assign.is_a?(AssignNode) assert assign.variable.literal == 'a' end def test_parsing_an_object_literal nodes = @par.parse("{one : 1\ntwo : 2}").expressions obj = nodes.first.literal - assert obj.is_a? ObjectNode + assert obj.is_a?(ObjectNode) assert obj.properties.first.variable.literal.value == "one" assert obj.properties.last.variable.literal.value == "two" end @@ -32,21 +32,21 @@ def test_parsing_an_function_definition code = @par.parse("x, y => x * y").expressions.first assert code.params == ['x', 'y'] body = code.body.expressions.first - assert body.is_a? OpNode + assert body.is_a?(OpNode) assert body.operator == '*' end def test_parsing_if_statement the_if = @par.parse("clap_your_hands() if happy").expressions.first - assert the_if.is_a? IfNode + assert the_if.is_a?(IfNode) assert the_if.condition.literal == 'happy' - assert the_if.body.is_a? CallNode + assert the_if.body.is_a?(CallNode) assert the_if.body.variable.literal == 'clap_your_hands' end def test_parsing_array_comprehension nodes = @par.parse("i for x, i in [10, 9, 8, 7, 6, 5] when i % 2 is 0").expressions - assert nodes.first.is_a? ForNode + assert nodes.first.is_a?(ForNode) assert nodes.first.body.literal == 'i' assert nodes.first.filter.operator == '===' assert nodes.first.source.literal.objects.last.literal.value == "5" @@ -54,7 +54,7 @@ def test_parsing_array_comprehension def test_parsing_comment nodes = @par.parse("a: 1\n# comment\nb: 2").expressions - assert nodes[1].is_a? CommentNode + assert nodes[1].is_a?(CommentNode) end def test_parsing_inner_comments @@ -65,9 +65,9 @@ def test_parsing_inner_comments def test_parsing nodes = @par.parse(File.read('test/fixtures/generation/each.coffee')) assign = nodes.expressions[1] - assert assign.is_a? AssignNode + assert assign.is_a?(AssignNode) assert assign.variable.literal == '_' - assert assign.value.is_a? CodeNode + assert assign.value.is_a?(CodeNode) assert assign.value.params == ['obj', 'iterator', 'context'] assert nodes.compile == File.read('test/fixtures/generation/each.js') end From d528db76e683f021dde175b503d6f941bd1fc168 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 17:41:14 -0500 Subject: [PATCH 172/303] special case for 'else if' in the lexer --- lib/coffee_script/lexer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index e7ac718bbb..37fe5fce36 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -255,7 +255,8 @@ def move_commas_outside_outdents # blocks, so it doesn't need to. def add_implicit_indentation scan_tokens do |prev, token, post, i| - if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT + if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT && + !(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks. line = token[1].line @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) idx = i + 1 From 62c51006a132cadbc0788aad664674dbfcdc9f84 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 17:45:47 -0500 Subject: [PATCH 173/303] removed the final shift/reduce errors -- back to zero for the first time in a long time --- lib/coffee_script/grammar.y | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index fdbf88c501..c30bed97f0 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -38,10 +38,6 @@ prechigh right RETURN '=>' preclow -# We expect 2 shift/reduce errors for optional syntax. -# There used to be 252 -- greatly improved. -expect 2 - rule # All parsing will end in this rule, being the trunk of the AST. @@ -56,7 +52,6 @@ rule Expression { result = Expressions.new(val) } | Expressions Terminator Expression { result = val[0] << val[2] } | Expressions Terminator { result = val[0] } - | Terminator Expressions { result = val[1] } ; # All types of expressions in our language. From 9a7c075e067638350937b98aff1fc83f12c56712 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 17:58:27 -0500 Subject: [PATCH 174/303] parser and test tweaks for whitespace -- tests are coming along --- documentation/coffee/switch.coffee | 16 ++++++++-------- lib/coffee_script/lexer.rb | 6 +++--- test/fixtures/execution/test_everything.coffee | 2 ++ test/unit/test_parser.rb | 4 ++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/documentation/coffee/switch.coffee b/documentation/coffee/switch.coffee index ee6963a386..7d9605e610 100644 --- a/documentation/coffee/switch.coffee +++ b/documentation/coffee/switch.coffee @@ -1,9 +1,9 @@ switch day -when "Tuesday" then eat_breakfast() -when "Wednesday" then go_to_the_park() -when "Saturday" - if day is bingo_day - go_to_bingo() - go_dancing() -when "Sunday" then go_to_church() -else go_to_work() \ No newline at end of file + when "Tuesday" then eat_breakfast() + when "Wednesday" then go_to_the_park() + when "Saturday" + if day is bingo_day + go_to_bingo() + go_dancing() + when "Sunday" then go_to_church() + else go_to_work() \ No newline at end of file diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 37fe5fce36..9f3cd91218 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -42,13 +42,13 @@ class Lexer BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]] # Outdents that come before these tokens don't signify the end of the - # expression. TODO: Is this safe? - EXPRESSION_TAIL = [:CATCH, :WHEN, :ELSE, ')', ']', '}'] + # expression. + EXPRESSION_TAIL = [:CATCH, :WHEN, :ELSE, :FINALLY, ')', ']', '}'] # Single-line flavors of block expressions that have unclosed endings. # The grammar can't disambiguate them, so we insert the implicit indentation. SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] - SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE] + SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT] # The inverse mappings of token pairs we're trying to fix up. INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} diff --git a/test/fixtures/execution/test_everything.coffee b/test/fixtures/execution/test_everything.coffee index 512f219bd3..87d444925c 100644 --- a/test/fixtures/execution/test_everything.coffee +++ b/test/fixtures/execution/test_everything.coffee @@ -8,6 +8,8 @@ func: => c: { "text": b + other: null + something_else: x => x + 5 } c: 'error' unless 42 > 41 diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 83d6ac645e..95ee835e62 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -78,9 +78,9 @@ def test_no_wrap end def test_no_wrapping_parens_around_statements - @par.parse("try thing() catch error fail()").compile + @par.parse("try thing() catch error then fail()").compile assert_raises(SyntaxError) do - @par.parse("(try thing() catch error fail())").compile + @par.parse("(try thing() catch error then fail())").compile end end From 5659d1bd498a2c880664962adf98c713a91a8f87 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 18:09:43 -0500 Subject: [PATCH 175/303] adding ')' as a SINGLE_CLOSER, although it's probably unsafe --- examples/code.coffee | 76 ++++++++++---------- lib/coffee_script/lexer.rb | 3 +- test/fixtures/execution/test_literals.coffee | 3 + 3 files changed, 42 insertions(+), 40 deletions(-) create mode 100644 test/fixtures/execution/test_literals.coffee diff --git a/examples/code.coffee b/examples/code.coffee index 23458fd834..2986df375d 100644 --- a/examples/code.coffee +++ b/examples/code.coffee @@ -1,16 +1,16 @@ # Functions: -square: x => x * x. +square: x => x * x -sum: x, y => x + y. +sum: x, y => x + y -odd: x => x % 2 is 0. +odd: x => x % 2 is 0 -even: x => x % 2 isnt 0. +even: x => x % 2 isnt 0 run_loop: => - fire_events( e => e.stopPropagation(). ) + fire_events(e => e.stopPropagation()) listen() - wait(). + wait() # Objects: dense_object_literal: {one: 1, two: 2, three: 3} @@ -22,14 +22,14 @@ spaced_out_multiline_object: { three: new Idea() inner_obj: { - freedom: => _.freedom(). + freedom: => _.freedom() } } # Arrays: stooges : [{moe: 45}, {curly: 43}, {larry: 46}] -exponents : [x => x., x => x * x., x => x * x * x.] +exponents : [(x => x), (x => x * x), (x => x * x * x)] empty: [] @@ -45,9 +45,9 @@ if submarine.shields_up else if submarine.sinking abandon_ship() else - run_away(). + run_away() -eldest: if 25 > 21 then liz else marge. +eldest: if 25 > 21 then liz else marge decoration: medal_of_honor if war_hero @@ -58,8 +58,8 @@ race: => run() walk() crawl() - if tired then return sleep(). - race(). + if tired then return sleep() + race() # Conditional assignment: good ||= evil @@ -81,22 +81,22 @@ try dogs_and_cats_living_together() throw "up" catch error - print( error ) + print(error) finally - clean_up(). + clean_up() -try all_hell_breaks_loose() catch error print(error) finally clean_up(). +try all_hell_breaks_loose() catch error then print(error) finally clean_up() # While loops, break and continue. while demand > supply sell() - restock(). + restock() -while supply > demand then buy(). +while supply > demand then buy() while true break if broken - continue if continuing. + continue if continuing # Unary operators. !!true @@ -105,30 +105,30 @@ while true a: 5 change_a_and_set_b: => a: 10 - b: 15. + b: 15 b: 20 # Array comprehensions. -supper: food.capitalize() for food in ['toast', 'cheese', 'wine']. +supper: food.capitalize() for food in ['toast', 'cheese', 'wine'] -drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] if even(i). +drink(bottle) for bottle, i in ['soda', 'wine', 'lemonade'] when even(i) # Switch statements ("else" serves as a default). activity: switch day -when "Tuesday" then eat_breakfast() -when "Sunday" then go_to_church() -when "Saturday" then go_to_the_park() -when "Wednesday" - if day is bingo_day - go_to_bingo() - else - eat_breakfast() - go_to_work() - eat_dinner(). -else go_to_work(). + when "Tuesday" then eat_breakfast() + when "Sunday" then go_to_church() + when "Saturday" then go_to_the_park() + when "Wednesday" + if day is bingo_day + go_to_bingo() + else + eat_breakfast() + go_to_work() + eat_dinner() + else go_to_work() # Semicolons can optionally be used instead of newlines. -wednesday: => eat_breakfast(); go_to_work(); eat_dinner(); . +wednesday: => eat_breakfast(); go_to_work(); eat_dinner() # Array slice literals. zero_to_nine: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] @@ -140,21 +140,21 @@ sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad." # Inheritance and calling super. -Animal: => . +Animal: => Animal.prototype.move: meters => - alert(this.name + " moved " + meters + "m."). + alert(this.name + " moved " + meters + "m.") -Snake: name => this.name: name. +Snake: name => this.name: name Snake extends Animal Snake.prototype.move: => alert('Slithering...') - super(5). + super(5) Horse: name => this.name: name. Horse extends Animal Horse.prototype.move: => alert('Galloping...') - super(45). + super(45) sam: new Snake("Sammy the Snake") tom: new Horse("Tommy the Horse") diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 9f3cd91218..b3bbae4040 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -48,7 +48,7 @@ class Lexer # Single-line flavors of block expressions that have unclosed endings. # The grammar can't disambiguate them, so we insert the implicit indentation. SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] - SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT] + SINGLE_CLOSERS = ["\n", ")", :CATCH, :FINALLY, :ELSE, :OUTDENT] # The inverse mappings of token pairs we're trying to fix up. INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} @@ -175,7 +175,6 @@ def whitespace_token # Use a trailing \ to escape newlines. def newline_token(newlines) lines = newlines.scan(MULTILINER).length - @line += lines token("\n", "\n") unless ["\n", "\\"].include?(last_value) @tokens.pop if last_value == "\\" true diff --git a/test/fixtures/execution/test_literals.coffee b/test/fixtures/execution/test_literals.coffee new file mode 100644 index 0000000000..73a5b4d259 --- /dev/null +++ b/test/fixtures/execution/test_literals.coffee @@ -0,0 +1,3 @@ +a: [(x => x), (x => x * x)] + +print(a.length is 2) \ No newline at end of file From ed1c1f7a0ef0590063797f0dc04cbcb032fe15c8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 18:28:16 -0500 Subject: [PATCH 176/303] fixing up documents example --- examples/documents.coffee | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/documents.coffee b/examples/documents.coffee index 1908a88bc4..439dd4ca56 100644 --- a/examples/documents.coffee +++ b/examples/documents.coffee @@ -1,7 +1,7 @@ # Document Model dc.model.Document: dc.Model.extend({ - constructor: attributes => this.base(attributes). + constructor: attributes => this.base(attributes) # For display, show either the highlighted search results, or the summary, # if no highlights are available. @@ -9,22 +9,22 @@ dc.model.Document: dc.Model.extend({ # version of the summary has all runs of whitespace squeezed out. displaySummary: => text: this.get('highlight') or this.get('summary') or '' - text and text.replace(/\s+/g, ' '). + text and text.replace(/\s+/g, ' ') # Return a list of the document's metadata. Think about caching this on the # document by binding to Metadata, instead of on-the-fly. metadata: => docId: this.id - _.select(Metadata.models() - meta => _.any(meta.get('instances') - instance => instance.document_id is docId.).). + _.select(Metadata.models(), (meta => + _.any(meta.get('instances'), instance => + instance.document_id is docId))) bookmark: pageNumber => bookmark: new dc.model.Bookmark({title: this.get('title'), page_number: pageNumber, document_id: this.id}) - Bookmarks.create(bookmark). + Bookmarks.create(bookmark) # Inspect. - toString: => 'Document ' + this.id + ' "' + this.get('title') + '"'. + toString: => 'Document ' + this.id + ' "' + this.get('title') + '"' }) @@ -37,31 +37,31 @@ dc.model.DocumentSet: dc.model.RESTfulSet.extend({ constructor: options => this.base(options) - _.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText'). + _.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText') - selected: => _.select(this.models(), m => m.get('selected').). + selected: => _.select(this.models(), m => m.get('selected')) - selectedIds: => _.pluck(this.selected(), 'id'). + selectedIds: => _.pluck(this.selected(), 'id') - countSelected: => this.selected().length. + countSelected: => this.selected().length downloadSelectedViewers: => - dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip'). + dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip') downloadSelectedPDF: => - if this.countSelected() <= 1 then return window.open(this.selected()[0].get('pdf_url')). - dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip'). + if this.countSelected() <= 1 then return window.open(this.selected()[0].get('pdf_url')) + dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip') downloadSelectedFullText: => - if this.countSelected() <= 1 then return window.open(this.selected()[0].get('full_text_url')). - dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip'). + if this.countSelected() <= 1 then return window.open(this.selected()[0].get('full_text_url')) + dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip') # We override "_onModelEvent" to fire selection changed events when documents # change their selected state. _onModelEvent: e, model => this.base(e, model) fire: e == dc.Model.CHANGED and model.hasChanged('selected') - if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this)).. + if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this)) }) From e6cd32f2fcd9a4c0ce8cc028cb75fee43c93bfbc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 18:52:03 -0500 Subject: [PATCH 177/303] balancing parens closing single-line blocks --- lib/coffee_script/lexer.rb | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index b3bbae4040..bf588c4876 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -48,7 +48,7 @@ class Lexer # Single-line flavors of block expressions that have unclosed endings. # The grammar can't disambiguate them, so we insert the implicit indentation. SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] - SINGLE_CLOSERS = ["\n", ")", :CATCH, :FINALLY, :ELSE, :OUTDENT] + SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT] # The inverse mappings of token pairs we're trying to fix up. INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} @@ -252,6 +252,7 @@ def move_commas_outside_outdents # Because our grammar is LALR(1), it can't handle some single-line # expressions that lack ending delimiters. Use the lexer to add the implicit # blocks, so it doesn't need to. + # ')' can close a single-line block, but we need to make sure it's balanced. def add_implicit_indentation scan_tokens do |prev, token, post, i| if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT && @@ -259,12 +260,17 @@ def add_implicit_indentation line = token[1].line @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) idx = i + 1 + parens = 0 loop do idx += 1 - if !@tokens[idx] || SINGLE_CLOSERS.include?(@tokens[idx][0]) + tok = @tokens[idx] + if !tok || SINGLE_CLOSERS.include?(tok[0]) || + (tok[0] == ')' && parens == 0) @tokens.insert(idx, [:OUTDENT, Value.new(2, line)]) break end + parens += 1 if tok[0] == '(' + parens -= 1 if tok[0] == ')' end @tokens.delete_at(i) if token[0] == :THEN end @@ -301,24 +307,34 @@ def ensure_balance(*pairs) # up balanced in the end. # def rewrite_closing_parens + verbose = ENV['VERBOSE'] stack, debt = [], Hash.new(0) + stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}" } + puts "original stream: #{@tokens.inspect}" if verbose scan_tokens do |prev, token, post, i| tag, inv = token[0], INVERSES[token[0]] - stack.push(token) if [:INDENT, '('].include?(tag) - if [:OUTDENT, ')'].include?(tag) + if [:INDENT, '('].include?(tag) + stack.push(token) + puts "pushing #{tag} #{stack_stats[]}" if verbose + elsif [:OUTDENT, ')'].include?(tag) # If the tag is already in our debt, swallow it. if debt[inv] > 0 debt[inv] -= 1 @tokens.delete_at(i) + puts "tag in debt #{tag} #{stack_stats[]}" if verbose next end # Pop the stack of open delimiters. match = stack.pop mtag = match[0] # Continue onwards if it's the expected tag. - next if tag == INVERSES[mtag] + if tag == INVERSES[mtag] + puts "expected tag #{tag} #{stack_stats[]}" if verbose + next + end # Unexpected close, insert correct close, adding to the debt. debt[mtag] += 1 + puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose val = mtag == :INDENT ? match[1] : ')' @tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)]) end From cd68f02981142db269719fe5f5904621852ae0ec Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 18:59:33 -0500 Subject: [PATCH 178/303] big milestone. examples/code.coffee now compiles correctly under the new whitespace regime --- examples/code.coffee | 2 +- lib/coffee_script/grammar.y | 2 +- lib/coffee_script/nodes.rb | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/code.coffee b/examples/code.coffee index 2986df375d..7c327ad376 100644 --- a/examples/code.coffee +++ b/examples/code.coffee @@ -150,7 +150,7 @@ Snake.prototype.move: => alert('Slithering...') super(5) -Horse: name => this.name: name. +Horse: name => this.name: name Horse extends Animal Horse.prototype.move: => alert('Galloping...') diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index c30bed97f0..6e6b377ab4 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -353,7 +353,7 @@ rule # An elsif portion of an if-else block. ElsIf: - ELSE IfBlock { result = val[1] } + ELSE IfBlock { result = val[1].force_statement } ; # Multiple elsifs can be chained together. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 82077906a9..11d5f1f444 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -684,6 +684,11 @@ def <<(else_body) @else_body ? @else_body << eb : @else_body = eb self end + + def force_statement + @tags[:statement] = true + self + end # Rewrite a chain of IfNodes with their switch condition for equality. def rewrite_condition(expression) From dc821f2e4c7e793f721cfebc3634861093c58da8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 19:26:37 -0500 Subject: [PATCH 179/303] be more vigorous about removing mid-expression newlines, 'when' closes implicit blocks, a better comment-detecting regex lexer that doesn't eat outdents --- lib/coffee_script/lexer.rb | 13 ++++++++----- lib/coffee_script/nodes.rb | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index bf588c4876..1f13c83c45 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -24,7 +24,7 @@ class Lexer JS = /\A(``|`(.*?)[^\\]`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t]+)/ - COMMENT = /\A((#[^\n]*\s*)+)/m + COMMENT = /\A((#[^\n]*(\s(?=#))*)+)/m CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ MULTI_DENT = /\A((\n+([ \t]*(?=\S))?)+)/ @@ -43,12 +43,13 @@ class Lexer # Outdents that come before these tokens don't signify the end of the # expression. - EXPRESSION_TAIL = [:CATCH, :WHEN, :ELSE, :FINALLY, ')', ']', '}'] + EXPRESSION_START = [:INDENT, '{', '(', '['] + EXPRESSION_TAIL = [:CATCH, :OUTDENT, :WHEN, :ELSE, :FINALLY, ')', ']', '}'] # Single-line flavors of block expressions that have unclosed endings. # The grammar can't disambiguate them, so we insert the implicit indentation. SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] - SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT] + SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :WHEN] # The inverse mappings of token pairs we're trying to fix up. INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} @@ -65,6 +66,7 @@ def tokenize(code) @chunk = @code[@i..-1] extract_next_token end + puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE'] close_indentation remove_mid_expression_newlines move_commas_outside_outdents @@ -235,7 +237,9 @@ def scan_tokens # this, remove their trailing newlines. def remove_mid_expression_newlines scan_tokens do |prev, token, post, i| - @tokens.delete_at(i) if post && EXPRESSION_TAIL.include?(post[0]) && token[0] == "\n" && prev[0] == :OUTDENT + @tokens.delete_at(i) if post && EXPRESSION_TAIL.include?(post[0]) && + token[0] == "\n" # && + # EXPRESSION_START.include?(prev[0]) end end @@ -310,7 +314,6 @@ def rewrite_closing_parens verbose = ENV['VERBOSE'] stack, debt = [], Hash.new(0) stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}" } - puts "original stream: #{@tokens.inspect}" if verbose scan_tokens do |prev, token, post, i| tag, inv = token[0], INVERSES[token[0]] if [:INDENT, '('].include?(tag) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 11d5f1f444..7b26876f6d 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -469,8 +469,8 @@ def compile(o={}) indent = o[:indent] o[:indent] += TAB props = @properties.map { |prop| - joiner = prop == @properties.last ? '' : prop.is_a?(CommentNode) ? "\n" : ",\n" - o[:indent] + prop.compile(o) + joiner + joiner = prop == @properties.first ? '' : prop.is_a?(CommentNode) ? "\n" : ",\n" + joiner + o[:indent] + prop.compile(o) }.join('') write("{\n#{props}\n#{indent}}") end From 5c7b77aa4d898f51dd60df246538ed6990fbc962 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 20:12:30 -0500 Subject: [PATCH 180/303] rewrote 'rewrite_closing_parens' with an explicit loop -- there was a bug when adding to @tokens in the middle of scan_tokens' while loop -- consider scan_tokens to be on probation until further notice --- lib/coffee_script/lexer.rb | 70 ++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 1f13c83c45..336cc9ece2 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -41,10 +41,14 @@ class Lexer # Tokens that must be balanced. BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]] - # Outdents that come before these tokens don't signify the end of the - # expression. - EXPRESSION_START = [:INDENT, '{', '(', '['] - EXPRESSION_TAIL = [:CATCH, :OUTDENT, :WHEN, :ELSE, :FINALLY, ')', ']', '}'] + # Tokens that signal the start of a balanced pair. + EXPRESSION_START = [:INDENT, '{', '(', '['] + + # Tokens that signal the end of a balanced pair. + EXPRESSION_TAIL = [:OUTDENT, '}', ')', ']'] + + # Tokens that indicate the close of a clause of an expression. + EXPRESSION_CLOSE = [:CATCH, :WHEN, :ELSE, :FINALLY] + EXPRESSION_TAIL # Single-line flavors of block expressions that have unclosed endings. # The grammar can't disambiguate them, so we insert the implicit indentation. @@ -52,7 +56,12 @@ class Lexer SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :WHEN] # The inverse mappings of token pairs we're trying to fix up. - INVERSES = {:INDENT => :OUTDENT, :OUTDENT => :INDENT, '(' => ')', ')' => '('} + INVERSES = { + :INDENT => :OUTDENT, :OUTDENT => :INDENT, + '(' => ')', ')' => '(', + '{' => '}', '}' => '{', + '[' => ']', ']' => '[' + } # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @@ -237,9 +246,7 @@ def scan_tokens # this, remove their trailing newlines. def remove_mid_expression_newlines scan_tokens do |prev, token, post, i| - @tokens.delete_at(i) if post && EXPRESSION_TAIL.include?(post[0]) && - token[0] == "\n" # && - # EXPRESSION_START.include?(prev[0]) + @tokens.delete_at(i) if post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n" end end @@ -313,33 +320,44 @@ def ensure_balance(*pairs) def rewrite_closing_parens verbose = ENV['VERBOSE'] stack, debt = [], Hash.new(0) - stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}" } - scan_tokens do |prev, token, post, i| + stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}\n\n" } + puts "rewrite_closing_original: #{@tokens.inspect}" if verbose + i = 0 + loop do + prev, token, post = @tokens[i-1], @tokens[i], @tokens[i+1] + break unless token tag, inv = token[0], INVERSES[token[0]] - if [:INDENT, '('].include?(tag) + if EXPRESSION_START.include?(tag) stack.push(token) + i += 1 puts "pushing #{tag} #{stack_stats[]}" if verbose - elsif [:OUTDENT, ')'].include?(tag) + elsif EXPRESSION_TAIL.include?(tag) + puts @tokens[i..-1].inspect if verbose # If the tag is already in our debt, swallow it. if debt[inv] > 0 debt[inv] -= 1 @tokens.delete_at(i) puts "tag in debt #{tag} #{stack_stats[]}" if verbose - next - end - # Pop the stack of open delimiters. - match = stack.pop - mtag = match[0] - # Continue onwards if it's the expected tag. - if tag == INVERSES[mtag] - puts "expected tag #{tag} #{stack_stats[]}" if verbose - next + else + # Pop the stack of open delimiters. + match = stack.pop + mtag = match[0] + # Continue onwards if it's the expected tag. + if tag == INVERSES[mtag] + puts "expected tag #{tag} #{stack_stats[]}" if verbose + i += 1 + else + # Unexpected close, insert correct close, adding to the debt. + debt[mtag] += 1 + puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose + val = mtag == :INDENT ? match[1] : INVERSES[mtag] + @tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)]) + i += 1 + end end - # Unexpected close, insert correct close, adding to the debt. - debt[mtag] += 1 - puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose - val = mtag == :INDENT ? match[1] : ')' - @tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)]) + else + # Uninteresting token: + i += 1 end end end From c8711b419eda12b64d977a2cd783faae593ebb2e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 20:24:24 -0500 Subject: [PATCH 181/303] fixed up the comment/assignment interleaving in nodes.rb --- examples/poignant.coffee | 44 +++++++++++++++++++------------------- lib/coffee_script/nodes.rb | 11 ++++++++-- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/examples/poignant.coffee b/examples/poignant.coffee index 973248ce29..9714c980a3 100644 --- a/examples/poignant.coffee +++ b/examples/poignant.coffee @@ -2,7 +2,7 @@ # ['toast', 'cheese', 'wine'].each { |food| print food.capitalize } -['toast', 'wine', 'cheese'].each( food => print(food.capitalize()). ) +['toast', 'wine', 'cheese'].each(food => print(food.capitalize())) @@ -14,10 +14,10 @@ # end LotteryTicket: { - get_picks: => this.picks. - set_picks: nums => this.picks: nums. - get_purchase: => this.purchase. - set_purchase: amount => this.purchase: amount. + get_picks: => this.picks + set_picks: nums => this.picks: nums + get_purchase: => this.purchase + set_purchase: amount => this.purchase: amount } @@ -33,8 +33,8 @@ LotteryTicket: { WishScanner: { scan_for_a_wish: => - wish: this.read().detect( thought => thought.index('wish: ') is 0. ) - wish.replace('wish: ', ''). + wish: this.read().detect(thought => thought.index('wish: ') is 0) + wish.replace('wish: ', '') } @@ -79,28 +79,28 @@ Creature : { # This method applies a hit taken during a fight. hit: damage => - p_up: Math.rand( this.charisma ) + p_up: Math.rand(this.charisma) if p_up % 9 is 7 this.life += p_up / 4 - puts( "[" + this.name + " magick powers up " + p_up + "!]" ). + puts("[" + this.name + " magick powers up " + p_up + "!]") this.life -= damage - if this.life <= 0 then puts( "[" + this.name + " has died.]" ).. + if this.life <= 0 then puts("[" + this.name + " has died.]") # This method takes one turn in a fight. fight: enemy, weapon => - if this.life <= 0 then return puts( "[" + this.name + "is too dead to fight!]" ). + if this.life <= 0 then return puts("[" + this.name + "is too dead to fight!]") # Attack the opponent. - your_hit: Math.rand( this.strength + weapon ) - puts( "[You hit with " + your_hit + "points of damage!]" ) - enemy.hit( your_hit ) + your_hit: Math.rand(this.strength + weapon) + puts("[You hit with " + your_hit + "points of damage!]") + enemy.hit(your_hit) # Retaliation. - puts( enemy ) + puts(enemy) if enemy.life > 0 - enemy_hit: Math.rand( enemy.strength + enemy.weapon ) - puts( "[Your enemy hit with " + enemy_hit + "points of damage!]" ) - this.hit( enemy_hit ).. + enemy_hit: Math.rand(enemy.strength + enemy.weapon) + puts("[Your enemy hit with " + enemy_hit + "points of damage!]") + this.hit(enemy_hit) } @@ -123,12 +123,12 @@ Creature : { # Get evil idea and swap in code words print("Enter your new idea: ") idea: gets() -code_words.each( real, code => idea.replace(real, code). ) +code_words.each(real, code => idea.replace(real, code)) # Save the jibberish to a new file print("File encoded. Please enter a name for this idea: ") idea_name: gets().strip() -File.open("idea-" + idea_name + '.txt', 'w', file => file.write(idea). ) +File.open("idea-" + idea_name + '.txt', 'w', file => file.write(idea)) @@ -149,5 +149,5 @@ wipe_mutterings_from: sentence => while sentence.indexOf('(') >= 0 open: sentence.indexOf('(') - 1 close: sentence.indexOf(')') + 1 - sentence: sentence[0..open] + sentence[close..sentence.length]. - sentence. \ No newline at end of file + sentence: sentence[0..open] + sentence[close..sentence.length] + sentence \ No newline at end of file diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7b26876f6d..fa64b205e8 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -464,13 +464,20 @@ def initialize(properties = []) @properties = properties end + # All the mucking about with commas is to make sure that CommentNodes and + # AssignNodes get interleaved correctly, with no trailing commas or + # commas affixed to comments. TODO: Extract this and add it to ArrayNode. def compile(o={}) o = super(o) indent = o[:indent] o[:indent] += TAB + joins = Hash.new("\n") + non_comments = @properties.select {|p| !p.is_a?(CommentNode) } + non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" } props = @properties.map { |prop| - joiner = prop == @properties.first ? '' : prop.is_a?(CommentNode) ? "\n" : ",\n" - joiner + o[:indent] + prop.compile(o) + join = joins[prop] + join = '' if prop == @properties.last + o[:indent] + prop.compile(o) + join }.join('') write("{\n#{props}\n#{indent}}") end From d43d491561093ae82a76fe059de3f1fd9b308879 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 20:36:47 -0500 Subject: [PATCH 182/303] regex cleanup -- eliminating some lookahead because Ruby regexps blow chunks (stackoverflows) when you look (ahead) at them funny. --- lib/coffee_script/lexer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 336cc9ece2..5ee0077d52 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -24,10 +24,10 @@ class Lexer JS = /\A(``|`(.*?)[^\\]`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t]+)/ - COMMENT = /\A((#[^\n]*(\s(?=#))*)+)/m + COMMENT = /\A(((\n[ \t]*)?#.*$)+)/ CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - MULTI_DENT = /\A((\n+([ \t]*(?=\S))?)+)/ + MULTI_DENT = /\A((\n([ \t]*(?=[^$]))?)+)/ LAST_DENT = /\n+([ \t]*)\Z/ # Token cleaning regexes. From 49714656e16ed0afffabf92322cf777be19f1f4f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 20:41:32 -0500 Subject: [PATCH 183/303] rolling back MULTI_DENT regex --- lib/coffee_script/lexer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 5ee0077d52..be90fb048c 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -27,7 +27,7 @@ class Lexer COMMENT = /\A(((\n[ \t]*)?#.*$)+)/ CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - MULTI_DENT = /\A((\n([ \t]*(?=[^$]))?)+)/ + MULTI_DENT = /\A((\n([ \t]*(?=\S))?)+)/ LAST_DENT = /\n+([ \t]*)\Z/ # Token cleaning regexes. From fd83759ef42bd8142ce237e2e5d656703144c406 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 21:11:54 -0500 Subject: [PATCH 184/303] more fiddling with the lexer -- the indentation is super fragile --- examples/underscore.coffee | 100 ++++++++++++++++++------------------- lib/coffee_script/lexer.rb | 8 +-- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 8c463fcd32..d6118b6cf9 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -17,16 +17,16 @@ 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 # Establish the object that gets thrown to break out of a loop iteration. -breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration. +breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration # Create a safe reference to the Underscore object for reference below. -_: root._: obj => new wrapper(obj). +_: root._: obj => new wrapper(obj) # Export the Underscore object for CommonJS. -if typeof(exports) != 'undefined' then exports._: _. +if typeof(exports) != 'undefined' then exports._: _ # Create quick reference variables for speed access to core prototypes. slice: Array.prototype.slice @@ -46,37 +46,37 @@ _.each: obj, iterator, context => index: 0 try return obj.forEach(iterator, context) if obj.forEach - return iterator.call(context, item, i, obj) for item, i in obj. if _.isArray(obj) or _.isArguments(obj) - iterator.call(context, obj[key], key, obj) for key in _.keys(obj). + (return iterator.call(context, item, i, obj) for item, i in obj) if _.isArray(obj) or _.isArguments(obj) + iterator.call(context, obj[key], key, obj) for key in _.keys(obj) catch e - throw e if e isnt breaker. - obj. + throw e if e isnt breaker + obj # Return the results of applying the iterator to each element. Use JavaScript # 1.6's version of map, if possible. _.map: obj, iterator, context => return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) results: [] - mapper: value, index, list => results.push(iterator.call(context, value, index, list)). + mapper: value, index, list => results.push(iterator.call(context, value, index, list)) _.each(obj, mapper) - results. + results # Reduce builds up a single result from a list of values. Also known as # inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. _.reduce: obj, memo, iterator, context => return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce)) - reducer: value, index, list => memo: iterator.call(context, memo, value, index, list). + reducer: value, index, list => memo: iterator.call(context, memo, value, index, list) _.each(obj, reducer) - memo. + memo # The right-associative version of reduce, also known as foldr. Uses # JavaScript 1.8's version of reduceRight, if available. _.reduceRight: obj, memo, iterator, context => return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight)) reversed: _.clone(_.toArray(obj)).reverse() - reverser: value, index => memo: iterator.call(context, memo, value, index, obj). + reverser: value, index => memo: iterator.call(context, memo, value, index, obj) _.each(reversed, reverser) - memo. + memo # Return the first value which passes a truth test. _.detect: obj, iterator, context => @@ -84,24 +84,24 @@ _.reduceRight: obj, memo, iterator, context => _.each(obj, (value, index, list => if iterator.call(context, value, index, list) result: value - _.breakLoop()..)) - result. + _.breakLoop())) + result # Return all the elements that pass a truth test. Use JavaScript 1.6's # filter(), if it exists. _.select: obj, iterator, context => - if obj and _.isFunction(obj.filter) then return obj.filter(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).)) - results. + 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).)) - results. + results.push(value) if not iterator.call(context, value, index, list))) + results # Determine whether all of the elements match a truth test. Delegate to # JavaScript 1.6's every(), if it is present. @@ -110,8 +110,8 @@ _.reduceRight: obj, memo, iterator, context => 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).)) - result. + _.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 # JavaScript 1.6's some(), if it exists. @@ -120,8 +120,8 @@ _.reduceRight: obj, memo, iterator, context => 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)).)) - result. + _.breakLoop() if (result: iterator.call(context, value, index, list)))) + result # Determine if a given value is included in the array or object, # based on '==='. @@ -129,28 +129,28 @@ _.reduceRight: obj, memo, iterator, context => return _.indexOf(obj, target) isnt -1 if _.isArray(obj) found: false _.each(obj, (value => - _.breakLoop() if (found: value is target).)) - found. + _.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).)). + (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 => - _.map(obj, (value => value[key].)). + _.map(obj, (value => value[key])) # Return the maximum item or (item-based computation). _.max: obj, iterator, context => return Math.max.apply(Math, obj) if !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. -# + computed: if iterator then iterator.call(context, value, index, list) else value + 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); @@ -313,7 +313,7 @@ _.reduceRight: obj, memo, iterator, context => # optionally). Binding with arguments is also known as 'curry'. _.bind: func, obj => args: _.rest(arguments, 2) - => func.apply(obj or root, args.concat(_.toArray(arguments))).. + => func.apply(obj or root, args.concat(_.toArray(arguments))) # # Bind all of an object's methods to that object. Useful for ensuring that # # all callbacks defined on an object belong to it. @@ -334,13 +334,13 @@ _.reduceRight: obj, memo, iterator, context => # Defers a function, scheduling it to run after the current call stack has # cleared. _.defer: func => - _.delay.apply(_, [func, 1].concat(_.rest(arguments))). + _.delay.apply(_, [func, 1].concat(_.rest(arguments))) # Returns the first function passed as an argument to the second, # allowing you to adjust arguments, run code before and after, and # conditionally execute the original function. _.wrap: func, wrapper => - => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))).. + => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))) # Returns a function that is the composition of a list of functions, each # consuming the return value of the function that follows. @@ -348,8 +348,8 @@ _.reduceRight: obj, memo, iterator, context => funcs: _.toArray(arguments) => args: _.toArray(arguments) - args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0]. - args[0].. + args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0] + args[0] # /* ------------------------- Object Functions: ---------------------------- */ # @@ -405,7 +405,7 @@ _.reduceRight: obj, memo, iterator, context => return a.source is b.source and \ a.global is b.global and \ a.ignoreCase is b.ignoreCase and \ - a.multiline is b.multiline. + a.multiline is b.multiline # If a is not an object by this point, we can't handle it. return false if atype isnt 'object' # Check for different array lengths before comparing contents. @@ -416,32 +416,32 @@ _.reduceRight: obj, memo, iterator, context => return false if aKeys.length isnt bKeys.length # Recursive comparison of contents. # for (var key in a) if (!_.isEqual(a[key], b[key])) return false; - return true. + 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 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 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 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'. + _.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. + obj # # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString # # functions based on their toString identifiers. @@ -459,13 +459,13 @@ _.reduceRight: obj, memo, iterator, context => # previous owner. Returns a reference to the Underscore object. _.noConflict: => root._: previousUnderscore - this. + this # Keep the identity function around for default iterators. - _.identity: value => value. + _.identity: value => value # Break out of the middle of an iteration. - _.breakLoop: => throw breaker. + _.breakLoop: => throw breaker # # Generate a unique integer id (unique within the entire client session). # # Useful for temporary DOM ids. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index be90fb048c..da61c7a09a 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -24,11 +24,11 @@ class Lexer JS = /\A(``|`(.*?)[^\\]`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t]+)/ - COMMENT = /\A(((\n[ \t]*)?#.*$)+)/ + COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/ CODE = /\A(=>)/ REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ - MULTI_DENT = /\A((\n([ \t]*(?=\S))?)+)/ - LAST_DENT = /\n+([ \t]*)\Z/ + MULTI_DENT = /\A((\n([ \t]*)?)+)/ + LAST_DENT = /\n([ \t]*)/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ @@ -156,7 +156,7 @@ def indent_token return false unless indent = @chunk[MULTI_DENT, 1] @line += indent.scan(MULTILINER).size @i += indent.size - size = indent[LAST_DENT, 1].length + size = indent.scan(LAST_DENT).last.last.length return newline_token(indent) if size == @indent if size > @indent token(:INDENT, size - @indent) From bbbfd92373288b8b9696aeba038880b38ca96a93 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 21:15:54 -0500 Subject: [PATCH 185/303] more fiddling with the lexer -- the indentation is super fragile --- examples/underscore.coffee | 354 ++++++++++++++++++------------------- 1 file changed, 177 insertions(+), 177 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index d6118b6cf9..227a69f39d 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -78,78 +78,78 @@ _.reduceRight: obj, memo, iterator, context => _.each(reversed, reverser) memo - # Return the first value which passes a truth test. - _.detect: obj, iterator, context => - result: null - _.each(obj, (value, index, list => - if iterator.call(context, value, index, list) - result: value - _.breakLoop())) - result - - # Return all the elements that pass a truth test. Use JavaScript 1.6's - # filter(), if it exists. - _.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))) - 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))) - results - - # Determine whether all of the elements match a truth test. Delegate to - # JavaScript 1.6's every(), if it is present. - _.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))) - result - - # Determine if at least one element in the object matches a truth test. Use - # JavaScript 1.6's some(), if it exists. - _.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)))) - result - - # Determine if a given value is included in the array or object, - # based on '==='. - _.include: obj, target => - return _.indexOf(obj, target) isnt -1 if _.isArray(obj) - found: false - _.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))) - - # Convenience version of a common use case of map: fetching a property. - _.pluck: obj, key => - _.map(obj, (value => value[key])) - - # Return the maximum item or (item-based computation). - _.max: obj, iterator, context => - return Math.max.apply(Math, obj) if !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 +# Return the first value which passes a truth test. +_.detect: obj, iterator, context => + result: null + _.each(obj, (value, index, list => + if iterator.call(context, value, index, list) + result: value + _.breakLoop())) + result + +# Return all the elements that pass a truth test. Use JavaScript 1.6's +# filter(), if it exists. +_.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))) + 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))) + results + +# Determine whether all of the elements match a truth test. Delegate to +# JavaScript 1.6's every(), if it is present. +_.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))) + result + +# Determine if at least one element in the object matches a truth test. Use +# JavaScript 1.6's some(), if it exists. +_.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)))) + result + +# Determine if a given value is included in the array or object, +# based on '==='. +_.include: obj, target => + return _.indexOf(obj, target) isnt -1 if _.isArray(obj) + found: false + _.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))) + +# Convenience version of a common use case of map: fetching a property. +_.pluck: obj, key => + _.map(obj, (value => value[key])) + +# Return the maximum item or (item-based computation). +_.max: obj, iterator, context => + return Math.max.apply(Math, obj) if !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 # # Return the minimum element (or element-based computation). # _.min = function(obj, iterator, context) { @@ -307,13 +307,13 @@ _.reduceRight: obj, memo, iterator, context => # } # }; - # ----------------------- Function Functions: ----------------------------- +# ----------------------- Function Functions: ----------------------------- - # Create a function bound to a given object (assigning 'this', and arguments, - # optionally). Binding with arguments is also known as 'curry'. - _.bind: func, obj => - args: _.rest(arguments, 2) - => func.apply(obj or root, args.concat(_.toArray(arguments))) +# Create a function bound to a given object (assigning 'this', and arguments, +# optionally). Binding with arguments is also known as 'curry'. +_.bind: func, obj => + args: _.rest(arguments, 2) + => func.apply(obj or root, args.concat(_.toArray(arguments))) # # Bind all of an object's methods to that object. Useful for ensuring that # # all callbacks defined on an object belong to it. @@ -331,25 +331,25 @@ _.reduceRight: obj, memo, iterator, context => # return setTimeout(function(){ return func.apply(func, args); }, wait); # }; - # Defers a function, scheduling it to run after the current call stack has - # cleared. - _.defer: func => - _.delay.apply(_, [func, 1].concat(_.rest(arguments))) - - # Returns the first function passed as an argument to the second, - # allowing you to adjust arguments, run code before and after, and - # conditionally execute the original function. - _.wrap: func, wrapper => - => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))) - - # Returns a function that is the composition of a list of functions, each - # consuming the return value of the function that follows. - _.compose: => - funcs: _.toArray(arguments) - => - args: _.toArray(arguments) - args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0] - args[0] +# Defers a function, scheduling it to run after the current call stack has +# cleared. +_.defer: func => + _.delay.apply(_, [func, 1].concat(_.rest(arguments))) + +# Returns the first function passed as an argument to the second, +# allowing you to adjust arguments, run code before and after, and +# conditionally execute the original function. +_.wrap: func, wrapper => + => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))) + +# Returns a function that is the composition of a list of functions, each +# consuming the return value of the function that follows. +_.compose: => + funcs: _.toArray(arguments) + => + args: _.toArray(arguments) + args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0] + args[0] # /* ------------------------- Object Functions: ---------------------------- */ # @@ -383,65 +383,65 @@ _.reduceRight: obj, memo, iterator, context => # return _.extend({}, obj); # }; - # Perform a deep comparison to check if two objects are equal. - _.isEqual: a, b => - # Check object identity. - return true if a is b - # Different types? - atype: typeof(a); btype: typeof(b) - return false if atype isnt btype - # Basic equality test (watch out for coercions). - return true if `a == b` - # One is falsy and the other truthy. - return false if (!a and b) or (a and !b) - # One of them implements an isEqual()? - return a.isEqual(b) if a.isEqual - # Check dates' integer values. - return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b) - # Both are NaN? - return true if _.isNaN(a) and _.isNaN(b) - # Compare regular expressions. - if _.isRegExp(a) and _.isRegExp(b) - return a.source is b.source and \ - a.global is b.global and \ - a.ignoreCase is b.ignoreCase and \ - a.multiline is b.multiline - # If a is not an object by this point, we can't handle it. - return false if atype isnt 'object' - # Check for different array lengths before comparing contents. - return false if a.length and (a.length isnt b.length) - # Nothing else worked, deep compare the contents. - aKeys: _.keys(a); bKeys: _.keys(b) - # Different object sizes? - return false if aKeys.length isnt bKeys.length - # Recursive comparison of contents. - # for (var key in a) if (!_.isEqual(a[key], b[key])) return false; - return true - - # Is a given array or object empty? - _.isEmpty: obj => _.keys(obj).length is 0 - - # Is a given value a DOM element? - _.isElement: obj => !!(obj and obj.nodeType is 1) - - # Is a given variable an arguments object? - _.isArguments: obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length') - - # 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) - - # Is a given value equal to 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 +# Perform a deep comparison to check if two objects are equal. +_.isEqual: a, b => + # Check object identity. + return true if a is b + # Different types? + atype: typeof(a); btype: typeof(b) + return false if atype isnt btype + # Basic equality test (watch out for coercions). + return true if `a == b` + # One is falsy and the other truthy. + return false if (!a and b) or (a and !b) + # One of them implements an isEqual()? + return a.isEqual(b) if a.isEqual + # Check dates' integer values. + return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b) + # Both are NaN? + return true if _.isNaN(a) and _.isNaN(b) + # Compare regular expressions. + if _.isRegExp(a) and _.isRegExp(b) + return a.source is b.source and \ + a.global is b.global and \ + a.ignoreCase is b.ignoreCase and \ + a.multiline is b.multiline + # If a is not an object by this point, we can't handle it. + return false if atype isnt 'object' + # Check for different array lengths before comparing contents. + return false if a.length and (a.length isnt b.length) + # Nothing else worked, deep compare the contents. + aKeys: _.keys(a); bKeys: _.keys(b) + # Different object sizes? + return false if aKeys.length isnt bKeys.length + # Recursive comparison of contents. + # for (var key in a) if (!_.isEqual(a[key], b[key])) return false; + return true + +# Is a given array or object empty? +_.isEmpty: obj => _.keys(obj).length is 0 + +# Is a given value a DOM element? +_.isElement: obj => !!(obj and obj.nodeType is 1) + +# Is a given variable an arguments object? +_.isArguments: obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length') + +# 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) + +# Is a given value equal to 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 # # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString # # functions based on their toString identifiers. @@ -453,19 +453,19 @@ _.reduceRight: obj, memo, iterator, context => # })(); # } - # -------------------------- Utility Functions: -------------------------- +# -------------------------- Utility Functions: -------------------------- - # Run Underscore.js in noConflict mode, returning the '_' variable to its - # previous owner. Returns a reference to the Underscore object. - _.noConflict: => - root._: previousUnderscore - this +# Run Underscore.js in noConflict mode, returning the '_' variable to its +# previous owner. Returns a reference to the Underscore object. +_.noConflict: => + root._: previousUnderscore + this - # Keep the identity function around for default iterators. - _.identity: value => value +# Keep the identity function around for default iterators. +_.identity: value => value - # Break out of the middle of an iteration. - _.breakLoop: => throw breaker +# 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. @@ -493,17 +493,17 @@ _.reduceRight: obj, memo, iterator, context => # return data ? fn(data) : fn; # }; - # ------------------------------- Aliases ---------------------------------- +# ------------------------------- Aliases ---------------------------------- - _.forEach: _.each - _.foldl: _.inject: _.reduce - _.foldr: _.reduceRight - _.filter: _.select - _.every: _.all - _.some: _.any - _.head: _.first - _.tail: _.rest - _.methods: _.functions +_.forEach: _.each +_.foldl: _.inject: _.reduce +_.foldr: _.reduceRight +_.filter: _.select +_.every: _.all +_.some: _.any +_.head: _.first +_.tail: _.rest +_.methods: _.functions # /*------------------------ Setup the OOP Wrapper: --------------------------*/ # @@ -549,4 +549,4 @@ _.reduceRight: obj, memo, iterator, context => # return this._wrapped; # }; # -# () +# From cae0b1a038bc9e02f796617ac1a1329ea554eb10 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 21:20:30 -0500 Subject: [PATCH 186/303] lex indents with higher precedence than comments --- lib/coffee_script/lexer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index da61c7a09a..0d31e38c88 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -93,8 +93,8 @@ def extract_next_token return if string_token return if js_token return if regex_token - return if comment_token return if indent_token + return if comment_token return if whitespace_token return literal_token end From 774e3d3e3153d3496279b2f58b5816a83c8995cb Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 21:41:01 -0500 Subject: [PATCH 187/303] moving the newline escaping detection up higher so indents don't overrule it --- lib/coffee_script/lexer.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 0d31e38c88..23a5e59760 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -156,6 +156,10 @@ def indent_token return false unless indent = @chunk[MULTI_DENT, 1] @line += indent.scan(MULTILINER).size @i += indent.size + if last_value == "\\" + @tokens.pop + return true + end size = indent.scan(LAST_DENT).last.last.length return newline_token(indent) if size == @indent if size > @indent @@ -185,9 +189,7 @@ def whitespace_token # Multiple newlines get merged together. # Use a trailing \ to escape newlines. def newline_token(newlines) - lines = newlines.scan(MULTILINER).length - token("\n", "\n") unless ["\n", "\\"].include?(last_value) - @tokens.pop if last_value == "\\" + token("\n", "\n") unless last_value == "\n" true end From dba84394253d1343c7128178434553f1a8f4917b Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 21:44:51 -0500 Subject: [PATCH 188/303] the underscore example parses now -- added line number information to parenthetical nodes --- examples/underscore.coffee | 3 ++- examples/whitespace.cs | 10 ---------- examples/whitespace.js | 8 -------- lib/coffee_script/grammar.y | 2 +- lib/coffee_script/nodes.rb | 5 +++-- 5 files changed, 6 insertions(+), 22 deletions(-) delete mode 100644 examples/whitespace.cs delete mode 100644 examples/whitespace.js diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 227a69f39d..f4dc9bc03d 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -46,7 +46,8 @@ _.each: obj, iterator, context => index: 0 try return obj.forEach(iterator, context) if obj.forEach - (return iterator.call(context, item, i, obj) for item, i in obj) if _.isArray(obj) or _.isArguments(obj) + if _.isArray(obj) or _.isArguments(obj) + return iterator.call(context, item, i, obj) for item, i in obj iterator.call(context, obj[key], key, obj) for key in _.keys(obj) catch e throw e if e isnt breaker diff --git a/examples/whitespace.cs b/examples/whitespace.cs deleted file mode 100644 index 0f26bd450c..0000000000 --- a/examples/whitespace.cs +++ /dev/null @@ -1,10 +0,0 @@ -square: x => x * x - -square: x => - x * x - -elements.each(el => - el.click(event => - el.show())) - -a: 5 \ No newline at end of file diff --git a/examples/whitespace.js b/examples/whitespace.js deleted file mode 100644 index 3d0af9e5fb..0000000000 --- a/examples/whitespace.js +++ /dev/null @@ -1,8 +0,0 @@ -(function(){ - - // square: x => x * x - var square = function(x) { - return x * x; - }; - var a = 5; -})(); \ No newline at end of file diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 6e6b377ab4..2b747738c6 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -294,7 +294,7 @@ rule # Parenthetical expressions. Parenthetical: - "(" Expression ")" { result = ParentheticalNode.new(val[1]) } + "(" Expression ")" { result = ParentheticalNode.new(val[1], val[0].line) } ; # The while loop. (there is no do..while). diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index fa64b205e8..ae8bed44ad 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -646,8 +646,9 @@ def compile(o={}) class ParentheticalNode < Node attr_reader :expressions - def initialize(expressions) + def initialize(expressions, line=nil) @expressions = expressions.unwrap + @line = line end def statement? @@ -663,7 +664,7 @@ def custom_return? end def compile(o={}) - raise SyntaxError, "parentheses can't be wrapped around a statement" if statement? + raise SyntaxError, "line #{@line}: parentheses can't be wrapped around a statement" if statement? o = super(o) compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' From ec60205014287c3224964abd557a4c9891cd4a52 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 21:51:23 -0500 Subject: [PATCH 189/303] getting there, finally ... all tests are green for whitespace --- test/fixtures/generation/each.tokens | 2 +- test/unit/test_lexer.rb | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/test/fixtures/generation/each.tokens b/test/fixtures/generation/each.tokens index 149a3170c7..aa5d853e2a 100644 --- a/test/fixtures/generation/each.tokens +++ b/test/fixtures/generation/each.tokens @@ -1 +1 @@ -[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [:ASSIGN, ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], ["\n", "\n"], [:IDENTIFIER, "index"], [:ASSIGN, ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], ["\n", "\n"], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["\n", "\n"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], ["\n", "\n"], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [".", "."], ["\n", "\n"], [:ELSE, "else"], ["\n", "\n"], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [".", "."], [".", "."], ["\n", "\n"], [:CATCH, "catch"], [:IDENTIFIER, "e"], ["\n", "\n"], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:ISNT, "isnt"], [:IDENTIFIER, "breaker"], [".", "."], ["\n", "\n"], [:IDENTIFIER, "obj"], [".", "."]] \ No newline at end of file +[[:COMMENT, [" The cornerstone, an each implementation.", " Handles objects implementing forEach, arrays, and raw objects."]], ["\n", "\n"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "each"], [:ASSIGN, ":"], [:PARAM, "obj"], [",", ","], [:PARAM, "iterator"], [",", ","], [:PARAM, "context"], ["=>", "=>"], [:INDENT, 2], [:IDENTIFIER, "index"], [:ASSIGN, ":"], [:NUMBER, "0"], ["\n", "\n"], [:TRY, "try"], [:INDENT, 2], [:IF, "if"], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], [:INDENT, 2], [:IDENTIFIER, "obj"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "forEach"], ["(", "("], [:IDENTIFIER, "iterator"], [",", ","], [:IDENTIFIER, "context"], [")", ")"], [:OUTDENT, 2], [:ELSE, "else"], [:IF, "if"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArray"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OR, "or"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "isArguments"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:INDENT, 2], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "item"], [",", ","], [:IDENTIFIER, "i"], [:IN, "in"], [:IDENTIFIER, "obj"], [:OUTDENT, 2], [:ELSE, "else"], [:INDENT, 2], [:IDENTIFIER, "iterator"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "call"], ["(", "("], [:IDENTIFIER, "context"], [",", ","], [:IDENTIFIER, "obj"], ["[", "["], [:IDENTIFIER, "key"], ["]", "]"], [",", ","], [:IDENTIFIER, "key"], [",", ","], [:IDENTIFIER, "obj"], [")", ")"], [:FOR, "for"], [:IDENTIFIER, "key"], [:IN, "in"], [:IDENTIFIER, "_"], [:PROPERTY_ACCESS, "."], [:IDENTIFIER, "keys"], ["(", "("], [:IDENTIFIER, "obj"], [")", ")"], [:OUTDENT, 2], [:OUTDENT, 2], [:CATCH, "catch"], [:IDENTIFIER, "e"], [:INDENT, 2], [:THROW, "throw"], [:IDENTIFIER, "e"], [:IF, "if"], [:IDENTIFIER, "e"], [:ISNT, "isnt"], [:IDENTIFIER, "breaker"], [:OUTDENT, 2], ["\n", "\n"], [:IDENTIFIER, "obj"], [:OUTDENT, 2], ["\n", "\n"]] \ No newline at end of file diff --git a/test/unit/test_lexer.rb b/test/unit/test_lexer.rb index 0d2ec1b598..5a811d3435 100644 --- a/test/unit/test_lexer.rb +++ b/test/unit/test_lexer.rb @@ -7,45 +7,47 @@ def setup end def test_lexing_an_empty_string - assert @lex.tokenize("") == [] + assert @lex.tokenize("") == [["\n", "\n"]] end def test_lexing_basic_assignment - code = "a: 'one'; b: [1, 2]" - assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"], - [:STRING, "'one'"], [";", ";"], [:IDENTIFIER, "b"], [:ASSIGN, ":"], - ["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"]] + code = "a: 'one'\nb: [1, 2]" + assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"], + [:STRING, "'one'"], ["\n", "\n"], [:IDENTIFIER, "b"], [:ASSIGN, ":"], + ["[", "["], [:NUMBER, "1"], [",", ","], [:NUMBER, "2"], ["]", "]"], + ["\n", "\n"]] end def test_lexing_object_literal code = "{one : 1}" assert @lex.tokenize(code) == [["{", "{"], [:IDENTIFIER, "one"], [:ASSIGN, ":"], - [:NUMBER, "1"], ["}", "}"]] + [:NUMBER, "1"], ["}", "}"], ["\n", "\n"]] end def test_lexing_function_definition - code = "x, y => x * y." + code = "x, y => x * y" assert @lex.tokenize(code) == [[:PARAM, "x"], [",", ","], [:PARAM, "y"], - ["=>", "=>"], [:IDENTIFIER, "x"], ["*", "*"], [:IDENTIFIER, "y"], [".", "."]] + ["=>", "=>"], [:INDENT, 2], [:IDENTIFIER, "x"], ["*", "*"], + [:IDENTIFIER, "y"], [:OUTDENT, 2], ["\n", "\n"]] end def test_lexing_if_statement code = "clap_your_hands() if happy" assert @lex.tokenize(code) == [[:IDENTIFIER, "clap_your_hands"], ["(", "("], - [")", ")"], [:IF, "if"], [:IDENTIFIER, "happy"]] + [")", ")"], [:IF, "if"], [:IDENTIFIER, "happy"], ["\n", "\n"]] end def test_lexing_comment - code = "a: 1\n # comment\n # on two lines\nb: 2" + code = "a: 1\n# comment\n# on two lines\nb: 2" assert @lex.tokenize(code) == [[:IDENTIFIER, "a"], [:ASSIGN, ":"], [:NUMBER, "1"], ["\n", "\n"], [:COMMENT, [" comment", " on two lines"]], ["\n", "\n"], - [:IDENTIFIER, "b"], [:ASSIGN, ":"], [:NUMBER, "2"]] + [:IDENTIFIER, "b"], [:ASSIGN, ":"], [:NUMBER, "2"], ["\n", "\n"]] end def test_lexing_newline_escaper code = "two: 1 + \\\n\n 1" assert @lex.tokenize(code) == [[:IDENTIFIER, "two"], [:ASSIGN, ":"], - [:NUMBER, "1"], ["+", "+"], [:NUMBER, "1"]] + [:NUMBER, "1"], ["+", "+"], [:NUMBER, "1"], ["\n", "\n"]] end def test_lexing From 942572d0819b6127a046ca8dbff67cd09f1da099 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 21:57:03 -0500 Subject: [PATCH 190/303] don't print the confusing indentation numbers when raising ParseErrors for indentation --- lib/coffee_script/parse_error.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/parse_error.rb b/lib/coffee_script/parse_error.rb index b9cc2e430b..52072c1cd0 100644 --- a/lib/coffee_script/parse_error.rb +++ b/lib/coffee_script/parse_error.rb @@ -13,7 +13,8 @@ def message line = @value.respond_to?(:line) ? @value.line : "END" line_part = "line #{line}:" id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.downcase}" : "" - "#{line_part} syntax error for '#{@value.to_s}'#{id_part}" + val_part = ['INDENT', 'OUTDENT'].include?(@token_id) ? '' : " for '#{@value.to_s}'" + "#{line_part} syntax error#{val_part}#{id_part}" end alias_method :inspect, :message From a6539a030c6b5730df2ab66acc286c8f4f58887c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 22:24:40 -0500 Subject: [PATCH 191/303] allowing any manner of indentation in the comments, by adjusting them in the lexer --- lib/coffee_script/lexer.rb | 19 +++++++++++++++++++ .../execution/test_funky_comments.coffee | 8 ++++++++ 2 files changed, 27 insertions(+) create mode 100644 test/fixtures/execution/test_funky_comments.coffee diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 23a5e59760..0531a917d1 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -77,6 +77,7 @@ def tokenize(code) end puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE'] close_indentation + adjust_comments remove_mid_expression_newlines move_commas_outside_outdents add_implicit_indentation @@ -244,6 +245,24 @@ def scan_tokens end end + # Massage newlines and indentations so that comments don't have to be + # correctly indented, or appear on their own line. + def adjust_comments + scan_tokens do |prev, token, post, i| + next unless token[0] == :COMMENT + before, after = @tokens[i - 2], @tokens[i + 2] + if before && after && + ((before[0] == :INDENT && after[0] == :OUTDENT) || + (before[0] == :OUTDENT && after[0] == :INDENT)) && + before[1] == after[1] + @tokens.delete_at(i + 2) + @tokens.delete_at(i - 2) + elsif !["\n", :INDENT, :OUTDENT].include?(prev[0]) + @tokens.insert(i, ["\n", Value.new("\n", token[1].line)]) + end + end + end + # Some blocks occur in the middle of expressions -- when we're expecting # this, remove their trailing newlines. def remove_mid_expression_newlines diff --git a/test/fixtures/execution/test_funky_comments.coffee b/test/fixtures/execution/test_funky_comments.coffee new file mode 100644 index 0000000000..2a24318d40 --- /dev/null +++ b/test/fixtures/execution/test_funky_comments.coffee @@ -0,0 +1,8 @@ +func: => + false + false # comment + false +# comment + true + +print(func()) \ No newline at end of file From 8762162dff9b1e3c37eaaac4c54a505d77024079 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 22:49:25 -0500 Subject: [PATCH 192/303] adding proper auto-newline escaping --- examples/underscore.coffee | 6 +++--- lib/coffee_script/lexer.rb | 12 ++++++++---- test/fixtures/execution/test_newline_escaping.coffee | 8 ++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index f4dc9bc03d..a6c5baf6f3 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -403,9 +403,9 @@ _.isEqual: a, b => return true if _.isNaN(a) and _.isNaN(b) # Compare regular expressions. if _.isRegExp(a) and _.isRegExp(b) - return a.source is b.source and \ - a.global is b.global and \ - a.ignoreCase is b.ignoreCase and \ + return a.source is b.source and + a.global is b.global and + a.ignoreCase is b.ignoreCase and a.multiline is b.multiline # If a is not an object by this point, we can't handle it. return false if atype isnt 'object' diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 0531a917d1..24b4a96ed2 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -34,6 +34,7 @@ class Lexer JS_CLEANER = /(\A`|`\Z)/ MULTILINER = /\n/ COMMENT_CLEANER = /(^\s*#|\n\s*$)/ + NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/ # Assignment tokens. ASSIGN = [':', '='] @@ -157,10 +158,7 @@ def indent_token return false unless indent = @chunk[MULTI_DENT, 1] @line += indent.scan(MULTILINER).size @i += indent.size - if last_value == "\\" - @tokens.pop - return true - end + return suppress_newlines(indent) if last_value.to_s.match(NO_NEWLINE) && last_value != "=>" size = indent.scan(LAST_DENT).last.last.length return newline_token(indent) if size == @indent if size > @indent @@ -193,6 +191,12 @@ def newline_token(newlines) token("\n", "\n") unless last_value == "\n" true end + + # Tokens to explicitly escape newlines are removed once their job is done. + def suppress_newlines(newlines) + @tokens.pop if last_value == "\\" + true + end # We treat all other single characters as a token. Eg.: ( ) , . ! # Multi-character operators are also literal tokens, so that Racc can assign diff --git a/test/fixtures/execution/test_newline_escaping.coffee b/test/fixtures/execution/test_newline_escaping.coffee index 972e2f795c..117c228479 100644 --- a/test/fixtures/execution/test_newline_escaping.coffee +++ b/test/fixtures/execution/test_newline_escaping.coffee @@ -1,6 +1,6 @@ -six: \ -1 + \ -2 + \ -3 +six: + 1 + + 2 + + 3 print(six is 6) \ No newline at end of file From e481a39a0d87a72cb31cc0c859e83949bd3dd2d1 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 23:13:22 -0500 Subject: [PATCH 193/303] making all functions named functions, if children of an immediate assignment --- lib/coffee_script/nodes.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index ae8bed44ad..442bb66006 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -189,8 +189,6 @@ def compile(o={}) # Node for a function invocation. Takes care of converting super() calls into # calls against the prototype's function of the same name. class CallNode < Node - LEADING_DOT = /\A\./ - attr_reader :variable, :arguments def initialize(variable, arguments=[]) @@ -215,7 +213,7 @@ def compile(o={}) end def compile_super(args, o) - methname = o[:last_assign].sub(LEADING_DOT, '') + methname = o[:last_assign] arg_part = args.empty? ? '' : ", #{args}" "#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})" end @@ -359,6 +357,7 @@ def compile(o={}) # Setting the value of a local variable, or the value of an object property. class AssignNode < Node PROTO_ASSIGN = /\A(\S+)\.prototype/ + LEADING_DOT = /\A\./ custom_return @@ -375,9 +374,10 @@ def line_ending def compile(o={}) o = super(o) name = @variable.compile(o) - last = @variable.last.to_s + last = @variable.last.to_s.sub(LEADING_DOT, '') proto = name[PROTO_ASSIGN, 1] o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto) + o[:immediate_assign] = last if @value.is_a?(CodeNode) return write("#{name}: #{@value.compile(o)}") if @context == :object o[:scope].find(name) unless @variable.properties? return write(@value.compile(o)) if @value.custom_assign? @@ -450,9 +450,11 @@ def compile(o={}) o[:indent] += TAB o.delete(:assign) o.delete(:no_wrap) + name = o.delete(:immediate_assign) @params.each {|id| o[:scope].parameter(id.to_s) } code = @body.compile(o, :code) - write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}") + name_part = name ? " #{name}" : '' + write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end end From b1fa06ff70c7ada7d830a6f8e3c8774a284ceb7e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 23:14:29 -0500 Subject: [PATCH 194/303] updating tests for named functions --- test/fixtures/generation/each.js | 2 +- test/fixtures/generation/each_no_wrap.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index 8787ea0642..a29e89fe5c 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -2,7 +2,7 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. - _.each = function(obj, iterator, context) { + _.each = function each(obj, iterator, context) { var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; index = 0; try { diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index aa88e142c2..2c7de7c0ce 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -1,7 +1,7 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. -_.each = function(obj, iterator, context) { +_.each = function each(obj, iterator, context) { var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; index = 0; try { From 7f76c228946abd1c4bca679b4384086f3b7bf94a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 30 Dec 2009 23:43:55 -0500 Subject: [PATCH 195/303] updating docs -- need to get back on the computer that has the syntax highlighter for UV installed --- documentation/js/expressions.js | 2 +- documentation/js/functions.js | 4 +- documentation/js/overview.js | 4 +- documentation/js/scope.js | 2 +- documentation/js/super.js | 12 +- index.html | 210 ++++++++++++++++---------------- 6 files changed, 117 insertions(+), 117 deletions(-) diff --git a/documentation/js/expressions.js b/documentation/js/expressions.js index 78f9244cba..eb2e983b0d 100644 --- a/documentation/js/expressions.js +++ b/documentation/js/expressions.js @@ -1,6 +1,6 @@ (function(){ var eldest, grade; - grade = function(student) { + grade = function grade(student) { if (student.excellent_work) { return "A+"; } else if (student.okay_stuff) { diff --git a/documentation/js/functions.js b/documentation/js/functions.js index d77c2c2695..80c1734413 100644 --- a/documentation/js/functions.js +++ b/documentation/js/functions.js @@ -1,9 +1,9 @@ (function(){ var cube, square; - square = function(x) { + square = function square(x) { return x * x; }; - cube = function(x) { + cube = function cube(x) { return square(x) * x; }; })(); \ No newline at end of file diff --git a/documentation/js/overview.js b/documentation/js/overview.js index 32cc4b3413..dbcd550db2 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -8,7 +8,7 @@ number = -42; } // Functions: - square = function(x) { + square = function square(x) { return x * x; }; // Arrays: @@ -17,7 +17,7 @@ math = { root: Math.sqrt, square: square, - cube: function(x) { + cube: function cube(x) { return x * square(x); } }; diff --git a/documentation/js/scope.js b/documentation/js/scope.js index f4f1b4d289..23b0c8e9dd 100644 --- a/documentation/js/scope.js +++ b/documentation/js/scope.js @@ -1,7 +1,7 @@ (function(){ var change_numbers, new_num, num; num = 1; - change_numbers = function() { + change_numbers = function change_numbers() { var new_num; num = 2; return (new_num = 3); diff --git a/documentation/js/super.js b/documentation/js/super.js index 252d06127f..11135d2368 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -1,27 +1,27 @@ (function(){ var Animal, Horse, Snake, sam, tom; - Animal = function() { + Animal = function Animal() { }; - Animal.prototype.move = function(meters) { + Animal.prototype.move = function move(meters) { return alert(this.name + " moved " + meters + "m."); }; - Snake = function(name) { + Snake = function Snake(name) { return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); Snake.prototype.constructor = Snake; - Snake.prototype.move = function() { + Snake.prototype.move = function move() { alert("Slithering..."); return Snake.__superClass__.move.call(this, 5); }; - Horse = function(name) { + Horse = function Horse(name) { return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); Horse.prototype.constructor = Horse; - Horse.prototype.move = function() { + Horse.prototype.move = function move() { alert("Galloping..."); return Horse.__superClass__.move.call(this, 45); }; diff --git a/index.html b/index.html index 2c572a7beb..2142949426 100644 --- a/index.html +++ b/index.html @@ -65,28 +65,28 @@

    Mini Overview

    CoffeeScript on the left, compiled JavaScript output on the right.

    -
    # Assignment:
    -number: 42
    -opposite_day: true
    +    
    # Assignment:
    +number: 42
    +opposite_day: true
     
    -# Conditions:
    -number: -42 if opposite_day
    +# Conditions:
    +number: -42 if opposite_day
     
    -# Functions:
    -square: x => x * x.
    +# Functions:
    +square: x => x * x
     
    -# Arrays:
    -list: [1, 2, 3, 4, 5]
    +# Arrays:
    +list: [1, 2, 3, 4, 5]
     
    -# Objects:
    -math: {
    -  root:   Math.sqrt
    -  square: square
    -  cube:   x => x * square(x).
    +# Objects:
    +math: {
    +  root:   Math.sqrt
    +  square: square
    +  cube:   x => x * square(x)
     }
     
    -# Array comprehensions:
    -cubed_list: math.cube(num) for num in list.
    +# Array comprehensions:
    +cubed_list: math.cube(num) for num in list
     
    var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, square;
     // Assignment:
     number = 42;
    @@ -96,7 +96,7 @@ 

    Mini Overview

    number = -42; } // Functions: -square = function(x) { +square = function square(x) { return x * x; }; // Arrays: @@ -105,7 +105,7 @@

    Mini Overview

    math = { root: Math.sqrt, square: square, - cube: function(x) { + cube: function cube(x) { return x * square(x); } }; @@ -126,7 +126,7 @@

    Mini Overview

    number = -42; } // Functions: -square = function(x) { +square = function square(x) { return x * x; }; // Arrays: @@ -135,7 +135,7 @@

    Mini Overview

    math = { root: Math.sqrt, square: square, - cube: function(x) { + cube: function cube(x) { return x * square(x); } }; @@ -290,20 +290,20 @@

    Language Reference

    Functions are defined by a list of parameters, an arrow, and the function body. The empty function looks like this: =>.

    -
    square: x => x * x.
    -cube:   x => square(x) * x.
    +    
    square: x => x * x
    +cube:   x => square(x) * x
     
    var cube, square;
    -square = function(x) {
    +square = function square(x) {
       return x * x;
     };
    -cube = function(x) {
    +cube = function cube(x) {
       return square(x) * x;
     };
     

    @@ -314,8 +314,8 @@

    Language Reference

    JSON. Equal signs are only needed for mathy things.

    -
    greeting: "Hello CoffeeScript"
    -difficulty: 0.5
    +    
    greeting: "Hello CoffeeScript"
    +difficulty: 0.5
     
    var difficulty, greeting;
     greeting = "Hello CoffeeScript";
     difficulty = 0.5;
    @@ -335,11 +335,11 @@ 

    Language Reference

    optional. In this way, assigning object properties looks the same as assigning local variables.

    -
    song: ["do", "re", "mi", "fa", "so"]
    -ages: {
    -  max: 10
    -  ida: 9
    -  tim: 11
    +    
    song: ["do", "re", "mi", "fa", "so"]
    +ages: {
    +  max: 10
    +  ida: 9
    +  tim: 11
     }
     
    var ages, song;
     song = ["do", "re", "mi", "fa", "so"];
    @@ -363,14 +363,14 @@ 

    Language Reference

    are properly declared within lexical scope — you never need to write var yourself.

    -
    num: 1
    -change_numbers: =>
    -  num: 2
    -  new_num: 3.
    -new_num: change_numbers()
    +    
    num: 1
    +change_numbers: =>
    +  num: 2
    +  new_num: 3
    +new_num: change_numbers()
     
    var change_numbers, new_num, num;
     num = 1;
    -change_numbers = function() {
    +change_numbers = function change_numbers() {
       var new_num;
       num = 2;
       return (new_num = 3);
    @@ -378,7 +378,7 @@ 

    Language Reference

    new_num = change_numbers();

    - +

    Resources

    - +

    Source Code
    Bugs and Feature Requests
    @@ -854,14 +854,9 @@

    Contributing

    • - A JavaScript version of the compiler, perhaps using Alessandro Warth's + A CoffeeScript version of the compiler, perhaps using Alessandro Warth's OMeta.
    • -
    • - Ideas for alternate syntax to end blocks of expressions — the periods - can look a little weird with deeply nested structure. (There's now a - 'whitespace' branch — help add significant whitespace over there.) -
    • Test cases for any syntax errors you encounter that you think CoffeeScript should be able to compile properly. @@ -882,19 +877,19 @@

      Contributing

    Change Log

    - +

    0.1.6 Bugfix for running coffee --interactive and --run - from outside of the CoffeeScript directory. Bugfix for nested - function/if-statements. + from outside of the CoffeeScript directory. Bugfix for nested + function/if-statements.

    - +

    0.1.5 Array slice literals and array comprehensions can now both take Ruby-style - ranges to specify the start and end. JavaScript variable declaration is - now pushed up to the top of the scope, making all assignment statements into + ranges to specify the start and end. JavaScript variable declaration is + now pushed up to the top of the scope, making all assignment statements into expressions. You can use \ to escape newlines. The coffee-script command is now called coffee.

    From a376e7f4a342de18e25ad62ec102ef4aa91deca7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 13:22:33 -0500 Subject: [PATCH 198/303] pulled out all token-stream-rewriting logic into the CoffeeScript::Rewriter -- let the lexer be simpleminded --- lib/coffee-script.rb | 1 + lib/coffee_script/lexer.rb | 185 +------------------------------- lib/coffee_script/rewriter.rb | 195 ++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 182 deletions(-) create mode 100644 lib/coffee_script/rewriter.rb diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index afae2b500d..fa2b726309 100644 --- a/lib/coffee-script.rb +++ b/lib/coffee-script.rb @@ -4,6 +4,7 @@ require "coffee_script/nodes" require "coffee_script/value" require "coffee_script/scope" +require "coffee_script/rewriter" require "coffee_script/parse_error" # Namespace for all CoffeeScript internal classes. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 24b4a96ed2..56f45c8c70 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -39,31 +39,6 @@ class Lexer # Assignment tokens. ASSIGN = [':', '='] - # Tokens that must be balanced. - BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]] - - # Tokens that signal the start of a balanced pair. - EXPRESSION_START = [:INDENT, '{', '(', '['] - - # Tokens that signal the end of a balanced pair. - EXPRESSION_TAIL = [:OUTDENT, '}', ')', ']'] - - # Tokens that indicate the close of a clause of an expression. - EXPRESSION_CLOSE = [:CATCH, :WHEN, :ELSE, :FINALLY] + EXPRESSION_TAIL - - # Single-line flavors of block expressions that have unclosed endings. - # The grammar can't disambiguate them, so we insert the implicit indentation. - SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] - SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :WHEN] - - # The inverse mappings of token pairs we're trying to fix up. - INVERSES = { - :INDENT => :OUTDENT, :OUTDENT => :INDENT, - '(' => ')', ')' => '(', - '{' => '}', '}' => '{', - '[' => ']', ']' => '[' - } - # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @@ -78,13 +53,7 @@ def tokenize(code) end puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE'] close_indentation - adjust_comments - remove_mid_expression_newlines - move_commas_outside_outdents - add_implicit_indentation - ensure_balance(*BALANCED_PAIRS) - rewrite_closing_parens - @tokens + Rewriter.new(self).rewrite(@tokens) end # At every position, run this list of match attempts, short-circuiting if @@ -158,7 +127,7 @@ def indent_token return false unless indent = @chunk[MULTI_DENT, 1] @line += indent.scan(MULTILINER).size @i += indent.size - return suppress_newlines(indent) if last_value.to_s.match(NO_NEWLINE) && last_value != "=>" + return suppress_newlines(indent) if last_value.to_s.match(NO_NEWLINE) && last_value != "=>" size = indent.scan(LAST_DENT).last.last.length return newline_token(indent) if size == @indent if size > @indent @@ -191,7 +160,7 @@ def newline_token(newlines) token("\n", "\n") unless last_value == "\n" true end - + # Tokens to explicitly escape newlines are removed once their job is done. def suppress_newlines(newlines) @tokens.pop if last_value == "\\" @@ -240,153 +209,5 @@ def close_indentation outdent_token(@indent) end - # Rewrite the token stream, looking one token ahead and behind. - def scan_tokens - i = 0 - while i < @tokens.length - yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i) - i += 1 - end - end - - # Massage newlines and indentations so that comments don't have to be - # correctly indented, or appear on their own line. - def adjust_comments - scan_tokens do |prev, token, post, i| - next unless token[0] == :COMMENT - before, after = @tokens[i - 2], @tokens[i + 2] - if before && after && - ((before[0] == :INDENT && after[0] == :OUTDENT) || - (before[0] == :OUTDENT && after[0] == :INDENT)) && - before[1] == after[1] - @tokens.delete_at(i + 2) - @tokens.delete_at(i - 2) - elsif !["\n", :INDENT, :OUTDENT].include?(prev[0]) - @tokens.insert(i, ["\n", Value.new("\n", token[1].line)]) - end - end - end - - # Some blocks occur in the middle of expressions -- when we're expecting - # this, remove their trailing newlines. - def remove_mid_expression_newlines - scan_tokens do |prev, token, post, i| - @tokens.delete_at(i) if post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n" - end - end - - # Make sure that we don't accidentally break trailing commas, which need - # to go on the outside of expression closers. - def move_commas_outside_outdents - scan_tokens do |prev, token, post, i| - next unless token[0] == :OUTDENT && prev[0] == ',' - @tokens.delete_at(i) - @tokens.insert(i - 1, token) - end - end - - # Because our grammar is LALR(1), it can't handle some single-line - # expressions that lack ending delimiters. Use the lexer to add the implicit - # blocks, so it doesn't need to. - # ')' can close a single-line block, but we need to make sure it's balanced. - def add_implicit_indentation - scan_tokens do |prev, token, post, i| - if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT && - !(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks. - line = token[1].line - @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) - idx = i + 1 - parens = 0 - loop do - idx += 1 - tok = @tokens[idx] - if !tok || SINGLE_CLOSERS.include?(tok[0]) || - (tok[0] == ')' && parens == 0) - @tokens.insert(idx, [:OUTDENT, Value.new(2, line)]) - break - end - parens += 1 if tok[0] == '(' - parens -= 1 if tok[0] == ')' - end - @tokens.delete_at(i) if token[0] == :THEN - end - end - end - - # Ensure that all listed pairs of tokens are correctly balanced throughout - # the course of the token stream. - def ensure_balance(*pairs) - levels = Hash.new(0) - scan_tokens do |prev, token, post, i| - pairs.each do |pair| - open, close = *pair - levels[open] += 1 if token[0] == open - levels[open] -= 1 if token[0] == close - raise ParseError.new(token[0], token[1], nil) if levels[open] < 0 - end - end - unclosed = levels.detect {|k, v| v > 0 } - raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed - end - - # We'd like to support syntax like this: - # el.click(event => - # el.hide()) - # In order to accomplish this, move outdents that follow closing parens - # inwards, safely. The steps to accomplish this are: - # - # 1. Check that all paired tokens are balanced and in order. - # 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it - # to the stack. If you see an ')' or OUTDENT, pop the stack and replace - # it with the inverse of what we've just popped. - # 3. Keep track of "debt" for tokens that we fake, to make sure we end - # up balanced in the end. - # - def rewrite_closing_parens - verbose = ENV['VERBOSE'] - stack, debt = [], Hash.new(0) - stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}\n\n" } - puts "rewrite_closing_original: #{@tokens.inspect}" if verbose - i = 0 - loop do - prev, token, post = @tokens[i-1], @tokens[i], @tokens[i+1] - break unless token - tag, inv = token[0], INVERSES[token[0]] - if EXPRESSION_START.include?(tag) - stack.push(token) - i += 1 - puts "pushing #{tag} #{stack_stats[]}" if verbose - elsif EXPRESSION_TAIL.include?(tag) - puts @tokens[i..-1].inspect if verbose - # If the tag is already in our debt, swallow it. - if debt[inv] > 0 - debt[inv] -= 1 - @tokens.delete_at(i) - puts "tag in debt #{tag} #{stack_stats[]}" if verbose - else - # Pop the stack of open delimiters. - match = stack.pop - mtag = match[0] - # Continue onwards if it's the expected tag. - if tag == INVERSES[mtag] - puts "expected tag #{tag} #{stack_stats[]}" if verbose - i += 1 - else - # Unexpected close, insert correct close, adding to the debt. - debt[mtag] += 1 - puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose - val = mtag == :INDENT ? match[1] : INVERSES[mtag] - @tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)]) - i += 1 - end - end - else - # Uninteresting token: - i += 1 - end - end - end - end - end \ No newline at end of file diff --git a/lib/coffee_script/rewriter.rb b/lib/coffee_script/rewriter.rb new file mode 100644 index 0000000000..2ba3b7bafb --- /dev/null +++ b/lib/coffee_script/rewriter.rb @@ -0,0 +1,195 @@ +module CoffeeScript + + # In order to keep the grammar simple, the stream of tokens that the Lexer + # emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested + # indentation, and single-line flavors of expressions. + class Rewriter + + # Tokens that must be balanced. + BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]] + + # Tokens that signal the start of a balanced pair. + EXPRESSION_START = BALANCED_PAIRS.map {|pair| pair.first } + + # Tokens that signal the end of a balanced pair. + EXPRESSION_TAIL = BALANCED_PAIRS.map {|pair| pair.last } + + # Tokens that indicate the close of a clause of an expression. + EXPRESSION_CLOSE = [:CATCH, :WHEN, :ELSE, :FINALLY] + EXPRESSION_TAIL + + # The inverse mappings of token pairs we're trying to fix up. + INVERSES = BALANCED_PAIRS.inject({}) do |memo, pair| + memo[pair.first] = pair.last + memo[pair.last] = pair.first + memo + end + + # Single-line flavors of block expressions that have unclosed endings. + # The grammar can't disambiguate them, so we insert the implicit indentation. + SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] + SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :WHEN] + + def initialize(lexer) + @lexer = lexer + end + + def rewrite(tokens) + @tokens = tokens + adjust_comments + remove_mid_expression_newlines + move_commas_outside_outdents + add_implicit_indentation + ensure_balance(*BALANCED_PAIRS) + rewrite_closing_parens + @tokens + end + + # Rewrite the token stream, looking one token ahead and behind. + def scan_tokens + i = 0 + while i < @tokens.length + yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i) + i += 1 + end + end + + # Massage newlines and indentations so that comments don't have to be + # correctly indented, or appear on their own line. + def adjust_comments + scan_tokens do |prev, token, post, i| + next unless token[0] == :COMMENT + before, after = @tokens[i - 2], @tokens[i + 2] + if before && after && + ((before[0] == :INDENT && after[0] == :OUTDENT) || + (before[0] == :OUTDENT && after[0] == :INDENT)) && + before[1] == after[1] + @tokens.delete_at(i + 2) + @tokens.delete_at(i - 2) + elsif !["\n", :INDENT, :OUTDENT].include?(prev[0]) + @tokens.insert(i, ["\n", Value.new("\n", token[1].line)]) + end + end + end + + # Some blocks occur in the middle of expressions -- when we're expecting + # this, remove their trailing newlines. + def remove_mid_expression_newlines + scan_tokens do |prev, token, post, i| + @tokens.delete_at(i) if post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n" + end + end + + # Make sure that we don't accidentally break trailing commas, which need + # to go on the outside of expression closers. + def move_commas_outside_outdents + scan_tokens do |prev, token, post, i| + next unless token[0] == :OUTDENT && prev[0] == ',' + @tokens.delete_at(i) + @tokens.insert(i - 1, token) + end + end + + # Because our grammar is LALR(1), it can't handle some single-line + # expressions that lack ending delimiters. Use the lexer to add the implicit + # blocks, so it doesn't need to. + # ')' can close a single-line block, but we need to make sure it's balanced. + def add_implicit_indentation + scan_tokens do |prev, token, post, i| + if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT && + !(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks. + line = token[1].line + @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) + idx = i + 1 + parens = 0 + loop do + idx += 1 + tok = @tokens[idx] + if !tok || SINGLE_CLOSERS.include?(tok[0]) || + (tok[0] == ')' && parens == 0) + @tokens.insert(idx, [:OUTDENT, Value.new(2, line)]) + break + end + parens += 1 if tok[0] == '(' + parens -= 1 if tok[0] == ')' + end + @tokens.delete_at(i) if token[0] == :THEN + end + end + end + + # Ensure that all listed pairs of tokens are correctly balanced throughout + # the course of the token stream. + def ensure_balance(*pairs) + levels = Hash.new(0) + scan_tokens do |prev, token, post, i| + pairs.each do |pair| + open, close = *pair + levels[open] += 1 if token[0] == open + levels[open] -= 1 if token[0] == close + raise ParseError.new(token[0], token[1], nil) if levels[open] < 0 + end + end + unclosed = levels.detect {|k, v| v > 0 } + raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed + end + + # We'd like to support syntax like this: + # el.click(event => + # el.hide()) + # In order to accomplish this, move outdents that follow closing parens + # inwards, safely. The steps to accomplish this are: + # + # 1. Check that all paired tokens are balanced and in order. + # 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it + # to the stack. If you see an ')' or OUTDENT, pop the stack and replace + # it with the inverse of what we've just popped. + # 3. Keep track of "debt" for tokens that we fake, to make sure we end + # up balanced in the end. + # + def rewrite_closing_parens + verbose = ENV['VERBOSE'] + stack, debt = [], Hash.new(0) + stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}\n\n" } + puts "rewrite_closing_original: #{@tokens.inspect}" if verbose + i = 0 + loop do + prev, token, post = @tokens[i-1], @tokens[i], @tokens[i+1] + break unless token + tag, inv = token[0], INVERSES[token[0]] + if EXPRESSION_START.include?(tag) + stack.push(token) + i += 1 + puts "pushing #{tag} #{stack_stats[]}" if verbose + elsif EXPRESSION_TAIL.include?(tag) + puts @tokens[i..-1].inspect if verbose + # If the tag is already in our debt, swallow it. + if debt[inv] > 0 + debt[inv] -= 1 + @tokens.delete_at(i) + puts "tag in debt #{tag} #{stack_stats[]}" if verbose + else + # Pop the stack of open delimiters. + match = stack.pop + mtag = match[0] + # Continue onwards if it's the expected tag. + if tag == INVERSES[mtag] + puts "expected tag #{tag} #{stack_stats[]}" if verbose + i += 1 + else + # Unexpected close, insert correct close, adding to the debt. + debt[mtag] += 1 + puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose + val = mtag == :INDENT ? match[1] : INVERSES[mtag] + @tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)]) + i += 1 + end + end + else + # Uninteresting token: + i += 1 + end + end + end + + end +end \ No newline at end of file From 2231d67cef5ab10d91a3ec6c0cf7f860c8782ad5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 13:26:38 -0500 Subject: [PATCH 199/303] making assignment token detection a regex like all the others --- lib/coffee_script/lexer.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 56f45c8c70..3fb03c7b86 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -29,6 +29,7 @@ class Lexer REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ MULTI_DENT = /\A((\n([ \t]*)?)+)/ LAST_DENT = /\n([ \t]*)/ + ASSIGNMENT = /\A(:|=)\Z/ # Token cleaning regexes. JS_CLEANER = /(\A`|`\Z)/ @@ -36,9 +37,6 @@ class Lexer COMMENT_CLEANER = /(^\s*#|\n\s*$)/ NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/ - # Assignment tokens. - ASSIGN = [':', '='] - # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @@ -174,7 +172,7 @@ def literal_token value = @chunk[OPERATOR, 1] tag_parameters if value && value.match(CODE) value ||= @chunk[0,1] - tag = ASSIGN.include?(value) ? :ASSIGN : value + tag = value.match(ASSIGNMENT) ? :ASSIGN : value token(tag, value) @i += value.length end From edf1fc769fdf33c26675b4a2f73e03302126f901 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 13:43:24 -0500 Subject: [PATCH 200/303] detailed scan_tokens so that the calling function can indicate the number of spaces to move forward (or backward) in the token stream --- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/rewriter.rb | 83 ++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 3fb03c7b86..48a0a32f37 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -51,7 +51,7 @@ def tokenize(code) end puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE'] close_indentation - Rewriter.new(self).rewrite(@tokens) + Rewriter.new.rewrite(@tokens) end # At every position, run this list of match attempts, short-circuiting if diff --git a/lib/coffee_script/rewriter.rb b/lib/coffee_script/rewriter.rb index 2ba3b7bafb..0fa0e100bd 100644 --- a/lib/coffee_script/rewriter.rb +++ b/lib/coffee_script/rewriter.rb @@ -29,10 +29,9 @@ class Rewriter SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :WHEN] - def initialize(lexer) - @lexer = lexer - end - + # Rewrite the token stream in multiple passes, one logical filter at + # a time. This could certainly be changed into a single pass through the + # stream, with a big ol' efficient switch, but it's much nicer like this. def rewrite(tokens) @tokens = tokens adjust_comments @@ -45,11 +44,15 @@ def rewrite(tokens) end # Rewrite the token stream, looking one token ahead and behind. + # Allow the return value of the block to tell us how many tokens to move + # forwards (or backwards) in the stream, to make sure we don't miss anything + # as the stream changes length under our feet. def scan_tokens i = 0 - while i < @tokens.length - yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i) - i += 1 + loop do + break unless @tokens[i] + move = yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i) + i += move end end @@ -57,7 +60,7 @@ def scan_tokens # correctly indented, or appear on their own line. def adjust_comments scan_tokens do |prev, token, post, i| - next unless token[0] == :COMMENT + next 1 unless token[0] == :COMMENT before, after = @tokens[i - 2], @tokens[i + 2] if before && after && ((before[0] == :INDENT && after[0] == :OUTDENT) || @@ -65,8 +68,12 @@ def adjust_comments before[1] == after[1] @tokens.delete_at(i + 2) @tokens.delete_at(i - 2) + next 0 elsif !["\n", :INDENT, :OUTDENT].include?(prev[0]) @tokens.insert(i, ["\n", Value.new("\n", token[1].line)]) + next 2 + else + next 1 end end end @@ -75,7 +82,9 @@ def adjust_comments # this, remove their trailing newlines. def remove_mid_expression_newlines scan_tokens do |prev, token, post, i| - @tokens.delete_at(i) if post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n" + next 1 unless post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n" + @tokens.delete_at(i) + next 0 end end @@ -83,9 +92,11 @@ def remove_mid_expression_newlines # to go on the outside of expression closers. def move_commas_outside_outdents scan_tokens do |prev, token, post, i| - next unless token[0] == :OUTDENT && prev[0] == ',' - @tokens.delete_at(i) - @tokens.insert(i - 1, token) + if token[0] == :OUTDENT && prev[0] == ',' + @tokens.delete_at(i) + @tokens.insert(i - 1, token) + end + next 1 end end @@ -95,25 +106,26 @@ def move_commas_outside_outdents # ')' can close a single-line block, but we need to make sure it's balanced. def add_implicit_indentation scan_tokens do |prev, token, post, i| - if SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT && + next 1 unless SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT && !(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks. - line = token[1].line - @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) - idx = i + 1 - parens = 0 - loop do - idx += 1 - tok = @tokens[idx] - if !tok || SINGLE_CLOSERS.include?(tok[0]) || - (tok[0] == ')' && parens == 0) - @tokens.insert(idx, [:OUTDENT, Value.new(2, line)]) - break - end - parens += 1 if tok[0] == '(' - parens -= 1 if tok[0] == ')' + line = token[1].line + @tokens.insert(i + 1, [:INDENT, Value.new(2, line)]) + idx = i + 1 + parens = 0 + loop do + idx += 1 + tok = @tokens[idx] + if !tok || SINGLE_CLOSERS.include?(tok[0]) || + (tok[0] == ')' && parens == 0) + @tokens.insert(idx, [:OUTDENT, Value.new(2, line)]) + break end - @tokens.delete_at(i) if token[0] == :THEN + parens += 1 if tok[0] == '(' + parens -= 1 if tok[0] == ')' end + next 1 unless token[0] == :THEN + @tokens.delete_at(i) + next 0 end end @@ -128,6 +140,7 @@ def ensure_balance(*pairs) levels[open] -= 1 if token[0] == close raise ParseError.new(token[0], token[1], nil) if levels[open] < 0 end + next 1 end unclosed = levels.detect {|k, v| v > 0 } raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed @@ -151,15 +164,12 @@ def rewrite_closing_parens stack, debt = [], Hash.new(0) stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}\n\n" } puts "rewrite_closing_original: #{@tokens.inspect}" if verbose - i = 0 - loop do - prev, token, post = @tokens[i-1], @tokens[i], @tokens[i+1] - break unless token + scan_tokens do |prev, token, post, i| tag, inv = token[0], INVERSES[token[0]] if EXPRESSION_START.include?(tag) stack.push(token) - i += 1 puts "pushing #{tag} #{stack_stats[]}" if verbose + next 1 elsif EXPRESSION_TAIL.include?(tag) puts @tokens[i..-1].inspect if verbose # If the tag is already in our debt, swallow it. @@ -167,6 +177,7 @@ def rewrite_closing_parens debt[inv] -= 1 @tokens.delete_at(i) puts "tag in debt #{tag} #{stack_stats[]}" if verbose + next 0 else # Pop the stack of open delimiters. match = stack.pop @@ -174,19 +185,19 @@ def rewrite_closing_parens # Continue onwards if it's the expected tag. if tag == INVERSES[mtag] puts "expected tag #{tag} #{stack_stats[]}" if verbose - i += 1 + next 1 else # Unexpected close, insert correct close, adding to the debt. debt[mtag] += 1 puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose val = mtag == :INDENT ? match[1] : INVERSES[mtag] @tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)]) - i += 1 + next 1 end end else # Uninteresting token: - i += 1 + next 1 end end end From 4e8a12d70de2212d23c51030b51a9a75020cb24c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 13:45:07 -0500 Subject: [PATCH 201/303] done commenting the rewriter --- lib/coffee_script/rewriter.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/coffee_script/rewriter.rb b/lib/coffee_script/rewriter.rb index 0fa0e100bd..b031962390 100644 --- a/lib/coffee_script/rewriter.rb +++ b/lib/coffee_script/rewriter.rb @@ -166,10 +166,12 @@ def rewrite_closing_parens puts "rewrite_closing_original: #{@tokens.inspect}" if verbose scan_tokens do |prev, token, post, i| tag, inv = token[0], INVERSES[token[0]] + # Push openers onto the stack. if EXPRESSION_START.include?(tag) stack.push(token) puts "pushing #{tag} #{stack_stats[]}" if verbose next 1 + # The end of an expression, check stack and debt for a pair. elsif EXPRESSION_TAIL.include?(tag) puts @tokens[i..-1].inspect if verbose # If the tag is already in our debt, swallow it. From 00538bd62c1efac67e8ea36797a690c1c3fa4243 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 14:52:14 -0500 Subject: [PATCH 202/303] adding weepy's suggestion to use (for .. in) for array comprehensions, which means that they're now object comprehensions as well --- lib/coffee_script/nodes.rb | 33 ++++++++++++------------ test/fixtures/generation/each.js | 29 +++++++++++---------- test/fixtures/generation/each_no_wrap.js | 29 +++++++++++---------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 442bb66006..afdbb531db 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -325,10 +325,10 @@ def greater_operator @exclusive ? '>' : '>=' end - def compile(o, fv, tv) + def compile(o, fv) fvv, tvv = @from.compile(o), @to.compile(o) - vars = "#{fv}=#{fvv}, #{tv}=#{tvv}" - compare = "(#{fvv} <= #{tvv} ? #{fv} #{less_operator} #{tv} : #{fv} #{greater_operator} #{tv})" + vars = "#{fv}=#{fvv}" + compare = "(#{fvv} <= #{tvv} ? #{fv} #{less_operator} #{tvv} : #{fv} #{greater_operator} #{tvv})" incr = "(#{fvv} <= #{tvv} ? #{fv} += 1 : #{fv} -= 1)" "#{vars}; #{compare}; #{incr}" end @@ -554,22 +554,22 @@ def compile(o={}) name_found = scope.find(@name) index_found = @index && scope.find(@index) svar = scope.free_variable - ivar = range ? name : scope.free_variable - lvar = scope.free_variable + ivar = range ? name : @index ? @index : scope.free_variable rvar = scope.free_variable - index_name = @index ? @index : nil if range - source_part = '' - var_part = '' - index_part = '' + body_dent = o[:indent] + TAB + source_part, var_part = '', '', '' + pre_cond, post_cond = '', '' index_var = scope.free_variable - for_part = "#{index_var}=0, #{@source.compile(o, ivar, lvar)}, #{index_var}++" + for_part = "#{index_var}=0, #{@source.compile(o, ivar)}, #{index_var}++" else index_var = nil + body_dent = o[:indent] + TAB + TAB source_part = "#{svar} = #{@source.compile(o)};\n#{o[:indent]}" - for_part = "#{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++" - var_part = "\n#{o[:indent] + TAB}#{@name} = #{svar}[#{ivar}];" - index_part = @index ? "\n#{o[:indent] + TAB}#{index_name} = #{ivar};" : '' + for_part = "#{ivar} in #{svar}" + pre_cond = "\n#{o[:indent] + TAB}if (#{svar}.hasOwnProperty(#{ivar})) {" + var_part = "\n#{body_dent}#{@name} = #{svar}[#{ivar}];" + post_cond = "\n#{o[:indent] + TAB}}" end body = @body suffix = ';' @@ -593,9 +593,8 @@ def compile(o={}) end return_result = "\n#{o[:indent]}#{return_result};" - indent = o[:indent] + TAB - body = body.compile(o.merge(:indent => indent)) - write("#{source_part}#{set_result}for (#{for_part}) {#{var_part}#{index_part}\n#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}") + body = body.compile(o.merge(:indent => body_dent)) + write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body_dent}#{save_result}#{body}#{suffix}#{post_cond}\n#{o[:indent]}}#{return_result}") end end @@ -694,7 +693,7 @@ def <<(else_body) @else_body ? @else_body << eb : @else_body = eb self end - + def force_statement @tags[:statement] = true self diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index a29e89fe5c..7d5a1f0b54 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -3,28 +3,31 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; + var __a, __b, __c, __d, __e, i, index, item, key; index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { __a = obj; - __d = []; - for (__b=0, __c=__a.length; __b<__c; __b++) { - item = __a[__b]; - i = __b; - __d[__b] = iterator.call(context, item, i, obj); + __b = []; + for (i in __a) { + if (__a.hasOwnProperty(i)) { + item = __a[i]; + __b[i] = iterator.call(context, item, i, obj); + } } - __d; + __b; } else { - __e = _.keys(obj); - __h = []; - for (__f=0, __g=__e.length; __f<__g; __f++) { - key = __e[__f]; - __h[__f] = iterator.call(context, obj[key], key, obj); + __c = _.keys(obj); + __e = []; + for (__d in __c) { + if (__c.hasOwnProperty(__d)) { + key = __c[__d]; + __e[__d] = iterator.call(context, obj[key], key, obj); + } } - __h; + __e; } } catch (e) { if (e !== breaker) { diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index 2c7de7c0ce..bd318d3bea 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -2,28 +2,31 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, __f, __g, __h, i, index, item, key; + var __a, __b, __c, __d, __e, i, index, item, key; index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { __a = obj; - __d = []; - for (__b=0, __c=__a.length; __b<__c; __b++) { - item = __a[__b]; - i = __b; - __d[__b] = iterator.call(context, item, i, obj); + __b = []; + for (i in __a) { + if (__a.hasOwnProperty(i)) { + item = __a[i]; + __b[i] = iterator.call(context, item, i, obj); + } } - __d; + __b; } else { - __e = _.keys(obj); - __h = []; - for (__f=0, __g=__e.length; __f<__g; __f++) { - key = __e[__f]; - __h[__f] = iterator.call(context, obj[key], key, obj); + __c = _.keys(obj); + __e = []; + for (__d in __c) { + if (__c.hasOwnProperty(__d)) { + key = __c[__d]; + __e[__d] = iterator.call(context, obj[key], key, obj); + } } - __h; + __e; } } catch (e) { if (e !== breaker) { From 07d0044718f7b5a291140fc672e3adc073c6b54d Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 15:03:32 -0500 Subject: [PATCH 203/303] using push for comprehension results so that it works with object keys, and adding a test for object comprehensions --- lib/coffee_script/nodes.rb | 6 ++---- test/fixtures/execution/test_array_comprehension.coffee | 6 +++++- test/fixtures/generation/each.js | 4 ++-- test/fixtures/generation/each_no_wrap.js | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index afdbb531db..52cc20cfac 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -574,8 +574,8 @@ def compile(o={}) body = @body suffix = ';' set_result = "#{rvar} = [];\n#{o[:indent]}" - save_result = "#{rvar}[#{index_var || ivar}] = " return_result = rvar + body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) if o[:return] || o[:assign] return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign] @@ -583,9 +583,7 @@ def compile(o={}) o.delete(:assign) o.delete(:return) if @filter - body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) body = IfNode.new(@filter, body, nil, :statement => true) - save_result = '' suffix = '' end elsif @filter @@ -594,7 +592,7 @@ def compile(o={}) return_result = "\n#{o[:indent]}#{return_result};" body = body.compile(o.merge(:indent => body_dent)) - write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body_dent}#{save_result}#{body}#{suffix}#{post_cond}\n#{o[:indent]}}#{return_result}") + write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body_dent}#{body}#{suffix}#{post_cond}\n#{o[:indent]}}#{return_result}") end end diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 8ce1e4f6fa..5cb6850609 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -1,8 +1,12 @@ nums: n * n for n in [1, 2, 3] when n % 2 isnt 0 results: n * 2 for n in nums +obj: {one: 1, two: 2, three: 3} +names: key + '!' for value, key in obj + # next: for n in [1, 2, 3] if n % 2 isnt 0 # print('hi') if false # n * n * 2 -print(results.join(',') is '2,18') \ No newline at end of file +print(results.join(',') is '2,18') +print(names.join(' ') is "one! two! three!") \ No newline at end of file diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index 7d5a1f0b54..f61587a7de 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -14,7 +14,7 @@ for (i in __a) { if (__a.hasOwnProperty(i)) { item = __a[i]; - __b[i] = iterator.call(context, item, i, obj); + __b.push(iterator.call(context, item, i, obj)); } } __b; @@ -24,7 +24,7 @@ for (__d in __c) { if (__c.hasOwnProperty(__d)) { key = __c[__d]; - __e[__d] = iterator.call(context, obj[key], key, obj); + __e.push(iterator.call(context, obj[key], key, obj)); } } __e; diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index bd318d3bea..b6f6ad7da5 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -13,7 +13,7 @@ _.each = function each(obj, iterator, context) { for (i in __a) { if (__a.hasOwnProperty(i)) { item = __a[i]; - __b[i] = iterator.call(context, item, i, obj); + __b.push(iterator.call(context, item, i, obj)); } } __b; @@ -23,7 +23,7 @@ _.each = function each(obj, iterator, context) { for (__d in __c) { if (__c.hasOwnProperty(__d)) { key = __c[__d]; - __e[__d] = iterator.call(context, obj[key], key, obj); + __e.push(iterator.call(context, obj[key], key, obj)); } } __e; From 44fec922a4e2aba07561dabbd40f5b92f0b287f8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 15:08:54 -0500 Subject: [PATCH 204/303] adding a filtered object comprehension test --- test/fixtures/execution/test_array_comprehension.coffee | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index 5cb6850609..eba50e30a0 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -1,12 +1,14 @@ -nums: n * n for n in [1, 2, 3] when n % 2 isnt 0 +nums: n * n for n in [1, 2, 3] when n % 2 isnt 0 results: n * 2 for n in nums -obj: {one: 1, two: 2, three: 3} +obj: {one: 1, two: 2, three: 3} names: key + '!' for value, key in obj +odds: key + '!' for value, key in obj when value % 2 isnt 0 # next: for n in [1, 2, 3] if n % 2 isnt 0 # print('hi') if false # n * n * 2 print(results.join(',') is '2,18') -print(names.join(' ') is "one! two! three!") \ No newline at end of file +print(names.join(' ') is "one! two! three!") +print(odds.join(' ') is "one! three!") \ No newline at end of file From 7ec91a1ee5a8dae3536de194cd9c505b8932ea69 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 16:09:27 -0500 Subject: [PATCH 205/303] enabling multi-line array and object comprehensions --- lib/coffee_script/nodes.rb | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 52cc20cfac..fed7e70f43 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -44,6 +44,7 @@ def custom_assign?; false; end # A collection of nodes, each one representing an expression. class Expressions < Node statement + custom_assign attr_reader :expressions STRIP_TRAILING_WHITESPACE = /\s+$/ @@ -107,7 +108,8 @@ def compile(options={}, parent=nil) end else o.delete(:return) and o.delete(:assign) - "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" + indent = node.statement? ? '' : o[:indent] + "#{indent}#{node.compile(o)}#{node.line_ending}" end end scope = options[:scope] @@ -371,6 +373,10 @@ def line_ending @value.custom_assign? ? '' : ';' end + def statement? + @value.custom_assign? + end + def compile(o={}) o = super(o) name = @variable.compile(o) @@ -556,6 +562,7 @@ def compile(o={}) svar = scope.free_variable ivar = range ? name : @index ? @index : scope.free_variable rvar = scope.free_variable + tvar = scope.free_variable if range body_dent = o[:indent] + TAB source_part, var_part = '', '', '' @@ -572,27 +579,26 @@ def compile(o={}) post_cond = "\n#{o[:indent] + TAB}}" end body = @body - suffix = ';' set_result = "#{rvar} = [];\n#{o[:indent]}" return_result = rvar - body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body]) - + temp_var = ValueNode.new(LiteralNode.new(tvar)) + body = Expressions.new([ + AssignNode.new(temp_var, @body), + CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var]) + ]) if o[:return] || o[:assign] return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign] return_result = "return #{return_result}" if o[:return] o.delete(:assign) o.delete(:return) - if @filter - body = IfNode.new(@filter, body, nil, :statement => true) - suffix = '' - end + body = IfNode.new(@filter, body, nil, :statement => true) if @filter elsif @filter body = IfNode.new(@filter, @body) end return_result = "\n#{o[:indent]}#{return_result};" body = body.compile(o.merge(:indent => body_dent)) - write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body_dent}#{body}#{suffix}#{post_cond}\n#{o[:indent]}}#{return_result}") + write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body}#{post_cond}\n#{o[:indent]}}#{return_result}") end end From bfd7455db487da852c11f5ec24caeb991d8b667e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 16:13:52 -0500 Subject: [PATCH 206/303] adding a test for multiline-array-comprehension-with-filter --- .../execution/test_array_comprehension.coffee | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee index eba50e30a0..27d0487118 100644 --- a/test/fixtures/execution/test_array_comprehension.coffee +++ b/test/fixtures/execution/test_array_comprehension.coffee @@ -1,14 +1,21 @@ nums: n * n for n in [1, 2, 3] when n % 2 isnt 0 results: n * 2 for n in nums +print(results.join(',') is '2,18') + + obj: {one: 1, two: 2, three: 3} names: key + '!' for value, key in obj odds: key + '!' for value, key in obj when value % 2 isnt 0 -# next: for n in [1, 2, 3] if n % 2 isnt 0 -# print('hi') if false -# n * n * 2 - -print(results.join(',') is '2,18') print(names.join(' ') is "one! two! three!") -print(odds.join(' ') is "one! three!") \ No newline at end of file +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 + num * -1 + +print(evens.join(', ') is '4, 6, 8') + From f299972713dba96738f67262a380d5e4965dfe79 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 16:50:46 -0500 Subject: [PATCH 207/303] expressions nested in expressions made for some indentation issues -- statements are now responsible for their own leading indentation --- documentation/js/array_comprehensions.js | 28 +++++----- documentation/js/overview.js | 13 +++-- documentation/js/scope.js | 2 +- documentation/js/super.js | 4 +- index.html | 66 ++++++++++++++---------- lib/coffee_script/nodes.rb | 27 +++++----- test/fixtures/generation/each.js | 20 +++---- test/fixtures/generation/each_no_wrap.js | 20 +++---- 8 files changed, 102 insertions(+), 78 deletions(-) diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index 636ccb49db..edc73121ad 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -1,20 +1,24 @@ (function(){ - var __a, __b, __c, __d, __e, __f, __g, __h, food, i, lunch, row; + var __a, __b, __c, __d, __e, __f, __g, food, i, lunch, row; // Eat lunch. __a = ['toast', 'cheese', 'wine']; - __d = []; - for (__b=0, __c=__a.length; __b<__c; __b++) { - food = __a[__b]; - __d[__b] = food.eat(); + __c = []; + for (__b in __a) { + if (__a.hasOwnProperty(__b)) { + food = __a[__b]; + __d = food.eat(); + __c.push(__d); + } } - lunch = __d; + lunch = __c; // Zebra-stripe a table. __e = table; - __h = []; - for (__f=0, __g=__e.length; __f<__g; __f++) { - row = __e[__f]; - i = __f; - __h[__f] = i % 2 === 0 ? highlight(row) : null; + __f = []; + for (i in __e) { + if (__e.hasOwnProperty(i)) { + row = __e[i]; + i % 2 === 0 ? highlight(row) : null; + } } - __h; + __f; })(); \ No newline at end of file diff --git a/documentation/js/overview.js b/documentation/js/overview.js index dbcd550db2..36379ca71f 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -23,10 +23,13 @@ }; // Array comprehensions: __a = list; - __d = []; - for (__b=0, __c=__a.length; __b<__c; __b++) { - num = __a[__b]; - __d[__b] = math.cube(num); + __c = []; + for (__b in __a) { + if (__a.hasOwnProperty(__b)) { + num = __a[__b]; + __d = math.cube(num); + __c.push(__d); + } } - cubed_list = __d; + cubed_list = __c; })(); \ No newline at end of file diff --git a/documentation/js/scope.js b/documentation/js/scope.js index 23b0c8e9dd..38d5e939ba 100644 --- a/documentation/js/scope.js +++ b/documentation/js/scope.js @@ -4,7 +4,7 @@ change_numbers = function change_numbers() { var new_num; num = 2; - return (new_num = 3); +return (new_num = 3); }; new_num = change_numbers(); })(); \ No newline at end of file diff --git a/documentation/js/super.js b/documentation/js/super.js index 11135d2368..040d6d2eaf 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -6,7 +6,7 @@ return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return (this.name = name); +return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -16,7 +16,7 @@ return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return (this.name = name); +return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); diff --git a/index.html b/index.html index c413e81b2d..55c4e961b4 100644 --- a/index.html +++ b/index.html @@ -111,12 +111,15 @@

    Mini Overview

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

    Installation and Usage

    @@ -373,7 +379,7 @@

    Language Reference

    change_numbers = function change_numbers() { var new_num; num = 2; - return (new_num = 3); +return (new_num = 3); }; new_num = change_numbers();

    @@ -579,24 +585,28 @@

    Language Reference

    # Zebra-stripe a table. highlight(row) for row, i in table when i % 2 is 0 -
    var __a, __b, __c, __d, __e, __f, __g, __h, food, i, lunch, row;
    +
    var __a, __b, __c, __d, __e, __f, __g, food, i, lunch, row;
     // Eat lunch.
     __a = ['toast', 'cheese', 'wine'];
    -__d = [];
    -for (__b=0, __c=__a.length; __b<__c; __b++) {
    -  food = __a[__b];
    -  __d[__b] = food.eat();
    +__c = [];
    +for (__b in __a) {
    +  if (__a.hasOwnProperty(__b)) {
    +    food = __a[__b];
    +    __d = food.eat();
    +    __c.push(__d);
    +  }
     }
    -lunch = __d;
    +lunch = __c;
     // Zebra-stripe a table.
     __e = table;
    -__h = [];
    -for (__f=0, __g=__e.length; __f<__g; __f++) {
    -  row = __e[__f];
    -  i = __f;
    -  __h[__f] = i % 2 === 0 ? highlight(row) : null;
    +__f = [];
    +for (i in __e) {
    +  if (__e.hasOwnProperty(i)) {
    +    row = __e[i];
    +    i % 2 === 0 ? highlight(row) : null;
    +  }
     }
    -__h;
    +__f;
     

    If you're not iterating over an actual array, you can use a range to @@ -678,7 +688,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return (this.name = name); +return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -688,7 +698,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return (this.name = name); +return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); @@ -708,7 +718,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return (this.name = name); +return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -718,7 +728,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return (this.name = name); +return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index fed7e70f43..060ae24d80 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -94,21 +94,21 @@ def compile(options={}, parent=nil) if last?(node) && (o[:return] || o[:assign]) if o[:return] if node.statement? || node.custom_return? - "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" + "#{node.compile(o)}#{node.line_ending}" else o.delete(:return) "#{o[:indent]}return #{node.compile(o)}#{node.line_ending}" end elsif o[:assign] if node.statement? || node.custom_assign? - "#{o[:indent]}#{node.compile(o)}#{node.line_ending}" + "#{node.compile(o)}#{node.line_ending}" else "#{o[:indent]}#{AssignNode.new(o[:assign], node).compile(o)};" end end else o.delete(:return) and o.delete(:assign) - indent = node.statement? ? '' : o[:indent] + indent = node.unwrap.statement? ? '' : o[:indent] "#{indent}#{node.compile(o)}#{node.line_ending}" end end @@ -140,7 +140,8 @@ def line_ending def compile(o={}) o = super(o) - write(@value.to_s) + indent = statement? ? o[:indent] : '' + write(indent + @value.to_s) end end @@ -163,7 +164,7 @@ def compile(o={}) o = super(o) return write(@expression.compile(o.merge(:return => true))) if @expression.custom_return? compiled = @expression.compile(o) - write(@expression.statement? ? "#{compiled}\n#{indent}return null" : "return #{compiled}") + write(@expression.statement? ? "#{compiled}\n#{o[:indent]}return null" : "#{o[:indent]}return #{compiled}") end end @@ -530,7 +531,7 @@ def compile(o={}) o.delete(:return) indent = o[:indent] + TAB cond = @condition.compile(o) - write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}") + write("#{o[:indent]}while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}") end end @@ -572,7 +573,7 @@ def compile(o={}) else index_var = nil body_dent = o[:indent] + TAB + TAB - source_part = "#{svar} = #{@source.compile(o)};\n#{o[:indent]}" + source_part = "#{o[:indent]}#{svar} = #{@source.compile(o)};\n#{o[:indent]}" for_part = "#{ivar} in #{svar}" pre_cond = "\n#{o[:indent] + TAB}if (#{svar}.hasOwnProperty(#{ivar})) {" var_part = "\n#{body_dent}#{@name} = #{svar}[#{ivar}];" @@ -593,7 +594,7 @@ def compile(o={}) o.delete(:return) body = IfNode.new(@filter, body, nil, :statement => true) if @filter elsif @filter - body = IfNode.new(@filter, @body) + body = Expressions.wrap(IfNode.new(@filter, @body)) end return_result = "\n#{o[:indent]}#{return_result};" @@ -625,7 +626,7 @@ def compile(o={}) error_part = @error ? " (#{@error}) " : ' ' catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}" finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:assign => nil, :return => nil))}\n#{indent}}" - write("try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}") + write("#{indent}try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}") end end @@ -641,7 +642,7 @@ def initialize(expression) def compile(o={}) o = super(o) - write("throw #{@expression.compile(o)}") + write("#{o[:indent]}throw #{@expression.compile(o)}") end end @@ -748,14 +749,16 @@ def compile(o={}) # force sub-else bodies into statement form. def compile_statement(o) indent = o[:indent] + child = o.delete(:chain_child) cond_o = o.dup cond_o.delete(:assign) cond_o.delete(:return) o[:indent] += TAB - if_part = "if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" + if_dent = child ? '' : indent + if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" return if_part unless @else_body else_part = chain? ? - " else #{@else_body.compile(o.merge(:indent => indent))}" : + " else #{@else_body.compile(o.merge(:indent => indent, :chain_child => true))}" : " else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{indent}}" if_part + else_part end diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index f61587a7de..9e3decfd87 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -3,7 +3,7 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, i, index, item, key; + var __a, __b, __c, __d, __e, __f, __g, i, index, item, key; index = 0; try { if (obj.forEach) { @@ -14,20 +14,22 @@ for (i in __a) { if (__a.hasOwnProperty(i)) { item = __a[i]; - __b.push(iterator.call(context, item, i, obj)); + __c = iterator.call(context, item, i, obj); + __b.push(__c); } } __b; } else { - __c = _.keys(obj); - __e = []; - for (__d in __c) { - if (__c.hasOwnProperty(__d)) { - key = __c[__d]; - __e.push(iterator.call(context, obj[key], key, obj)); + __d = _.keys(obj); + __f = []; + for (__e in __d) { + if (__d.hasOwnProperty(__e)) { + key = __d[__e]; + __g = iterator.call(context, obj[key], key, obj); + __f.push(__g); } } - __e; + __f; } } catch (e) { if (e !== breaker) { diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index b6f6ad7da5..4fb7dede53 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -2,7 +2,7 @@ // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, i, index, item, key; + var __a, __b, __c, __d, __e, __f, __g, i, index, item, key; index = 0; try { if (obj.forEach) { @@ -13,20 +13,22 @@ _.each = function each(obj, iterator, context) { for (i in __a) { if (__a.hasOwnProperty(i)) { item = __a[i]; - __b.push(iterator.call(context, item, i, obj)); + __c = iterator.call(context, item, i, obj); + __b.push(__c); } } __b; } else { - __c = _.keys(obj); - __e = []; - for (__d in __c) { - if (__c.hasOwnProperty(__d)) { - key = __c[__d]; - __e.push(iterator.call(context, obj[key], key, obj)); + __d = _.keys(obj); + __f = []; + for (__e in __d) { + if (__d.hasOwnProperty(__e)) { + key = __d[__e]; + __g = iterator.call(context, obj[key], key, obj); + __f.push(__g); } } - __e; + __f; } } catch (e) { if (e !== breaker) { From 1d2bb3b2be093034b1cdbc9475e6420181d51c42 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 17:50:12 -0500 Subject: [PATCH 208/303] adding splats to function definitions --- .../Syntaxes/CoffeeScript.tmLanguage | 4 +-- lib/coffee_script/grammar.y | 11 +++++-- lib/coffee_script/lexer.rb | 16 +++++++--- lib/coffee_script/nodes.rb | 30 ++++++++++++++++++- test/fixtures/execution/test_splats.coffee | 6 ++++ 5 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/execution/test_splats.coffee diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 88bb3bffc5..e2922226bc 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -39,7 +39,7 @@ comment match stuff like: funcName: => … match - ([a-zA-Z0-9_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) + ([a-zA-Z0-9_?.$*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) name meta.function.coffee @@ -60,7 +60,7 @@ comment match stuff like: a => … match - ([a-zA-Z0-9_?., $]*)\s*(=>) + ([a-zA-Z0-9_?., $*]*)\s*(=>) name meta.inline.function.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 2b747738c6..049482f3a0 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -5,7 +5,7 @@ token IF ELSE UNLESS token NUMBER STRING REGEX token TRUE FALSE YES NO ON OFF token IDENTIFIER PROPERTY_ACCESS -token CODE PARAM NEW RETURN +token CODE PARAM SPLAT NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE @@ -187,8 +187,13 @@ rule # The parameters to a function definition. ParamList: - PARAM { result = val } - | ParamList "," PARAM { result = val[0] << val[2] } + Param { result = val } + | ParamList "," Param { result = val[0] << val[2] } + ; + + Param: + PARAM + | SPLAT { result = SplatNode.new(val[0]) } ; # Expressions that can be treated as values. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 48a0a32f37..0234149761 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -190,15 +190,23 @@ def last_value # A source of ambiguity in our grammar was parameter lists in function # definitions (as opposed to argument lists in function calls). Tag - # parameter identifiers in order to avoid this. + # parameter identifiers in order to avoid this. Also, parameter lists can + # make use of splats. def tag_parameters - index = 0 + i = 0 loop do - tok = @tokens[index -= 1] + i -= 1 + tok, prev = @tokens[i], @tokens[i - 1] return if !tok next if tok[0] == ',' return if tok[0] != :IDENTIFIER - tok[0] = :PARAM + if prev && prev[0] == '*' + tok[0] = :SPLAT + @tokens.delete_at(i - 1) + i -= 1 + else + tok[0] = :PARAM + end end end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 060ae24d80..d2683292eb 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -64,6 +64,11 @@ def <<(node) self end + def unshift(node) + @expressions.unshift(node) + self + end + # If this Expressions consists of a single node, pull it back out. def unwrap @expressions.length == 1 ? @expressions.first : self @@ -389,7 +394,7 @@ def compile(o={}) o[:scope].find(name) unless @variable.properties? return write(@value.compile(o)) if @value.custom_assign? val = "#{name} = #{@value.compile(o)}" - write(o[:return] && !@value.custom_return? ? "return (#{val})" : val) + write(o[:return] && !@value.custom_return? ? "#{o[:indent]}return (#{val})" : val) end end @@ -459,12 +464,35 @@ def compile(o={}) o.delete(:no_wrap) name = o.delete(:immediate_assign) @params.each {|id| o[:scope].parameter(id.to_s) } + if @params.last.is_a?(SplatNode) + splat = @params.pop + splat.index = @params.length + @body.unshift(splat) + end code = @body.compile(o, :code) name_part = name ? " #{name}" : '' write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end end + # A parameter splat in a function definition. + class SplatNode < Node + attr_accessor :index + attr_reader :name + + def initialize(name) + @name = name + end + + def to_s + @name + end + + def compile(o={}) + "var #{@name} = Array.prototype.slice.call(arguments, #{@index})" + end + end + # An object literal. class ObjectNode < Node attr_reader :properties diff --git a/test/fixtures/execution/test_splats.coffee b/test/fixtures/execution/test_splats.coffee new file mode 100644 index 0000000000..6b2731379f --- /dev/null +++ b/test/fixtures/execution/test_splats.coffee @@ -0,0 +1,6 @@ +func: first, second, *rest => + rest.join(' ') + +result: func(1, 2, 3, 4, 5) + +print(result is "3 4 5") \ No newline at end of file From 26c89cef06d93e0c20965e68ef070fb6341534d5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 18:03:39 -0500 Subject: [PATCH 209/303] reserving variables for splats the regular way, not through a custom 'var' declaration --- lib/coffee_script/nodes.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index d2683292eb..737709d138 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -463,12 +463,12 @@ def compile(o={}) o.delete(:assign) o.delete(:no_wrap) name = o.delete(:immediate_assign) - @params.each {|id| o[:scope].parameter(id.to_s) } if @params.last.is_a?(SplatNode) splat = @params.pop splat.index = @params.length @body.unshift(splat) end + @params.each {|id| o[:scope].parameter(id.to_s) } code = @body.compile(o, :code) name_part = name ? " #{name}" : '' write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}") @@ -484,12 +484,9 @@ def initialize(name) @name = name end - def to_s - @name - end - def compile(o={}) - "var #{@name} = Array.prototype.slice.call(arguments, #{@index})" + o[:scope].find(@name) + "#{@name} = Array.prototype.slice.call(arguments, #{@index})" end end From 43edd29d2242d6378b639722dbf2d45d8188dd25 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 18:09:48 -0500 Subject: [PATCH 210/303] comment about test_execution being the most important --- test/unit/test_execution.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 0737fdd3ae..64efc8f829 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -5,6 +5,9 @@ class ExecutionTest < Test::Unit::TestCase NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/ ALLS_WELL = /\A\n?(true\n)+\Z/m + # This is by far the most important test. It evaluates all of the + # CoffeeScript in test/fixtures/execution, ensuring that all our + # syntax actually works. def test_execution_of_coffeescript sources = ['test/fixtures/execution/*.coffee'].join(' ') assert `bin/coffee -r #{sources}`.match(ALLS_WELL) From abfc9f5a2d144b6131548336b98e9b0a4e7ceddf Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 18:22:51 -0500 Subject: [PATCH 211/303] adding a note in the docs about how to build the parser and install the gem --- documentation/index.html.erb | 15 +++++++++++---- documentation/js/scope.js | 2 +- documentation/js/super.js | 4 ++-- index.html | 27 +++++++++++++++++---------- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 35500a6ab5..71b8cc862f 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -427,10 +427,17 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Resources

    -

    - Source Code
    - Bugs and Feature Requests
    -

    +
      +
    • + Source Code
      + Use bin/coffee to test your changes, or rake gem:install to + create and install a custom version of the gem. If you're hacking on the + parser, use rake build:parser to rebuild it. +
    • +
    • + Bugs and Feature Requests +
    • +

    Contributing

    diff --git a/documentation/js/scope.js b/documentation/js/scope.js index 38d5e939ba..23b0c8e9dd 100644 --- a/documentation/js/scope.js +++ b/documentation/js/scope.js @@ -4,7 +4,7 @@ change_numbers = function change_numbers() { var new_num; num = 2; -return (new_num = 3); + return (new_num = 3); }; new_num = change_numbers(); })(); \ No newline at end of file diff --git a/documentation/js/super.js b/documentation/js/super.js index 040d6d2eaf..11135d2368 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -6,7 +6,7 @@ return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { -return (this.name = name); + return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -16,7 +16,7 @@ return (this.name = name); return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { -return (this.name = name); + return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); diff --git a/index.html b/index.html index 55c4e961b4..87043ea6d1 100644 --- a/index.html +++ b/index.html @@ -379,7 +379,7 @@

    Language Reference

    change_numbers = function change_numbers() { var new_num; num = 2; -return (new_num = 3); + return (new_num = 3); }; new_num = change_numbers();

    @@ -688,7 +688,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { -return (this.name = name); + return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -698,7 +698,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { -return (this.name = name); + return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); @@ -718,7 +718,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { -return (this.name = name); + return (this.name = name); }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -728,7 +728,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { -return (this.name = name); + return (this.name = name); }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); @@ -850,10 +850,17 @@

    Language Reference

    Resources

    -

    - Source Code
    - Bugs and Feature Requests
    -

    +
      +
    • + Source Code
      + Use bin/coffee to test your changes, or rake gem:install to + create and install a custom version of the gem. If you're hacking on the + parser, use rake build:parser to rebuild it. +
    • +
    • + Bugs and Feature Requests +
    • +

    Contributing

    From 409283a30f37583660d4f9b3ea5836cf7cc40717 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 19:52:13 -0500 Subject: [PATCH 212/303] adding splats as arguments to function calls --- lib/coffee_script/grammar.y | 11 ++++-- lib/coffee_script/lexer.rb | 12 ++----- lib/coffee_script/nodes.rb | 40 +++++++++++++++++++--- test/fixtures/execution/test_splats.coffee | 31 ++++++++++++++++- test/unit/test_execution.rb | 23 ++++++++----- 5 files changed, 90 insertions(+), 27 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 049482f3a0..e83ad1d1f5 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -5,7 +5,7 @@ token IF ELSE UNLESS token NUMBER STRING REGEX token TRUE FALSE YES NO ON OFF token IDENTIFIER PROPERTY_ACCESS -token CODE PARAM SPLAT NEW RETURN +token CODE PARAM NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN WHILE @@ -19,7 +19,7 @@ token INDENT OUTDENT # Declare order of operations. prechigh - nonassoc UMINUS NOT '!' '!!' '~' '++' '--' + nonassoc UMINUS SPLAT NOT '!' '!!' '~' '++' '--' left '*' '/' '%' left '+' '-' left '<<' '>>' '>>>' @@ -70,6 +70,7 @@ rule | For | Switch | Extends + | Splat | Comment ; @@ -193,7 +194,11 @@ rule Param: PARAM - | SPLAT { result = SplatNode.new(val[0]) } + | '*' PARAM = SPLAT { result = ParamSplatNode.new(val[1]) } + ; + + Splat: + '*' Value = SPLAT { result = ArgSplatNode.new(val[1]) } ; # Expressions that can be treated as values. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 0234149761..5878981566 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -196,17 +196,11 @@ def tag_parameters i = 0 loop do i -= 1 - tok, prev = @tokens[i], @tokens[i - 1] + tok = @tokens[i] return if !tok - next if tok[0] == ',' + next if ['*', ','].include?(tok[0]) return if tok[0] != :IDENTIFIER - if prev && prev[0] == '*' - tok[0] = :SPLAT - @tokens.delete_at(i - 1) - i -= 1 - else - tok[0] = :PARAM - end + tok[0] = :PARAM end end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 737709d138..682b511251 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -212,11 +212,16 @@ def super? @variable == :super end + def prefix + @new ? "new " : '' + end + def compile(o={}) o = super(o) + @splat = @arguments.detect {|a| a.is_a?(ArgSplatNode) } + return write(compile_splat(o)) if @splat args = @arguments.map{|a| a.compile(o) }.join(', ') return write(compile_super(args, o)) if super? - prefix = @new ? "new " : '' write("#{prefix}#{@variable.compile(o)}(#{args})") end @@ -225,6 +230,17 @@ def compile_super(args, o) arg_part = args.empty? ? '' : ", #{args}" "#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})" end + + def compile_splat(o) + meth = @variable.compile(o) + obj = @variable.source || 'this' + args = @arguments.map do |arg| + code = arg.compile(o) + code = arg == @splat ? code : "[#{code}]" + arg.equal?(@arguments.first) ? code : ".concat(#{code})" + end + "#{prefix}#{meth}.apply(#{obj}, #{args.join('')})" + end end # Node to extend an object's prototype with an ancestor object. @@ -247,7 +263,7 @@ def compile(o={}) # A value, indexed or dotted into, or vanilla. class ValueNode < Node - attr_reader :literal, :properties, :last + attr_reader :literal, :properties, :last, :source def initialize(literal, properties=[]) @literal, @properties = literal, properties @@ -280,6 +296,7 @@ def compile(o={}) val.respond_to?(:compile) ? val.compile(o) : val.to_s end @last = parts.last + @source = parts.length > 1 ? parts[0...-1].join('') : nil write(parts.join('')) end end @@ -463,7 +480,7 @@ def compile(o={}) o.delete(:assign) o.delete(:no_wrap) name = o.delete(:immediate_assign) - if @params.last.is_a?(SplatNode) + if @params.last.is_a?(ParamSplatNode) splat = @params.pop splat.index = @params.length @body.unshift(splat) @@ -476,7 +493,7 @@ def compile(o={}) end # A parameter splat in a function definition. - class SplatNode < Node + class ParamSplatNode < Node attr_accessor :index attr_reader :name @@ -486,8 +503,21 @@ def initialize(name) def compile(o={}) o[:scope].find(@name) - "#{@name} = Array.prototype.slice.call(arguments, #{@index})" + write("#{@name} = Array.prototype.slice.call(arguments, #{@index})") + end + end + + class ArgSplatNode < Node + attr_reader :value + + def initialize(value) + @value = value end + + def compile(o={}) + write(@value.compile(o)) + end + end # An object literal. diff --git a/test/fixtures/execution/test_splats.coffee b/test/fixtures/execution/test_splats.coffee index 6b2731379f..c5881bbe79 100644 --- a/test/fixtures/execution/test_splats.coffee +++ b/test/fixtures/execution/test_splats.coffee @@ -3,4 +3,33 @@ func: first, second, *rest => result: func(1, 2, 3, 4, 5) -print(result is "3 4 5") \ No newline at end of file +print(result is "3 4 5") + + +gold: silver: bronze: the_field: null + +medalists: first, second, third, *rest => + gold: first + silver: second + bronze: third + the_field: rest + +contenders: [ + "Michael Phelps" + "Liu Xiang" + "Yao Ming" + "Allyson Felix" + "Shawn Johnson" + "Roman Sebrle" + "Guo Jingjing" + "Tyson Gay" + "Asafa Powell" + "Usain Bolt" +] + +medalists("Mighty Mouse", *contenders) + +print(gold is "Mighty Mouse") +print(silver is "Michael Phelps") +print(bronze is "Liu Xiang") +print(the_field.length is 8) \ No newline at end of file diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 64efc8f829..04186305d6 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -2,30 +2,35 @@ class ExecutionTest < Test::Unit::TestCase - NO_WARNINGS = /\A(0 error\(s\), 0 warning\(s\)\n)+\Z/ - ALLS_WELL = /\A\n?(true\n)+\Z/m + NO_WARNINGS = "0 error(s), 0 warning(s)" # This is by far the most important test. It evaluates all of the # CoffeeScript in test/fixtures/execution, ensuring that all our # syntax actually works. def test_execution_of_coffeescript sources = ['test/fixtures/execution/*.coffee'].join(' ') - assert `bin/coffee -r #{sources}`.match(ALLS_WELL) + (`bin/coffee -r #{sources}`).split("\n").each do |line| + assert line == "true" + end end def test_lintless_coffeescript - lint_results = `bin/coffee -l test/fixtures/execution/*.coffee` - assert lint_results.match(NO_WARNINGS) + no_warnings `bin/coffee -l test/fixtures/execution/*.coffee` end def test_lintless_examples - lint_results = `bin/coffee -l examples/*.coffee` - assert lint_results.match(NO_WARNINGS) + no_warnings `bin/coffee -l examples/*.coffee` end def test_lintless_documentation - lint_results = `bin/coffee -l documentation/coffee/*.coffee` - assert lint_results.match(NO_WARNINGS) + no_warnings `bin/coffee -l documentation/coffee/*.coffee` + end + + + private + + def no_warnings(output) + output.split("\n").each {|line| assert line == NO_WARNINGS } end end From 41056ca2bd78b97b1738213d8085605d1b0b8bbd Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 31 Dec 2009 20:02:15 -0500 Subject: [PATCH 213/303] fix for multiple splats in a function call --- lib/coffee_script/nodes.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 682b511251..3928d31f12 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -216,10 +216,13 @@ def prefix @new ? "new " : '' end + def splat? + @arguments.any? {|a| a.is_a?(ArgSplatNode) } + end + def compile(o={}) o = super(o) - @splat = @arguments.detect {|a| a.is_a?(ArgSplatNode) } - return write(compile_splat(o)) if @splat + return write(compile_splat(o)) if splat? args = @arguments.map{|a| a.compile(o) }.join(', ') return write(compile_super(args, o)) if super? write("#{prefix}#{@variable.compile(o)}(#{args})") @@ -236,7 +239,7 @@ def compile_splat(o) obj = @variable.source || 'this' args = @arguments.map do |arg| code = arg.compile(o) - code = arg == @splat ? code : "[#{code}]" + code = arg.is_a?(ArgSplatNode) ? code : "[#{code}]" arg.equal?(@arguments.first) ? code : ".concat(#{code})" end "#{prefix}#{meth}.apply(#{obj}, #{args.join('')})" From ad5b5fa45802bb2dda885ccc4a5dcdbee2b3aa63 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 09:49:18 -0500 Subject: [PATCH 214/303] fixing the regex lexer to make it less agressive when we know it can't possibly be a regex --- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/lexer.rb | 12 +++++++++++- test/fixtures/execution/test_literals.coffee | 8 +++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index e2922226bc..238589377c 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -240,7 +240,7 @@ match - !|\$|%|&|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b + !|\$|%|&|\*|\/|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b name keyword.operator.coffee diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 5878981566..70c605588d 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -37,6 +37,10 @@ class Lexer COMMENT_CLEANER = /(^\s*#|\n\s*$)/ NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/ + # Tokens which a regular expression will never immediately follow. + # See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions + NOT_REGEX = [:IDENTIFIER, :NUMBER, :STRING] + # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) @code = code.chomp # Cleanup code by remove extra line breaks @@ -107,6 +111,7 @@ def js_token # Matches regular expression literals. def regex_token return false unless regex = @chunk[REGEX, 1] + return false if NOT_REGEX.include?(last_tag) token(:REGEX, regex) @i += regex.length end @@ -183,11 +188,16 @@ def token(tag, value) @tokens << [tag, Value.new(value, @line)] end - # Peek at the previous token. + # Peek at the previous token's value. def last_value @tokens.last && @tokens.last[1] end + # Peek at the previous token's tag. + def last_tag + @tokens.last && @tokens.last[0] + end + # A source of ambiguity in our grammar was parameter lists in function # definitions (as opposed to argument lists in function calls). Tag # parameter identifiers in order to avoid this. Also, parameter lists can diff --git a/test/fixtures/execution/test_literals.coffee b/test/fixtures/execution/test_literals.coffee index 73a5b4d259..24f54e65fc 100644 --- a/test/fixtures/execution/test_literals.coffee +++ b/test/fixtures/execution/test_literals.coffee @@ -1,3 +1,9 @@ a: [(x => x), (x => x * x)] -print(a.length is 2) \ No newline at end of file +print(a.length is 2) + + +regex: /match/i +words: "I think there is a match in here." + +print(!!words.match(regex)) \ No newline at end of file From caf9d627cb0953af24048099d21bae483642beae Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 10:15:22 -0500 Subject: [PATCH 215/303] allowing expressions within range literals --- lib/coffee_script/grammar.y | 6 ++++-- lib/coffee_script/lexer.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index e83ad1d1f5..2a3e6588e8 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -265,8 +265,10 @@ rule # The range literal. Range: - "[" Value "." "." Value "]" { result = RangeNode.new(val[1], val[4]) } - | "[" Value "." "." "." Value "]" { result = RangeNode.new(val[1], val[5], true) } + "[" Expression + "." "." Expression "]" { result = RangeNode.new(val[1], val[4]) } + | "[" Expression + "." "." "." Expression "]" { result = RangeNode.new(val[1], val[5], true) } ; # The array literal. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 70c605588d..0c9a202ab0 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -19,7 +19,7 @@ class Lexer # Token matching regexes. 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 + 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 OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ From 2cef93b381b71c81c2be905f0198c67ad0299fe6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 10:17:33 -0500 Subject: [PATCH 216/303] test for uminus --- test/fixtures/execution/test_literals.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/fixtures/execution/test_literals.coffee b/test/fixtures/execution/test_literals.coffee index 24f54e65fc..27fec2d36e 100644 --- a/test/fixtures/execution/test_literals.coffee +++ b/test/fixtures/execution/test_literals.coffee @@ -6,4 +6,9 @@ print(a.length is 2) regex: /match/i words: "I think there is a match in here." -print(!!words.match(regex)) \ No newline at end of file +print(!!words.match(regex)) + + +neg: (3 -4) + +print(neg is -1) \ No newline at end of file From 02fbd28e5859e4a62d95859c24fb12c34e403ab9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 10:20:29 -0500 Subject: [PATCH 217/303] adding a test for expressions in range comprehensions --- test/fixtures/execution/test_range_comprehension.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/execution/test_range_comprehension.coffee b/test/fixtures/execution/test_range_comprehension.coffee index 7c29d96241..5702ff07f5 100644 --- a/test/fixtures/execution/test_range_comprehension.coffee +++ b/test/fixtures/execution/test_range_comprehension.coffee @@ -1,6 +1,6 @@ nums: i * 3 for i in [1..3] -negs: x for x in [-20..-10] +negs: x for x in [-20..-5*2] negs: negs[0..2] result: nums.concat(negs).join(', ') From a93a570603aae3703aa22cc6f8cd69228632cfca Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 10:38:28 -0500 Subject: [PATCH 218/303] allowing indentation within function calls --- lib/coffee_script/grammar.y | 4 +++- test/fixtures/execution/test_arguments.coffee | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/execution/test_arguments.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 2a3e6588e8..8bf0ad07b7 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -280,10 +280,12 @@ rule ArgList: /* nothing */ { result = [] } | Expression { result = val } + | INDENT Expression { result = [val[1]] } | ArgList "," Expression { result = val[0] << val[2] } | ArgList Terminator Expression { result = val[0] << val[2] } | ArgList "," Terminator Expression { result = val[0] << val[3] } - | INDENT ArgList OUTDENT { result = val[1] } + | ArgList "," INDENT Expression { result = val[0] << val[3] } + | ArgList OUTDENT { result = val[0] } ; # Try/catch/finally exception handling blocks. diff --git a/test/fixtures/execution/test_arguments.coffee b/test/fixtures/execution/test_arguments.coffee new file mode 100644 index 0000000000..7375828b86 --- /dev/null +++ b/test/fixtures/execution/test_arguments.coffee @@ -0,0 +1,17 @@ +area: x, y, x1, y1 => + (x - x1) * (x - y1) + +x: y: 10 +x1: y1: 20 + +print(area(x, y, x1, y1) is 100 ) + +print(area(x, y, + x1, y1) is 100) + +print(area( + x + y + x1 + y1 +) is 100) From f622fc43bc0d4a49e5f35ab57d26b20928e06952 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 10:40:29 -0500 Subject: [PATCH 219/303] -- --- test/fixtures/execution/test_arguments.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/execution/test_arguments.coffee b/test/fixtures/execution/test_arguments.coffee index 7375828b86..21fdbcac30 100644 --- a/test/fixtures/execution/test_arguments.coffee +++ b/test/fixtures/execution/test_arguments.coffee @@ -4,7 +4,7 @@ area: x, y, x1, y1 => x: y: 10 x1: y1: 20 -print(area(x, y, x1, y1) is 100 ) +print(area(x, y, x1, y1) is 100) print(area(x, y, x1, y1) is 100) From 305f883eabeddac1e947cdc71bcfdcf078e4e8a2 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 10:55:43 -0500 Subject: [PATCH 220/303] making range comprehensions compile safely, even when you assign to the same variable as your endposts. --- lib/coffee_script/nodes.rb | 22 ++++++++++++------- .../execution/test_range_comprehension.coffee | 9 +++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 3928d31f12..7d6280dada 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -334,7 +334,7 @@ def compile(o={}) # A range literal. Ranges can be used to extract portions (slices) of arrays, # or to specify a range for array comprehensions. - class RangeNode + class RangeNode < Node attr_reader :from, :to def initialize(from, to, exclusive=false) @@ -353,12 +353,18 @@ def greater_operator @exclusive ? '>' : '>=' end + def compile_variables(o) + idt = o[:indent] + @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable + from_val, to_val = @from.compile(o), @to.compile(o) + write("#{idt}#{@from_var} = #{from_val};\n#{idt}#{@to_var} = #{to_val};\n#{idt}") + end + def compile(o, fv) - fvv, tvv = @from.compile(o), @to.compile(o) - vars = "#{fv}=#{fvv}" - compare = "(#{fvv} <= #{tvv} ? #{fv} #{less_operator} #{tvv} : #{fv} #{greater_operator} #{tvv})" - incr = "(#{fvv} <= #{tvv} ? #{fv} += 1 : #{fv} -= 1)" - "#{vars}; #{compare}; #{incr}" + vars = "#{fv}=#{@from_var}" + compare = "(#{@from_var} <= #{@to_var} ? #{fv} #{less_operator} #{@to_var} : #{fv} #{greater_operator} #{@to_var})" + incr = "(#{@from_var} <= #{@to_var} ? #{fv} += 1 : #{fv} -= 1)" + write("#{vars}; #{compare}; #{incr}") end end @@ -624,9 +630,9 @@ def compile(o={}) tvar = scope.free_variable if range body_dent = o[:indent] + TAB - source_part, var_part = '', '', '' - pre_cond, post_cond = '', '' + var_part, pre_cond, post_cond = '', '', '' index_var = scope.free_variable + source_part = @source.compile_variables(o) for_part = "#{index_var}=0, #{@source.compile(o, ivar)}, #{index_var}++" else index_var = nil diff --git a/test/fixtures/execution/test_range_comprehension.coffee b/test/fixtures/execution/test_range_comprehension.coffee index 5702ff07f5..e4856201c9 100644 --- a/test/fixtures/execution/test_range_comprehension.coffee +++ b/test/fixtures/execution/test_range_comprehension.coffee @@ -5,4 +5,11 @@ negs: negs[0..2] result: nums.concat(negs).join(', ') -print(result is '3, 6, 9, -20, -19, -18') \ No newline at end of file +print(result is '3, 6, 9, -20, -19, -18') + +# Ensure that ranges are safe. This used to infinite loop: +j = 5 +result: for j in [j..(j+3)] + j + +print(result.join(' ') is '5 6 7 8') \ No newline at end of file From e30a267c9da8f9b470c122d4d3d16345ca04567e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 11:19:57 -0500 Subject: [PATCH 221/303] fixing precedence order, so that you can nest range comprehensions --- lib/coffee_script/grammar.y | 3 ++- lib/coffee_script/nodes.rb | 9 +++++---- .../execution/test_nested_comprehensions.coffee | 11 +++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/execution/test_nested_comprehensions.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 8bf0ad07b7..30351fdade 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -32,7 +32,8 @@ prechigh left '.' right INDENT left OUTDENT - right THROW FOR IN WHILE WHEN NEW SUPER ELSE + right WHEN IN + right THROW FOR WHILE NEW SUPER ELSE left UNLESS EXTENDS IF left ASSIGN '||=' '&&=' right RETURN '=>' diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7d6280dada..14b7282007 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -360,10 +360,11 @@ def compile_variables(o) write("#{idt}#{@from_var} = #{from_val};\n#{idt}#{@to_var} = #{to_val};\n#{idt}") end - def compile(o, fv) - vars = "#{fv}=#{@from_var}" - compare = "(#{@from_var} <= #{@to_var} ? #{fv} #{less_operator} #{@to_var} : #{fv} #{greater_operator} #{@to_var})" - incr = "(#{@from_var} <= #{@to_var} ? #{fv} += 1 : #{fv} -= 1)" + def compile(o, idx=nil) + raise SyntaxError, "unexpected range literal" unless idx + vars = "#{idx}=#{@from_var}" + compare = "(#{@from_var} <= #{@to_var} ? #{idx} #{less_operator} #{@to_var} : #{idx} #{greater_operator} #{@to_var})" + incr = "(#{@from_var} <= #{@to_var} ? #{idx} += 1 : #{idx} -= 1)" write("#{vars}; #{compare}; #{incr}") end diff --git a/test/fixtures/execution/test_nested_comprehensions.coffee b/test/fixtures/execution/test_nested_comprehensions.coffee new file mode 100644 index 0000000000..ce6952d857 --- /dev/null +++ b/test/fixtures/execution/test_nested_comprehensions.coffee @@ -0,0 +1,11 @@ +multi_liner: + for x in [3..5] + for y in [3..5] + [x, y] + +single_liner: + [x, y] for y in [3..5] for x in [3..5] + +print(multi_liner.length is single_liner.length) +print(5 is multi_liner[2][2][1]) +print(5 is single_liner[2][2][1]) From 0f81dbe913fa2c30b4132dc4b3a7d63a21a90f4f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 11:54:59 -0500 Subject: [PATCH 222/303] adding steps to range comprehensions --- .../Syntaxes/CoffeeScript.tmLanguage | 2 +- lib/coffee_script/grammar.y | 16 +++++++++------- lib/coffee_script/lexer.rb | 2 +- lib/coffee_script/nodes.rb | 16 ++++++++++------ .../execution/test_range_comprehension.coffee | 7 ++++++- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 238589377c..5d35aa1022 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -204,7 +204,7 @@ match - \b(break|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|when|while)\b + \b(break|by|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|when|while)\b name keyword.control.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 30351fdade..61c3acd2b7 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 WHILE +token FOR IN BY WHILE token SWITCH WHEN token DELETE INSTANCEOF TYPEOF token SUPER EXTENDS @@ -32,7 +32,7 @@ prechigh left '.' right INDENT left OUTDENT - right WHEN IN + right WHEN IN BY right THROW FOR WHILE NEW SUPER ELSE left UNLESS EXTENDS IF left ASSIGN '||=' '&&=' @@ -321,8 +321,8 @@ rule # Looks a little confusing, check nodes.rb for the arguments to ForNode. For: Expression FOR - ForVariables ForSource { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) } - | FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2][0], val[1][0], val[2][1], val[1][1]) } + ForVariables ForSource { result = ForNode.new(val[0], val[3], val[2][0], val[2][1]) } + | FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2], val[1][0], val[1][1]) } ; # An array comprehension has variables for the current element and index. @@ -333,9 +333,11 @@ rule # The source of the array comprehension can optionally be filtered. ForSource: - IN Expression { result = [val[1]] } - | IN Expression - WHEN Expression { result = [val[1], val[3]] } + IN Expression { result = {:source => val[1]} } + | ForSource + WHEN Expression { result = val[0].merge(:filter => val[2]) } + | ForSource + BY Expression { result = val[0].merge(:step => val[2]) } ; # Switch/When blocks. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 0c9a202ab0..3421b1b0e3 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -12,7 +12,7 @@ class Lexer "new", "return", "try", "catch", "finally", "throw", "break", "continue", - "for", "in", "where", "while", + "for", "in", "by", "where", "while", "switch", "when", "super", "extends", "delete", "instanceof", "typeof"] diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 14b7282007..9b701e5c5f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -360,11 +360,12 @@ def compile_variables(o) write("#{idt}#{@from_var} = #{from_val};\n#{idt}#{@to_var} = #{to_val};\n#{idt}") end - def compile(o, idx=nil) + def compile(o, idx=nil, step=nil) raise SyntaxError, "unexpected range literal" unless idx vars = "#{idx}=#{@from_var}" + step = step ? step.compile(o) : '1' compare = "(#{@from_var} <= #{@to_var} ? #{idx} #{less_operator} #{@to_var} : #{idx} #{greater_operator} #{@to_var})" - incr = "(#{@from_var} <= #{@to_var} ? #{idx} += 1 : #{idx} -= 1)" + incr = "(#{@from_var} <= #{@to_var} ? #{idx} += #{step} : #{idx} -= #{step})" write("#{vars}; #{compare}; #{incr}") end @@ -609,10 +610,13 @@ class ForNode < Node custom_return custom_assign - attr_reader :body, :source, :name, :filter, :index + attr_reader :body, :source, :name, :index, :filter, :step - def initialize(body, source, name, filter, index=nil) - @body, @source, @name, @filter, @index = body, source, name, filter, index + def initialize(body, source, name, index=nil) + @body, @name, @index = body, name, index + @source = source[:source] + @filter = source[:filter] + @step = source[:step] end def line_ending @@ -634,7 +638,7 @@ def compile(o={}) var_part, pre_cond, post_cond = '', '', '' index_var = scope.free_variable source_part = @source.compile_variables(o) - for_part = "#{index_var}=0, #{@source.compile(o, ivar)}, #{index_var}++" + for_part = "#{index_var}=0, #{@source.compile(o, ivar, @step)}, #{index_var}++" else index_var = nil body_dent = o[:indent] + TAB + TAB diff --git a/test/fixtures/execution/test_range_comprehension.coffee b/test/fixtures/execution/test_range_comprehension.coffee index e4856201c9..8917550b41 100644 --- a/test/fixtures/execution/test_range_comprehension.coffee +++ b/test/fixtures/execution/test_range_comprehension.coffee @@ -12,4 +12,9 @@ j = 5 result: for j in [j..(j+3)] j -print(result.join(' ') is '5 6 7 8') \ No newline at end of file +print(result.join(' ') is '5 6 7 8') + +# With range comprehensions, you can loop in steps. +results: x for x in [0..25] by 5 + +print(results.join(' ') is '0 5 10 15 20 25') \ No newline at end of file From 2f69bc1708f7359d03499149632c70588ad2006e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 12:08:36 -0500 Subject: [PATCH 223/303] expanding the list of tokens that regexes may not follow, according to the Mozilla JS 2.0 docs --- lib/coffee_script/lexer.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 3421b1b0e3..70ed3ee6b5 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -37,9 +37,14 @@ class Lexer COMMENT_CLEANER = /(^\s*#|\n\s*$)/ NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/ - # Tokens which a regular expression will never immediately follow. + # Tokens which a regular expression will never immediately follow, but which + # a division operator might. # See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions - NOT_REGEX = [:IDENTIFIER, :NUMBER, :STRING] + NOT_REGEX = [ + :IDENTIFIER, :NUMBER, :REGEX, :STRING, + ')', '++', '--', ']', '}', + :FALSE, :NULL, :THIS, :TRUE + ] # Scan by attempting to match tokens one character at a time. Slow and steady. def tokenize(code) From 3489eec6ee513226901cecedebe108022d7ca281 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 12:11:35 -0500 Subject: [PATCH 224/303] commenting the lexer a bit more --- lib/coffee_script/lexer.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 70ed3ee6b5..0e593e55a1 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -63,8 +63,8 @@ def tokenize(code) Rewriter.new.rewrite(@tokens) end - # At every position, run this list of match attempts, short-circuiting if - # any of them succeed. + # At every position, run through this list of attempted matches, + # short-circuiting if any of them succeed. def extract_next_token return if identifier_token return if number_token @@ -77,11 +77,13 @@ def extract_next_token return literal_token end + # Tokenizers ========================================================== + # Matches identifying literals: variables, keywords, method names, etc. def identifier_token return false unless identifier = @chunk[IDENTIFIER, 1] - # Keywords are special identifiers tagged with their own name, 'if' will result - # in an [:IF, "if"] token + # Keywords are special identifiers tagged with their own name, + # 'if' will result in an [:IF, "if"] token. tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.') token(tag, identifier) @@ -147,6 +149,8 @@ def indent_token @indent = size end + # Record an oudent token or tokens, if we're moving back inwards past + # multiple recorded indents. def outdent_token(move_out) while move_out > 0 && !@indents.empty? last_indent = @indents.pop @@ -187,6 +191,8 @@ def literal_token @i += value.length end + # Helpers ========================================================== + # Add a token to the results, taking note of the line number, and # immediately-preceding comment. def token(tag, value) From ff80f8d423689fbf1d2ef58d35335b89c815c70f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 12:31:05 -0500 Subject: [PATCH 225/303] adding the notion of existence -- postfixing an expression with a question mark will check if to see if it's not null or undefined --- lib/coffee_script/grammar.y | 7 ++++++- lib/coffee_script/nodes.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 61c3acd2b7..e1c86bdb43 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -19,7 +19,7 @@ token INDENT OUTDENT # Declare order of operations. prechigh - nonassoc UMINUS SPLAT NOT '!' '!!' '~' '++' '--' + nonassoc UMINUS SPLAT NOT '!' '!!' '~' '++' '--' '?' left '*' '/' '%' left '+' '-' left '<<' '>>' '>>>' @@ -72,6 +72,7 @@ rule | Switch | Extends | Splat + | Existence | Comment ; @@ -181,6 +182,10 @@ rule | Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) } ; + Existence: + Expression '?' { result = ExistenceNode.new(val[0]) } + ; + # Function definition. Code: ParamList "=>" Block { result = CodeNode.new(val[0], val[2]) } diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 9b701e5c5f..55fdd6efae 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -715,6 +715,19 @@ def compile(o={}) end end + # Check an expression for existence (meaning not null or undefined). + class ExistenceNode < Node + attr_reader :expression + + def initialize(expression) + @expression = expression + end + + def compile(o={}) + write("(#{@expression.compile(super(o))} != undefined)") + end + end + # An extra set of parentheses, supplied by the script source. # You can't wrap parentheses around bits that get compiled into JS statements, # unfortunately. From 75d9e23df4ab9eac94d983c04173e92de9bd8c2e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 12:41:55 -0500 Subject: [PATCH 226/303] better existence test, with tests --- lib/coffee_script/nodes.rb | 3 ++- test/fixtures/execution/test_existence.coffee | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/execution/test_existence.coffee diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 55fdd6efae..005914102b 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -724,7 +724,8 @@ def initialize(expression) end def compile(o={}) - write("(#{@expression.compile(super(o))} != undefined)") + val = @expression.compile(super(o)) + write("(typeof #{val} !== 'undefined' && #{val} !== null)") end end diff --git a/test/fixtures/execution/test_existence.coffee b/test/fixtures/execution/test_existence.coffee new file mode 100644 index 0000000000..47bfbfab65 --- /dev/null +++ b/test/fixtures/execution/test_existence.coffee @@ -0,0 +1,5 @@ +print(if my_special_variable? then false else true) + +my_special_variable: false + +print(if my_special_variable? then true else false) \ No newline at end of file From 34add7d7bfb4625021dff1f9e7841293b83e1558 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 17:11:48 -0500 Subject: [PATCH 227/303] adding consistent highlighting to variable assignment, whether functions or values --- examples/code.coffee | 4 ++-- .../Syntaxes/CoffeeScript.tmLanguage | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/examples/code.coffee b/examples/code.coffee index 7c327ad376..0b65aa20ef 100644 --- a/examples/code.coffee +++ b/examples/code.coffee @@ -27,9 +27,9 @@ spaced_out_multiline_object: { } # Arrays: -stooges : [{moe: 45}, {curly: 43}, {larry: 46}] +stooges: [{moe: 45}, {curly: 43}, {larry: 46}] -exponents : [(x => x), (x => x * x), (x => x * x * x)] +exponents: [(x => x), (x => x * x), (x => x * x * x)] empty: [] diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 5d35aa1022..2ff3cd30d3 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -208,6 +208,25 @@ name keyword.control.coffee + + match + \b([a-zA-Z$_]\w*)(\:)\s + name + variable.assignment.coffee + captures + + 1 + + name + entity.name.function.coffee + + 2 + + name + keyword.operator.coffee + + + match \b(true|on|yes)\b From c3d0e50e8f99630075293bdedcb7e7f93ccaace4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 17:16:34 -0500 Subject: [PATCH 228/303] fixing the food/eat array comprehension in the docs to not pretend like there's a made-up method on String.prototype --- documentation/coffee/array_comprehensions.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/coffee/array_comprehensions.coffee b/documentation/coffee/array_comprehensions.coffee index a04ab395f9..c272944ea5 100644 --- a/documentation/coffee/array_comprehensions.coffee +++ b/documentation/coffee/array_comprehensions.coffee @@ -1,5 +1,5 @@ # Eat lunch. -lunch: food.eat() for food in ['toast', 'cheese', 'wine'] +lunch: this.eat(food) for food in ['toast', 'cheese', 'wine'] # Zebra-stripe a table. highlight(row) for row, i in table when i % 2 is 0 \ No newline at end of file From 38520bfeced51a7b4005dfe09ee241770229a1e5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 1 Jan 2010 22:00:34 -0500 Subject: [PATCH 229/303] rebuilding narwhal uncovered a bug with named functions --- .../narwhal/coffee-script.coffee | 5 ++- .../narwhal/lib/coffee-script.js | 42 +++++++++---------- .../narwhal/lib/coffee-script/loader.js | 4 +- lib/coffee_script/nodes.rb | 2 +- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index 289e58b4a4..d687c42b32 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -21,8 +21,9 @@ checkForErrors: coffeeProcess => # command. exports.run: args => if args.length - exports.evalCS(File.read(path)) for path in args - delete args[i] for path, i in args + for path, i in args + exports.evalCS(File.read(path)) + delete args[i] return true while true diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index 12ec02b1be..99a2c40c12 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -1,14 +1,16 @@ (function(){ var File, OS, Readline, checkForErrors, coffeePath; - // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee Executes the `coffee` Ruby program to convert from CoffeeScript - // to Javascript. Eventually this will hopefully happen entirely within JS. Require external dependencies. + // This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee + // Executes the `coffee` Ruby program to convert from CoffeeScript + // to Javascript. Eventually this will hopefully happen entirely within JS. + // Require external dependencies. OS = require('os'); File = require('file'); Readline = require('readline'); // The path to the CoffeeScript Compiler. coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee'); // Our general-purpose error handler. - checkForErrors = function(coffeeProcess) { + checkForErrors = function checkForErrors(coffeeProcess) { if (coffeeProcess.wait() === 0) { return true; } @@ -17,24 +19,20 @@ }; // Run a simple REPL, round-tripping to the CoffeeScript compiler for every // command. - exports.run = function(args) { - var __a, __b, __c, __d, __e, __f, __g, __h, i, path, result; + exports.run = function run(args) { + var __a, __b, __c, i, path, result; if (args.length) { __a = args; - __d = []; - for (__b=0, __c=__a.length; __b<__c; __b++) { - path = __a[__b]; - __d[__b] = exports.evalCS(File.read(path)); - } - __d; - __e = args; - __h = []; - for (__f=0, __g=__e.length; __f<__g; __f++) { - path = __e[__f]; - i = __f; - __h[__f] = delete args[i]; + __b = []; + for (i in __a) { + if (__a.hasOwnProperty(i)) { + path = __a[i]; + exports.evalCS(File.read(path)); + __c = delete args[i]; + __b.push(__c); + } } - __h; + __b; return true; } while (true) { @@ -50,14 +48,14 @@ } }; // Compile a given CoffeeScript file into JavaScript. - exports.compileFile = function(path) { + exports.compileFile = function compileFile(path) { var coffee; coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]); checkForErrors(coffee); return coffee.stdout.read(); }; // Compile a string of CoffeeScript into JavaScript. - exports.compile = function(source) { + exports.compile = function compile(source) { var coffee; coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]); coffee.stdin.write(source).flush().close(); @@ -65,11 +63,11 @@ return coffee.stdout.read(); }; // Evaluating a string of CoffeeScript first compiles it externally. - exports.evalCS = function(source) { + exports.evalCS = function evalCS(source) { return eval(exports.compile(source)); }; // Make a factory for the CoffeeScript environment. - exports.makeNarwhalFactory = function(path) { + exports.makeNarwhalFactory = function makeNarwhalFactory(path) { var code, factoryText; code = exports.compileFile(path); factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}"; diff --git a/lib/coffee_script/narwhal/lib/coffee-script/loader.js b/lib/coffee_script/narwhal/lib/coffee-script/loader.js index b668033239..24b7ef27bb 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script/loader.js +++ b/lib/coffee_script/narwhal/lib/coffee-script/loader.js @@ -6,14 +6,14 @@ }; loader = { // Reload the coffee-script environment from source. - reload: function(topId, path) { + reload: function reload(topId, path) { coffeescript = coffeescript || require('coffee-script'); return (factories[topId] = function() { return coffeescript.makeNarwhalFactory(path); }); }, // Ensure that the coffee-script environment is loaded. - load: function(topId, path) { + load: function load(topId, path) { return factories[topId] = factories[topId] || this.reload(topId, path); } }; diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 005914102b..2ea0a3369b 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -417,7 +417,7 @@ def compile(o={}) last = @variable.last.to_s.sub(LEADING_DOT, '') proto = name[PROTO_ASSIGN, 1] o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto) - o[:immediate_assign] = last if @value.is_a?(CodeNode) + o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER) return write("#{name}: #{@value.compile(o)}") if @context == :object o[:scope].find(name) unless @variable.properties? return write(@value.compile(o)) if @value.custom_assign? From 7ee5be674d9456f15ea419fc9a69ee3b635fcc17 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 2 Jan 2010 00:20:24 -0500 Subject: [PATCH 230/303] adding splice literals, with tests --- lib/coffee_script/nodes.rb | 16 ++++++++++++++-- test/fixtures/execution/test_splices.coffee | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/execution/test_splices.coffee diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 2ea0a3369b..29ab1aa14c 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -294,8 +294,10 @@ def custom_return? end def compile(o={}) - o = super(o) - parts = [@literal, @properties].flatten.map do |val| + o = super(o) + only = o.delete(:only_first) + props = only ? @properties[0...-1] : @properties + parts = [@literal, props].flatten.map do |val| val.respond_to?(:compile) ? val.compile(o) : val.to_s end @last = parts.last @@ -413,6 +415,7 @@ def statement? def compile(o={}) o = super(o) + return compile_splice(o) if @variable.properties.last.is_a?(SliceNode) name = @variable.compile(o) last = @variable.last.to_s.sub(LEADING_DOT, '') proto = name[PROTO_ASSIGN, 1] @@ -424,6 +427,15 @@ def compile(o={}) val = "#{name} = #{@value.compile(o)}" write(o[:return] && !@value.custom_return? ? "#{o[:indent]}return (#{val})" : val) end + + def compile_splice(o) + var = @variable.compile(o.merge(:only_first => true)) + range = @variable.properties.last.range + plus = range.exclusive? ? '' : ' + 1' + from = range.from.compile(o) + to = "#{range.to.compile(o)} - #{from}#{plus}" + write("#{var}.splice.apply(#{var}, [#{from}, #{to}].concat(#{@value.compile(o)}))") + end end # Simple Arithmetic and logical operations. Performs some conversion from diff --git a/test/fixtures/execution/test_splices.coffee b/test/fixtures/execution/test_splices.coffee new file mode 100644 index 0000000000..0ac1135f99 --- /dev/null +++ b/test/fixtures/execution/test_splices.coffee @@ -0,0 +1,5 @@ +array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +array[5..10]: [0, 0, 0] + +print(array.join(' ') is '0 1 2 3 4 0 0 0') \ No newline at end of file From 7eff8786bc6bd3e9c9ce69d91df1cfac2cec2275 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 2 Jan 2010 01:00:03 -0500 Subject: [PATCH 231/303] todo --- documentation/index.html.erb | 15 +++++++++++++++ documentation/js/array_comprehensions.js | 2 +- index.html | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 71b8cc862f..ced17b85c9 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -1,3 +1,18 @@ +<%# + TODO: + Multiline and nested array comprehensions (and filters with 'when'). + Range comprehension examples (expression endpoints), with steps. + Object comprehension examples. + Significant Whitespace Rules. + Newline-delimited Matrix. + Automatic newline escaping. + All functions are named functions. + Splats in function definitions. + (Multiple) splats as arguments to a function call. + Exists? + Array assignment splice literals. +%> + <% require 'uv' def code_for(file, executable=false) diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index edc73121ad..e2d3b39e47 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -6,7 +6,7 @@ for (__b in __a) { if (__a.hasOwnProperty(__b)) { food = __a[__b]; - __d = food.eat(); + __d = this.eat(food); __c.push(__d); } } diff --git a/index.html b/index.html index 87043ea6d1..483b37b668 100644 --- a/index.html +++ b/index.html @@ -581,7 +581,7 @@

    Language Reference

    would use a loop, each/forEach, map, or select/filter.

    # Eat lunch.
    -lunch: food.eat() for food in ['toast', 'cheese', 'wine']
    +lunch: this.eat(food) for food in ['toast', 'cheese', 'wine']
     
     # Zebra-stripe a table.
     highlight(row) for row, i in table when i % 2 is 0
    @@ -592,7 +592,7 @@ 

    Language Reference

    for (__b in __a) { if (__a.hasOwnProperty(__b)) { food = __a[__b]; - __d = food.eat(); + __d = this.eat(food); __c.push(__d); } } From 21a0cc83aedf85feffa69a4a6be391a0bcfc9a65 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 10:19:39 -0500 Subject: [PATCH 232/303] Adding kamatsu's proposed block literal syntax --- lib/coffee_script/grammar.y | 13 +++++++++++-- lib/coffee_script/nodes.rb | 4 ++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index e1c86bdb43..469716e292 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -17,6 +17,9 @@ token COMMENT token JS token INDENT OUTDENT +# We expect one shift-reduce conflict. Because of the lexer, it will never occur. +expect 1 + # Declare order of operations. prechigh nonassoc UMINUS SPLAT NOT '!' '!!' '~' '++' '--' '?' @@ -260,8 +263,14 @@ rule # A generic function invocation. Invocation: - Value "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } - | Invocation "(" ArgList ")" { result = CallNode.new(val[0], val[2]) } + Value Arguments { result = CallNode.new(val[0], val[1]) } + | Invocation Arguments { result = CallNode.new(val[0], val[1]) } + # | Invocation Code { result = val[0] << val[1] } + ; + + Arguments: + "(" ArgList ")" { result = val[1] } + | "(" ArgList ")" Code { result = val[1] << val[3] } ; # Calling super. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 29ab1aa14c..e7c3ecf891 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -220,6 +220,10 @@ def splat? @arguments.any? {|a| a.is_a?(ArgSplatNode) } end + def <<(argument) + @arguments << argument + end + def compile(o={}) o = super(o) return write(compile_splat(o)) if splat? From ba3c5298f7789202ab5b85da00f5d42fa1d523c4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 10:46:37 -0500 Subject: [PATCH 233/303] adding a block test and using PARAM_SPLAT to remove the last shift/reduce conflict --- lib/coffee_script/grammar.y | 9 +++------ lib/coffee_script/lexer.rb | 3 ++- test/fixtures/execution/test_blocks.coffee | 4 ++++ 3 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/execution/test_blocks.coffee diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 469716e292..f821370d26 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -5,7 +5,7 @@ token IF ELSE UNLESS token NUMBER STRING REGEX token TRUE FALSE YES NO ON OFF token IDENTIFIER PROPERTY_ACCESS -token CODE PARAM NEW RETURN +token CODE PARAM PARAM_SPLAT NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE token FOR IN BY WHILE @@ -17,12 +17,9 @@ token COMMENT token JS token INDENT OUTDENT -# We expect one shift-reduce conflict. Because of the lexer, it will never occur. -expect 1 - # Declare order of operations. prechigh - nonassoc UMINUS SPLAT NOT '!' '!!' '~' '++' '--' '?' + nonassoc UMINUS PARAM_SPLAT SPLAT NOT '!' '!!' '~' '++' '--' '?' left '*' '/' '%' left '+' '-' left '<<' '>>' '>>>' @@ -203,7 +200,7 @@ rule Param: PARAM - | '*' PARAM = SPLAT { result = ParamSplatNode.new(val[1]) } + | PARAM_SPLAT PARAM { result = ParamSplatNode.new(val[1]) } ; Splat: diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 0e593e55a1..706b1d51f0 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -219,7 +219,8 @@ def tag_parameters i -= 1 tok = @tokens[i] return if !tok - next if ['*', ','].include?(tok[0]) + next if tok[0] == ',' + next tok[0] = :PARAM_SPLAT if tok[0] == '*' return if tok[0] != :IDENTIFIER tok[0] = :PARAM end diff --git a/test/fixtures/execution/test_blocks.coffee b/test/fixtures/execution/test_blocks.coffee new file mode 100644 index 0000000000..a28eecc955 --- /dev/null +++ b/test/fixtures/execution/test_blocks.coffee @@ -0,0 +1,4 @@ +results: [1, 2, 3].map() x => + x * x + +print(results.join(' ') is '1 4 9') \ No newline at end of file From de4eddcad4eb3d4ecf5d763fb4e649ec3f30f0dc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 11:01:50 -0500 Subject: [PATCH 234/303] bumping version numbers to 0.2.0 in anticipation of release soon-ish --- coffee-script.gemspec | 4 ++-- lib/coffee-script.rb | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index ec801ec371..1f7ae5c150 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.1.6' # Keep version in sync with coffee-script.rb - s.date = '2009-12-27' + s.version = '0.2.0' # Keep version in sync with coffee-script.rb + s.date = '2010-1-3' s.homepage = "http://jashkenas.github.com/coffee-script/" s.summary = "The CoffeeScript Compiler" diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index fa2b726309..287378c559 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.1.6' # Keep in sync with the gemspec. + VERSION = '0.2.0' # 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 d3811285cc..bb842ff87a 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,5 @@ "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", - "version": "0.1.6" + "version": "0.2.0" } From 78c4957ba87f95e2f7a28908fcb37743b8e94cfe Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 13:59:17 -0500 Subject: [PATCH 235/303] first rough rough rough draft of kamatsu's closure suggestion -- test.coffee runs, but probably nothing else --- lib/coffee_script/grammar.y | 12 +-- lib/coffee_script/nodes.rb | 178 ++++++++++-------------------------- lib/coffee_script/scope.rb | 13 +-- 3 files changed, 62 insertions(+), 141 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index f821370d26..4810adf169 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -43,14 +43,14 @@ rule # All parsing will end in this rule, being the trunk of the AST. Root: - /* nothing */ { result = Expressions.new([]) } - | Terminator { result = Expressions.new([]) } + /* nothing */ { result = Expressions.new } + | Terminator { result = Expressions.new } | Expressions { result = val[0] } ; # Any list of expressions or method body, seperated by line breaks or semis. Expressions: - Expression { result = Expressions.new(val) } + Expression { result = Expressions.wrap(val) } | Expressions Terminator Expression { result = val[0] << val[2] } | Expressions Terminator { result = val[0] } ; @@ -78,7 +78,7 @@ rule Block: INDENT Expressions OUTDENT { result = val[1] } - | INDENT OUTDENT { result = Expressions.new([]) } + | INDENT OUTDENT { result = Expressions.new } ; # All tokens that can terminate an expression. @@ -405,8 +405,8 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: IfBlock IfEnd { result = val[0].add_else(val[1]) } - | Expression IF Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true}) } - | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.new([val[0]]), nil, {:statement => true, :invert => true}) } + | Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) } + | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) } ; end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index e7c3ecf891..c9f2fd7db1 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -11,17 +11,11 @@ def self.statement class_eval "def statement?; true; end" end - # Tag this node as having a custom return, meaning that instead of returning - # it from the outside, you ask it to return itself, and it obliges. - def self.custom_return - class_eval "def custom_return?; true; end" - end - - # Tag this node as having a custom assignment, meaning that instead of - # assigning it to a variable name from the outside, you pass it the variable - # name and let it take care of it. - def self.custom_assign - class_eval "def custom_assign?; true; end" + # Tag this node as a statement that cannot be transformed into an expression. + # (break, continue, etc.) It doesn't make sense to try to transform it. + def self.statement_only + statement + class_eval "def statement_only?; true; end" end def write(code) @@ -33,29 +27,31 @@ def compile(o={}) @options = o.dup end + def compile_closure(o={}) + "(#{CodeNode.new([], Expressions.wrap(self)).compile(o, o[:scope])})()" + end + # Default implementations of the common node methods. - def unwrap; self; end - def line_ending; ';'; end - def statement?; false; end - def custom_return?; false; end - def custom_assign?; false; end + def unwrap; self; end + def statement?; false; end + def statement_only?; false; end end # A collection of nodes, each one representing an expression. class Expressions < Node statement - custom_assign attr_reader :expressions STRIP_TRAILING_WHITESPACE = /\s+$/ # Wrap up a node as an Expressions, unless it already is. - def self.wrap(node) - node.is_a?(Expressions) ? node : Expressions.new([node]) + def self.wrap(*nodes) + return nodes[0] if nodes.length == 1 && nodes[0].is_a?(Expressions) + Expressions.new(*nodes) end - def initialize(nodes) - @expressions = nodes + def initialize(*nodes) + @expressions = nodes.flatten end # Tack an expression onto the end of this node. @@ -83,43 +79,36 @@ def last?(node) # If this is the top-level Expressions, wrap everything in a safety closure. def root_compile(o={}) indent = o[:no_wrap] ? '' : TAB - code = compile(o.merge(:indent => indent, :scope => Scope.new), o[:no_wrap] ? nil : :code) + code = compile(o.merge(:indent => indent, :scope => Scope.new(nil, self)), o[:no_wrap] ? nil : :code) code.gsub!(STRIP_TRAILING_WHITESPACE, '') o[:no_wrap] ? code : "(function(){\n#{code}\n})();" end - # The extra fancy is to handle pushing down returns and assignments - # recursively to the final lines of inner statements. - # Variables first defined within the Expressions body have their - # declarations pushed up to the top scope. + # The extra fancy is to handle pushing down returns to the final lines of + # inner statements. Variables first defined within the Expressions body + # have their declarations pushed up top of the closest scope. def compile(options={}, parent=nil) return root_compile(options) unless options[:scope] compiled = @expressions.map do |node| o = super(options) - if last?(node) && (o[:return] || o[:assign]) - if o[:return] - if node.statement? || node.custom_return? - "#{node.compile(o)}#{node.line_ending}" - else - o.delete(:return) - "#{o[:indent]}return #{node.compile(o)}#{node.line_ending}" - end - elsif o[:assign] - if node.statement? || node.custom_assign? - "#{node.compile(o)}#{node.line_ending}" - else - "#{o[:indent]}#{AssignNode.new(o[:assign], node).compile(o)};" - end + returns = o.delete(:return) + code = node.compile(o) + if last?(node) && returns && !node.statement_only? + if node.statement? + node.compile(o.merge(:return => true)) + else + "#{o[:indent]}return #{node.compile(o)};" end else - o.delete(:return) and o.delete(:assign) - indent = node.unwrap.statement? ? '' : o[:indent] - "#{indent}#{node.compile(o)}#{node.line_ending}" + ending = node.statement_only? ? '' : ';' + indent = node.statement? ? '' : o[:indent] + "#{indent}#{node.compile(o)}#{ending}" end end scope = options[:scope] - declarations = scope.any_declared? && parent == :code ? "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" : '' - code = declarations + compiled.join("\n") + decls = '' + decls = "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" if parent == :code && scope.declarations?(self) + code = decls + compiled.join("\n") write(code) end end @@ -138,10 +127,7 @@ def initialize(value) def statement? STATEMENTS.include?(@value.to_s) end - - def line_ending - @value.to_s[-1..-1] == ';' ? '' : ';' - end + alias_method :statement_only?, :statement? def compile(o={}) o = super(o) @@ -152,8 +138,7 @@ def compile(o={}) # Try to return your expression, or tell it to return itself. class ReturnNode < Node - statement - custom_return + statement_only attr_reader :expression @@ -161,10 +146,6 @@ def initialize(expression) @expression = expression end - def line_ending - @expression.custom_return? ? '' : ';' - end - def compile(o={}) o = super(o) return write(@expression.compile(o.merge(:return => true))) if @expression.custom_return? @@ -176,16 +157,12 @@ def compile(o={}) # Pass through CoffeeScript comments into JavaScript comments at the # same position. class CommentNode < Node - statement + statement_only def initialize(lines) @lines = lines.value end - def line_ending - '' - end - def compile(o={}) delimiter = "\n#{o[:indent]}//" comment = "#{delimiter}#{@lines.join(delimiter)}" @@ -253,6 +230,7 @@ def compile_splat(o) # Node to extend an object's prototype with an ancestor object. # After goog.inherits from the Closure Library. class ExtendsNode < Node + statement attr_reader :sub_object, :super_object def initialize(sub_object, super_object) @@ -289,14 +267,6 @@ def statement? @literal.is_a?(Node) && @literal.statement? && !properties? end - def custom_assign? - @literal.is_a?(Node) && @literal.custom_assign? && !properties? - end - - def custom_return? - @literal.is_a?(Node) && @literal.custom_return? && !properties? - end - def compile(o={}) o = super(o) only = o.delete(:only_first) @@ -401,35 +371,25 @@ class AssignNode < Node PROTO_ASSIGN = /\A(\S+)\.prototype/ LEADING_DOT = /\A\./ - custom_return - attr_reader :variable, :value, :context def initialize(variable, value, context=nil) @variable, @value, @context = variable, value, context end - def line_ending - @value.custom_assign? ? '' : ';' - end - - def statement? - @value.custom_assign? - end - def compile(o={}) o = super(o) return compile_splice(o) if @variable.properties.last.is_a?(SliceNode) name = @variable.compile(o) last = @variable.last.to_s.sub(LEADING_DOT, '') proto = name[PROTO_ASSIGN, 1] - o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto) + o = o.merge(:last_assign => last, :proto_assign => proto) o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER) return write("#{name}: #{@value.compile(o)}") if @context == :object o[:scope].find(name) unless @variable.properties? - return write(@value.compile(o)) if @value.custom_assign? - val = "#{name} = #{@value.compile(o)}" - write(o[:return] && !@value.custom_return? ? "#{o[:indent]}return (#{val})" : val) + code = @value.statement? ? @value.compile_closure(o) : @value.compile(o) + val = "#{name} = #{code}" + write(o[:return] ? "#{o[:indent]}return (#{val})" : val) end def compile_splice(o) @@ -498,13 +458,12 @@ def initialize(params, body) @body = body end - def compile(o={}) + def compile(o={}, shared_scope=nil) o = super(o) - o[:scope] = Scope.new(o[:scope]) + o[:scope] = shared_scope || Scope.new(o[:scope], @body) o[:return] = true indent = o[:indent] o[:indent] += TAB - o.delete(:assign) o.delete(:no_wrap) name = o.delete(:immediate_assign) if @params.last.is_a?(ParamSplatNode) @@ -604,10 +563,6 @@ def initialize(condition, body) @condition, @body = condition, body end - def line_ending - '' - end - def compile(o={}) o = super(o) o.delete(:return) @@ -623,8 +578,6 @@ def compile(o={}) # the current index of the loop as a second parameter. class ForNode < Node statement - custom_return - custom_assign attr_reader :body, :source, :name, :index, :filter, :step @@ -635,10 +588,6 @@ def initialize(body, source, name, index=nil) @step = source[:step] end - def line_ending - '' - end - def compile(o={}) o = super(o) range = @source.is_a?(RangeNode) @@ -668,14 +617,12 @@ def compile(o={}) set_result = "#{rvar} = [];\n#{o[:indent]}" return_result = rvar temp_var = ValueNode.new(LiteralNode.new(tvar)) - body = Expressions.new([ + body = Expressions.wrap( AssignNode.new(temp_var, @body), CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var]) - ]) - if o[:return] || o[:assign] - return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign] + ) + if o[:return] return_result = "return #{return_result}" if o[:return] - o.delete(:assign) o.delete(:return) body = IfNode.new(@filter, body, nil, :statement => true) if @filter elsif @filter @@ -691,8 +638,6 @@ def compile(o={}) # A try/catch/finally block. class TryNode < Node statement - custom_return - custom_assign attr_reader :try, :error, :recovery, :finally @@ -700,24 +645,20 @@ def initialize(try, error, recovery, finally=nil) @try, @error, @recovery, @finally = try, error, recovery, finally end - def line_ending - '' - end - def compile(o={}) o = super(o) indent = o[:indent] o[:indent] += TAB error_part = @error ? " (#{@error}) " : ' ' catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}" - finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:assign => nil, :return => nil))}\n#{indent}}" + finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{indent}}" write("#{indent}try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}") end end # Throw an exception. class ThrowNode < Node - statement + statement_only attr_reader :expression @@ -760,14 +701,6 @@ def statement? @expressions.unwrap.statement? end - def custom_assign? - @expressions.custom_assign? - end - - def custom_return? - @expressions.custom_return? - end - def compile(o={}) raise SyntaxError, "line #{@line}: parentheses can't be wrapped around a statement" if statement? o = super(o) @@ -827,18 +760,6 @@ def statement? @is_statement ||= !!(@tags[:statement] || @body.statement? || (@else_body && @else_body.statement?)) end - def custom_return? - statement? - end - - def custom_assign? - statement? - end - - def line_ending - statement? ? '' : ';' - end - def compile(o={}) o = super(o) write(statement? ? compile_statement(o) : compile_ternary(o)) @@ -850,7 +771,6 @@ def compile_statement(o) indent = o[:indent] child = o.delete(:chain_child) cond_o = o.dup - cond_o.delete(:assign) cond_o.delete(:return) o[:indent] += TAB if_dent = child ? '' : indent diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index 5e510b12ca..303e5f2bcd 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -5,11 +5,12 @@ module CoffeeScript # whether a variable has been seen before or if it needs to be declared. class Scope - attr_reader :parent, :variables, :temp_variable + attr_reader :parent, :expressions, :variables, :temp_variable - # Initialize a scope with its parent, for lookups up the chain. - def initialize(parent=nil) - @parent = parent + # Initialize a scope with its parent, for lookups up the chain, + # as well as the Expressions body where it should declare its variables. + def initialize(parent, expressions) + @parent, @expressions = parent, expressions @variables = {} @temp_variable = @parent ? @parent.temp_variable : '__a' end @@ -46,8 +47,8 @@ def free_variable @temp_variable.dup end - def any_declared? - !declared_variables.empty? + def declarations?(body) + !declared_variables.empty? && body == @expressions end # Return the list of variables first declared in current scope. From 4814d5baa59ae3a7cb2f51eccfa13ff39c30dca9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 15:13:59 -0500 Subject: [PATCH 236/303] more progress -- you can wrap parentheses around statements now --- lib/coffee_script/nodes.rb | 133 +++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 72 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index c9f2fd7db1..5e49387895 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -25,10 +25,14 @@ def write(code) def compile(o={}) @options = o.dup + top = @options.delete(:top) + statement? && !statement_only? && !top ? compile_closure(@options) : compile_node(@options) end def compile_closure(o={}) - "(#{CodeNode.new([], Expressions.wrap(self)).compile(o, o[:scope])})()" + indent = o[:indent] + o[:indent] += TAB + "(function() {\n#{compile_node(o)}\n#{indent}})()" end # Default implementations of the common node methods. @@ -76,21 +80,16 @@ def last?(node) node == @expressions[@last_index] end - # If this is the top-level Expressions, wrap everything in a safety closure. - def root_compile(o={}) - indent = o[:no_wrap] ? '' : TAB - code = compile(o.merge(:indent => indent, :scope => Scope.new(nil, self)), o[:no_wrap] ? nil : :code) - code.gsub!(STRIP_TRAILING_WHITESPACE, '') - o[:no_wrap] ? code : "(function(){\n#{code}\n})();" + def compile(o={}) + o[:scope] ? compile_node(o) : compile_root(o) end # The extra fancy is to handle pushing down returns to the final lines of # inner statements. Variables first defined within the Expressions body # have their declarations pushed up top of the closest scope. - def compile(options={}, parent=nil) - return root_compile(options) unless options[:scope] + def compile_node(options={}) compiled = @expressions.map do |node| - o = super(options) + o = options.dup returns = o.delete(:return) code = node.compile(o) if last?(node) && returns && !node.statement_only? @@ -102,15 +101,27 @@ def compile(options={}, parent=nil) else ending = node.statement_only? ? '' : ';' indent = node.statement? ? '' : o[:indent] - "#{indent}#{node.compile(o)}#{ending}" + "#{indent}#{node.compile(o.merge(:top => true))}#{ending}" end end - scope = options[:scope] + write(compiled.join("\n")) + end + + # If this is the top-level Expressions, wrap everything in a safety closure. + def compile_root(o={}) + indent = o[:no_wrap] ? '' : TAB + o.merge!(:indent => indent, :scope => Scope.new(nil, self)) + code = o[:no_wrap] ? compile_node(o) : compile_with_declarations(o) + code.gsub!(STRIP_TRAILING_WHITESPACE, '') + o[:no_wrap] ? code : "(function(){\n#{code}\n})();" + end + + def compile_with_declarations(o={}) decls = '' - decls = "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" if parent == :code && scope.declarations?(self) - code = decls + compiled.join("\n") - write(code) + decls = "#{o[:indent]}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self) + code = decls + compile_node(o) end + end # Literals are static values that have a Ruby representation, eg.: a string, a number, @@ -129,8 +140,7 @@ def statement? end alias_method :statement_only?, :statement? - def compile(o={}) - o = super(o) + def compile_node(o) indent = statement? ? o[:indent] : '' write(indent + @value.to_s) end @@ -146,9 +156,8 @@ def initialize(expression) @expression = expression end - def compile(o={}) - o = super(o) - return write(@expression.compile(o.merge(:return => true))) if @expression.custom_return? + def compile_node(o) + return write(@expression.compile(o.merge(:return => true))) if @expression.statement? compiled = @expression.compile(o) write(@expression.statement? ? "#{compiled}\n#{o[:indent]}return null" : "#{o[:indent]}return #{compiled}") end @@ -163,7 +172,7 @@ def initialize(lines) @lines = lines.value end - def compile(o={}) + def compile_node(o={}) delimiter = "\n#{o[:indent]}//" comment = "#{delimiter}#{@lines.join(delimiter)}" write(comment) @@ -201,8 +210,7 @@ def <<(argument) @arguments << argument end - def compile(o={}) - o = super(o) + def compile_node(o) return write(compile_splat(o)) if splat? args = @arguments.map{|a| a.compile(o) }.join(', ') return write(compile_super(args, o)) if super? @@ -237,7 +245,7 @@ def initialize(sub_object, super_object) @sub_object, @super_object = sub_object, super_object end - def compile(o={}) + def compile_node(o={}) sub, sup = @sub_object.compile(o), @super_object.compile(o) "#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" + "#{sub}.prototype = new #{sup}();\n#{o[:indent]}" + @@ -267,8 +275,7 @@ def statement? @literal.is_a?(Node) && @literal.statement? && !properties? end - def compile(o={}) - o = super(o) + def compile_node(o) only = o.delete(:only_first) props = only ? @properties[0...-1] : @properties parts = [@literal, props].flatten.map do |val| @@ -288,8 +295,7 @@ def initialize(name) @name = name end - def compile(o={}) - o = super(o) + def compile_node(o) write(".#{@name}") end end @@ -302,8 +308,7 @@ def initialize(index) @index = index end - def compile(o={}) - o = super(o) + def compile_node(o) write("[#{@index.compile(o)}]") end end @@ -336,7 +341,8 @@ def compile_variables(o) write("#{idt}#{@from_var} = #{from_val};\n#{idt}#{@to_var} = #{to_val};\n#{idt}") end - def compile(o, idx=nil, step=nil) + def compile_node(o) + idx, step = o.delete(:index), o.delete(:step) raise SyntaxError, "unexpected range literal" unless idx vars = "#{idx}=#{@from_var}" step = step ? step.compile(o) : '1' @@ -357,8 +363,7 @@ def initialize(range) @range = range end - def compile(o={}) - o = super(o) + def compile_node(o) from = @range.from.compile(o) to = @range.to.compile(o) plus_part = @range.exclusive? ? '' : ' + 1' @@ -377,8 +382,7 @@ def initialize(variable, value, context=nil) @variable, @value, @context = variable, value, context end - def compile(o={}) - o = super(o) + def compile_node(o) return compile_splice(o) if @variable.properties.last.is_a?(SliceNode) name = @variable.compile(o) last = @variable.last.to_s.sub(LEADING_DOT, '') @@ -387,8 +391,7 @@ def compile(o={}) o[:immediate_assign] = last if @value.is_a?(CodeNode) && last.match(Lexer::IDENTIFIER) return write("#{name}: #{@value.compile(o)}") if @context == :object o[:scope].find(name) unless @variable.properties? - code = @value.statement? ? @value.compile_closure(o) : @value.compile(o) - val = "#{name} = #{code}" + val = "#{name} = #{@value.compile(o)}" write(o[:return] ? "#{o[:indent]}return (#{val})" : val) end @@ -428,8 +431,7 @@ def unary? @second.nil? end - def compile(o={}) - o = super(o) + def compile_node(o) return write(compile_conditional(o)) if CONDITIONALS.include?(@operator.to_sym) return write(compile_unary(o)) if unary? write("#{@first.compile(o)} #{@operator} #{@second.compile(o)}") @@ -458,12 +460,12 @@ def initialize(params, body) @body = body end - def compile(o={}, shared_scope=nil) - o = super(o) - o[:scope] = shared_scope || Scope.new(o[:scope], @body) - o[:return] = true - indent = o[:indent] - o[:indent] += TAB + def compile_node(o) + shared_scope = o.delete(:shared_scope) + o[:scope] = shared_scope || Scope.new(o[:scope], @body) + o[:return] = true + indent = o[:indent] + o[:indent] += TAB o.delete(:no_wrap) name = o.delete(:immediate_assign) if @params.last.is_a?(ParamSplatNode) @@ -472,7 +474,7 @@ def compile(o={}, shared_scope=nil) @body.unshift(splat) end @params.each {|id| o[:scope].parameter(id.to_s) } - code = @body.compile(o, :code) + code = @body.compile_with_declarations(o) name_part = name ? " #{name}" : '' write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}") end @@ -487,7 +489,7 @@ def initialize(name) @name = name end - def compile(o={}) + def compile_node(o={}) o[:scope].find(@name) write("#{@name} = Array.prototype.slice.call(arguments, #{@index})") end @@ -500,7 +502,7 @@ def initialize(value) @value = value end - def compile(o={}) + def compile_node(o={}) write(@value.compile(o)) end @@ -517,8 +519,7 @@ def initialize(properties = []) # All the mucking about with commas is to make sure that CommentNodes and # AssignNodes get interleaved correctly, with no trailing commas or # commas affixed to comments. TODO: Extract this and add it to ArrayNode. - def compile(o={}) - o = super(o) + def compile_node(o) indent = o[:indent] o[:indent] += TAB joins = Hash.new("\n") @@ -541,8 +542,7 @@ def initialize(objects=[]) @objects = objects end - def compile(o={}) - o = super(o) + def compile_node(o) objects = @objects.map { |obj| joiner = obj.is_a?(CommentNode) ? "\n#{o[:indent] + TAB}" : obj == @objects.last ? '' : ', ' obj.compile(o.merge(:indent => o[:indent] + TAB)) + joiner @@ -563,8 +563,7 @@ def initialize(condition, body) @condition, @body = condition, body end - def compile(o={}) - o = super(o) + def compile_node(o) o.delete(:return) indent = o[:indent] + TAB cond = @condition.compile(o) @@ -588,8 +587,7 @@ def initialize(body, source, name, index=nil) @step = source[:step] end - def compile(o={}) - o = super(o) + def compile_node(o) range = @source.is_a?(RangeNode) scope = o[:scope] name_found = scope.find(@name) @@ -603,7 +601,7 @@ def compile(o={}) var_part, pre_cond, post_cond = '', '', '' index_var = scope.free_variable source_part = @source.compile_variables(o) - for_part = "#{index_var}=0, #{@source.compile(o, ivar, @step)}, #{index_var}++" + for_part = "#{index_var}=0, #{@source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++" else index_var = nil body_dent = o[:indent] + TAB + TAB @@ -645,8 +643,7 @@ def initialize(try, error, recovery, finally=nil) @try, @error, @recovery, @finally = try, error, recovery, finally end - def compile(o={}) - o = super(o) + def compile_node(o) indent = o[:indent] o[:indent] += TAB error_part = @error ? " (#{@error}) " : ' ' @@ -666,8 +663,7 @@ def initialize(expression) @expression = expression end - def compile(o={}) - o = super(o) + def compile_node(o) write("#{o[:indent]}throw #{@expression.compile(o)}") end end @@ -680,8 +676,8 @@ def initialize(expression) @expression = expression end - def compile(o={}) - val = @expression.compile(super(o)) + def compile_node(o) + val = @expression.compile(o) write("(typeof #{val} !== 'undefined' && #{val} !== null)") end end @@ -697,13 +693,7 @@ def initialize(expressions, line=nil) @line = line end - def statement? - @expressions.unwrap.statement? - end - - def compile(o={}) - raise SyntaxError, "line #{@line}: parentheses can't be wrapped around a statement" if statement? - o = super(o) + def compile_node(o) compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' write("(#{compiled})") @@ -760,8 +750,7 @@ def statement? @is_statement ||= !!(@tags[:statement] || @body.statement? || (@else_body && @else_body.statement?)) end - def compile(o={}) - o = super(o) + def compile_node(o) write(statement? ? compile_statement(o) : compile_ternary(o)) end From 099944e0aa338ee881d921e9a71926177b777ea1 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 15:59:33 -0500 Subject: [PATCH 237/303] All execution tests are now passing with statements everywhere --- lib/coffee_script/grammar.y | 10 ++++++---- lib/coffee_script/nodes.rb | 24 ++++++++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 4810adf169..e381f7ab86 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -33,10 +33,11 @@ prechigh right INDENT left OUTDENT right WHEN IN BY - right THROW FOR WHILE NEW SUPER ELSE - left UNLESS EXTENDS IF + right THROW FOR WHILE NEW SUPER ELSE IF + left EXTENDS left ASSIGN '||=' '&&=' right RETURN '=>' + right UNLESS POSTFIX_IF preclow rule @@ -315,7 +316,7 @@ rule # Throw an exception. Throw: - THROW Expression { result = ThrowNode.new(val[1]) } + THROW Expression { result = ThrowNode.new(val[1]) } ; # Parenthetical expressions. @@ -405,7 +406,8 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: IfBlock IfEnd { result = val[0].add_else(val[1]) } - | Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) } + | Expression + IF Expression = POSTFIX_IF { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) } | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) } ; diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 5e49387895..f79896b9df 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -32,7 +32,7 @@ def compile(o={}) def compile_closure(o={}) indent = o[:indent] o[:indent] += TAB - "(function() {\n#{compile_node(o)}\n#{indent}})()" + "(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()" end # Default implementations of the common node methods. @@ -81,7 +81,7 @@ def last?(node) end def compile(o={}) - o[:scope] ? compile_node(o) : compile_root(o) + o[:scope] ? super(o) : compile_root(o) end # The extra fancy is to handle pushing down returns to the final lines of @@ -99,7 +99,7 @@ def compile_node(options={}) "#{o[:indent]}return #{node.compile(o)};" end else - ending = node.statement_only? ? '' : ';' + ending = node.statement? ? '' : ';' indent = node.statement? ? '' : o[:indent] "#{indent}#{node.compile(o.merge(:top => true))}#{ending}" end @@ -247,9 +247,9 @@ def initialize(sub_object, super_object) def compile_node(o={}) sub, sup = @sub_object.compile(o), @super_object.compile(o) - "#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" + + "#{o[:indent]}#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" + "#{sub}.prototype = new #{sup}();\n#{o[:indent]}" + - "#{sub}.prototype.constructor = #{sub}" + "#{sub}.prototype.constructor = #{sub};" end end @@ -565,9 +565,11 @@ def initialize(condition, body) def compile_node(o) o.delete(:return) - indent = o[:indent] + TAB - cond = @condition.compile(o) - write("#{o[:indent]}while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}") + indent = o[:indent] + o[:indent] += TAB + o[:top] = true + cond = @condition.compile(o) + write("#{indent}while (#{cond}) {\n#{@body.compile(o)}\n#{indent}}") end end @@ -616,7 +618,7 @@ def compile_node(o) return_result = rvar temp_var = ValueNode.new(LiteralNode.new(tvar)) body = Expressions.wrap( - AssignNode.new(temp_var, @body), + AssignNode.new(temp_var, @body.unwrap), CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var]) ) if o[:return] @@ -628,7 +630,7 @@ def compile_node(o) end return_result = "\n#{o[:indent]}#{return_result};" - body = body.compile(o.merge(:indent => body_dent)) + body = body.compile(o.merge(:indent => body_dent, :top => true)) write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body}#{post_cond}\n#{o[:indent]}}#{return_result}") end end @@ -646,6 +648,7 @@ def initialize(try, error, recovery, finally=nil) def compile_node(o) indent = o[:indent] o[:indent] += TAB + o[:top] = true error_part = @error ? " (#{@error}) " : ' ' catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}" finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{indent}}" @@ -762,6 +765,7 @@ def compile_statement(o) cond_o = o.dup cond_o.delete(:return) o[:indent] += TAB + o[:top] = true if_dent = child ? '' : indent if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" return if_part unless @else_body From 6160fe3c17d5dc21c0961f98498da9f81ca383d8 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 16:32:59 -0500 Subject: [PATCH 238/303] beautiful -- all examples, tests, and docs are now compiling without JSLint warnings --- lib/coffee_script/nodes.rb | 17 ++++++++++++----- test/unit/test_parser.rb | 7 ------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index f79896b9df..5cfa46fa5f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -23,10 +23,15 @@ def write(code) code end + # This is extremely important -- we convert JS statements into expressions + # by wrapping them in a closure, only if it's possible, and we're not at + # the top level of a block (which would be unnecessary), and we haven't + # already been asked to return the result. def compile(o={}) @options = o.dup top = @options.delete(:top) - statement? && !statement_only? && !top ? compile_closure(@options) : compile_node(@options) + closure = statement? && !statement_only? && !top && !@options[:return] + closure ? compile_closure(@options) : compile_node(@options) end def compile_closure(o={}) @@ -142,7 +147,8 @@ def statement? def compile_node(o) indent = statement? ? o[:indent] : '' - write(indent + @value.to_s) + ending = statement? ? ';' : '' + write(indent + @value.to_s + ending) end end @@ -159,7 +165,7 @@ def initialize(expression) def compile_node(o) return write(@expression.compile(o.merge(:return => true))) if @expression.statement? compiled = @expression.compile(o) - write(@expression.statement? ? "#{compiled}\n#{o[:indent]}return null" : "#{o[:indent]}return #{compiled}") + write(@expression.statement? ? "#{compiled}\n#{o[:indent]}return null;" : "#{o[:indent]}return #{compiled};") end end @@ -462,9 +468,10 @@ def initialize(params, body) def compile_node(o) shared_scope = o.delete(:shared_scope) + indent = o[:indent] o[:scope] = shared_scope || Scope.new(o[:scope], @body) o[:return] = true - indent = o[:indent] + o[:top] = true o[:indent] += TAB o.delete(:no_wrap) name = o.delete(:immediate_assign) @@ -667,7 +674,7 @@ def initialize(expression) end def compile_node(o) - write("#{o[:indent]}throw #{@expression.compile(o)}") + write("#{o[:indent]}throw #{@expression.compile(o)};") end end diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index 95ee835e62..dfcc4763ad 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -77,11 +77,4 @@ def test_no_wrap assert nodes.compile(:no_wrap => true) == File.read('test/fixtures/generation/each_no_wrap.js') end - def test_no_wrapping_parens_around_statements - @par.parse("try thing() catch error then fail()").compile - assert_raises(SyntaxError) do - @par.parse("(try thing() catch error then fail())").compile - end - end - end From bb0dfa5cd45a36061e368083237bfddbb2bd397f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:07:03 -0500 Subject: [PATCH 239/303] removing the silly newlines from comments --- lib/coffee_script/nodes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 5cfa46fa5f..bdfec0defb 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -179,7 +179,7 @@ def initialize(lines) end def compile_node(o={}) - delimiter = "\n#{o[:indent]}//" + delimiter = "#{o[:indent]}//" comment = "#{delimiter}#{@lines.join(delimiter)}" write(comment) end From 37247789895ccaf047962f55bf70852f276ac40a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:11:53 -0500 Subject: [PATCH 240/303] lowering the precedence of if/else/while --- lib/coffee_script/grammar.y | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index e381f7ab86..83ace2e314 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -33,11 +33,10 @@ prechigh right INDENT left OUTDENT right WHEN IN BY - right THROW FOR WHILE NEW SUPER ELSE IF + right THROW FOR NEW SUPER left EXTENDS left ASSIGN '||=' '&&=' - right RETURN '=>' - right UNLESS POSTFIX_IF + right RETURN '=>' UNLESS IF ELSE WHILE preclow rule @@ -406,8 +405,7 @@ rule # The full complement of if blocks, including postfix one-liner ifs and unlesses. If: IfBlock IfEnd { result = val[0].add_else(val[1]) } - | Expression - IF Expression = POSTFIX_IF { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) } + | Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) } | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) } ; From 56015bd23fbca5ee6634724921ed49c1fe3e5eaa Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:22:10 -0500 Subject: [PATCH 241/303] subtle call order bug was preventing variable declarations --- lib/coffee_script/nodes.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index bdfec0defb..66fe422b08 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -122,9 +122,10 @@ def compile_root(o={}) end def compile_with_declarations(o={}) + code = compile_node(o) decls = '' decls = "#{o[:indent]}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self) - code = decls + compile_node(o) + decls + code end end From bd2597c6b8004d886b3e8e7b910b18fab223486c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:27:26 -0500 Subject: [PATCH 242/303] change lexical scoping example to use var names that haven't been already defined --- examples/code.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/code.coffee b/examples/code.coffee index 0b65aa20ef..ca78218c55 100644 --- a/examples/code.coffee +++ b/examples/code.coffee @@ -102,11 +102,11 @@ while true !!true # Lexical scoping. -a: 5 +v_1: 5 change_a_and_set_b: => - a: 10 - b: 15 -b: 20 + v_1: 10 + v_2: 15 +v_2: 20 # Array comprehensions. supper: food.capitalize() for food in ['toast', 'cheese', 'wine'] From 97f16c0e9c5f19ccb7804b82844c8fffbfc2febf Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:35:03 -0500 Subject: [PATCH 243/303] fixing comment-within-objecta-and-array-literal printing for the new expression regime. --- lib/coffee_script/nodes.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 66fe422b08..6212d8d6b3 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -533,10 +533,11 @@ def compile_node(o) joins = Hash.new("\n") non_comments = @properties.select {|p| !p.is_a?(CommentNode) } non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" } - props = @properties.map { |prop| + props = @properties.map { |prop| join = joins[prop] join = '' if prop == @properties.last - o[:indent] + prop.compile(o) + join + idt = prop.is_a?(CommentNode) ? '' : o[:indent] + "#{idt}#{prop.compile(o)}#{join}" }.join('') write("{\n#{props}\n#{indent}}") end @@ -551,11 +552,14 @@ def initialize(objects=[]) end def compile_node(o) + indent = o[:indent] + o[:indent] += TAB objects = @objects.map { |obj| - joiner = obj.is_a?(CommentNode) ? "\n#{o[:indent] + TAB}" : obj == @objects.last ? '' : ', ' - obj.compile(o.merge(:indent => o[:indent] + TAB)) + joiner + code = obj.compile(o) + obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" : + obj == @objects.last ? code : "#{code}, " }.join('') - ending = objects.include?("\n") ? "\n#{o[:indent]}]" : ']' + ending = objects.include?("\n") ? "\n#{indent}]" : ']' write("[#{objects}#{ending}") end end From 8e6486a1d7fbbf60ce851b6ad7af8a25cce3e158 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:47:23 -0500 Subject: [PATCH 244/303] logic error in Expressions was causing over-compilation by a factor of the depth of the tree --- lib/coffee_script/nodes.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 6212d8d6b3..9f75ea4615 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -96,7 +96,6 @@ def compile_node(options={}) compiled = @expressions.map do |node| o = options.dup returns = o.delete(:return) - code = node.compile(o) if last?(node) && returns && !node.statement_only? if node.statement? node.compile(o.merge(:return => true)) From d53d85d03ed44d73ee6f863921f051a5cb1bc224 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:49:00 -0500 Subject: [PATCH 245/303] updating fixtures -- all tests now pass -- back to master --- test/fixtures/generation/each.js | 4 +--- test/fixtures/generation/each_no_wrap.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index 9e3decfd87..2db559e3ea 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -1,7 +1,5 @@ (function(){ - - // The cornerstone, an each implementation. - // Handles objects implementing forEach, arrays, and raw objects. + // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { var __a, __b, __c, __d, __e, __f, __g, i, index, item, key; index = 0; diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index 4fb7dede53..b9578a4e42 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -1,6 +1,4 @@ - -// The cornerstone, an each implementation. -// Handles objects implementing forEach, arrays, and raw objects. +// The cornerstone, an each implementation.// Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { var __a, __b, __c, __d, __e, __f, __g, i, index, item, key; index = 0; From cb1815885ca63d4e8025a0c67df4bbc919c17f0b Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 18:58:34 -0500 Subject: [PATCH 246/303] adding a statement-as-expression test, and returning null from while loops, if asked --- lib/coffee_script/nodes.rb | 5 +++-- .../generation/statements_as_expressions.coffee | 11 +++++++++++ test/unit/test_execution.rb | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/generation/statements_as_expressions.coffee diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 9f75ea4615..c4a4fda3c1 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -575,12 +575,13 @@ def initialize(condition, body) end def compile_node(o) - o.delete(:return) + returns = o.delete(:return) indent = o[:indent] o[:indent] += TAB o[:top] = true cond = @condition.compile(o) - write("#{indent}while (#{cond}) {\n#{@body.compile(o)}\n#{indent}}") + post = returns ? "\n#{indent}return null;" : '' + write("#{indent}while (#{cond}) {\n#{@body.compile(o)}\n#{indent}}#{post}") end end diff --git a/test/fixtures/generation/statements_as_expressions.coffee b/test/fixtures/generation/statements_as_expressions.coffee new file mode 100644 index 0000000000..5bfb9f43cd --- /dev/null +++ b/test/fixtures/generation/statements_as_expressions.coffee @@ -0,0 +1,11 @@ +# Everything should be able to be an expression. + +result: while sunny? + go_outside() + +print(3 + try + nonexistent.no_way + catch error + print(error) + 3 +) \ No newline at end of file diff --git a/test/unit/test_execution.rb b/test/unit/test_execution.rb index 04186305d6..1a72f9943c 100644 --- a/test/unit/test_execution.rb +++ b/test/unit/test_execution.rb @@ -14,8 +14,8 @@ def test_execution_of_coffeescript end end - def test_lintless_coffeescript - no_warnings `bin/coffee -l test/fixtures/execution/*.coffee` + def test_lintless_tests + no_warnings `bin/coffee -l test/fixtures/*/*.coffee` end def test_lintless_examples From ecdb47107baba818ac6ab1b3ed7745e506c55b98 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 19:00:08 -0500 Subject: [PATCH 247/303] -- --- test/fixtures/generation/statements_as_expressions.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/fixtures/generation/statements_as_expressions.coffee b/test/fixtures/generation/statements_as_expressions.coffee index 5bfb9f43cd..63152ec973 100644 --- a/test/fixtures/generation/statements_as_expressions.coffee +++ b/test/fixtures/generation/statements_as_expressions.coffee @@ -8,4 +8,7 @@ print(3 + try catch error print(error) 3 -) \ No newline at end of file +) + +func: x => + return throw x \ No newline at end of file From 32cd15f038abf008dd5436abdafdde632b91eec6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 19:08:41 -0500 Subject: [PATCH 248/303] mo' expression examples --- .../generation/statements_as_expressions.coffee | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/fixtures/generation/statements_as_expressions.coffee b/test/fixtures/generation/statements_as_expressions.coffee index 63152ec973..0541d95fe2 100644 --- a/test/fixtures/generation/statements_as_expressions.coffee +++ b/test/fixtures/generation/statements_as_expressions.coffee @@ -4,11 +4,13 @@ result: while sunny? go_outside() print(3 + try - nonexistent.no_way - catch error - print(error) - 3 + nonexistent.no_way +catch error + print(error) + 3 ) func: x => - return throw x \ No newline at end of file + return throw x + +print(x * x for x in [1..100]) \ No newline at end of file From 672dd70bdb9666486256e0ef01d41213fcfb4bf9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 3 Jan 2010 22:25:38 -0500 Subject: [PATCH 249/303] more underscore examples raised a slight bug with a lexing ambiguity between leading whens (in switches), and trailing whens (in comprehensions) -- made two different tokens to distinguish them --- examples/underscore.coffee | 275 +++++++++++++++------------------- lib/coffee_script/grammar.y | 11 +- lib/coffee_script/lexer.rb | 1 + lib/coffee_script/rewriter.rb | 2 +- 4 files changed, 125 insertions(+), 164 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index a6c5baf6f3..8e2328e680 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -47,7 +47,7 @@ _.each: obj, iterator, context => try return obj.forEach(iterator, context) if obj.forEach if _.isArray(obj) or _.isArguments(obj) - return iterator.call(context, item, i, obj) for item, i in obj + return iterator.call(context, item, i, obj) for item, i in obj iterator.call(context, obj[key], key, obj) for key in _.keys(obj) catch e throw e if e isnt breaker @@ -58,34 +58,33 @@ _.each: obj, iterator, context => _.map: obj, iterator, context => return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) results: [] - mapper: value, index, list => results.push(iterator.call(context, value, index, list)) - _.each(obj, mapper) + _.each(obj) value, index, list => + results.push(iterator.call(context, value, index, list)) results # Reduce builds up a single result from a list of values. Also known as # inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. _.reduce: obj, memo, iterator, context => return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce)) - reducer: value, index, list => memo: iterator.call(context, memo, value, index, list) - _.each(obj, reducer) + _.each(obj) value, index, list => + memo: iterator.call(context, memo, value, index, list) memo # The right-associative version of reduce, also known as foldr. Uses # JavaScript 1.8's version of reduceRight, if available. _.reduceRight: obj, memo, iterator, context => return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight)) - reversed: _.clone(_.toArray(obj)).reverse() - reverser: value, index => memo: iterator.call(context, memo, value, index, obj) - _.each(reversed, reverser) + _.each(_.clone(_.toArray(obj)).reverse()) value, index => + memo: iterator.call(context, memo, value, index, obj) memo # Return the first value which passes a truth test. _.detect: obj, iterator, context => result: null - _.each(obj, (value, index, list => + _.each(obj) value, index, list => if iterator.call(context, value, index, list) result: value - _.breakLoop())) + _.breakLoop() result # Return all the elements that pass a truth test. Use JavaScript 1.6's @@ -187,72 +186,59 @@ _.max: obj, iterator, context => # } # return low; # }; -# -# # Convert anything iterable into a real, live array. -# _.toArray = function(iterable) { -# if (!iterable) return []; -# if (iterable.toArray) return iterable.toArray(); -# if (_.isArray(iterable)) return iterable; -# if (_.isArguments(iterable)) return slice.call(iterable); -# return _.map(iterable, function(val){ return val; }); -# }; -# -# # Return the number of elements in an object. -# _.size = function(obj) { -# return _.toArray(obj).length; -# }; -# -# /*-------------------------- Array Functions: ------------------------------*/ -# -# # Get the first element of an array. Passing "n" will return the first N -# # values in the array. Aliased as "head". The "guard" check allows it to work -# # with _.map. -# _.first = function(array, n, guard) { -# return n && !guard ? slice.call(array, 0, n) : array[0]; -# }; -# -# # Returns everything but the first entry of the array. Aliased as "tail". -# # Especially useful on the arguments object. Passing an "index" will return -# # the rest of the values in the array from that index onward. The "guard" -# //check allows it to work with _.map. -# _.rest = function(array, index, guard) { -# return slice.call(array, _.isUndefined(index) || guard ? 1 : index); -# }; -# -# # Get the last element of an array. -# _.last = function(array) { -# return array[array.length - 1]; -# }; -# -# # Trim out all falsy values from an array. -# _.compact = function(array) { -# return _.select(array, function(value){ return !!value; }); -# }; -# -# # Return a completely flattened version of an array. -# _.flatten = function(array) { -# return _.reduce(array, [], function(memo, value) { -# if (_.isArray(value)) return memo.concat(_.flatten(value)); -# memo.push(value); -# return memo; -# }); -# }; -# -# # Return a version of the array that does not contain the specified value(s). -# _.without = function(array) { -# var values = _.rest(arguments); -# return _.select(array, function(value){ return !_.include(values, value); }); -# }; -# -# # Produce a duplicate-free version of the array. If the array has already -# # been sorted, you have the option of using a faster algorithm. -# _.uniq = function(array, isSorted) { -# return _.reduce(array, [], function(memo, el, i) { -# if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); -# return memo; -# }); -# }; -# + +# Convert anything iterable into a real, live array. +_.toArray: iterable => + return [] if (!iterable) + return iterable.toArray() if (iterable.toArray) + return iterable if (_.isArray(iterable)) + return slice.call(iterable) if (_.isArguments(iterable)) + _.values(iterable) + +# Return the number of elements in an object. +_.size: obj => _.toArray(obj).length + +# -------------------------- Array Functions: ------------------------------ + +# Get the first element of an array. Passing "n" will return the first N +# values in the array. Aliased as "head". The "guard" check allows it to work +# with _.map. +_.first: array, n, guard => + if n and not guard then slice.call(array, 0, n) else array[0] + +# Returns everything but the first entry of the array. Aliased as "tail". +# Especially useful on the arguments object. Passing an "index" will return +# the rest of the values in the array from that index onward. The "guard" +# check allows it to work with _.map. +_.rest: array, index, guard => + slice.call(array, if _.isUndefined(index) or guard then 1 else index) + +# Get the last element of an array. +_.last: array => array[array.length - 1] + +# Trim out all falsy values from an array. +_.compact: array => el for el in array when el + +# Return a completely flattened version of an array. +_.flatten: array => + _.reduce(array, []) memo, value => + return memo.concat(_.flatten(value)) if _.isArray(value) + memo.push(value) + memo + +# Return a version of the array that does not contain the specified value(s). +_.without: array => + values: _.rest(arguments) + _.select(array, (value => not _.include(values, value))) + +# Produce a duplicate-free version of the array. If the array has already +# been sorted, you have the option of using a faster algorithm. +_.uniq: array, isSorted => + _.reduce(array, []) memo, el, i => + if (i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))) + memo.push(el) + memo + # # Produce an array that contains every item shared between all the # # passed-in arrays. # _.intersect = function(array) { @@ -316,21 +302,18 @@ _.bind: func, obj => args: _.rest(arguments, 2) => func.apply(obj or root, args.concat(_.toArray(arguments))) -# # Bind all of an object's methods to that object. Useful for ensuring that -# # all callbacks defined on an object belong to it. -# _.bindAll = function(obj) { -# var funcs = _.rest(arguments); -# if (funcs.length == 0) funcs = _.functions(obj); -# _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); -# return obj; -# }; -# -# # Delays a function for the given number of milliseconds, and then calls -# # it with the arguments supplied. -# _.delay = function(func, wait) { -# var args = _.rest(arguments, 2); -# return setTimeout(function(){ return func.apply(func, args); }, wait); -# }; +# Bind all of an object's methods to that object. Useful for ensuring that +# all callbacks defined on an object belong to it. +_.bindAll: obj => + funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj) + _.each(funcs, (f => obj[f]: _.bind(obj[f], obj))) + obj + +# Delays a function for the given number of milliseconds, and then calls +# it with the arguments supplied. +_.delay: func, wait => + args: _.rest(arguments, 2) + setTimeout((=> func.apply(func, args)), wait) # Defers a function, scheduling it to run after the current call stack has # cleared. @@ -352,37 +335,30 @@ _.compose: => args: [funcs[i]].apply(this, args) for i in [(funcs.length - 1)..0] args[0] -# /* ------------------------- Object Functions: ---------------------------- */ -# -# # Retrieve the names of an object's properties. -# _.keys = function(obj) { -# if(_.isArray(obj)) return _.range(0, obj.length); -# var keys = []; -# for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key); -# return keys; -# }; -# -# # Retrieve the values of an object's properties. -# _.values = function(obj) { -# return _.map(obj, _.identity); -# }; -# -# # Return a sorted list of the function names available in Underscore. -# _.functions = function(obj) { -# return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort(); -# }; -# -# # Extend a given object with all of the properties in a source object. -# _.extend = function(destination, source) { -# for (var property in source) destination[property] = source[property]; -# return destination; -# }; -# -# # Create a (shallow-cloned) duplicate of an object. -# _.clone = function(obj) { -# if (_.isArray(obj)) return obj.slice(0); -# return _.extend({}, obj); -# }; +# ------------------------- Object Functions: ---------------------------- + +# Retrieve the names of an object's properties. +_.keys: obj => + return _.range(0, obj.length) if _.isArray(obj) + key for val, key in obj + +# Retrieve the values of an object's properties. +_.values: obj => + _.map(obj, _.identity) + +# Return a sorted list of the function names available in Underscore. +_.functions: obj => + _.select(_.keys(obj), key => _.isFunction(obj[key])).sort() + +# Extend a given object with all of the properties in a source object. +_.extend: destination, source => + destination[key]: val for val, key in source + destination + +# Create a (shallow-cloned) duplicate of an object. +_.clone: obj => + return obj.slice(0) if _.isArray(ob) + _.extend({}, obj) # Perform a deep comparison to check if two objects are equal. _.isEqual: a, b => @@ -444,16 +420,6 @@ _.tap: obj, interceptor => interceptor(obj) obj -# # Define the isArray, isDate, isFunction, isNumber, isRegExp, and isString -# # functions based on their toString identifiers. -# var types = ['Array', 'Date', 'Function', 'Number', 'RegExp', 'String']; -# for (var i=0, l=types.length; i + if chain then _(obj).chain() else obj + # # # Add all of the Underscore functions to the wrapper object. # _.each(_.functions(_), function(name) { @@ -530,24 +496,17 @@ _.methods: _.functions # return result(this._wrapped, this._chain); # }; # }); -# -# # Add all accessor Array functions to the wrapper. -# _.each(['concat', 'join', 'slice'], function(name) { -# var method = Array.prototype[name]; -# wrapper.prototype[name] = function() { -# return result(method.apply(this._wrapped, arguments), this._chain); -# }; -# }); -# -# # Start chaining a wrapped Underscore object. -# wrapper.prototype.chain = function() { -# this._chain = true; -# return this; -# }; -# -# # Extracts the result from a wrapped and chained object. -# wrapper.prototype.value = function() { -# return this._wrapped; -# }; -# -# + +# Add all accessor Array functions to the wrapper. +_.each(['concat', 'join', 'slice']) name => + method: Array.prototype[name] + wrapper.prototype[name]: => + result(method.apply(this._wrapped, arguments), this._chain) + +# Start chaining a wrapped Underscore object. +wrapper.prototype.chain: => + this._chain: true + this + +# Extracts the result from a wrapped and chained object. +wrapper.prototype.value: => this._wrapped diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 83ace2e314..f1c280bf47 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -8,8 +8,8 @@ token IDENTIFIER PROPERTY_ACCESS token CODE PARAM PARAM_SPLAT NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE -token FOR IN BY WHILE -token SWITCH WHEN +token FOR IN BY WHEN WHILE +token SWITCH LEADING_WHEN token DELETE INSTANCEOF TYPEOF token SUPER EXTENDS token NEWLINE @@ -32,7 +32,7 @@ prechigh left '.' right INDENT left OUTDENT - right WHEN IN BY + right WHEN LEADING_WHEN IN BY right THROW FOR NEW SUPER left EXTENDS left ASSIGN '||=' '&&=' @@ -367,8 +367,9 @@ rule # An individual when. When: - WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } - | WHEN Expression Block Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } + LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } + | LEADING_WHEN Expression Block + Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } | Comment ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 706b1d51f0..d357264fc7 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -85,6 +85,7 @@ def identifier_token # Keywords are special identifiers tagged with their own name, # 'if' will result in an [:IF, "if"] token. tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER + tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag) @tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.') token(tag, identifier) @i += identifier.length diff --git a/lib/coffee_script/rewriter.rb b/lib/coffee_script/rewriter.rb index b031962390..88a31066d5 100644 --- a/lib/coffee_script/rewriter.rb +++ b/lib/coffee_script/rewriter.rb @@ -27,7 +27,7 @@ class Rewriter # Single-line flavors of block expressions that have unclosed endings. # The grammar can't disambiguate them, so we insert the implicit indentation. SINGLE_LINERS = [:ELSE, "=>", :TRY, :FINALLY, :THEN] - SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :WHEN] + SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN] # Rewrite the token stream in multiple passes, one logical filter at # a time. This could certainly be changed into a single pass through the From 69283fcaddabee4182f7e5abdcc81b1bac888304 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 00:16:38 -0500 Subject: [PATCH 250/303] 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 8e2328e680..ea1ae34e98 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}) + result.value + +# 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}))) + 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; -# }; +# 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 303e5f2bcd..5fe57c3a7a 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -12,12 +12,12 @@ class Scope 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 @@ def parameter(name) 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. From f75d98e4476df9b0b1be12ab0a5ca3dae8dc061f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 00:28:52 -0500 Subject: [PATCH 251/303] pretty amazing -- fully functional draft of underscore.js 0.5.2, all in CoffeeScript --- examples/underscore.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index ea1ae34e98..1953dc457a 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -49,8 +49,8 @@ _.each: obj, iterator, context => try return obj.forEach(iterator, context) if obj.forEach if _.isArray(obj) or _.isArguments(obj) - return iterator.call(context, item, i, obj) for item, i in obj - iterator.call(context, obj[key], key, obj) for key in _.keys(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 catch e throw e if e isnt breaker obj @@ -211,7 +211,7 @@ _.rest: array, index, guard => _.last: array => array[array.length - 1] # Trim out all falsy values from an array. -_.compact: array => el for el in array when el +_.compact: array => array[i] for i in [0...array.length] when array[i] # Return a completely flattened version of an array. _.flatten: array => @@ -247,7 +247,7 @@ _.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[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), @@ -284,8 +284,8 @@ _.range: start, stop, step => idx: 0 while true return range if (if step > 0 then i - stop else stop - i) >= 0 - idx++ range[idx]: i + idx++ i+= step # ----------------------- Function Functions: ----------------------------- @@ -326,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: ---------------------------- From 94bc7c1f92101f86447a3e5d85d3ee0a88d70653 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 01:06:31 -0500 Subject: [PATCH 252/303] putting in a special check for returns within array comprehensions -- not very nice --- examples/underscore.coffee | 18 +++++----- lib/coffee_script/nodes.rb | 67 +++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 1953dc457a..4a39758f69 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -49,7 +49,7 @@ _.each: obj, iterator, context => try 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]) + 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 catch e throw e if e isnt breaker @@ -129,10 +129,9 @@ _.any: obj, iterator, context => # based on '==='. _.include: obj, target => return _.indexOf(obj, target) isnt -1 if _.isArray(obj) - found: false - _.each(obj) value => - _.breakLoop() if found: value is target - found + for val in obj + return true if val is target + false # Invoke a method with arguments on every item in a collection. _.invoke: obj, method => @@ -247,7 +246,8 @@ _.zip: => args: _.toArray(arguments) length: _.max(_.pluck(args, 'length')) results: new Array(length) - (results[i]: _.pluck(args, String(i))) for i in [0...length] + for i in [0...length] + results[i]: _.pluck(args, String(i)) results # If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), @@ -326,7 +326,8 @@ _.compose: => funcs: _.toArray(arguments) => args: _.toArray(arguments) - (args: [funcs[i].apply(this, args)]) for i in [(funcs.length - 1)..0] + for i in [(funcs.length - 1)..0] + args: [funcs[i].apply(this, args)] args[0] # ------------------------- Object Functions: ---------------------------- @@ -346,7 +347,8 @@ _.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 + for val, key in source + destination[key]: val destination # Create a (shallow-cloned) duplicate of an object. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index c4a4fda3c1..4e01f8a21f 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -18,6 +18,10 @@ def self.statement_only class_eval "def statement_only?; true; end" end + def self.no_return + class_eval "def returns?; false; end" + end + def write(code) puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE'] code @@ -40,6 +44,10 @@ def compile_closure(o={}) "(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()" end + def returns? + children.any? {|node| node && node.returns? } + end + # Default implementations of the common node methods. def unwrap; self; end def statement?; false; end @@ -50,6 +58,7 @@ def statement_only?; false; end class Expressions < Node statement attr_reader :expressions + alias_method :children, :expressions STRIP_TRAILING_WHITESPACE = /\s+$/ @@ -132,6 +141,7 @@ def compile_with_declarations(o={}) # Literals are static values that have a Ruby representation, eg.: a string, a number, # true, false, nil, etc. class LiteralNode < Node + no_return STATEMENTS = ['break', 'continue'] attr_reader :value @@ -162,6 +172,10 @@ def initialize(expression) @expression = expression end + def returns? + true + end + def compile_node(o) return write(@expression.compile(o.merge(:return => true))) if @expression.statement? compiled = @expression.compile(o) @@ -173,6 +187,7 @@ def compile_node(o) # same position. class CommentNode < Node statement_only + no_return def initialize(lines) @lines = lines.value @@ -189,6 +204,7 @@ def compile_node(o={}) # Node for a function invocation. Takes care of converting super() calls into # calls against the prototype's function of the same name. class CallNode < Node + no_return attr_reader :variable, :arguments def initialize(variable, arguments=[]) @@ -245,6 +261,7 @@ def compile_splat(o) # After goog.inherits from the Closure Library. class ExtendsNode < Node statement + no_return attr_reader :sub_object, :super_object def initialize(sub_object, super_object) @@ -262,6 +279,7 @@ def compile_node(o={}) # A value, indexed or dotted into, or vanilla. class ValueNode < Node + no_return attr_reader :literal, :properties, :last, :source def initialize(literal, properties=[]) @@ -295,6 +313,7 @@ def compile_node(o) # A dotted accessor into a part of a value. class AccessorNode < Node + no_return attr_reader :name def initialize(name) @@ -308,6 +327,7 @@ def compile_node(o) # An indexed accessor into a part of an array or object. class IndexNode < Node + no_return attr_reader :index def initialize(index) @@ -322,6 +342,7 @@ def compile_node(o) # A range literal. Ranges can be used to extract portions (slices) of arrays, # or to specify a range for array comprehensions. class RangeNode < Node + no_return attr_reader :from, :to def initialize(from, to, exclusive=false) @@ -363,6 +384,7 @@ def compile_node(o) # specifies the index of the end of the slice (just like the first parameter) # is the index of the beginning. class SliceNode < Node + no_return attr_reader :range def initialize(range) @@ -388,6 +410,10 @@ def initialize(variable, value, context=nil) @variable, @value, @context = variable, value, context end + def children + [@value] + end + def compile_node(o) return compile_splice(o) if @variable.properties.last.is_a?(SliceNode) name = @variable.compile(o) @@ -433,6 +459,10 @@ def initialize(operator, first, second=nil, flip=false) @operator = CONVERSIONS[operator.to_sym] || operator end + def children + [@first, @second] + end + def unary? @second.nil? end @@ -459,6 +489,7 @@ def compile_unary(o) # A function definition. The only node that creates a new Scope. class CodeNode < Node + no_return attr_reader :params, :body def initialize(params, body) @@ -489,6 +520,7 @@ def compile_node(o) # A parameter splat in a function definition. class ParamSplatNode < Node + no_return attr_accessor :index attr_reader :name @@ -503,6 +535,7 @@ def compile_node(o={}) end class ArgSplatNode < Node + no_return attr_reader :value def initialize(value) @@ -517,6 +550,7 @@ def compile_node(o={}) # An object literal. class ObjectNode < Node + no_return attr_reader :properties def initialize(properties = []) @@ -544,6 +578,7 @@ def compile_node(o) # An array literal. class ArrayNode < Node + no_return attr_reader :objects def initialize(objects=[]) @@ -574,6 +609,10 @@ def initialize(condition, body) @condition, @body = condition, body end + def children + [@body] + end + def compile_node(o) returns = o.delete(:return) indent = o[:indent] @@ -601,6 +640,10 @@ def initialize(body, source, name, index=nil) @step = source[:step] end + def children + [@body] + end + def compile_node(o) range = @source.is_a?(RangeNode) scope = o[:scope] @@ -629,10 +672,12 @@ def compile_node(o) set_result = "#{rvar} = [];\n#{o[:indent]}" return_result = rvar temp_var = ValueNode.new(LiteralNode.new(tvar)) - body = Expressions.wrap( - AssignNode.new(temp_var, @body.unwrap), - CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var]) - ) + unless @body.returns? + body = Expressions.wrap( + AssignNode.new(temp_var, @body.unwrap), + CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var]) + ) + end if o[:return] return_result = "return #{return_result}" if o[:return] o.delete(:return) @@ -657,6 +702,10 @@ def initialize(try, error, recovery, finally=nil) @try, @error, @recovery, @finally = try, error, recovery, finally end + def children + [@try, @recovery, @finally] + end + def compile_node(o) indent = o[:indent] o[:indent] += TAB @@ -670,6 +719,7 @@ def compile_node(o) # Throw an exception. class ThrowNode < Node + no_return statement_only attr_reader :expression @@ -685,6 +735,7 @@ def compile_node(o) # Check an expression for existence (meaning not null or undefined). class ExistenceNode < Node + no_return attr_reader :expression def initialize(expression) @@ -708,6 +759,10 @@ def initialize(expressions, line=nil) @line = line end + def children + [@expressions] + end + def compile_node(o) compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' @@ -730,6 +785,10 @@ def initialize(condition, body, else_body=nil, tags={}) @condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert] end + def children + [@body, @else_body] + end + def <<(else_body) eb = else_body.unwrap @else_body ? @else_body << eb : @else_body = eb From 5efaff506c7631391f0b201d2811cd6111e08e4e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 01:43:45 -0500 Subject: [PATCH 253/303] more underscore --- examples/underscore.coffee | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 4a39758f69..0ea0d5fb3b 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -136,12 +136,11 @@ _.include: obj, target => # 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) + (if method then val[method] else val).apply(val, args) for val in obj # Convenience version of a common use case of map: fetching a property. _.pluck: obj, key => - _.map(obj, (value => value[key])) + _.map(obj, (val => val[key])) # Return the maximum item or (item-based computation). _.max: obj, iterator, context => @@ -222,15 +221,15 @@ _.flatten: array => # Return a version of the array that does not contain the specified value(s). _.without: array => values: _.rest(arguments) - _.select(array, (value => not _.include(values, value))) + val for val in _.toArray(array) when not _.include(values, val) # Produce a duplicate-free version of the array. If the array has already # been sorted, you have the option of using a faster algorithm. _.uniq: array, isSorted => - _.reduce(array, []) memo, el, i => - if (i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))) - memo.push(el) - memo + memo: [] + for el, i in _.toArray(array) + memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el)) + memo # Produce an array that contains every item shared between all the # passed-in arrays. From da9e38808c83f2b3d9c9f211c0bb329029925682 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 09:43:50 -0500 Subject: [PATCH 254/303] nicer scope inspects --- lib/coffee_script/scope.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/coffee_script/scope.rb b/lib/coffee_script/scope.rb index 5fe57c3a7a..4cb61b0de8 100644 --- a/lib/coffee_script/scope.rb +++ b/lib/coffee_script/scope.rb @@ -56,6 +56,10 @@ def declared_variables @variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort end + def inspect + "" + end + end end \ No newline at end of file From 2c1033f5da2958a303888fd64e875c4706e0dd68 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 18:57:10 -0500 Subject: [PATCH 255/303] removed the whole messy notions of looking downwards for returns or children -- ForNodes now peek at top-level status, and if they're being asked to return a value from the outside --- lib/coffee_script/nodes.rb | 74 ++++-------------------- test/fixtures/generation/each.js | 24 ++++---- test/fixtures/generation/each_no_wrap.js | 24 ++++---- 3 files changed, 30 insertions(+), 92 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 4e01f8a21f..59afdaef96 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -18,10 +18,6 @@ def self.statement_only class_eval "def statement_only?; true; end" end - def self.no_return - class_eval "def returns?; false; end" - end - def write(code) puts "#{self.class.to_s}:\n#{@options.inspect}\n#{code}\n\n" if ENV['VERBOSE'] code @@ -33,7 +29,7 @@ def write(code) # already been asked to return the result. def compile(o={}) @options = o.dup - top = @options.delete(:top) + top = self.is_a?(ForNode) ? @options[:top] : @options.delete(:top) closure = statement? && !statement_only? && !top && !@options[:return] closure ? compile_closure(@options) : compile_node(@options) end @@ -44,10 +40,6 @@ def compile_closure(o={}) "(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()" end - def returns? - children.any? {|node| node && node.returns? } - end - # Default implementations of the common node methods. def unwrap; self; end def statement?; false; end @@ -58,7 +50,6 @@ def statement_only?; false; end class Expressions < Node statement attr_reader :expressions - alias_method :children, :expressions STRIP_TRAILING_WHITESPACE = /\s+$/ @@ -141,7 +132,6 @@ def compile_with_declarations(o={}) # Literals are static values that have a Ruby representation, eg.: a string, a number, # true, false, nil, etc. class LiteralNode < Node - no_return STATEMENTS = ['break', 'continue'] attr_reader :value @@ -172,10 +162,6 @@ def initialize(expression) @expression = expression end - def returns? - true - end - def compile_node(o) return write(@expression.compile(o.merge(:return => true))) if @expression.statement? compiled = @expression.compile(o) @@ -187,14 +173,13 @@ def compile_node(o) # same position. class CommentNode < Node statement_only - no_return def initialize(lines) @lines = lines.value end def compile_node(o={}) - delimiter = "#{o[:indent]}//" + delimiter = "\n#{o[:indent]}//" comment = "#{delimiter}#{@lines.join(delimiter)}" write(comment) end @@ -204,7 +189,6 @@ def compile_node(o={}) # Node for a function invocation. Takes care of converting super() calls into # calls against the prototype's function of the same name. class CallNode < Node - no_return attr_reader :variable, :arguments def initialize(variable, arguments=[]) @@ -261,7 +245,6 @@ def compile_splat(o) # After goog.inherits from the Closure Library. class ExtendsNode < Node statement - no_return attr_reader :sub_object, :super_object def initialize(sub_object, super_object) @@ -279,7 +262,6 @@ def compile_node(o={}) # A value, indexed or dotted into, or vanilla. class ValueNode < Node - no_return attr_reader :literal, :properties, :last, :source def initialize(literal, properties=[]) @@ -313,7 +295,6 @@ def compile_node(o) # A dotted accessor into a part of a value. class AccessorNode < Node - no_return attr_reader :name def initialize(name) @@ -327,7 +308,6 @@ def compile_node(o) # An indexed accessor into a part of an array or object. class IndexNode < Node - no_return attr_reader :index def initialize(index) @@ -342,7 +322,6 @@ def compile_node(o) # A range literal. Ranges can be used to extract portions (slices) of arrays, # or to specify a range for array comprehensions. class RangeNode < Node - no_return attr_reader :from, :to def initialize(from, to, exclusive=false) @@ -384,7 +363,6 @@ def compile_node(o) # specifies the index of the end of the slice (just like the first parameter) # is the index of the beginning. class SliceNode < Node - no_return attr_reader :range def initialize(range) @@ -410,10 +388,6 @@ def initialize(variable, value, context=nil) @variable, @value, @context = variable, value, context end - def children - [@value] - end - def compile_node(o) return compile_splice(o) if @variable.properties.last.is_a?(SliceNode) name = @variable.compile(o) @@ -459,10 +433,6 @@ def initialize(operator, first, second=nil, flip=false) @operator = CONVERSIONS[operator.to_sym] || operator end - def children - [@first, @second] - end - def unary? @second.nil? end @@ -489,7 +459,6 @@ def compile_unary(o) # A function definition. The only node that creates a new Scope. class CodeNode < Node - no_return attr_reader :params, :body def initialize(params, body) @@ -520,7 +489,6 @@ def compile_node(o) # A parameter splat in a function definition. class ParamSplatNode < Node - no_return attr_accessor :index attr_reader :name @@ -535,7 +503,6 @@ def compile_node(o={}) end class ArgSplatNode < Node - no_return attr_reader :value def initialize(value) @@ -550,7 +517,6 @@ def compile_node(o={}) # An object literal. class ObjectNode < Node - no_return attr_reader :properties def initialize(properties = []) @@ -578,7 +544,6 @@ def compile_node(o) # An array literal. class ArrayNode < Node - no_return attr_reader :objects def initialize(objects=[]) @@ -609,10 +574,6 @@ def initialize(condition, body) @condition, @body = condition, body end - def children - [@body] - end - def compile_node(o) returns = o.delete(:return) indent = o[:indent] @@ -640,18 +601,15 @@ def initialize(body, source, name, index=nil) @step = source[:step] end - def children - [@body] - end - def compile_node(o) + top_level = o.delete(:top) && !o[:return] range = @source.is_a?(RangeNode) scope = o[:scope] name_found = scope.find(@name) index_found = @index && scope.find(@index) svar = scope.free_variable ivar = range ? name : @index ? @index : scope.free_variable - rvar = scope.free_variable + rvar = scope.free_variable unless top_level tvar = scope.free_variable if range body_dent = o[:indent] + TAB @@ -669,10 +627,12 @@ def compile_node(o) post_cond = "\n#{o[:indent] + TAB}}" end body = @body - set_result = "#{rvar} = [];\n#{o[:indent]}" - return_result = rvar + set_result = rvar ? "#{rvar} = [];\n#{o[:indent]}" : '' + return_result = rvar || '' temp_var = ValueNode.new(LiteralNode.new(tvar)) - unless @body.returns? + if top_level + body = Expressions.wrap(body) + else body = Expressions.wrap( AssignNode.new(temp_var, @body.unwrap), CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var]) @@ -686,7 +646,7 @@ def compile_node(o) body = Expressions.wrap(IfNode.new(@filter, @body)) end - return_result = "\n#{o[:indent]}#{return_result};" + return_result = "\n#{o[:indent]}#{return_result};" unless top_level body = body.compile(o.merge(:indent => body_dent, :top => true)) write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body}#{post_cond}\n#{o[:indent]}}#{return_result}") end @@ -702,10 +662,6 @@ def initialize(try, error, recovery, finally=nil) @try, @error, @recovery, @finally = try, error, recovery, finally end - def children - [@try, @recovery, @finally] - end - def compile_node(o) indent = o[:indent] o[:indent] += TAB @@ -719,7 +675,6 @@ def compile_node(o) # Throw an exception. class ThrowNode < Node - no_return statement_only attr_reader :expression @@ -735,7 +690,6 @@ def compile_node(o) # Check an expression for existence (meaning not null or undefined). class ExistenceNode < Node - no_return attr_reader :expression def initialize(expression) @@ -759,10 +713,6 @@ def initialize(expressions, line=nil) @line = line end - def children - [@expressions] - end - def compile_node(o) compiled = @expressions.compile(o) compiled = compiled[0...-1] if compiled[-1..-1] == ';' @@ -785,10 +735,6 @@ def initialize(condition, body, else_body=nil, tags={}) @condition = OpNode.new("!", ParentheticalNode.new(@condition)) if @tags[:invert] end - def children - [@body, @else_body] - end - def <<(else_body) eb = else_body.unwrap @else_body ? @else_body << eb : @else_body = eb diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js index 2db559e3ea..3dfdaa3f77 100644 --- a/test/fixtures/generation/each.js +++ b/test/fixtures/generation/each.js @@ -1,33 +1,29 @@ (function(){ - // The cornerstone, an each implementation. // Handles objects implementing forEach, arrays, and raw objects. + + // The cornerstone, an each implementation. + // Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, __f, __g, i, index, item, key; + var __a, __b, __c, __d, __e, i, index, item, key; index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { __a = obj; - __b = []; for (i in __a) { if (__a.hasOwnProperty(i)) { item = __a[i]; - __c = iterator.call(context, item, i, obj); - __b.push(__c); + iterator.call(context, item, i, obj); } } - __b; } else { - __d = _.keys(obj); - __f = []; - for (__e in __d) { - if (__d.hasOwnProperty(__e)) { - key = __d[__e]; - __g = iterator.call(context, obj[key], key, obj); - __f.push(__g); + __c = _.keys(obj); + for (__d in __c) { + if (__c.hasOwnProperty(__d)) { + key = __c[__d]; + iterator.call(context, obj[key], key, obj); } } - __f; } } catch (e) { if (e !== breaker) { diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js index b9578a4e42..40ee9e4cc8 100644 --- a/test/fixtures/generation/each_no_wrap.js +++ b/test/fixtures/generation/each_no_wrap.js @@ -1,32 +1,28 @@ -// The cornerstone, an each implementation.// Handles objects implementing forEach, arrays, and raw objects. + +// The cornerstone, an each implementation. +// Handles objects implementing forEach, arrays, and raw objects. _.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, __f, __g, i, index, item, key; + var __a, __b, __c, __d, __e, i, index, item, key; index = 0; try { if (obj.forEach) { obj.forEach(iterator, context); } else if (_.isArray(obj) || _.isArguments(obj)) { __a = obj; - __b = []; for (i in __a) { if (__a.hasOwnProperty(i)) { item = __a[i]; - __c = iterator.call(context, item, i, obj); - __b.push(__c); + iterator.call(context, item, i, obj); } } - __b; } else { - __d = _.keys(obj); - __f = []; - for (__e in __d) { - if (__d.hasOwnProperty(__e)) { - key = __d[__e]; - __g = iterator.call(context, obj[key], key, obj); - __f.push(__g); + __c = _.keys(obj); + for (__d in __c) { + if (__c.hasOwnProperty(__d)) { + key = __c[__d]; + iterator.call(context, obj[key], key, obj); } } - __f; } } catch (e) { if (e !== breaker) { From 3e24cef69fc999ca3eb092ce8490879f601e28e5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 19:15:24 -0500 Subject: [PATCH 256/303] minor doc updates -- let's try pulling in the underscore test suite --- .gitignore | 1 + documentation/index.html.erb | 5 ++ documentation/js/array_comprehensions.js | 24 +++---- documentation/js/overview.js | 20 +++--- documentation/js/scope.js | 2 +- documentation/js/super.js | 4 +- index.html | 83 ++++++++++++++---------- 7 files changed, 79 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index 4cc33381e8..34de3574ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ test.coffee parser.output +test/fixtures/underscore *.gem \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index ced17b85c9..0e0232b9e7 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -63,6 +63,11 @@ output is quite readable — pretty-printed, with comments preserved intact.

    + +

    + Latest Version: + 0.2.0 +

    Table of Contents

    diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index e2d3b39e47..b35ed908eb 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -1,24 +1,24 @@ (function(){ - var __a, __b, __c, __d, __e, __f, __g, food, i, lunch, row; + var __a, __b, __c, __d, __e, __f, food, i, lunch, row; // Eat lunch. - __a = ['toast', 'cheese', 'wine']; - __c = []; - for (__b in __a) { - if (__a.hasOwnProperty(__b)) { - food = __a[__b]; - __d = this.eat(food); - __c.push(__d); + lunch = (function() { + __a = ['toast', 'cheese', 'wine']; + __c = []; + for (__b in __a) { + if (__a.hasOwnProperty(__b)) { + food = __a[__b]; + __d = this.eat(food); + __c.push(__d); + } } - } - lunch = __c; + return __c; + })(); // Zebra-stripe a table. __e = table; - __f = []; for (i in __e) { if (__e.hasOwnProperty(i)) { row = __e[i]; i % 2 === 0 ? highlight(row) : null; } } - __f; })(); \ No newline at end of file diff --git a/documentation/js/overview.js b/documentation/js/overview.js index 36379ca71f..637697abae 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -22,14 +22,16 @@ } }; // Array comprehensions: - __a = list; - __c = []; - for (__b in __a) { - if (__a.hasOwnProperty(__b)) { - num = __a[__b]; - __d = math.cube(num); - __c.push(__d); + cubed_list = (function() { + __a = list; + __c = []; + for (__b in __a) { + if (__a.hasOwnProperty(__b)) { + num = __a[__b]; + __d = math.cube(num); + __c.push(__d); + } } - } - cubed_list = __c; + return __c; + })(); })(); \ No newline at end of file diff --git a/documentation/js/scope.js b/documentation/js/scope.js index 23b0c8e9dd..0590b247f9 100644 --- a/documentation/js/scope.js +++ b/documentation/js/scope.js @@ -4,7 +4,7 @@ change_numbers = function change_numbers() { var new_num; num = 2; - return (new_num = 3); + return new_num = 3; }; new_num = change_numbers(); })(); \ No newline at end of file diff --git a/documentation/js/super.js b/documentation/js/super.js index 11135d2368..ce26399ee7 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -6,7 +6,7 @@ return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return (this.name = name); + return this.name = name; }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -16,7 +16,7 @@ return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return (this.name = name); + return this.name = name; }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); diff --git a/index.html b/index.html index 483b37b668..4294fbd7f3 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,8 @@ + + @@ -34,6 +36,11 @@

    CoffeeScript

    output is quite readable — pretty-printed, with comments preserved intact.

    + +

    + Latest Version: + 0.2.0 +

    Table of Contents

    @@ -110,16 +117,18 @@

    Mini Overview

    } }; // Array comprehensions: -__a = list; -__c = []; -for (__b in __a) { - if (__a.hasOwnProperty(__b)) { - num = __a[__b]; - __d = math.cube(num); - __c.push(__d); +cubed_list = (function() { + __a = list; + __c = []; + for (__b in __a) { + if (__a.hasOwnProperty(__b)) { + num = __a[__b]; + __d = math.cube(num); + __c.push(__d); + } } -} -cubed_list = __c; + return __c; +})();

    Installation and Usage

    @@ -379,7 +390,7 @@

    Language Reference

    change_numbers = function change_numbers() { var new_num; num = 2; - return (new_num = 3); + return new_num = 3; }; new_num = change_numbers();

    @@ -585,28 +596,28 @@

    Language Reference

    # Zebra-stripe a table. highlight(row) for row, i in table when i % 2 is 0 -
    var __a, __b, __c, __d, __e, __f, __g, food, i, lunch, row;
    +
    var __a, __b, __c, __d, __e, __f, food, i, lunch, row;
     // Eat lunch.
    -__a = ['toast', 'cheese', 'wine'];
    -__c = [];
    -for (__b in __a) {
    -  if (__a.hasOwnProperty(__b)) {
    -    food = __a[__b];
    -    __d = this.eat(food);
    -    __c.push(__d);
    +lunch = (function() {
    +  __a = ['toast', 'cheese', 'wine'];
    +  __c = [];
    +  for (__b in __a) {
    +    if (__a.hasOwnProperty(__b)) {
    +      food = __a[__b];
    +      __d = this.eat(food);
    +      __c.push(__d);
    +    }
       }
    -}
    -lunch = __c;
    +  return __c;
    +})();
     // Zebra-stripe a table.
     __e = table;
    -__f = [];
     for (i in __e) {
       if (__e.hasOwnProperty(i)) {
         row = __e[i];
         i % 2 === 0 ? highlight(row) : null;
       }
     }
    -__f;
     

    If you're not iterating over an actual array, you can use a range to @@ -688,7 +699,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return (this.name = name); + return this.name = name; }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -698,7 +709,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return (this.name = name); + return this.name = name; }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); @@ -718,7 +729,7 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return (this.name = name); + return this.name = name; }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -728,7 +739,7 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return (this.name = name); + return this.name = name; }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); From 6e63a18f86dc1d44216c150959b25e193f365aed Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 22:19:45 -0500 Subject: [PATCH 257/303] first pass at 0.2.0 docs --- .../coffee/array_comprehensions.coffee | 8 +- documentation/coffee/existence.coffee | 1 + .../coffee/expressions_comprehension.coffee | 3 + documentation/coffee/expressions_try.coffee | 6 + .../coffee/object_comprehensions.coffee | 3 + .../coffee/objects_and_arrays.coffee | 9 +- .../coffee/range_comprehensions.coffee | 3 + documentation/coffee/scope.coffee | 4 +- documentation/coffee/splices.coffee | 5 + documentation/index.html.erb | 113 ++++--- documentation/js/array_comprehensions.js | 26 +- documentation/js/existence.js | 6 + documentation/js/expressions_comprehension.js | 16 + documentation/js/expressions_try.js | 9 + documentation/js/object_comprehensions.js | 20 ++ documentation/js/objects_and_arrays.js | 3 +- documentation/js/range_comprehensions.js | 9 + documentation/js/scope.js | 4 +- documentation/js/splices.js | 5 + index.html | 288 ++++++++++++++---- lib/coffee_script/grammar.y | 5 +- 21 files changed, 430 insertions(+), 116 deletions(-) create mode 100644 documentation/coffee/existence.coffee create mode 100644 documentation/coffee/expressions_comprehension.coffee create mode 100644 documentation/coffee/expressions_try.coffee create mode 100644 documentation/coffee/object_comprehensions.coffee create mode 100644 documentation/coffee/range_comprehensions.coffee create mode 100644 documentation/coffee/splices.coffee create mode 100644 documentation/js/existence.js create mode 100644 documentation/js/expressions_comprehension.js create mode 100644 documentation/js/expressions_try.js create mode 100644 documentation/js/object_comprehensions.js create mode 100644 documentation/js/range_comprehensions.js create mode 100644 documentation/js/splices.js diff --git a/documentation/coffee/array_comprehensions.coffee b/documentation/coffee/array_comprehensions.coffee index c272944ea5..3510e1fb0a 100644 --- a/documentation/coffee/array_comprehensions.coffee +++ b/documentation/coffee/array_comprehensions.coffee @@ -1,5 +1,7 @@ # Eat lunch. -lunch: this.eat(food) for food in ['toast', 'cheese', 'wine'] +lunch: eat(food) for food in ['toast', 'cheese', 'wine'] -# Zebra-stripe a table. -highlight(row) for row, i in table when i % 2 is 0 \ No newline at end of file +# Naive collision detection. +for roid in asteroids + for roid2 in asteroids when roid isnt roid2 + roid.explode() if roid.overlaps(roid2) \ No newline at end of file diff --git a/documentation/coffee/existence.coffee b/documentation/coffee/existence.coffee new file mode 100644 index 0000000000..68dc041190 --- /dev/null +++ b/documentation/coffee/existence.coffee @@ -0,0 +1 @@ +solipsism: true if mind? and not world? \ No newline at end of file diff --git a/documentation/coffee/expressions_comprehension.coffee b/documentation/coffee/expressions_comprehension.coffee new file mode 100644 index 0000000000..c608dd218a --- /dev/null +++ b/documentation/coffee/expressions_comprehension.coffee @@ -0,0 +1,3 @@ +# The first ten global properties. + +globals: (name for property, name in window)[0...10] \ No newline at end of file diff --git a/documentation/coffee/expressions_try.coffee b/documentation/coffee/expressions_try.coffee new file mode 100644 index 0000000000..3fe3a7f407 --- /dev/null +++ b/documentation/coffee/expressions_try.coffee @@ -0,0 +1,6 @@ +alert( + try + nonexistent / undefined + catch error + "The error is: " + 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 0000000000..8b637082b2 --- /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 age, child in years_old \ No newline at end of file diff --git a/documentation/coffee/objects_and_arrays.coffee b/documentation/coffee/objects_and_arrays.coffee index ff10c6ed37..f66a8a2590 100644 --- a/documentation/coffee/objects_and_arrays.coffee +++ b/documentation/coffee/objects_and_arrays.coffee @@ -1,6 +1,13 @@ song: ["do", "re", "mi", "fa", "so"] + ages: { max: 10 ida: 9 tim: 11 -} \ No newline at end of file +} + +matrix: [ + 1, 0, 1 + 0, 0, 1 + 1, 1, 0 +] \ No newline at end of file diff --git a/documentation/coffee/range_comprehensions.coffee b/documentation/coffee/range_comprehensions.coffee new file mode 100644 index 0000000000..d13a669ade --- /dev/null +++ b/documentation/coffee/range_comprehensions.coffee @@ -0,0 +1,3 @@ +for i in [0...eggs.length] by 12 + dozen_eggs: eggs[i...i+12] + deliver(new egg_carton(dozen)) diff --git a/documentation/coffee/scope.coffee b/documentation/coffee/scope.coffee index 691072ab5b..b074dad293 100644 --- a/documentation/coffee/scope.coffee +++ b/documentation/coffee/scope.coffee @@ -1,5 +1,5 @@ num: 1 change_numbers: => - num: 2 - new_num: 3 + new_num: -1 + num: 10 new_num: change_numbers() \ No newline at end of file diff --git a/documentation/coffee/splices.coffee b/documentation/coffee/splices.coffee new file mode 100644 index 0000000000..745237aea5 --- /dev/null +++ b/documentation/coffee/splices.coffee @@ -0,0 +1,5 @@ +numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +numbers[3..6]: [-3, -4, -5, -6] + + diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 0e0232b9e7..13b24d49c8 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -63,7 +63,7 @@ output is quite readable — pretty-printed, with comments preserved intact.

    - +

    Latest Version: 0.2.0 @@ -74,17 +74,18 @@

    Mini Overview
    Installation and Usage
    - Punctuation Primer
    + Significant Whitespace
    Functions and Invocation
    Assignment
    Objects and Arrays
    Lexical Scoping and Variable Safety
    Conditionals, Ternaries, and Conditional Assignment
    + The Existence Operator
    Everything is an Expression
    Aliases
    While Loops
    - Array Comprehensions
    - Slicing Arrays with Ranges
    + Comprehensions (Arrays, Objects, and Ranges)
    + Array Slicing and Splicing with Ranges
    Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    @@ -225,22 +226,22 @@ coffee --print app/scripts/*.coffee > concatenation.js

    -

    - Punctuation Primer - You don't need to use semicolons ; to terminate expressions, ending - the line will do just as well. All other whitespace is - not significant. Instead of using curly braces { } - to delimit a block of code, use a period . to mark the end of a - block, for - functions, +

    + Significant Whitespace + CoffeeScript uses Python-style significant whitespace: You don't need to + use semicolons ; to terminate expressions, ending + the line will do just as well. Semicolons can still be used to fit + multiple expressions onto a single line. Instead of using curly braces + { } to delimit a block of code (like functions, if-statements, - switch, and try/catch. + switch, and try/catch), + use indentation.

    Functions and Invocation Functions are defined by a list of parameters, an arrow, and the - function body. The empty function looks like this: =>. + function body. The empty function looks like this: =>

    <%= code_for('functions', 'cube(5)') %> @@ -252,8 +253,8 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('assignment', 'greeting') %>

    - Declarations of new variables are pushed up to the top of the current scope, - so that assignments may always be used within expressions. + Declarations of new variables are pushed up to the top of the nearest + lexical scope, so that assignment may always be used within expressions.

    @@ -261,7 +262,8 @@ coffee --print app/scripts/*.coffee > concatenation.js Object and Array literals look very similar to their JavaScript cousins. When you spread out each assignment on a separate line, the commas are optional. In this way, assigning object properties looks the same as - assigning local variables. + assigning local variables, and can be moved around freely. You can mix + and match the two styles.

    <%= code_for('objects_and_arrays', 'song.join(",")') %> @@ -273,25 +275,29 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('scope', 'new_num') %>

    - Notice how the variables are declared with var the first time - they appear. The second reference of num, within the function, - is not redeclared because num is still in scope. As opposed - to the second occurrence of new_num, in the last line. + Notice how the all of the variable declarations have been pushed up to + the top of the closest scope, the first time they appear. + num is not redeclared within the inner function, because it's + already in scope; the new_num within the function, on the other hand, + should not be able to change the value of the external variable of the same name, and + therefore has a declaration of its own.

    Although suppressed within this documentation for clarity, all CoffeeScript output is wrapped in an anonymous function: (function(){ ... })(); This safety wrapper, combined with the automatic generation of the var keyword, make it exceedingly difficult - to pollute the global namespace by accident. + to pollute the global namespace by accident. If you'd like to create + global variables, attach them as properties on window, + or on the exports object in CommonJS.

    Conditionals, Ternaries, and Conditional Assignment If/else statements can be written without the use of parentheses and - curly brackets. As with functions and other block expressions, conditionals - are closed with periods. No period is necessary when using the single-line - postfix form, with the if at the end. + curly brackets. As with functions and other block expressions, + multi-line conditionals are delimited by indentation. There's also a handy + postfix form, with the if or unless at the end.

    CoffeeScript will compile if statements using the ternary operator @@ -299,12 +305,22 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('conditionals') %>

    - The conditional assignment operators are available: ||=, + The conditional assignment operators are included: ||=, which only assigns a value to a variable if the variable's current value is falsy, and &&=, which only replaces the value of truthy variables.

    +

    + The Existence Operator + It's a little difficult to check for the existence of a variable in + JavaScript. if (variable) ... comes close, but fails for zero, + the empty string, and false. The existence operator ? returns true unless + a variable is null or undefined, which makes it analogous + to nil? in Ruby. +

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

    Everything is an Expression (at least, as much as possible) You might have noticed how even though we don't add return statements @@ -316,14 +332,22 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('expressions', 'eldest') %>

    - The same mechanism is used to push down assignment through switch - statements, and if-elses (although the ternary operator is preferred). - Another part of manipulating assignment statements is - CoffeeScript's declaration of new variables at the top of the - current scope. This allows assignment to be used as a piece of an - expression. + Because variable declarations occur at the top of scope, assignment can + be used within expressions, even for variables that haven't been seen before:

    <%= code_for('expressions_assignment', 'six') %> +

    + Things that would otherwise be statements in JavaScript, when used + as part of an expression in CoffeeScript, are converted into expressions + by wrapping them in a closure. This lets you do useful things, like assign + the result of a comprehension to a variable: +

    + <%= code_for('expressions_comprehension', 'globals') %> +

    + As well as silly things, like passing a try/catch statement directly + into a function call: +

    + <%= code_for('expressions_try', true) %>

    Aliases @@ -367,8 +391,8 @@ coffee --print app/scripts/*.coffee > concatenation.js each (forEach) style iterators, or...

    -

    - Array Comprehensions +

    + Comprehensions (Arrays, Objects, and Ranges) For your looping needs, CoffeeScript provides array comprehensions similar to Python's. They replace (and compile into) for loops, with optional guard clauses and the value of the current array index. @@ -378,13 +402,19 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('array_comprehensions') %>

    - If you're not iterating over an actual array, you can use a range to - specify the start and end of an array comprehension: - coundown(i) for i in [10..1]. + 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: +

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

    + Comprehensions can also be used to iterate over the values and keys in + an object:

    + <%= code_for('object_comprehensions', 'ages.join(", ")') %> -

    - Slicing Arrays with Ranges +

    + Array Slicing and Splicing with Ranges CoffeeScript borrows Ruby's range syntax for extracting slices of arrays. With two dots (3..5), the range @@ -393,6 +423,11 @@ coffee --print app/scripts/*.coffee > concatenation.js a range that excludes the end.

    <%= code_for('slices', 'numbers_copy') %> +

    + The same syntax can be used with assignment to replace a segment of an + array with new values (to splice it). +

    + <%= code_for('splices', 'numbers') %>

    Inheritance, and Calling Super from a Subclass diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index b35ed908eb..089b9fcf35 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, food, i, lunch, row; + var __a, __b, __c, __d, __e, __f, __g, __h, __i, __j, food, lunch, roid, roid2; // Eat lunch. lunch = (function() { __a = ['toast', 'cheese', 'wine']; @@ -7,18 +7,28 @@ for (__b in __a) { if (__a.hasOwnProperty(__b)) { food = __a[__b]; - __d = this.eat(food); + __d = eat(food); __c.push(__d); } } return __c; })(); - // Zebra-stripe a table. - __e = table; - for (i in __e) { - if (__e.hasOwnProperty(i)) { - row = __e[i]; - i % 2 === 0 ? highlight(row) : null; + // Naive collision detection. + __e = asteroids; + for (__f in __e) { + if (__e.hasOwnProperty(__f)) { + roid = __e[__f]; + __h = asteroids; + for (__i in __h) { + if (__h.hasOwnProperty(__i)) { + roid2 = __h[__i]; + if (roid !== roid2) { + if (roid.overlaps(roid2)) { + roid.explode(); + } + } + } + } } } })(); \ No newline at end of file diff --git a/documentation/js/existence.js b/documentation/js/existence.js new file mode 100644 index 0000000000..60e9e922b9 --- /dev/null +++ b/documentation/js/existence.js @@ -0,0 +1,6 @@ +(function(){ + var solipsism; + if ((typeof mind !== 'undefined' && mind !== null) && !(typeof world !== 'undefined' && world !== null)) { + solipsism = true; + } +})(); \ No newline at end of file diff --git a/documentation/js/expressions_comprehension.js b/documentation/js/expressions_comprehension.js new file mode 100644 index 0000000000..5dea5cf044 --- /dev/null +++ b/documentation/js/expressions_comprehension.js @@ -0,0 +1,16 @@ +(function(){ + var __a, __b, __c, globals, name, property; + // The first ten global properties. + globals = ((function() { + __a = window; + __b = []; + for (name in __a) { + if (__a.hasOwnProperty(name)) { + property = __a[name]; + __c = name; + __b.push(__c); + } + } + return __b; + })()).slice(0, 10); +})(); \ No newline at end of file diff --git a/documentation/js/expressions_try.js b/documentation/js/expressions_try.js new file mode 100644 index 0000000000..9c236f65e8 --- /dev/null +++ b/documentation/js/expressions_try.js @@ -0,0 +1,9 @@ +(function(){ + alert((function() { + try { + return nonexistent / undefined; + } catch (error) { + return "The error is: " + 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 0000000000..757ad863c6 --- /dev/null +++ b/documentation/js/object_comprehensions.js @@ -0,0 +1,20 @@ +(function(){ + var __a, __b, __c, age, ages, child, years_old; + years_old = { + max: 10, + ida: 9, + tim: 11 + }; + ages = (function() { + __a = years_old; + __b = []; + for (child in __a) { + if (__a.hasOwnProperty(child)) { + age = __a[child]; + __c = child + " is " + age; + __b.push(__c); + } + } + return __b; + })(); +})(); \ No newline at end of file diff --git a/documentation/js/objects_and_arrays.js b/documentation/js/objects_and_arrays.js index cfd1011ed6..3c7de98f03 100644 --- a/documentation/js/objects_and_arrays.js +++ b/documentation/js/objects_and_arrays.js @@ -1,9 +1,10 @@ (function(){ - var ages, song; + var ages, matrix, song; song = ["do", "re", "mi", "fa", "so"]; ages = { max: 10, ida: 9, tim: 11 }; + matrix = [1, 0, 1, 0, 0, 1, 1, 1, 0]; })(); \ No newline at end of file diff --git a/documentation/js/range_comprehensions.js b/documentation/js/range_comprehensions.js new file mode 100644 index 0000000000..7457b7f4a8 --- /dev/null +++ b/documentation/js/range_comprehensions.js @@ -0,0 +1,9 @@ +(function(){ + var __a, __b, __c, __d, __e, dozen_eggs, i; + __d = 0; + __e = eggs.length; + for (__c=0, i=__d; (__d <= __e ? i < __e : i > __e); (__d <= __e ? i += 12 : i -= 12), __c++) { + dozen_eggs = eggs.slice(i, i + 12); + deliver(new egg_carton(dozen)); + } +})(); \ No newline at end of file diff --git a/documentation/js/scope.js b/documentation/js/scope.js index 0590b247f9..3319f50c45 100644 --- a/documentation/js/scope.js +++ b/documentation/js/scope.js @@ -3,8 +3,8 @@ num = 1; change_numbers = function change_numbers() { var new_num; - num = 2; - return new_num = 3; + new_num = -1; + return num = 10; }; new_num = change_numbers(); })(); \ No newline at end of file diff --git a/documentation/js/splices.js b/documentation/js/splices.js new file mode 100644 index 0000000000..0192c5e5e8 --- /dev/null +++ b/documentation/js/splices.js @@ -0,0 +1,5 @@ +(function(){ + var numbers; + numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6])); +})(); \ No newline at end of file diff --git a/index.html b/index.html index 4294fbd7f3..11397c73af 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@

    CoffeeScript

    output is quite readable — pretty-printed, with comments preserved intact.

    - +

    Latest Version: 0.2.0 @@ -47,17 +47,18 @@

    Table of Contents

    Mini Overview
    Installation and Usage
    - Punctuation Primer
    + Significant Whitespace
    Functions and Invocation
    Assignment
    Objects and Arrays
    Lexical Scoping and Variable Safety
    Conditionals, Ternaries, and Conditional Assignment
    + The Existence Operator
    Everything is an Expression
    Aliases
    While Loops
    - Array Comprehensions
    - Slicing Arrays with Ranges
    + Comprehensions (Arrays, Objects, and Ranges)
    + Array Slicing and Splicing with Ranges
    Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    @@ -290,22 +291,22 @@

    Language Reference

    -

    - Punctuation Primer - You don't need to use semicolons ; to terminate expressions, ending - the line will do just as well. All other whitespace is - not significant. Instead of using curly braces { } - to delimit a block of code, use a period . to mark the end of a - block, for - functions, +

    + Significant Whitespace + CoffeeScript uses Python-style significant whitespace: You don't need to + use semicolons ; to terminate expressions, ending + the line will do just as well. Semicolons can still be used to fit + multiple expressions onto a single line. Instead of using curly braces + { } to delimit a block of code (like functions, if-statements, - switch, and try/catch. + switch, and try/catch), + use indentation.

    Functions and Invocation Functions are defined by a list of parameters, an arrow, and the - function body. The empty function looks like this: =>. + function body. The empty function looks like this: =>

    square: x => x * x
     cube:   x => square(x) * x
    @@ -341,8 +342,8 @@ 

    Language Reference

    difficulty = 0.5; ;alert(greeting);'>run: greeting

    - Declarations of new variables are pushed up to the top of the current scope, - so that assignments may always be used within expressions. + Declarations of new variables are pushed up to the top of the nearest + lexical scope, so that assignment may always be used within expressions.

    @@ -350,28 +351,38 @@

    Language Reference

    Object and Array literals look very similar to their JavaScript cousins. When you spread out each assignment on a separate line, the commas are optional. In this way, assigning object properties looks the same as - assigning local variables. + assigning local variables, and can be moved around freely. You can mix + and match the two styles.

    song: ["do", "re", "mi", "fa", "so"]
    +
     ages: {
       max: 10
       ida: 9
       tim: 11
     }
    -
    var ages, song;
    +
    +matrix: [
    +  1, 0, 1
    +  0, 0, 1
    +  1, 1, 0
    +]
    +
    var ages, matrix, song;
     song = ["do", "re", "mi", "fa", "so"];
     ages = {
       max: 10,
       ida: 9,
       tim: 11
     };
    -

    @@ -382,46 +393,50 @@

    Language Reference

    num: 1
     change_numbers: =>
    -  num: 2
    -  new_num: 3
    +  new_num: -1
    +  num: 10
     new_num: change_numbers()
     
    var change_numbers, new_num, num;
     num = 1;
     change_numbers = function change_numbers() {
       var new_num;
    -  num = 2;
    -  return new_num = 3;
    +  new_num = -1;
    +  return num = 10;
     };
     new_num = change_numbers();
     

    - Notice how the variables are declared with var the first time - they appear. The second reference of num, within the function, - is not redeclared because num is still in scope. As opposed - to the second occurrence of new_num, in the last line. + Notice how the all of the variable declarations have been pushed up to + the top of the closest scope, the first time they appear. + num is not redeclared within the inner function, because it's + already in scope; the new_num within the function, on the other hand, + should not be able to change the value of the external variable of the same name, and + therefore has a declaration of its own.

    Although suppressed within this documentation for clarity, all CoffeeScript output is wrapped in an anonymous function: (function(){ ... })(); This safety wrapper, combined with the automatic generation of the var keyword, make it exceedingly difficult - to pollute the global namespace by accident. + to pollute the global namespace by accident. If you'd like to create + global variables, attach them as properties on window, + or on the exports object in CommonJS.

    Conditionals, Ternaries, and Conditional Assignment If/else statements can be written without the use of parentheses and - curly brackets. As with functions and other block expressions, conditionals - are closed with periods. No period is necessary when using the single-line - postfix form, with the if at the end. + curly brackets. As with functions and other block expressions, + multi-line conditionals are delimited by indentation. There's also a handy + postfix form, with the if or unless at the end.

    CoffeeScript will compile if statements using the ternary operator @@ -448,12 +463,27 @@

    Language Reference

    expensive = expensive || do_the_math();

    - The conditional assignment operators are available: ||=, + The conditional assignment operators are included: ||=, which only assigns a value to a variable if the variable's current value is falsy, and &&=, which only replaces the value of truthy variables.

    +

    + The Existence Operator + It's a little difficult to check for the existence of a variable in + JavaScript. if (variable) ... comes close, but fails for zero, + the empty string, and false. The existence operator ? returns true unless + a variable is null or undefined, which makes it analogous + to nil? in Ruby. +

    +
    solipsism: true if mind? and not world?
    +
    var solipsism;
    +if ((typeof mind !== 'undefined' && mind !== null) && !(typeof world !== 'undefined' && world !== null)) {
    +  solipsism = true;
    +}
    +

    +

    Everything is an Expression (at least, as much as possible) You might have noticed how even though we don't add return statements @@ -496,12 +526,8 @@

    Language Reference

    eldest = 24 > 21 ? "Liz" : "Ike"; ;alert(eldest);'>run: eldest

    - The same mechanism is used to push down assignment through switch - statements, and if-elses (although the ternary operator is preferred). - Another part of manipulating assignment statements is - CoffeeScript's declaration of new variables at the top of the - current scope. This allows assignment to be used as a piece of an - expression. + Because variable declarations occur at the top of scope, assignment can + be used within expressions, even for variables that haven't been seen before:

    six: (one: 1) + (two: 2) + (three: 3)
     
    var one, six, three, two;
    @@ -509,6 +535,69 @@ 

    Language Reference


    +

    + Things that would otherwise be statements in JavaScript, when used + as part of an expression in CoffeeScript, are converted into expressions + by wrapping them in a closure. This lets you do useful things, like assign + the result of a comprehension to a variable: +

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

    +

    + As well as silly things, like passing a try/catch statement directly + into a function call: +

    +
    alert(
    +  try
    +    nonexistent / undefined
    +  catch error
    +    "The error is: " + error
    +)
    +
    alert((function() {
    +  try {
    +    return nonexistent / undefined;
    +  } catch (error) {
    +    return "The error is: " + error;
    +  }
    +})());
    +

    Aliases @@ -582,8 +671,8 @@

    Language Reference

    each (forEach) style iterators, or...

    -

    - Array Comprehensions +

    + Comprehensions (Arrays, Objects, and Ranges) For your looping needs, CoffeeScript provides array comprehensions similar to Python's. They replace (and compile into) for loops, with optional guard clauses and the value of the current array index. @@ -592,11 +681,13 @@

    Language Reference

    would use a loop, each/forEach, map, or select/filter.

    # Eat lunch.
    -lunch: this.eat(food) for food in ['toast', 'cheese', 'wine']
    +lunch: eat(food) for food in ['toast', 'cheese', 'wine']
     
    -# Zebra-stripe a table.
    -highlight(row) for row, i in table when i % 2 is 0
    -
    var __a, __b, __c, __d, __e, __f, food, i, lunch, row;
    +# Naive collision detection.
    +for roid in asteroids
    +  for roid2 in asteroids when roid isnt roid2
    +    roid.explode() if roid.overlaps(roid2)
    +
    var __a, __b, __c, __d, __e, __f, __g, __h, __i, __j, food, lunch, roid, roid2;
     // Eat lunch.
     lunch = (function() {
       __a = ['toast', 'cheese', 'wine'];
    @@ -604,29 +695,94 @@ 

    Language Reference

    for (__b in __a) { if (__a.hasOwnProperty(__b)) { food = __a[__b]; - __d = this.eat(food); + __d = eat(food); __c.push(__d); } } return __c; })(); -// Zebra-stripe a table. -__e = table; -for (i in __e) { - if (__e.hasOwnProperty(i)) { - row = __e[i]; - i % 2 === 0 ? highlight(row) : null; +// Naive collision detection. +__e = asteroids; +for (__f in __e) { + if (__e.hasOwnProperty(__f)) { + roid = __e[__f]; + __h = asteroids; + for (__i in __h) { + if (__h.hasOwnProperty(__i)) { + roid2 = __h[__i]; + if (roid !== roid2) { + if (roid.overlaps(roid2)) { + roid.explode(); + } + } + } + } } }

    - If you're not iterating over an actual array, you can use a range to - specify the start and end of an array comprehension: - coundown(i) for i in [10..1]. + 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: +

    +
    for i in [0...eggs.length] by 12
    +  dozen_eggs: eggs[i...i+12]
    +  deliver(new egg_carton(dozen))
    +
    var __a, __b, __c, __d, __e, dozen_eggs, i;
    +__d = 0;
    +__e = eggs.length;
    +for (__c=0, i=__d; (__d <= __e ? i < __e : i > __e); (__d <= __e ? i += 12 : i -= 12), __c++) {
    +  dozen_eggs = eggs.slice(i, i + 12);
    +  deliver(new egg_carton(dozen));
    +}
    +

    +

    + Comprehensions can also be used to iterate over the values and keys in + an object:

    +
    years_old: {max: 10, ida: 9, tim: 11}
     
    -    

    - Slicing Arrays with Ranges +ages: child + " is " + age for age, child in years_old +

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

    + +

    + Array Slicing and Splicing with Ranges CoffeeScript borrows Ruby's range syntax for extracting slices of arrays. With two dots (3..5), the range @@ -649,6 +805,22 @@

    Language Reference

    three_to_six = numbers.slice(3, 6 + 1); numbers_copy = numbers.slice(0, numbers.length); ;alert(numbers_copy);'>run: numbers_copy
    +

    + The same syntax can be used with assignment to replace a segment of an + array with new values (to splice it). +

    +
    numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    +
    +numbers[3..6]: [-3, -4, -5, -6]
    +
    +
    +
    var numbers;
    +numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    +numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6]));
    +

    Inheritance, and Calling Super from a Subclass diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index f1c280bf47..541c932e61 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -19,7 +19,8 @@ token INDENT OUTDENT # Declare order of operations. prechigh - nonassoc UMINUS PARAM_SPLAT SPLAT NOT '!' '!!' '~' '++' '--' '?' + left '?' + nonassoc UMINUS PARAM_SPLAT SPLAT NOT '!' '!!' '~' '++' '--' left '*' '/' '%' left '+' '-' left '<<' '>>' '>>>' @@ -368,7 +369,7 @@ rule # An individual when. When: LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } - | LEADING_WHEN Expression Block + | LEADING_WHEN Expression Block Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } | Comment ; From ae603749be49913ac4a9875ce30375ada58b475e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 22:51:02 -0500 Subject: [PATCH 258/303] adding the complete underscore.coffee example to the docs --- Rakefile | 1 + documentation/index.html.erb | 65 +- documentation/underscore.html | 612 +++++++++++++++++ examples/underscore.coffee | 1106 ++++++++++++++++-------------- index.html | 239 +++---- lib/coffee_script/grammar.y | 1 + lib/coffee_script/lexer.rb | 3 +- lib/coffee_script/parse_error.rb | 4 +- 8 files changed, 1368 insertions(+), 663 deletions(-) create mode 100644 documentation/underscore.html diff --git a/Rakefile b/Rakefile index 7f527a1a44..1086cefdad 100644 --- a/Rakefile +++ b/Rakefile @@ -37,6 +37,7 @@ task :doc do child = fork { exec "bin/coffee documentation/coffee/*.coffee -o documentation/js -w" } at_exit { Process.kill("INT", child) } Signal.trap("INT") { exit } + # `uv -t idle -s coffeescript -h examples/underscore.coffee > documentation/underscore.html` loop do mtime = File.stat(source).mtime if !@mtime || mtime > @mtime diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 13b24d49c8..5cb307cdce 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -81,11 +81,11 @@ Lexical Scoping and Variable Safety
    Conditionals, Ternaries, and Conditional Assignment
    The Existence Operator
    - Everything is an Expression
    Aliases
    While Loops
    Comprehensions (Arrays, Objects, and Ranges)
    Array Slicing and Splicing with Ranges
    + Everything is an Expression
    Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    @@ -101,6 +101,13 @@

    CoffeeScript on the left, compiled JavaScript output on the right.

    <%= code_for('overview', 'cubed_list') %> + +

    + For a longer CoffeeScript example, check out + Underscore.coffee, a port + of Underscore.js + to CoffeeScript, which, when compiled, passes the complete Underscore test suite. +

    Installation and Usage

    @@ -321,34 +328,6 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('existence') %> -

    - Everything is an Expression (at least, as much as possible) - You might have noticed how even though we don't add return statements - to CoffeeScript functions, they nonetheless return their final value. - The CoffeeScript compiler tries to make sure that all statements in the - language can be used as expressions. Watch how the return gets - pushed down into each possible branch of execution, in the function - below. -

    - <%= code_for('expressions', 'eldest') %> -

    - Because variable declarations occur at the top of scope, assignment can - be used within expressions, even for variables that haven't been seen before: -

    - <%= code_for('expressions_assignment', 'six') %> -

    - Things that would otherwise be statements in JavaScript, when used - as part of an expression in CoffeeScript, are converted into expressions - by wrapping them in a closure. This lets you do useful things, like assign - the result of a comprehension to a variable: -

    - <%= code_for('expressions_comprehension', 'globals') %> -

    - As well as silly things, like passing a try/catch statement directly - into a function call: -

    - <%= code_for('expressions_try', true) %> -

    Aliases Because the == operator frequently causes undesirable coercion, @@ -428,6 +407,34 @@ coffee --print app/scripts/*.coffee > concatenation.js array with new values (to splice it).

    <%= code_for('splices', 'numbers') %> + +

    + Everything is an Expression (at least, as much as possible) + You might have noticed how even though we don't add return statements + to CoffeeScript functions, they nonetheless return their final value. + The CoffeeScript compiler tries to make sure that all statements in the + language can be used as expressions. Watch how the return gets + pushed down into each possible branch of execution, in the function + below. +

    + <%= code_for('expressions', 'eldest') %> +

    + Because variable declarations occur at the top of scope, assignment can + be used within expressions, even for variables that haven't been seen before: +

    + <%= code_for('expressions_assignment', 'six') %> +

    + Things that would otherwise be statements in JavaScript, when used + as part of an expression in CoffeeScript, are converted into expressions + by wrapping them in a closure. This lets you do useful things, like assign + the result of a comprehension to a variable: +

    + <%= code_for('expressions_comprehension', 'globals') %> +

    + As well as silly things, like passing a try/catch statement directly + into a function call: +

    + <%= code_for('expressions_try', true) %>

    Inheritance, and Calling Super from a Subclass diff --git a/documentation/underscore.html b/documentation/underscore.html new file mode 100644 index 0000000000..674adb89f7 --- /dev/null +++ b/documentation/underscore.html @@ -0,0 +1,612 @@ + + + + + + + Underscore.coffee + + + + + +

       1 
    +   2    # Underscore.coffee
    +   3    # (c) 2009 Jeremy Ashkenas, DocumentCloud Inc.
    +   4    # Underscore is freely distributable under the terms of the MIT license.
    +   5    # Portions of Underscore are inspired by or borrowed from Prototype.js,
    +   6    # Oliver Steele's Functional, and John Resig's Micro-Templating.
    +   7    # For all details and documentation:
    +   8    # http://documentcloud.github.com/underscore/
    +   9 
    +  10 
    +  11    # ------------------------- Baseline setup ---------------------------------
    +  12 
    +  13    # Establish the root object, "window" in the browser, or "global" on the server.
    +  14    root: this
    +  15 
    +  16 
    +  17    # Save the previous value of the "_" variable.
    +  18    previousUnderscore: root._
    +  19 
    +  20 
    +  21    # If Underscore is called as a function, it returns a wrapped object that
    +  22    # can be used OO-style. This wrapper holds altered versions of all the
    +  23    # underscore functions. Wrapped objects may be chained.
    +  24    wrapper: obj =>
    +  25      this._wrapped: obj
    +  26      this
    +  27 
    +  28 
    +  29    # Establish the object that gets thrown to break out of a loop iteration.
    +  30    breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration
    +  31 
    +  32 
    +  33    # Create a safe reference to the Underscore object for reference below.
    +  34    _: root._: obj => new wrapper(obj)
    +  35 
    +  36 
    +  37    # Export the Underscore object for CommonJS.
    +  38    if typeof(exports) != 'undefined' then exports._: _
    +  39 
    +  40 
    +  41    # Create quick reference variables for speed access to core prototypes.
    +  42    slice:                Array.prototype.slice
    +  43    unshift:              Array.prototype.unshift
    +  44    toString:             Object.prototype.toString
    +  45    hasOwnProperty:       Object.prototype.hasOwnProperty
    +  46    propertyIsEnumerable: Object.prototype.propertyIsEnumerable
    +  47 
    +  48 
    +  49    # Current version.
    +  50    _.VERSION: '0.5.3'
    +  51 
    +  52 
    +  53    # ------------------------ Collection Functions: ---------------------------
    +  54 
    +  55    # The cornerstone, an each implementation.
    +  56    # Handles objects implementing forEach, arrays, and raw objects.
    +  57    _.each: obj, iterator, context =>
    +  58      index: 0
    +  59      try
    +  60        return obj.forEach(iterator, context) if obj.forEach
    +  61        if _.isArray(obj) or _.isArguments(obj)
    +  62          return iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
    +  63        iterator.call(context, val, key, obj) for val, key in obj
    +  64      catch e
    +  65        throw e if e isnt breaker
    +  66      obj
    +  67 
    +  68 
    +  69    # Return the results of applying the iterator to each element. Use JavaScript
    +  70    # 1.6's version of map, if possible.
    +  71    _.map: obj, iterator, context =>
    +  72      return obj.map(iterator, context) if (obj and _.isFunction(obj.map))
    +  73      results: []
    +  74      _.each(obj) value, index, list =>
    +  75        results.push(iterator.call(context, value, index, list))
    +  76      results
    +  77 
    +  78 
    +  79    # Reduce builds up a single result from a list of values. Also known as
    +  80    # inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
    +  81    _.reduce: obj, memo, iterator, context =>
    +  82      return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce))
    +  83      _.each(obj) value, index, list =>
    +  84        memo: iterator.call(context, memo, value, index, list)
    +  85      memo
    +  86 
    +  87 
    +  88    # The right-associative version of reduce, also known as foldr. Uses
    +  89    # JavaScript 1.8's version of reduceRight, if available.
    +  90    _.reduceRight: obj, memo, iterator, context =>
    +  91      return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight))
    +  92      _.each(_.clone(_.toArray(obj)).reverse()) value, index =>
    +  93        memo: iterator.call(context, memo, value, index, obj)
    +  94      memo
    +  95 
    +  96 
    +  97    # Return the first value which passes a truth test.
    +  98    _.detect: obj, iterator, context =>
    +  99      result: null
    + 100      _.each(obj) value, index, list =>
    + 101        if iterator.call(context, value, index, list)
    + 102          result: value
    + 103          _.breakLoop()
    + 104      result
    + 105 
    + 106 
    + 107    # Return all the elements that pass a truth test. Use JavaScript 1.6's
    + 108    # filter(), if it exists.
    + 109    _.select: obj, iterator, context =>
    + 110      if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context)
    + 111      results: []
    + 112      _.each(obj) value, index, list =>
    + 113        results.push(value) if iterator.call(context, value, index, list)
    + 114      results
    + 115 
    + 116 
    + 117    # Return all the elements for which a truth test fails.
    + 118    _.reject: obj, iterator, context =>
    + 119      results: []
    + 120      _.each(obj) value, index, list =>
    + 121        results.push(value) if not iterator.call(context, value, index, list)
    + 122      results
    + 123 
    + 124 
    + 125    # Determine whether all of the elements match a truth test. Delegate to
    + 126    # JavaScript 1.6's every(), if it is present.
    + 127    _.all: obj, iterator, context =>
    + 128      iterator ||= _.identity
    + 129      return obj.every(iterator, context) if obj and _.isFunction(obj.every)
    + 130      result: true
    + 131      _.each(obj) value, index, list =>
    + 132        _.breakLoop() unless (result: result and iterator.call(context, value, index, list))
    + 133      result
    + 134 
    + 135 
    + 136    # Determine if at least one element in the object matches a truth test. Use
    + 137    # JavaScript 1.6's some(), if it exists.
    + 138    _.any: obj, iterator, context =>
    + 139      iterator ||= _.identity
    + 140      return obj.some(iterator, context) if obj and _.isFunction(obj.some)
    + 141      result: false
    + 142      _.each(obj) value, index, list =>
    + 143        _.breakLoop() if (result: iterator.call(context, value, index, list))
    + 144      result
    + 145 
    + 146 
    + 147    # Determine if a given value is included in the array or object,
    + 148    # based on '==='.
    + 149    _.include: obj, target =>
    + 150      return _.indexOf(obj, target) isnt -1 if _.isArray(obj)
    + 151      for val in obj
    + 152        return true if val is target
    + 153      false
    + 154 
    + 155 
    + 156    # Invoke a method with arguments on every item in a collection.
    + 157    _.invoke: obj, method =>
    + 158      args: _.rest(arguments, 2)
    + 159      (if method then val[method] else val).apply(val, args) for val in obj
    + 160 
    + 161 
    + 162    # Convenience version of a common use case of map: fetching a property.
    + 163    _.pluck: obj, key =>
    + 164      _.map(obj, (val => val[key]))
    + 165 
    + 166 
    + 167    # Return the maximum item or (item-based computation).
    + 168    _.max: obj, iterator, context =>
    + 169      return Math.max.apply(Math, obj) if not iterator and _.isArray(obj)
    + 170      result: {computed: -Infinity}
    + 171      _.each(obj) value, index, list =>
    + 172        computed: if iterator then iterator.call(context, value, index, list) else value
    + 173        computed >= result.computed and (result: {value: value, computed: computed})
    + 174      result.value
    + 175 
    + 176 
    + 177    # Return the minimum element (or element-based computation).
    + 178    _.min: obj, iterator, context =>
    + 179      return Math.min.apply(Math, obj) if not iterator and _.isArray(obj)
    + 180      result: {computed: Infinity}
    + 181      _.each(obj) value, index, list =>
    + 182        computed: if iterator then iterator.call(context, value, index, list) else value
    + 183        computed < result.computed and (result: {value: value, computed: computed})
    + 184      result.value
    + 185 
    + 186 
    + 187    # Sort the object's values by a criteria produced by an iterator.
    + 188    _.sortBy: obj, iterator, context =>
    + 189      _.pluck(((_.map(obj) value, index, list =>
    + 190        {value: value, criteria: iterator.call(context, value, index, list)}
    + 191      ).sort() left, right =>
    + 192        a: left.criteria; b: right.criteria
    + 193        if a < b then -1 else if a > b then 1 else 0
    + 194      ), 'value')
    + 195 
    + 196 
    + 197    # Use a comparator function to figure out at what index an object should
    + 198    # be inserted so as to maintain order. Uses binary search.
    + 199    _.sortedIndex: array, obj, iterator =>
    + 200      iterator ||= _.identity
    + 201      low: 0; high: array.length
    + 202      while low < high
    + 203        mid: (low + high) >> 1
    + 204        if iterator(array[mid]) < iterator(obj) then low: mid + 1 else high: mid
    + 205      low
    + 206 
    + 207 
    + 208    # Convert anything iterable into a real, live array.
    + 209    _.toArray: iterable =>
    + 210      return []                   if (!iterable)
    + 211      return iterable.toArray()   if (iterable.toArray)
    + 212      return iterable             if (_.isArray(iterable))
    + 213      return slice.call(iterable) if (_.isArguments(iterable))
    + 214      _.values(iterable)
    + 215 
    + 216 
    + 217    # Return the number of elements in an object.
    + 218    _.size: obj => _.toArray(obj).length
    + 219 
    + 220 
    + 221    # -------------------------- Array Functions: ------------------------------
    + 222 
    + 223    # Get the first element of an array. Passing "n" will return the first N
    + 224    # values in the array. Aliased as "head". The "guard" check allows it to work
    + 225    # with _.map.
    + 226    _.first: array, n, guard =>
    + 227      if n and not guard then slice.call(array, 0, n) else array[0]
    + 228 
    + 229 
    + 230    # Returns everything but the first entry of the array. Aliased as "tail".
    + 231    # Especially useful on the arguments object. Passing an "index" will return
    + 232    # the rest of the values in the array from that index onward. The "guard"
    + 233    # check allows it to work with _.map.
    + 234    _.rest: array, index, guard =>
    + 235      slice.call(array, if _.isUndefined(index) or guard then 1 else index)
    + 236 
    + 237 
    + 238    # Get the last element of an array.
    + 239    _.last: array => array[array.length - 1]
    + 240 
    + 241 
    + 242    # Trim out all falsy values from an array.
    + 243    _.compact: array => array[i] for i in [0...array.length] when array[i]
    + 244 
    + 245 
    + 246    # Return a completely flattened version of an array.
    + 247    _.flatten: array =>
    + 248      _.reduce(array, []) memo, value =>
    + 249        return memo.concat(_.flatten(value)) if _.isArray(value)
    + 250        memo.push(value)
    + 251        memo
    + 252 
    + 253 
    + 254    # Return a version of the array that does not contain the specified value(s).
    + 255    _.without: array =>
    + 256      values: _.rest(arguments)
    + 257      val for val in _.toArray(array) when not _.include(values, val)
    + 258 
    + 259 
    + 260    # Produce a duplicate-free version of the array. If the array has already
    + 261    # been sorted, you have the option of using a faster algorithm.
    + 262    _.uniq: array, isSorted =>
    + 263      memo: []
    + 264      for el, i in _.toArray(array)
    + 265        memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
    + 266      memo
    + 267 
    + 268 
    + 269    # Produce an array that contains every item shared between all the
    + 270    # passed-in arrays.
    + 271    _.intersect: array =>
    + 272      rest: _.rest(arguments)
    + 273      _.select(_.uniq(array)) item =>
    + 274        _.all(rest) other =>
    + 275          _.indexOf(other, item) >= 0
    + 276 
    + 277 
    + 278    # Zip together multiple lists into a single array -- elements that share
    + 279    # an index go together.
    + 280    _.zip: =>
    + 281      args:       _.toArray(arguments)
    + 282      length:     _.max(_.pluck(args, 'length'))
    + 283      results:    new Array(length)
    + 284      for i in [0...length]
    + 285        results[i]: _.pluck(args, String(i))
    + 286      results
    + 287 
    + 288 
    + 289    # If the browser doesn't supply us with indexOf (I'm looking at you, MSIE),
    + 290    # we need this function. Return the position of the first occurence of an
    + 291    # item in an array, or -1 if the item is not included in the array.
    + 292    _.indexOf: array, item =>
    + 293      return array.indexOf(item) if array.indexOf
    + 294      i: 0; l: array.length
    + 295      while l - i
    + 296        if array[i] is item then return i else i++
    + 297      -1
    + 298 
    + 299 
    + 300    # Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
    + 301    # if possible.
    + 302    _.lastIndexOf: array, item =>
    + 303      return array.lastIndexOf(item) if array.lastIndexOf
    + 304      i: array.length
    + 305      while i
    + 306        if array[i] is item then return i else i--
    + 307      -1
    + 308 
    + 309 
    + 310    # Generate an integer Array containing an arithmetic progression. A port of
    + 311    # the native Python range() function. See:
    + 312    # http://docs.python.org/library/functions.html#range
    + 313    _.range: start, stop, step =>
    + 314      a:        _.toArray(arguments)
    + 315      solo:     a.length <= 1
    + 316      i: start: if solo then 0 else a[0];
    + 317      stop:     if solo then a[0] else a[1];
    + 318      step:     a[2] or 1
    + 319      len:      Math.ceil((stop - start) / step)
    + 320      return [] if len <= 0
    + 321      range:    new Array(len)
    + 322      idx:      0
    + 323      while true
    + 324        return range if (if step > 0 then i - stop else stop - i) >= 0
    + 325        range[idx]: i
    + 326        idx++
    + 327        i+= step
    + 328 
    + 329 
    + 330    # ----------------------- Function Functions: -----------------------------
    + 331 
    + 332    # Create a function bound to a given object (assigning 'this', and arguments,
    + 333    # optionally). Binding with arguments is also known as 'curry'.
    + 334    _.bind: func, obj =>
    + 335      args: _.rest(arguments, 2)
    + 336      => func.apply(obj or root, args.concat(_.toArray(arguments)))
    + 337 
    + 338 
    + 339    # Bind all of an object's methods to that object. Useful for ensuring that
    + 340    # all callbacks defined on an object belong to it.
    + 341    _.bindAll: obj =>
    + 342      funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
    + 343      _.each(funcs, (f => obj[f]: _.bind(obj[f], obj)))
    + 344      obj
    + 345 
    + 346 
    + 347    # Delays a function for the given number of milliseconds, and then calls
    + 348    # it with the arguments supplied.
    + 349    _.delay: func, wait =>
    + 350      args: _.rest(arguments, 2)
    + 351      setTimeout((=> func.apply(func, args)), wait)
    + 352 
    + 353 
    + 354    # Defers a function, scheduling it to run after the current call stack has
    + 355    # cleared.
    + 356    _.defer: func =>
    + 357      _.delay.apply(_, [func, 1].concat(_.rest(arguments)))
    + 358 
    + 359 
    + 360    # Returns the first function passed as an argument to the second,
    + 361    # allowing you to adjust arguments, run code before and after, and
    + 362    # conditionally execute the original function.
    + 363    _.wrap: func, wrapper =>
    + 364      => wrapper.apply(wrapper, [func].concat(_.toArray(arguments)))
    + 365 
    + 366 
    + 367    # Returns a function that is the composition of a list of functions, each
    + 368    # consuming the return value of the function that follows.
    + 369    _.compose: =>
    + 370      funcs: _.toArray(arguments)
    + 371      =>
    + 372        args: _.toArray(arguments)
    + 373        for i in [(funcs.length - 1)..0]
    + 374          args: [funcs[i].apply(this, args)]
    + 375        args[0]
    + 376 
    + 377 
    + 378    # ------------------------- Object Functions: ----------------------------
    + 379 
    + 380    # Retrieve the names of an object's properties.
    + 381    _.keys: obj =>
    + 382      return _.range(0, obj.length) if _.isArray(obj)
    + 383      key for val, key in obj
    + 384 
    + 385 
    + 386    # Retrieve the values of an object's properties.
    + 387    _.values: obj =>
    + 388      _.map(obj, _.identity)
    + 389 
    + 390 
    + 391    # Return a sorted list of the function names available in Underscore.
    + 392    _.functions: obj =>
    + 393      _.select(_.keys(obj), key => _.isFunction(obj[key])).sort()
    + 394 
    + 395 
    + 396    # Extend a given object with all of the properties in a source object.
    + 397    _.extend: destination, source =>
    + 398      for val, key in source
    + 399        destination[key]: val
    + 400      destination
    + 401 
    + 402 
    + 403    # Create a (shallow-cloned) duplicate of an object.
    + 404    _.clone: obj =>
    + 405      return obj.slice(0) if _.isArray(obj)
    + 406      _.extend({}, obj)
    + 407 
    + 408 
    + 409    # Invokes interceptor with the obj, and then returns obj.
    + 410    # The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
    + 411    _.tap: obj, interceptor =>
    + 412      interceptor(obj)
    + 413      obj
    + 414 
    + 415 
    + 416    # Perform a deep comparison to check if two objects are equal.
    + 417    _.isEqual: a, b =>
    + 418      # Check object identity.
    + 419      return true if a is b
    + 420      # Different types?
    + 421      atype: typeof(a); btype: typeof(b)
    + 422      return false if atype isnt btype
    + 423      # Basic equality test (watch out for coercions).
    + 424      return true if `a == b`
    + 425      # One is falsy and the other truthy.
    + 426      return false if (!a and b) or (a and !b)
    + 427      # One of them implements an isEqual()?
    + 428      return a.isEqual(b) if a.isEqual
    + 429      # Check dates' integer values.
    + 430      return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b)
    + 431      # Both are NaN?
    + 432      return true if _.isNaN(a) and _.isNaN(b)
    + 433      # Compare regular expressions.
    + 434      if _.isRegExp(a) and _.isRegExp(b)
    + 435        return a.source     is b.source and
    + 436               a.global     is b.global and
    + 437               a.ignoreCase is b.ignoreCase and
    + 438               a.multiline  is b.multiline
    + 439      # If a is not an object by this point, we can't handle it.
    + 440      return false if atype isnt 'object'
    + 441      # Check for different array lengths before comparing contents.
    + 442      return false if a.length and (a.length isnt b.length)
    + 443      # Nothing else worked, deep compare the contents.
    + 444      aKeys: _.keys(a); bKeys: _.keys(b)
    + 445      # Different object sizes?
    + 446      return false if aKeys.length isnt bKeys.length
    + 447      # Recursive comparison of contents.
    + 448      # for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
    + 449      return true
    + 450 
    + 451 
    + 452    # Is a given array or object empty?
    + 453    _.isEmpty:      obj => _.keys(obj).length is 0
    + 454 
    + 455 
    + 456    # Is a given value a DOM element?
    + 457    _.isElement:    obj => obj and obj.nodeType is 1
    + 458 
    + 459 
    + 460    # Is a given value an array?
    + 461    _.isArray:      obj => !!(obj and obj.concat and obj.unshift)
    + 462 
    + 463 
    + 464    # Is a given variable an arguments object?
    + 465    _.isArguments:  obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length')
    + 466 
    + 467 
    + 468    # Is the given value a function?
    + 469    _.isFunction:   obj => !!(obj and obj.constructor and obj.call and obj.apply)
    + 470 
    + 471 
    + 472    # Is the given value a string?
    + 473    _.isString:     obj => !!(obj is '' or (obj and obj.charCodeAt and obj.substr))
    + 474 
    + 475 
    + 476    # Is a given value a number?
    + 477    _.isNumber:     obj => toString.call(obj) is '[object Number]'
    + 478 
    + 479 
    + 480    # Is a given value a Date?
    + 481    _.isDate:       obj => !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear)
    + 482 
    + 483 
    + 484    # Is the given value a regular expression?
    + 485    _.isRegExp:     obj => !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false))
    + 486 
    + 487 
    + 488    # Is the given value NaN -- this one is interesting. NaN != NaN, and
    + 489    # isNaN(undefined) == true, so we make sure it's a number first.
    + 490    _.isNaN:        obj => _.isNumber(obj) and window.isNaN(obj)
    + 491 
    + 492 
    + 493    # Is a given value equal to null?
    + 494    _.isNull:       obj => obj is null
    + 495 
    + 496 
    + 497    # Is a given variable undefined?
    + 498    _.isUndefined:  obj => typeof obj is 'undefined'
    + 499 
    + 500 
    + 501    # -------------------------- Utility Functions: --------------------------
    + 502 
    + 503    # Run Underscore.js in noConflict mode, returning the '_' variable to its
    + 504    # previous owner. Returns a reference to the Underscore object.
    + 505    _.noConflict: =>
    + 506      root._: previousUnderscore
    + 507      this
    + 508 
    + 509 
    + 510    # Keep the identity function around for default iterators.
    + 511    _.identity: value => value
    + 512 
    + 513 
    + 514    # Break out of the middle of an iteration.
    + 515    _.breakLoop: => throw breaker
    + 516 
    + 517 
    + 518    # Generate a unique integer id (unique within the entire client session).
    + 519    # Useful for temporary DOM ids.
    + 520    idCounter: 0
    + 521    _.uniqueId: prefix =>
    + 522      (prefix or '') + idCounter++
    + 523 
    + 524 
    + 525    # JavaScript templating a-la ERB, pilfered from John Resig's
    + 526    # "Secrets of the JavaScript Ninja", page 83.
    + 527    _.template: str, data =>
    + 528      `var fn = new Function('obj',
    + 529        'var p=[],print=function(){p.push.apply(p,arguments);};' +
    + 530        'with(obj){p.push(\'' +
    + 531        str.
    + 532          replace(/[\r\t\n]/g, " ").
    + 533          split("<%").join("\t").
    + 534          replace(/((^|%>)[^\t]*)'/g, "$1\r").
    + 535          replace(/\t=(.*?)%>/g, "',$1,'").
    + 536          split("\t").join("');").
    + 537          split("%>").join("p.push('").
    + 538          split("\r").join("\\'") +
    + 539        "');}return p.join('');")`
    + 540      if data then fn(data) else fn
    + 541 
    + 542 
    + 543    # ------------------------------- Aliases ----------------------------------
    + 544 
    + 545    _.forEach: _.each
    + 546    _.foldl:   _.inject:      _.reduce
    + 547    _.foldr:   _.reduceRight
    + 548    _.filter:  _.select
    + 549    _.every:   _.all
    + 550    _.some:    _.any
    + 551    _.head:    _.first
    + 552    _.tail:    _.rest
    + 553    _.methods: _.functions
    + 554 
    + 555 
    + 556    #   /*------------------------ Setup the OOP Wrapper: --------------------------*/
    + 557 
    + 558    # Helper function to continue chaining intermediate results.
    + 559    result: obj, chain =>
    + 560      if chain then _(obj).chain() else obj
    + 561 
    + 562 
    + 563    # Add all of the Underscore functions to the wrapper object.
    + 564    _.each(_.functions(_)) name =>
    + 565      method: _[name]
    + 566      wrapper.prototype[name]: =>
    + 567        unshift.call(arguments, this._wrapped)
    + 568        result(method.apply(_, arguments), this._chain)
    + 569 
    + 570 
    + 571    # Add all mutator Array functions to the wrapper.
    + 572    _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']) name =>
    + 573      method: Array.prototype[name]
    + 574      wrapper.prototype[name]: =>
    + 575        method.apply(this._wrapped, arguments)
    + 576        result(this._wrapped, this._chain)
    + 577 
    + 578 
    + 579    # Add all accessor Array functions to the wrapper.
    + 580    _.each(['concat', 'join', 'slice']) name =>
    + 581      method: Array.prototype[name]
    + 582      wrapper.prototype[name]: =>
    + 583        result(method.apply(this._wrapped, arguments), this._chain)
    + 584 
    + 585 
    + 586    # Start chaining a wrapped Underscore object.
    + 587    wrapper.prototype.chain: =>
    + 588      this._chain: true
    + 589      this
    + 590 
    + 591 
    + 592    # Extracts the result from a wrapped and chained object.
    + 593    wrapper.prototype.value: => this._wrapped
    +
    + + diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 0ea0d5fb3b..71e56d0362 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -1,517 +1,593 @@ -# Underscore.js -# (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. -# Underscore is freely distributable under the terms of the MIT license. -# Portions of Underscore are inspired by or borrowed from Prototype.js, -# Oliver Steele's Functional, and John Resig's Micro-Templating. -# For all details and documentation: -# http://documentcloud.github.com/underscore/ - -# ------------------------- Baseline setup --------------------------------- - -# Establish the root object, "window" in the browser, or "global" on the server. -root: this - -# Save the previous value of the "_" variable. -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 - this - -# Establish the object that gets thrown to break out of a loop iteration. -breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration - -# Create a safe reference to the Underscore object for reference below. -_: root._: obj => new wrapper(obj) - -# Export the Underscore object for CommonJS. -if typeof(exports) != 'undefined' then exports._: _ - -# Create quick reference variables for speed access to core prototypes. -slice: Array.prototype.slice -unshift: Array.prototype.unshift -toString: Object.prototype.toString -hasOwnProperty: Object.prototype.hasOwnProperty -propertyIsEnumerable: Object.prototype.propertyIsEnumerable - -# Current version. -_.VERSION: '0.5.2' - -# ------------------------ Collection Functions: --------------------------- - -# The cornerstone, an each implementation. -# Handles objects implementing forEach, arrays, and raw objects. -_.each: obj, iterator, context => - index: 0 - try - 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 - catch e - throw e if e isnt breaker - obj - -# Return the results of applying the iterator to each element. Use JavaScript -# 1.6's version of map, if possible. -_.map: obj, iterator, context => - return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) - results: [] - _.each(obj) value, index, list => - results.push(iterator.call(context, value, index, list)) - results - -# Reduce builds up a single result from a list of values. Also known as -# inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. -_.reduce: obj, memo, iterator, context => - return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce)) - _.each(obj) value, index, list => - memo: iterator.call(context, memo, value, index, list) - memo - -# The right-associative version of reduce, also known as foldr. Uses -# JavaScript 1.8's version of reduceRight, if available. -_.reduceRight: obj, memo, iterator, context => - return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight)) - _.each(_.clone(_.toArray(obj)).reverse()) value, index => - memo: iterator.call(context, memo, value, index, obj) - memo - -# Return the first value which passes a truth test. -_.detect: obj, iterator, context => - result: null - _.each(obj) value, index, list => - if iterator.call(context, value, index, list) - result: value - _.breakLoop() - result - -# Return all the elements that pass a truth test. Use JavaScript 1.6's -# filter(), if it exists. -_.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) - 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) - results - -# Determine whether all of the elements match a truth test. Delegate to -# JavaScript 1.6's every(), if it is present. -_.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)) - result - -# Determine if at least one element in the object matches a truth test. Use -# JavaScript 1.6's some(), if it exists. -_.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)) - result - -# Determine if a given value is included in the array or object, -# based on '==='. -_.include: obj, target => - return _.indexOf(obj, target) isnt -1 if _.isArray(obj) - for val in obj - return true if val is target - false - -# Invoke a method with arguments on every item in a collection. -_.invoke: obj, method => - args: _.rest(arguments, 2) - (if method then val[method] else val).apply(val, args) for val in obj - -# Convenience version of a common use case of map: fetching a property. -_.pluck: obj, key => - _.map(obj, (val => val[key])) - -# Return the maximum item or (item-based computation). -_.max: obj, iterator, context => - return Math.max.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 - -# 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 => - return [] if (!iterable) - return iterable.toArray() if (iterable.toArray) - return iterable if (_.isArray(iterable)) - return slice.call(iterable) if (_.isArguments(iterable)) - _.values(iterable) - -# Return the number of elements in an object. -_.size: obj => _.toArray(obj).length - -# -------------------------- Array Functions: ------------------------------ - -# Get the first element of an array. Passing "n" will return the first N -# values in the array. Aliased as "head". The "guard" check allows it to work -# with _.map. -_.first: array, n, guard => - if n and not guard then slice.call(array, 0, n) else array[0] - -# Returns everything but the first entry of the array. Aliased as "tail". -# Especially useful on the arguments object. Passing an "index" will return -# the rest of the values in the array from that index onward. The "guard" -# check allows it to work with _.map. -_.rest: array, index, guard => - slice.call(array, if _.isUndefined(index) or guard then 1 else index) - -# Get the last element of an array. -_.last: array => array[array.length - 1] - -# Trim out all falsy values from an array. -_.compact: array => array[i] for i in [0...array.length] when array[i] - -# Return a completely flattened version of an array. -_.flatten: array => - _.reduce(array, []) memo, value => - return memo.concat(_.flatten(value)) if _.isArray(value) - memo.push(value) + + # Underscore.coffee + # (c) 2009 Jeremy Ashkenas, DocumentCloud Inc. + # Underscore is freely distributable under the terms of the MIT license. + # Portions of Underscore are inspired by or borrowed from Prototype.js, + # Oliver Steele's Functional, and John Resig's Micro-Templating. + # For all details and documentation: + # http://documentcloud.github.com/underscore/ + + + # ------------------------- Baseline setup --------------------------------- + + # Establish the root object, "window" in the browser, or "global" on the server. + root: this + + + # Save the previous value of the "_" variable. + 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 + this + + + # Establish the object that gets thrown to break out of a loop iteration. + breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration + + + # Create a safe reference to the Underscore object for reference below. + _: root._: obj => new wrapper(obj) + + + # Export the Underscore object for CommonJS. + if typeof(exports) != 'undefined' then exports._: _ + + + # Create quick reference variables for speed access to core prototypes. + slice: Array.prototype.slice + unshift: Array.prototype.unshift + toString: Object.prototype.toString + hasOwnProperty: Object.prototype.hasOwnProperty + propertyIsEnumerable: Object.prototype.propertyIsEnumerable + + + # Current version. + _.VERSION: '0.5.3' + + + # ------------------------ Collection Functions: --------------------------- + + # The cornerstone, an each implementation. + # Handles objects implementing forEach, arrays, and raw objects. + _.each: obj, iterator, context => + index: 0 + try + 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 + catch e + throw e if e isnt breaker + obj + + + # Return the results of applying the iterator to each element. Use JavaScript + # 1.6's version of map, if possible. + _.map: obj, iterator, context => + return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) + results: [] + _.each(obj) value, index, list => + results.push(iterator.call(context, value, index, list)) + results + + + # Reduce builds up a single result from a list of values. Also known as + # inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. + _.reduce: obj, memo, iterator, context => + return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce)) + _.each(obj) value, index, list => + memo: iterator.call(context, memo, value, index, list) + memo + + + # The right-associative version of reduce, also known as foldr. Uses + # JavaScript 1.8's version of reduceRight, if available. + _.reduceRight: obj, memo, iterator, context => + return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight)) + _.each(_.clone(_.toArray(obj)).reverse()) value, index => + memo: iterator.call(context, memo, value, index, obj) + memo + + + # Return the first value which passes a truth test. + _.detect: obj, iterator, context => + result: null + _.each(obj) value, index, list => + if iterator.call(context, value, index, list) + result: value + _.breakLoop() + result + + + # Return all the elements that pass a truth test. Use JavaScript 1.6's + # filter(), if it exists. + _.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) + 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) + results + + + # Determine whether all of the elements match a truth test. Delegate to + # JavaScript 1.6's every(), if it is present. + _.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)) + result + + + # Determine if at least one element in the object matches a truth test. Use + # JavaScript 1.6's some(), if it exists. + _.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)) + result + + + # Determine if a given value is included in the array or object, + # based on '==='. + _.include: obj, target => + return _.indexOf(obj, target) isnt -1 if _.isArray(obj) + for val in obj + return true if val is target + false + + + # Invoke a method with arguments on every item in a collection. + _.invoke: obj, method => + args: _.rest(arguments, 2) + (if method then val[method] else val).apply(val, args) for val in obj + + + # Convenience version of a common use case of map: fetching a property. + _.pluck: obj, key => + _.map(obj, (val => val[key])) + + + # Return the maximum item or (item-based computation). + _.max: obj, iterator, context => + return Math.max.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 + + + # 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 => + return [] if (!iterable) + return iterable.toArray() if (iterable.toArray) + return iterable if (_.isArray(iterable)) + return slice.call(iterable) if (_.isArguments(iterable)) + _.values(iterable) + + + # Return the number of elements in an object. + _.size: obj => _.toArray(obj).length + + + # -------------------------- Array Functions: ------------------------------ + + # Get the first element of an array. Passing "n" will return the first N + # values in the array. Aliased as "head". The "guard" check allows it to work + # with _.map. + _.first: array, n, guard => + if n and not guard then slice.call(array, 0, n) else array[0] + + + # Returns everything but the first entry of the array. Aliased as "tail". + # Especially useful on the arguments object. Passing an "index" will return + # the rest of the values in the array from that index onward. The "guard" + # check allows it to work with _.map. + _.rest: array, index, guard => + slice.call(array, if _.isUndefined(index) or guard then 1 else index) + + + # Get the last element of an array. + _.last: array => array[array.length - 1] + + + # Trim out all falsy values from an array. + _.compact: array => array[i] for i in [0...array.length] when array[i] + + + # Return a completely flattened version of an array. + _.flatten: array => + _.reduce(array, []) memo, value => + return memo.concat(_.flatten(value)) if _.isArray(value) + memo.push(value) + memo + + + # Return a version of the array that does not contain the specified value(s). + _.without: array => + values: _.rest(arguments) + val for val in _.toArray(array) when not _.include(values, val) + + + # Produce a duplicate-free version of the array. If the array has already + # been sorted, you have the option of using a faster algorithm. + _.uniq: array, isSorted => + memo: [] + for el, i in _.toArray(array) + memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el)) memo -# Return a version of the array that does not contain the specified value(s). -_.without: array => - values: _.rest(arguments) - val for val in _.toArray(array) when not _.include(values, val) - -# Produce a duplicate-free version of the array. If the array has already -# been sorted, you have the option of using a faster algorithm. -_.uniq: array, isSorted => - memo: [] - for el, i in _.toArray(array) - memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el)) - memo - -# 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) - for i in [0...length] - results[i]: _.pluck(args, String(i)) - 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 - range[idx]: i - idx++ - i+= step - -# ----------------------- Function Functions: ----------------------------- - -# Create a function bound to a given object (assigning 'this', and arguments, -# optionally). Binding with arguments is also known as 'curry'. -_.bind: func, obj => - args: _.rest(arguments, 2) - => func.apply(obj or root, args.concat(_.toArray(arguments))) - -# Bind all of an object's methods to that object. Useful for ensuring that -# all callbacks defined on an object belong to it. -_.bindAll: obj => - funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj) - _.each(funcs, (f => obj[f]: _.bind(obj[f], obj))) - obj - -# Delays a function for the given number of milliseconds, and then calls -# it with the arguments supplied. -_.delay: func, wait => - args: _.rest(arguments, 2) - setTimeout((=> func.apply(func, args)), wait) - -# Defers a function, scheduling it to run after the current call stack has -# cleared. -_.defer: func => - _.delay.apply(_, [func, 1].concat(_.rest(arguments))) - -# Returns the first function passed as an argument to the second, -# allowing you to adjust arguments, run code before and after, and -# conditionally execute the original function. -_.wrap: func, wrapper => - => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))) - -# Returns a function that is the composition of a list of functions, each -# consuming the return value of the function that follows. -_.compose: => - funcs: _.toArray(arguments) - => - args: _.toArray(arguments) - for i in [(funcs.length - 1)..0] - args: [funcs[i].apply(this, args)] - args[0] - -# ------------------------- Object Functions: ---------------------------- - -# Retrieve the names of an object's properties. -_.keys: obj => - return _.range(0, obj.length) if _.isArray(obj) - key for val, key in obj - -# Retrieve the values of an object's properties. -_.values: obj => - _.map(obj, _.identity) - -# Return a sorted list of the function names available in Underscore. -_.functions: obj => - _.select(_.keys(obj), key => _.isFunction(obj[key])).sort() - -# Extend a given object with all of the properties in a source object. -_.extend: destination, source => - for val, key in source - destination[key]: val - destination - -# Create a (shallow-cloned) duplicate of an object. -_.clone: obj => - 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. - return true if a is b - # Different types? - atype: typeof(a); btype: typeof(b) - return false if atype isnt btype - # Basic equality test (watch out for coercions). - return true if `a == b` - # One is falsy and the other truthy. - return false if (!a and b) or (a and !b) - # One of them implements an isEqual()? - return a.isEqual(b) if a.isEqual - # Check dates' integer values. - return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b) - # Both are NaN? - return true if _.isNaN(a) and _.isNaN(b) - # Compare regular expressions. - if _.isRegExp(a) and _.isRegExp(b) - return a.source is b.source and - a.global is b.global and - a.ignoreCase is b.ignoreCase and - a.multiline is b.multiline - # If a is not an object by this point, we can't handle it. - return false if atype isnt 'object' - # Check for different array lengths before comparing contents. - return false if a.length and (a.length isnt b.length) - # Nothing else worked, deep compare the contents. - aKeys: _.keys(a); bKeys: _.keys(b) - # Different object sizes? - return false if aKeys.length isnt bKeys.length - # Recursive comparison of contents. - # for (var key in a) if (!_.isEqual(a[key], b[key])) return false; - return true - -# Is a given array or object empty? -_.isEmpty: obj => _.keys(obj).length is 0 - -# Is a given value a DOM element? -_.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')) - -# 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 window.isNaN(obj)) - -# Is a given value equal to null? -_.isNull: obj => obj is null - -# Is a given variable undefined? -_.isUndefined: obj => typeof obj is 'undefined' - -# -------------------------- Utility Functions: -------------------------- - -# Run Underscore.js in noConflict mode, returning the '_' variable to its -# previous owner. Returns a reference to the Underscore object. -_.noConflict: => - root._: previousUnderscore - this - -# Keep the identity function around for default iterators. -_.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. -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 ---------------------------------- - -_.forEach: _.each -_.foldl: _.inject: _.reduce -_.foldr: _.reduceRight -_.filter: _.select -_.every: _.all -_.some: _.any -_.head: _.first -_.tail: _.rest -_.methods: _.functions - -# /*------------------------ Setup the OOP Wrapper: --------------------------*/ - -# Helper function to continue chaining intermediate results. -result: obj, chain => - if chain then _(obj).chain() else obj - -# Add all of the Underscore functions to the wrapper object. -_.each(_.functions(_)) 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 => - method: Array.prototype[name] - wrapper.prototype[name]: => - result(method.apply(this._wrapped, arguments), this._chain) - -# Start chaining a wrapped Underscore object. -wrapper.prototype.chain: => - this._chain: true - this - -# Extracts the result from a wrapped and chained object. -wrapper.prototype.value: => this._wrapped + + # 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) + for i in [0...length] + results[i]: _.pluck(args, String(i)) + 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 + range[idx]: i + idx++ + i+= step + + + # ----------------------- Function Functions: ----------------------------- + + # Create a function bound to a given object (assigning 'this', and arguments, + # optionally). Binding with arguments is also known as 'curry'. + _.bind: func, obj => + args: _.rest(arguments, 2) + => func.apply(obj or root, args.concat(_.toArray(arguments))) + + + # Bind all of an object's methods to that object. Useful for ensuring that + # all callbacks defined on an object belong to it. + _.bindAll: obj => + funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj) + _.each(funcs, (f => obj[f]: _.bind(obj[f], obj))) + obj + + + # Delays a function for the given number of milliseconds, and then calls + # it with the arguments supplied. + _.delay: func, wait => + args: _.rest(arguments, 2) + setTimeout((=> func.apply(func, args)), wait) + + + # Defers a function, scheduling it to run after the current call stack has + # cleared. + _.defer: func => + _.delay.apply(_, [func, 1].concat(_.rest(arguments))) + + + # Returns the first function passed as an argument to the second, + # allowing you to adjust arguments, run code before and after, and + # conditionally execute the original function. + _.wrap: func, wrapper => + => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))) + + + # Returns a function that is the composition of a list of functions, each + # consuming the return value of the function that follows. + _.compose: => + funcs: _.toArray(arguments) + => + args: _.toArray(arguments) + for i in [(funcs.length - 1)..0] + args: [funcs[i].apply(this, args)] + args[0] + + + # ------------------------- Object Functions: ---------------------------- + + # Retrieve the names of an object's properties. + _.keys: obj => + return _.range(0, obj.length) if _.isArray(obj) + key for val, key in obj + + + # Retrieve the values of an object's properties. + _.values: obj => + _.map(obj, _.identity) + + + # Return a sorted list of the function names available in Underscore. + _.functions: obj => + _.select(_.keys(obj), key => _.isFunction(obj[key])).sort() + + + # Extend a given object with all of the properties in a source object. + _.extend: destination, source => + for val, key in source + destination[key]: val + destination + + + # Create a (shallow-cloned) duplicate of an object. + _.clone: obj => + 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. + return true if a is b + # Different types? + atype: typeof(a); btype: typeof(b) + return false if atype isnt btype + # Basic equality test (watch out for coercions). + return true if `a == b` + # One is falsy and the other truthy. + return false if (!a and b) or (a and !b) + # One of them implements an isEqual()? + return a.isEqual(b) if a.isEqual + # Check dates' integer values. + return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b) + # Both are NaN? + return true if _.isNaN(a) and _.isNaN(b) + # Compare regular expressions. + if _.isRegExp(a) and _.isRegExp(b) + return a.source is b.source and + a.global is b.global and + a.ignoreCase is b.ignoreCase and + a.multiline is b.multiline + # If a is not an object by this point, we can't handle it. + return false if atype isnt 'object' + # Check for different array lengths before comparing contents. + return false if a.length and (a.length isnt b.length) + # Nothing else worked, deep compare the contents. + aKeys: _.keys(a); bKeys: _.keys(b) + # Different object sizes? + return false if aKeys.length isnt bKeys.length + # Recursive comparison of contents. + # for (var key in a) if (!_.isEqual(a[key], b[key])) return false; + return true + + + # Is a given array or object empty? + _.isEmpty: obj => _.keys(obj).length is 0 + + + # Is a given value a DOM element? + _.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') + + + # 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 window.isNaN(obj) + + + # Is a given value equal to null? + _.isNull: obj => obj is null + + + # Is a given variable undefined? + _.isUndefined: obj => typeof obj is 'undefined' + + + # -------------------------- Utility Functions: -------------------------- + + # Run Underscore.js in noConflict mode, returning the '_' variable to its + # previous owner. Returns a reference to the Underscore object. + _.noConflict: => + root._: previousUnderscore + this + + + # Keep the identity function around for default iterators. + _.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. + 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 ---------------------------------- + + _.forEach: _.each + _.foldl: _.inject: _.reduce + _.foldr: _.reduceRight + _.filter: _.select + _.every: _.all + _.some: _.any + _.head: _.first + _.tail: _.rest + _.methods: _.functions + + + # /*------------------------ Setup the OOP Wrapper: --------------------------*/ + + # Helper function to continue chaining intermediate results. + result: obj, chain => + if chain then _(obj).chain() else obj + + + # Add all of the Underscore functions to the wrapper object. + _.each(_.functions(_)) 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 => + method: Array.prototype[name] + wrapper.prototype[name]: => + result(method.apply(this._wrapped, arguments), this._chain) + + + # Start chaining a wrapped Underscore object. + wrapper.prototype.chain: => + this._chain: true + this + + + # Extracts the result from a wrapped and chained object. + wrapper.prototype.value: => this._wrapped diff --git a/index.html b/index.html index 11397c73af..a64d1be71f 100644 --- a/index.html +++ b/index.html @@ -54,11 +54,11 @@

    Table of Contents

    Lexical Scoping and Variable Safety
    Conditionals, Ternaries, and Conditional Assignment
    The Existence Operator
    - Everything is an Expression
    Aliases
    While Loops
    Comprehensions (Arrays, Objects, and Ranges)
    Array Slicing and Splicing with Ranges
    + Everything is an Expression
    Inheritance, and Calling Super from a Subclass
    Embedded JavaScript
    Switch/When/Else
    @@ -166,6 +166,13 @@

    Mini Overview

    return __c; })(); ;alert(cubed_list);'>run: cubed_list
    + +

    + For a longer CoffeeScript example, check out + Underscore.coffee, a port + of Underscore.js + to CoffeeScript, which, when compiled, passes the complete Underscore test suite. +

    Installation and Usage

    @@ -484,121 +491,6 @@

    Language Reference

    }
    -

    - Everything is an Expression (at least, as much as possible) - You might have noticed how even though we don't add return statements - to CoffeeScript functions, they nonetheless return their final value. - The CoffeeScript compiler tries to make sure that all statements in the - language can be used as expressions. Watch how the return gets - pushed down into each possible branch of execution, in the function - below. -

    -
    grade: student =>
    -  if student.excellent_work
    -    "A+"
    -  else if student.okay_stuff
    -    if student.tried_hard then "B" else "B-"
    -  else
    -    "C"
    -
    -eldest: if 24 > 21 then "Liz" else "Ike"
    -
    var eldest, grade;
    -grade = function grade(student) {
    -  if (student.excellent_work) {
    -    return "A+";
    -  } else if (student.okay_stuff) {
    -    return student.tried_hard ? "B" : "B-";
    -  } else {
    -    return "C";
    -  }
    -};
    -eldest = 24 > 21 ? "Liz" : "Ike";
    -

    -

    - Because variable declarations occur at the top of scope, assignment can - be used within expressions, even for variables that haven't been seen before: -

    -
    six: (one: 1) + (two: 2) + (three: 3)
    -
    var one, six, three, two;
    -six = (one = 1) + (two = 2) + (three = 3);
    -

    -

    - Things that would otherwise be statements in JavaScript, when used - as part of an expression in CoffeeScript, are converted into expressions - by wrapping them in a closure. This lets you do useful things, like assign - the result of a comprehension to a variable: -

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

    -

    - As well as silly things, like passing a try/catch statement directly - into a function call: -

    -
    alert(
    -  try
    -    nonexistent / undefined
    -  catch error
    -    "The error is: " + error
    -)
    -
    alert((function() {
    -  try {
    -    return nonexistent / undefined;
    -  } catch (error) {
    -    return "The error is: " + error;
    -  }
    -})());
    -

    -

    Aliases Because the == operator frequently causes undesirable coercion, @@ -821,6 +713,121 @@

    Language Reference

    numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6])); ;alert(numbers);'>run: numbers
    + +

    + Everything is an Expression (at least, as much as possible) + You might have noticed how even though we don't add return statements + to CoffeeScript functions, they nonetheless return their final value. + The CoffeeScript compiler tries to make sure that all statements in the + language can be used as expressions. Watch how the return gets + pushed down into each possible branch of execution, in the function + below. +

    +
    grade: student =>
    +  if student.excellent_work
    +    "A+"
    +  else if student.okay_stuff
    +    if student.tried_hard then "B" else "B-"
    +  else
    +    "C"
    +
    +eldest: if 24 > 21 then "Liz" else "Ike"
    +
    var eldest, grade;
    +grade = function grade(student) {
    +  if (student.excellent_work) {
    +    return "A+";
    +  } else if (student.okay_stuff) {
    +    return student.tried_hard ? "B" : "B-";
    +  } else {
    +    return "C";
    +  }
    +};
    +eldest = 24 > 21 ? "Liz" : "Ike";
    +

    +

    + Because variable declarations occur at the top of scope, assignment can + be used within expressions, even for variables that haven't been seen before: +

    +
    six: (one: 1) + (two: 2) + (three: 3)
    +
    var one, six, three, two;
    +six = (one = 1) + (two = 2) + (three = 3);
    +

    +

    + Things that would otherwise be statements in JavaScript, when used + as part of an expression in CoffeeScript, are converted into expressions + by wrapping them in a closure. This lets you do useful things, like assign + the result of a comprehension to a variable: +

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

    +

    + As well as silly things, like passing a try/catch statement directly + into a function call: +

    +
    alert(
    +  try
    +    nonexistent / undefined
    +  catch error
    +    "The error is: " + error
    +)
    +
    alert((function() {
    +  try {
    +    return nonexistent / undefined;
    +  } catch (error) {
    +    return "The error is: " + error;
    +  }
    +})());
    +

    Inheritance, and Calling Super from a Subclass diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 541c932e61..105ac1100a 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -47,6 +47,7 @@ rule /* nothing */ { result = Expressions.new } | Terminator { result = Expressions.new } | Expressions { result = val[0] } + | Block Terminator { result = val[0] } ; # Any list of expressions or method body, seperated by line breaks or semis. diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index d357264fc7..f67cddbd10 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -227,7 +227,8 @@ def tag_parameters end end - # Close up all remaining open blocks. + # Close up all remaining open blocks. IF the first token is an indent, + # axe it. def close_indentation outdent_token(@indent) end diff --git a/lib/coffee_script/parse_error.rb b/lib/coffee_script/parse_error.rb index 52072c1cd0..3415ed1207 100644 --- a/lib/coffee_script/parse_error.rb +++ b/lib/coffee_script/parse_error.rb @@ -12,8 +12,8 @@ def initialize(token_id, value, stack) def message line = @value.respond_to?(:line) ? @value.line : "END" line_part = "line #{line}:" - id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.downcase}" : "" - val_part = ['INDENT', 'OUTDENT'].include?(@token_id) ? '' : " for '#{@value.to_s}'" + id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.to_s.downcase}" : "" + val_part = ['INDENT', 'OUTDENT'].include?(@token_id) ? '' : " for '#{@value.to_s}'" "#{line_part} syntax error#{val_part}#{id_part}" end alias_method :inspect, :message From 0cf7801f36f967b8d570d29a6f64e4db62ad6fa6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 4 Jan 2010 23:26:27 -0500 Subject: [PATCH 259/303] more docs for 0.2 -- blocks and splats --- documentation/coffee/blocks.coffee | 4 ++ documentation/coffee/splats.coffee | 25 +++++++ documentation/index.html.erb | 40 +++++++++-- documentation/js/blocks.js | 8 +++ documentation/js/splats.js | 16 +++++ index.html | 103 +++++++++++++++++++++++++++-- 6 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 documentation/coffee/blocks.coffee create mode 100644 documentation/coffee/splats.coffee create mode 100644 documentation/js/blocks.js create mode 100644 documentation/js/splats.js diff --git a/documentation/coffee/blocks.coffee b/documentation/coffee/blocks.coffee new file mode 100644 index 0000000000..6f31ee164c --- /dev/null +++ b/documentation/coffee/blocks.coffee @@ -0,0 +1,4 @@ +$('table.list').each() table => + $('tr.account', table).each() row => + row.show() + row.highlight() diff --git a/documentation/coffee/splats.coffee b/documentation/coffee/splats.coffee new file mode 100644 index 0000000000..de02d2428f --- /dev/null +++ b/documentation/coffee/splats.coffee @@ -0,0 +1,25 @@ +gold: silver: the_field: "unknown" + +medalists: first, second, *rest => + gold: first + silver: second + the_field: rest + +contenders: [ + "Michael Phelps" + "Liu Xiang" + "Yao Ming" + "Allyson Felix" + "Shawn Johnson" + "Roman Sebrle" + "Guo Jingjing" + "Tyson Gay" + "Asafa Powell" + "Usain Bolt" +] + +medalists(*contenders) + +alert("Gold: " + gold) +alert("Silver: " + silver) +alert("The Field: " + the_field) \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 5cb307cdce..d520428f66 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -82,11 +82,13 @@ Conditionals, Ternaries, and Conditional Assignment
    The Existence Operator
    Aliases
    + Splats
    While Loops
    Comprehensions (Arrays, Objects, and Ranges)
    Array Slicing and Splicing with Ranges
    Everything is an Expression
    Inheritance, and Calling Super from a Subclass
    + Blocks
    Embedded JavaScript
    Switch/When/Else
    Try/Catch/Finally
    @@ -101,9 +103,9 @@

    CoffeeScript on the left, compiled JavaScript output on the right.

    <%= code_for('overview', 'cubed_list') %> - +

    - For a longer CoffeeScript example, check out + For a longer CoffeeScript example, check out Underscore.coffee, a port of Underscore.js to CoffeeScript, which, when compiled, passes the complete Underscore test suite. @@ -141,7 +143,7 @@ gem install coffee-script

  • @@ -177,7 +179,7 @@ gem install coffee-script @@ -248,7 +250,8 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Functions and Invocation Functions are defined by a list of parameters, an arrow, and the - function body. The empty function looks like this: => + function body. The empty function looks like this: =>. All + functions in CoffeeScript are named, for the benefit of debug messages.

    <%= code_for('functions', 'cube(5)') %> @@ -261,7 +264,7 @@ coffee --print app/scripts/*.coffee > concatenation.js <%= code_for('assignment', 'greeting') %>

    Declarations of new variables are pushed up to the top of the nearest - lexical scope, so that assignment may always be used within expressions. + lexical scope, so that assignment may always be performed within expressions.

    @@ -358,6 +361,15 @@ coffee --print app/scripts/*.coffee > concatenation.js

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

    + Splats + The JavaScript arguments object is a useful way to work with + functions that accept variable numbers of arguments. CoffeeScript provides + splats *, both for function definition as well as invocation, + making variable arguments a little bit more palatable. +

    + <%= code_for('splats', true) %> +

    While Loops The only low-level loop that CoffeeScript provides is the while loop. @@ -407,7 +419,7 @@ coffee --print app/scripts/*.coffee > concatenation.js array with new values (to splice it).

    <%= code_for('splices', 'numbers') %> - +

    Everything is an Expression (at least, as much as possible) You might have noticed how even though we don't add return statements @@ -455,6 +467,15 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('super', true) %> +

    + Blocks + Many common looping functions (in Prototype, jQuery, and Underscore, + for example) take a single function as their final argument. To functions + easier to pass, CoffeeScript includes Ruby-style block syntax, so you don't + have to close the parentheses on the other side. +

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

    Embedded JavaScript If you ever need to interpolate literal JavaScript snippets, you can @@ -499,6 +520,11 @@ coffee --print app/scripts/*.coffee > concatenation.js

  • Bugs and Feature Requests
  • +
  • + BistroCar
    + A Rails plugin by Jonas Nicklas that includes CoffeeScript helpers, + bundling and minification. +
  • Contributing

    diff --git a/documentation/js/blocks.js b/documentation/js/blocks.js new file mode 100644 index 0000000000..f95dd31b45 --- /dev/null +++ b/documentation/js/blocks.js @@ -0,0 +1,8 @@ +(function(){ + $('table.list').each(function(table) { + return $('tr.account', table).each(function(row) { + row.show(); + return row.highlight(); + }); + }); +})(); \ No newline at end of file diff --git a/documentation/js/splats.js b/documentation/js/splats.js new file mode 100644 index 0000000000..31f25a791d --- /dev/null +++ b/documentation/js/splats.js @@ -0,0 +1,16 @@ +(function(){ + var contenders, gold, medalists, silver, the_field; + gold = silver = the_field = "unknown"; + medalists = function medalists(first, second) { + var rest; + rest = Array.prototype.slice.call(arguments, 2); + gold = first; + silver = second; + return the_field = rest; + }; + contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Allyson Felix", "Shawn Johnson", "Roman Sebrle", "Guo Jingjing", "Tyson Gay", "Asafa Powell", "Usain Bolt"]; + medalists.apply(this, contenders); + alert("Gold: " + gold); + alert("Silver: " + silver); + alert("The Field: " + the_field); +})(); \ No newline at end of file diff --git a/index.html b/index.html index a64d1be71f..044c1a4fc2 100644 --- a/index.html +++ b/index.html @@ -55,11 +55,13 @@

    Table of Contents

    Conditionals, Ternaries, and Conditional Assignment
    The Existence Operator
    Aliases
    + Splats
    While Loops
    Comprehensions (Arrays, Objects, and Ranges)
    Array Slicing and Splicing with Ranges
    Everything is an Expression
    Inheritance, and Calling Super from a Subclass
    + Blocks
    Embedded JavaScript
    Switch/When/Else
    Try/Catch/Finally
    @@ -166,9 +168,9 @@

    Mini Overview

    return __c; })(); ;alert(cubed_list);'>run: cubed_list
    - +

    - For a longer CoffeeScript example, check out + For a longer CoffeeScript example, check out Underscore.coffee, a port of Underscore.js to CoffeeScript, which, when compiled, passes the complete Underscore test suite. @@ -206,7 +208,7 @@

    Installation and Usage

    @@ -242,7 +244,7 @@

    Installation and Usage

    @@ -313,7 +315,8 @@

    Language Reference

    Functions and Invocation Functions are defined by a list of parameters, an arrow, and the - function body. The empty function looks like this: => + function body. The empty function looks like this: =>. All + functions in CoffeeScript are named, for the benefit of debug messages.

    square: x => x * x
     cube:   x => square(x) * x
    @@ -350,7 +353,7 @@ 

    Language Reference

    ;alert(greeting);'>run: greeting

    Declarations of new variables are pushed up to the top of the nearest - lexical scope, so that assignment may always be used within expressions. + lexical scope, so that assignment may always be performed within expressions.

    @@ -539,6 +542,68 @@

    Language Reference

    car.speed < speed_limit ? accelerate() : null;
    +

    + Splats + The JavaScript arguments object is a useful way to work with + functions that accept variable numbers of arguments. CoffeeScript provides + splats *, both for function definition as well as invocation, + making variable arguments a little bit more palatable. +

    +
    gold: silver: the_field: "unknown"
    +
    +medalists: first, second, *rest =>
    +  gold:       first
    +  silver:     second
    +  the_field:  rest
    +
    +contenders: [
    +  "Michael Phelps"
    +  "Liu Xiang"
    +  "Yao Ming"
    +  "Allyson Felix"
    +  "Shawn Johnson"
    +  "Roman Sebrle"
    +  "Guo Jingjing"
    +  "Tyson Gay"
    +  "Asafa Powell"
    +  "Usain Bolt"
    +]
    +
    +medalists(*contenders)
    +
    +alert("Gold: " + gold)
    +alert("Silver: " + silver)
    +alert("The Field: " + the_field)
    +
    var contenders, gold, medalists, silver, the_field;
    +gold = silver = the_field = "unknown";
    +medalists = function medalists(first, second) {
    +  var rest;
    +  rest = Array.prototype.slice.call(arguments, 2);
    +  gold = first;
    +  silver = second;
    +  return the_field = rest;
    +};
    +contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Allyson Felix", "Shawn Johnson", "Roman Sebrle", "Guo Jingjing", "Tyson Gay", "Asafa Powell", "Usain Bolt"];
    +medalists.apply(this, contenders);
    +alert("Gold: " + gold);
    +alert("Silver: " + silver);
    +alert("The Field: " + the_field);
    +

    +

    While Loops The only low-level loop that CoffeeScript provides is the while loop. @@ -713,7 +778,7 @@

    Language Reference

    numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; numbers.splice.apply(numbers, [3, 6 - 3 + 1].concat([-3, -4, -5, -6])); ;alert(numbers);'>run: numbers
    - +

    Everything is an Expression (at least, as much as possible) You might have noticed how even though we don't add return statements @@ -933,6 +998,25 @@

    Language Reference

    tom.move(); ;'>run
    +

    + Blocks + Many common looping functions (in Prototype, jQuery, and Underscore, + for example) take a single function as their final argument. To functions + easier to pass, CoffeeScript includes Ruby-style block syntax, so you don't + have to close the parentheses on the other side. +

    +
    $('table.list').each() table =>
    +  $('tr.account', table).each() row =>
    +    row.show()
    +    row.highlight()
    +
    $('table.list').each(function(table) {
    +  return $('tr.account', table).each(function(row) {
    +    row.show();
    +    return row.highlight();
    +  });
    +});
    +

    +

    Embedded JavaScript If you ever need to interpolate literal JavaScript snippets, you can @@ -1050,6 +1134,11 @@

    Resources

  • Bugs and Feature Requests
  • +
  • + BistroCar
    + A Rails plugin by Jonas Nicklas that includes CoffeeScript helpers, + bundling and minification. +
  • Contributing

    From 724d4c966023b31bc8da8bc8d22ef04fb3068e3a Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 5 Jan 2010 00:19:22 -0500 Subject: [PATCH 260/303] tweaking docs --- documentation/index.html.erb | 26 +++++++++++++++++++------- index.html | 26 +++++++++++++++++++------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index d520428f66..1ed90f1afc 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -241,7 +241,7 @@ coffee --print app/scripts/*.coffee > concatenation.js use semicolons ; to terminate expressions, ending the line will do just as well. Semicolons can still be used to fit multiple expressions onto a single line. Instead of using curly braces - { } to delimit a block of code (like functions, + { } to delimit blocks of code (like functions, if-statements, switch, and try/catch), use indentation. @@ -327,7 +327,7 @@ coffee --print app/scripts/*.coffee > concatenation.js JavaScript. if (variable) ... comes close, but fails for zero, the empty string, and false. The existence operator ? returns true unless a variable is null or undefined, which makes it analogous - to nil? in Ruby. + to Ruby's nil?

    <%= code_for('existence') %> @@ -470,9 +470,9 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Blocks Many common looping functions (in Prototype, jQuery, and Underscore, - for example) take a single function as their final argument. To functions - easier to pass, CoffeeScript includes Ruby-style block syntax, so you don't - have to close the parentheses on the other side. + for example) take a single function as their final argument. To make + final functions easier to pass, CoffeeScript includes block syntax, + so you don't have to close the parentheses on the other side.

    <%= code_for('blocks') %> @@ -502,7 +502,7 @@ coffee --print app/scripts/*.coffee > concatenation.js

    <%= code_for('try') %> -

    +

    Multiline Strings Multiline strings are allowed in CoffeeScript.

    @@ -522,7 +522,9 @@ coffee --print app/scripts/*.coffee > concatenation.js
  • BistroCar
    - A Rails plugin by Jonas Nicklas that includes CoffeeScript helpers, + A Rails plugin by + Jonas Nicklas + that includes CoffeeScript helpers, bundling and minification.
  • @@ -560,6 +562,16 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Change Log

    +

    + 0.2.0 + Major release. Significant whitespace. Better statement-to-expression + conversion. Splats. Splice literals. Object comprehensions. Blocks. + The existence operator. Many thanks to all the folks who posted issues, + with special thanks to + Liam O'Connor-Davis for whitespace + and expression help. +

    +

    0.1.6 Bugfix for running coffee --interactive and --run diff --git a/index.html b/index.html index 044c1a4fc2..ca92d381a4 100644 --- a/index.html +++ b/index.html @@ -306,7 +306,7 @@

    Language Reference

    use semicolons ; to terminate expressions, ending the line will do just as well. Semicolons can still be used to fit multiple expressions onto a single line. Instead of using curly braces - { } to delimit a block of code (like functions, + { } to delimit blocks of code (like functions, if-statements, switch, and try/catch), use indentation. @@ -485,7 +485,7 @@

    Language Reference

    JavaScript. if (variable) ... comes close, but fails for zero, the empty string, and false. The existence operator ? returns true unless a variable is null or undefined, which makes it analogous - to nil? in Ruby. + to Ruby's nil?

    solipsism: true if mind? and not world?
     
    var solipsism;
    @@ -1001,9 +1001,9 @@ 

    Language Reference

    Blocks Many common looping functions (in Prototype, jQuery, and Underscore, - for example) take a single function as their final argument. To functions - easier to pass, CoffeeScript includes Ruby-style block syntax, so you don't - have to close the parentheses on the other side. + for example) take a single function as their final argument. To make + final functions easier to pass, CoffeeScript includes block syntax, + so you don't have to close the parentheses on the other side.

    $('table.list').each() table =>
       $('tr.account', table).each() row =>
    @@ -1094,7 +1094,7 @@ 

    Language Reference

    }

    -

    +

    Multiline Strings Multiline strings are allowed in CoffeeScript.

    @@ -1136,7 +1136,9 @@

    Resources

  • BistroCar
    - A Rails plugin by Jonas Nicklas that includes CoffeeScript helpers, + A Rails plugin by + Jonas Nicklas + that includes CoffeeScript helpers, bundling and minification.
  • @@ -1174,6 +1176,16 @@

    Contributing

    Change Log

    +

    + 0.2.0 + Major release. Significant whitespace. Better statement-to-expression + conversion. Splats. Splice literals. Object comprehensions. Blocks. + The existence operator. Many thanks to all the folks who posted issues, + with special thanks to + Liam O'Connor-Davis for whitespace + and expression help. +

    +

    0.1.6 Bugfix for running coffee --interactive and --run From 6658250c8eb73c451b667358dd578d829b456abe Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 5 Jan 2010 00:34:18 -0500 Subject: [PATCH 261/303] more docs --- documentation/coffee/overview.coffee | 7 +++++ documentation/index.html.erb | 7 ++++- documentation/js/existence.js | 2 +- documentation/js/overview.js | 12 ++++++++- documentation/underscore.html | 6 ++++- index.html | 40 +++++++++++++++++++++++++--- lib/coffee_script/nodes.rb | 2 +- 7 files changed, 67 insertions(+), 9 deletions(-) diff --git a/documentation/coffee/overview.coffee b/documentation/coffee/overview.coffee index 44e4ec9592..295f38a132 100644 --- a/documentation/coffee/overview.coffee +++ b/documentation/coffee/overview.coffee @@ -18,5 +18,12 @@ math: { cube: x => x * square(x) } +# Splats: +race: winner, *runners => + print(winner, runners) + +# Existence: +alert("I knew it!") if elvis? + # Array comprehensions: cubed_list: math.cube(num) for num in list diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 1ed90f1afc..6ba321da9f 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -108,7 +108,7 @@ For a longer CoffeeScript example, check out Underscore.coffee, a port of Underscore.js - to CoffeeScript, which, when compiled, passes the complete Underscore test suite. + to CoffeeScript, which, when compiled, can pass the complete Underscore test suite.

    Installation and Usage

    @@ -537,6 +537,11 @@ coffee --print app/scripts/*.coffee > concatenation.js

      +
    • + A clean, safe syntax for manipulating the prototype chain, and performing + inheritance. extends and super are the start of this, but + aren't a complete answer. +
    • A CoffeeScript version of the compiler, perhaps using Alessandro Warth's OMeta. diff --git a/documentation/js/existence.js b/documentation/js/existence.js index 60e9e922b9..aa58531626 100644 --- a/documentation/js/existence.js +++ b/documentation/js/existence.js @@ -1,6 +1,6 @@ (function(){ var solipsism; - if ((typeof mind !== 'undefined' && mind !== null) && !(typeof world !== 'undefined' && world !== null)) { + if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) { solipsism = true; } })(); \ No newline at end of file diff --git a/documentation/js/overview.js b/documentation/js/overview.js index 637697abae..e3832187ee 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -1,5 +1,5 @@ (function(){ - var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, square; + var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, race, square; // Assignment: number = 42; opposite_day = true; @@ -21,6 +21,16 @@ return x * square(x); } }; + // Splats: + race = function race(winner) { + var runners; + runners = Array.prototype.slice.call(arguments, 1); + return print(winner, runners); + }; + // Existence: + if ((typeof elvis !== "undefined" && elvis !== null)) { + alert("I knew it!"); + } // Array comprehensions: cubed_list = (function() { __a = list; diff --git a/documentation/underscore.html b/documentation/underscore.html index 674adb89f7..b502cf222b 100644 --- a/documentation/underscore.html +++ b/documentation/underscore.html @@ -9,6 +9,10 @@ body { margin: 0; padding: 0; } + pre.idle { + font-family: "Monaco", "Consolas", monospace; + font-size: 12px; + } @@ -607,6 +611,6 @@ 591 592 # Extracts the result from a wrapped and chained object. 593 wrapper.prototype.value: => this._wrapped - + diff --git a/index.html b/index.html index ca92d381a4..914f6a18ce 100644 --- a/index.html +++ b/index.html @@ -95,9 +95,16 @@

      Mini Overview

      cube: x => x * square(x) } +# Splats: +race: winner, *runners => + print(winner, runners) + +# Existence: +alert("I knew it!") if elvis? + # Array comprehensions: cubed_list: math.cube(num) for num in list -
      var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, square;
      +
      var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, race, square;
       // Assignment:
       number = 42;
       opposite_day = true;
      @@ -119,6 +126,16 @@ 

      Mini Overview

      return x * square(x); } }; +// Splats: +race = function race(winner) { + var runners; + runners = Array.prototype.slice.call(arguments, 1); + return print(winner, runners); +}; +// Existence: +if ((typeof elvis !== "undefined" && elvis !== null)) { + alert("I knew it!"); +} // Array comprehensions: cubed_list = (function() { __a = list; @@ -132,7 +149,7 @@

      Mini Overview

      } return __c; })(); -

    + +

    + Arguments are Arrays + If you reference the arguments object directly, it will be converted + into a real Array, making all of the + Array methods + available. +

    +
    backwards: =>
    +  alert(arguments.reverse())
    +
    +backwards("stairway", "to", "heaven")
    +
    var backwards;
    +backwards = function backwards() {
    +  return alert(Array.prototype.slice.call(arguments, 0).reverse());
    +};
    +backwards("stairway", "to", "heaven");
    +

    @@ -1030,8 +1054,8 @@

    Language Reference

    Blocks Many common looping functions (in Prototype, jQuery, and Underscore, - for example) take a single function as their final argument. To make - final functions easier to pass, CoffeeScript includes block syntax, + for example) take a single function as their final argument. To make + final functions easier to pass, CoffeeScript includes block syntax, so you don't have to close the parentheses on the other side.

    $('table.list').each() table =>
    @@ -1165,7 +1189,7 @@ 

    Resources

  • BistroCar
    - A Rails plugin by + A Rails plugin by Jonas Nicklas that includes CoffeeScript helpers, bundling and minification. @@ -1182,7 +1206,7 @@

    Contributing

    • A clean, safe syntax for manipulating the prototype chain, and performing - inheritance. extends and super are the start of this, but + inheritance. extends and super are the start of this, but aren't a complete answer.
    • @@ -1210,12 +1234,17 @@

      Contributing

      Change Log

      +

      + 0.2.1 + Arguments objects are now converted into real arrays when referenced. +

      +

      0.2.0 Major release. Significant whitespace. Better statement-to-expression conversion. Splats. Splice literals. Object comprehensions. Blocks. The existence operator. Many thanks to all the folks who posted issues, - with special thanks to + with special thanks to Liam O'Connor-Davis for whitespace and expression help.

      From 1040e522463a74d5241272114677b85af99fdbc9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 5 Jan 2010 09:30:48 -0500 Subject: [PATCH 265/303] CoffeeScript 0.2.1 --- coffee-script.gemspec | 2 +- documentation/index.html.erb | 2 +- index.html | 2 +- lib/coffee-script.rb | 2 +- package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/coffee-script.gemspec b/coffee-script.gemspec index 2f77736167..ebf191b694 100644 --- a/coffee-script.gemspec +++ b/coffee-script.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'coffee-script' - s.version = '0.2.0' # Keep version in sync with coffee-script.rb + s.version = '0.2.1' # Keep version in sync with coffee-script.rb s.date = '2010-1-5' s.homepage = "http://jashkenas.github.com/coffee-script/" diff --git a/documentation/index.html.erb b/documentation/index.html.erb index dee4b3e031..ecf0ca55ed 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -66,7 +66,7 @@

      Latest Version: - 0.2.0 + 0.2.1

      Table of Contents

      diff --git a/index.html b/index.html index 88daad03fe..e80a0b4bab 100644 --- a/index.html +++ b/index.html @@ -39,7 +39,7 @@

      CoffeeScript

      Latest Version: - 0.2.0 + 0.2.1

      Table of Contents

      diff --git a/lib/coffee-script.rb b/lib/coffee-script.rb index 287378c559..b4534fe31e 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.0' # Keep in sync with the gemspec. + VERSION = '0.2.1' # 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 bb842ff87a..cb514c4a47 100644 --- a/package.json +++ b/package.json @@ -5,5 +5,5 @@ "description": "Unfancy JavaScript", "keywords": ["javascript", "language"], "author": "Jeremy Ashkenas", - "version": "0.2.0" + "version": "0.2.1" } From c2bb93b5f8df960131a68b919642ac5900828f31 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Tue, 5 Jan 2010 21:40:36 -0500 Subject: [PATCH 266/303] ellipsis is the new splat --- documentation/coffee/overview.coffee | 2 +- documentation/coffee/splats.coffee | 4 +-- documentation/index.html.erb | 32 ++++++++-------------- index.html | 25 ++++++++++------- lib/coffee_script/grammar.y | 8 +++--- lib/coffee_script/lexer.rb | 3 +- test/fixtures/execution/test_splats.coffee | 6 ++-- 7 files changed, 38 insertions(+), 42 deletions(-) diff --git a/documentation/coffee/overview.coffee b/documentation/coffee/overview.coffee index 295f38a132..6c940d9fd2 100644 --- a/documentation/coffee/overview.coffee +++ b/documentation/coffee/overview.coffee @@ -19,7 +19,7 @@ math: { } # Splats: -race: winner, *runners => +race: winner, runners... => print(winner, runners) # Existence: diff --git a/documentation/coffee/splats.coffee b/documentation/coffee/splats.coffee index de02d2428f..035b902f51 100644 --- a/documentation/coffee/splats.coffee +++ b/documentation/coffee/splats.coffee @@ -1,6 +1,6 @@ gold: silver: the_field: "unknown" -medalists: first, second, *rest => +medalists: first, second, rest... => gold: first silver: second the_field: rest @@ -18,7 +18,7 @@ contenders: [ "Usain Bolt" ] -medalists(*contenders) +medalists(contenders...) alert("Gold: " + gold) alert("Silver: " + silver) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index ecf0ca55ed..9a927bf304 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -1,18 +1,3 @@ -<%# - TODO: - Multiline and nested array comprehensions (and filters with 'when'). - Range comprehension examples (expression endpoints), with steps. - Object comprehension examples. - Significant Whitespace Rules. - Newline-delimited Matrix. - Automatic newline escaping. - All functions are named functions. - Splats in function definitions. - (Multiple) splats as arguments to a function call. - Exists? - Array assignment splice literals. -%> - <% require 'uv' def code_for(file, executable=false) @@ -82,7 +67,7 @@ Conditionals, Ternaries, and Conditional Assignment
      The Existence Operator
      Aliases
      - Splats
      + Splats...
      Arguments are Arrays
      While Loops
      Comprehensions (Arrays, Objects, and Ranges)
      @@ -250,6 +235,13 @@ coffee --print app/scripts/*.coffee > concatenation.js
  • use indentation.

    +

    + You can use newlines to break up your expression into smaller pieces, + as long as CoffeeScript can tell that the line hasn't finished + (similar to how Ruby handles it). For example, + if the line ends in an operator, dot, or keyword. +

    +

    Functions and Invocation Functions are defined by a list of parameters, an arrow, and the @@ -365,10 +357,10 @@ coffee --print app/scripts/*.coffee > concatenation.js <%= code_for('aliases') %>

    - Splats + Splats... The JavaScript arguments object is a useful way to work with functions that accept variable numbers of arguments. CoffeeScript provides - splats *, both for function definition as well as invocation, + splats ..., both for function definition as well as invocation, making variable arguments a little bit more palatable.

    <%= code_for('splats', true) %> @@ -376,8 +368,8 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Arguments are Arrays If you reference the arguments object directly, it will be converted - into a real Array, making all of the - Array methods + into a real Array, making all of the + Array methods available.

    <%= code_for('arguments', true) %> diff --git a/index.html b/index.html index e80a0b4bab..dcbabd77a1 100644 --- a/index.html +++ b/index.html @@ -1,8 +1,6 @@ - - @@ -55,7 +53,7 @@

    Table of Contents

    Conditionals, Ternaries, and Conditional Assignment
    The Existence Operator
    Aliases
    - Splats
    + Splats...
    Arguments are Arrays
    While Loops
    Comprehensions (Arrays, Objects, and Ranges)
    @@ -97,7 +95,7 @@

    Mini Overview

    } # Splats: -race: winner, *runners => +race: winner, runners... => print(winner, runners) # Existence: @@ -342,6 +340,13 @@

    Language Reference

    use indentation.

    +

    + You can use newlines to break up your expression into smaller pieces, + as long as CoffeeScript can tell that the line hasn't finished + (similar to how Ruby handles it). For example, + if the line ends in an operator, dot, or keyword. +

    +

    Functions and Invocation Functions are defined by a list of parameters, an arrow, and the @@ -573,15 +578,15 @@

    Language Reference


    - Splats + Splats... The JavaScript arguments object is a useful way to work with functions that accept variable numbers of arguments. CoffeeScript provides - splats *, both for function definition as well as invocation, + splats ..., both for function definition as well as invocation, making variable arguments a little bit more palatable.

    gold: silver: the_field: "unknown"
     
    -medalists: first, second, *rest =>
    +medalists: first, second, rest... =>
       gold:       first
       silver:     second
       the_field:  rest
    @@ -599,7 +604,7 @@ 

    Language Reference

    "Usain Bolt" ] -medalists(*contenders) +medalists(contenders...) alert("Gold: " + gold) alert("Silver: " + silver) @@ -637,8 +642,8 @@

    Language Reference

    Arguments are Arrays If you reference the arguments object directly, it will be converted - into a real Array, making all of the - Array methods + into a real Array, making all of the + Array methods available.

    backwards: =>
    diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
    index 5ba10e26d7..6979c317ca 100644
    --- a/lib/coffee_script/grammar.y
    +++ b/lib/coffee_script/grammar.y
    @@ -5,7 +5,7 @@ token IF ELSE UNLESS
     token NUMBER STRING REGEX
     token TRUE FALSE YES NO ON OFF
     token IDENTIFIER PROPERTY_ACCESS
    -token CODE PARAM PARAM_SPLAT NEW RETURN
    +token CODE PARAM NEW RETURN
     token TRY CATCH FINALLY THROW
     token BREAK CONTINUE
     token FOR IN BY WHEN WHILE
    @@ -21,7 +21,7 @@ token INDENT OUTDENT
     # Declare order of operations.
     prechigh
       left     '?'
    -  nonassoc UMINUS PARAM_SPLAT SPLAT NOT '!' '!!' '~' '++' '--'
    +  nonassoc UMINUS NOT '!' '!!' '~' '++' '--'
       left     '*' '/' '%'
       left     '+' '-'
       left     '<<' '>>' '>>>'
    @@ -204,11 +204,11 @@ rule
     
       Param:
         PARAM
    -  | PARAM_SPLAT PARAM                 { result = ParamSplatNode.new(val[1]) }
    +  | PARAM "." "." "."                 { result = ParamSplatNode.new(val[0]) }
       ;
     
       Splat:
    -    '*' Value = SPLAT                 { result = ArgSplatNode.new(val[1]) }
    +    Expression "." "." "."            { result = ArgSplatNode.new(val[0])}
       ;
     
       # Expressions that can be treated as values.
    diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb
    index 798156a6c2..742ef132dc 100644
    --- a/lib/coffee_script/lexer.rb
    +++ b/lib/coffee_script/lexer.rb
    @@ -221,8 +221,7 @@ def tag_parameters
             i -= 1
             tok = @tokens[i]
             return if !tok
    -        next if tok[0] == ','
    -        next tok[0] = :PARAM_SPLAT if tok[0] == '*'
    +        next if ['.', ','].include?(tok[0])
             return if tok[0] != :IDENTIFIER
             tok[0] = :PARAM
           end
    diff --git a/test/fixtures/execution/test_splats.coffee b/test/fixtures/execution/test_splats.coffee
    index c5881bbe79..683998d3e9 100644
    --- a/test/fixtures/execution/test_splats.coffee
    +++ b/test/fixtures/execution/test_splats.coffee
    @@ -1,4 +1,4 @@
    -func: first, second, *rest =>
    +func: first, second, rest... =>
       rest.join(' ')
     
     result: func(1, 2, 3, 4, 5)
    @@ -8,7 +8,7 @@ print(result is "3 4 5")
     
     gold: silver: bronze: the_field: null
     
    -medalists: first, second, third, *rest =>
    +medalists: first, second, third, rest... =>
       gold:       first
       silver:     second
       bronze:     third
    @@ -27,7 +27,7 @@ contenders: [
       "Usain Bolt"
     ]
     
    -medalists("Mighty Mouse", *contenders)
    +medalists("Mighty Mouse", contenders...)
     
     print(gold is "Mighty Mouse")
     print(silver is "Michael Phelps")
    
    From 46f1977ea1bf9a6db3f634db8cb2451e98e82079 Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Tue, 5 Jan 2010 22:17:09 -0500
    Subject: [PATCH 267/303] adding ranges as expressions, they get expanded into
     the equivalent array
    
    ---
     lib/coffee_script/nodes.rb | 11 ++++++++++-
     lib/coffee_script/value.rb |  2 +-
     2 files changed, 11 insertions(+), 2 deletions(-)
    
    diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb
    index ffc2998cf8..8f3d38deac 100644
    --- a/lib/coffee_script/nodes.rb
    +++ b/lib/coffee_script/nodes.rb
    @@ -354,7 +354,7 @@ def compile_variables(o)
     
         def compile_node(o)
           idx, step = o.delete(:index), o.delete(:step)
    -      raise SyntaxError, "unexpected range literal" unless idx
    +      return compile_array(o) unless idx
           vars     = "#{idx}=#{@from_var}"
           step     = step ? step.compile(o) : '1'
           compare  = "(#{@from_var} <= #{@to_var} ? #{idx} #{less_operator} #{@to_var} : #{idx} #{greater_operator} #{@to_var})"
    @@ -362,6 +362,15 @@ def compile_node(o)
           write("#{vars}; #{compare}; #{incr}")
         end
     
    +    # Expand the range into the equivalent array, if it's not being used as
    +    # 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')))
    +      arr  = Expressions.wrap(ForNode.new(body, {:source => self}, Value.new('i')))
    +      ParentheticalNode.new(CallNode.new(CodeNode.new([], arr))).compile(o)
    +    end
    +
       end
     
       # An array slice literal. Unlike JavaScript's Array#slice, the second parameter
    diff --git a/lib/coffee_script/value.rb b/lib/coffee_script/value.rb
    index 532e1db815..49d659794f 100644
    --- a/lib/coffee_script/value.rb
    +++ b/lib/coffee_script/value.rb
    @@ -5,7 +5,7 @@ module CoffeeScript
       class Value
         attr_reader :value, :line
     
    -    def initialize(value, line)
    +    def initialize(value, line=nil)
           @value, @line = value, line
         end
     
    
    From 001e42b85ce79cbb047663cbe82eb1a55e826c4c Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Tue, 5 Jan 2010 22:49:51 -0500
    Subject: [PATCH 268/303] fixing range literals by making them values, not
     expressions, and adding tests
    
    ---
     lib/coffee_script/grammar.y                           |  2 +-
     lib/coffee_script/nodes.rb                            | 11 ++++++-----
     test/fixtures/execution/test_ranges_and_slices.coffee |  5 ++++-
     3 files changed, 11 insertions(+), 7 deletions(-)
    
    diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
    index 6979c317ca..aa0a7caadf 100644
    --- a/lib/coffee_script/grammar.y
    +++ b/lib/coffee_script/grammar.y
    @@ -64,7 +64,6 @@ rule
       | Call
       | Code
       | Operation
    -  | Range
       | Assign
       | If
       | Try
    @@ -218,6 +217,7 @@ rule
       | Array                             { result = ValueNode.new(val[0]) }
       | Object                            { result = ValueNode.new(val[0]) }
       | Parenthetical                     { result = ValueNode.new(val[0]) }
    +  | Range                             { result = ValueNode.new(val[0]) }
       | Value Accessor                    { result = val[0] << val[1] }
       | Invocation Accessor               { result = ValueNode.new(val[0], [val[1]]) }
       ;
    diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb
    index 8f3d38deac..f89cd96b02 100644
    --- a/lib/coffee_script/nodes.rb
    +++ b/lib/coffee_script/nodes.rb
    @@ -367,7 +367,7 @@ def compile_node(o)
         # TODO: This generates pretty ugly code ... shrink it.
         def compile_array(o)
           body = Expressions.wrap(LiteralNode.new(Value.new('i')))
    -      arr  = Expressions.wrap(ForNode.new(body, {:source => self}, Value.new('i')))
    +      arr  = Expressions.wrap(ForNode.new(body, {:source => ValueNode.new(self)}, Value.new('i')))
           ParentheticalNode.new(CallNode.new(CodeNode.new([], arr))).compile(o)
         end
     
    @@ -617,7 +617,8 @@ def initialize(body, source, name, index=nil)
     
         def compile_node(o)
           top_level     = o.delete(:top) && !o[:return]
    -      range         = @source.is_a?(RangeNode)
    +      range         = @source.is_a?(ValueNode) && @source.literal.is_a?(RangeNode) && @source.properties.empty?
    +      source        = range ? @source.literal : @source
           scope         = o[:scope]
           name_found    = scope.find(@name)
           index_found   = @index && scope.find(@index)
    @@ -629,12 +630,12 @@ def compile_node(o)
             body_dent   = o[:indent] + TAB
             var_part, pre_cond, post_cond = '', '', ''
             index_var   = scope.free_variable
    -        source_part = @source.compile_variables(o)
    -        for_part    = "#{index_var}=0, #{@source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
    +        source_part = source.compile_variables(o)
    +        for_part    = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++"
           else
             index_var   = nil
             body_dent   = o[:indent] + TAB + TAB
    -        source_part = "#{o[:indent]}#{svar} = #{@source.compile(o)};\n#{o[:indent]}"
    +        source_part = "#{o[:indent]}#{svar} = #{source.compile(o)};\n#{o[:indent]}"
             for_part    = "#{ivar} in #{svar}"
             pre_cond    = "\n#{o[:indent] + TAB}if (#{svar}.hasOwnProperty(#{ivar})) {"
             var_part    = "\n#{body_dent}#{@name} = #{svar}[#{ivar}];"
    diff --git a/test/fixtures/execution/test_ranges_and_slices.coffee b/test/fixtures/execution/test_ranges_and_slices.coffee
    index 63165c5d07..3850067a48 100644
    --- a/test/fixtures/execution/test_ranges_and_slices.coffee
    +++ b/test/fixtures/execution/test_ranges_and_slices.coffee
    @@ -5,4 +5,7 @@ b: array[2...4]
     
     result: a.concat(b).join(' ')
     
    -print(result is "7 8 9 2 3")
    \ No newline at end of file
    +print(result is "7 8 9 2 3")
    +
    +countdown: [10..1].join(' ')
    +print(countdown is "10 9 8 7 6 5 4 3 2 1")
    \ No newline at end of file
    
    From 0275e7775f7e9e0aa20d9400bc2e747c3bc3dad0 Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Tue, 5 Jan 2010 23:15:32 -0500
    Subject: [PATCH 269/303] fixing weepy's comment scenario
    
    ---
     lib/coffee_script/rewriter.rb                      | 4 ++++
     test/fixtures/execution/test_funky_comments.coffee | 6 ++++--
     2 files changed, 8 insertions(+), 2 deletions(-)
    
    diff --git a/lib/coffee_script/rewriter.rb b/lib/coffee_script/rewriter.rb
    index 88a31066d5..0325f7404f 100644
    --- a/lib/coffee_script/rewriter.rb
    +++ b/lib/coffee_script/rewriter.rb
    @@ -69,6 +69,10 @@ def adjust_comments
               @tokens.delete_at(i + 2)
               @tokens.delete_at(i - 2)
               next 0
    +        elsif prev[0] == "\n" && [:INDENT, :OUTDENT].include?(after[0])
    +          @tokens.delete_at(i + 2)
    +          @tokens[i - 1] = after
    +          next 1
             elsif !["\n", :INDENT, :OUTDENT].include?(prev[0])
               @tokens.insert(i, ["\n", Value.new("\n", token[1].line)])
               next 2
    diff --git a/test/fixtures/execution/test_funky_comments.coffee b/test/fixtures/execution/test_funky_comments.coffee
    index 2a24318d40..038c450a25 100644
    --- a/test/fixtures/execution/test_funky_comments.coffee
    +++ b/test/fixtures/execution/test_funky_comments.coffee
    @@ -1,8 +1,10 @@
    +  # comment
     func: =>
    +# comment
       false
       false   # comment
       false
     # comment
       true
    -  
    -print(func())
    \ No newline at end of file
    +
    +print(func())
    
    From 4fc40e4841fbcd52b412b27b2d00a7d26b6976a2 Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Tue, 5 Jan 2010 23:29:43 -0500
    Subject: [PATCH 270/303] adding the 'in' operator
    
    ---
     lib/coffee_script/grammar.y                             | 1 +
     test/fixtures/execution/test_array_comprehension.coffee | 4 ++++
     2 files changed, 5 insertions(+)
    
    diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
    index aa0a7caadf..885e7562b8 100644
    --- a/lib/coffee_script/grammar.y
    +++ b/lib/coffee_script/grammar.y
    @@ -183,6 +183,7 @@ rule
       | Expression '&&=' Expression       { result = OpNode.new(val[1], val[0], val[2]) }
     
       | Expression INSTANCEOF Expression  { result = OpNode.new(val[1], val[0], val[2]) }
    +  | Expression IN Expression          { result = OpNode.new(val[1], val[0], val[2]) }
       ;
     
       Existence:
    diff --git a/test/fixtures/execution/test_array_comprehension.coffee b/test/fixtures/execution/test_array_comprehension.coffee
    index 27d0487118..5fdfe1df7d 100644
    --- a/test/fixtures/execution/test_array_comprehension.coffee
    +++ b/test/fixtures/execution/test_array_comprehension.coffee
    @@ -19,3 +19,7 @@ evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
     
     print(evens.join(', ') is '4, 6, 8')
     
    +# Make sure that the "in" operator still works.
    +
    +print(2 in evens)
    +
    
    From de49465dc6ad828c1432a4791284f8d5cf88038f Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Wed, 6 Jan 2010 01:27:58 -0500
    Subject: [PATCH 271/303] comments
    
    ---
     lib/coffee_script/grammar.y | 23 ++++++++++++++---------
     1 file changed, 14 insertions(+), 9 deletions(-)
    
    diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
    index 885e7562b8..8b459ee87c 100644
    --- a/lib/coffee_script/grammar.y
    +++ b/lib/coffee_script/grammar.y
    @@ -1,6 +1,6 @@
     class Parser
     
    -# Declare tokens produced by the lexer
    +# Declare terminal tokens produced by the lexer.
     token IF ELSE UNLESS
     token NUMBER STRING REGEX
     token TRUE FALSE YES NO ON OFF
    @@ -58,7 +58,8 @@ rule
       | Expressions Terminator            { result = val[0] }
       ;
     
    -  # All types of expressions in our language.
    +  # All types of expressions in our language. The basic unit of CoffeeScript
    +  # is the expression.
       Expression:
         Value
       | Call
    @@ -78,18 +79,20 @@ rule
       | Comment
       ;
     
    +  # A block of expressions. Note that the Rewriter will convert some postfix
    +  # forms into blocks for us, by altering the token stream.
       Block:
         INDENT Expressions OUTDENT        { result = val[1] }
       | INDENT OUTDENT                    { result = Expressions.new }
       ;
     
    -  # All tokens that can terminate an expression.
    +  # Tokens that can terminate an expression.
       Terminator:
         "\n"
       | ";"
       ;
     
    -  # All hard-coded values.
    +  # All hard-coded values. These can be printed straight to JavaScript.
       Literal:
         NUMBER                            { result = LiteralNode.new(val[0]) }
       | STRING                            { result = LiteralNode.new(val[0]) }
    @@ -106,12 +109,12 @@ rule
       | OFF                               { result = LiteralNode.new(false) }
       ;
     
    -  # Assignment to a variable.
    +  # Assignment to a variable (or index).
       Assign:
         Value ASSIGN Expression           { result = AssignNode.new(val[0], val[2]) }
       ;
     
    -  # Assignment within an object literal.
    +  # Assignment within an object literal (can be quoted).
       AssignObj:
         IDENTIFIER ASSIGN Expression      { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
       | STRING ASSIGN Expression          { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
    @@ -186,6 +189,7 @@ rule
       | Expression IN Expression          { result = OpNode.new(val[1], val[0], val[2]) }
       ;
     
    +  # The existence operator.
       Existence:
         Expression '?'                    { result = ExistenceNode.new(val[0]) }
       ;
    @@ -202,11 +206,13 @@ rule
       | ParamList "," Param               { result = val[0] << val[2] }
       ;
     
    +  # A Parameter (or ParamSplat) in a function definition.
       Param:
         PARAM
       | PARAM "." "." "."                 { result = ParamSplatNode.new(val[0]) }
       ;
     
    +  # A regular splat.
       Splat:
         Expression "." "." "."            { result = ArgSplatNode.new(val[0])}
       ;
    @@ -270,6 +276,7 @@ rule
       # | Invocation Code                   { result = val[0] << val[1] }
       ;
     
    +  # The list of arguments to a function invocation.
       Arguments:
         "(" ArgList ")"                   { result = val[1] }
       | "(" ArgList ")" Code              { result = val[1] << val[3] }
    @@ -378,9 +385,7 @@ rule
       | Comment
       ;
     
    -  # All of the following nutso if-else destructuring is to make the
    -  # grammar expand unambiguously.
    -
    +  # The most basic form of "if".
       IfBlock:
         IF Expression Block               { result = IfNode.new(val[1], val[2]) }
       ;
    
    From 3124869e1ddea5415209766d35c46d91e94dade3 Mon Sep 17 00:00:00 2001
    From: Matt Lyon 
    Date: Tue, 5 Jan 2010 22:44:44 -0800
    Subject: [PATCH 272/303] add folding for multi-line object literals
    
    ---
     .../CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage    | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    index 2ff3cd30d3..f365988115 100644
    --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    @@ -10,6 +10,10 @@
     	
     	name
     	CoffeeScript
    +	foldingStartMarker
    +	^.*: \{[^}]*
    +	foldingStopMarker
    +	^\s*\}
     	patterns
     	
     		
    
    From 22d3238b2accde3823a2be88060b4169acf0352e Mon Sep 17 00:00:00 2001
    From: Matt Lyon 
    Date: Wed, 6 Jan 2010 00:03:11 -0800
    Subject: [PATCH 273/303] if we escape the close-brackets, it won't think
     single-line objects need folding
    
    ---
     .../Syntaxes/CoffeeScript.tmLanguage          | 354 +-----------------
     1 file changed, 11 insertions(+), 343 deletions(-)
    
    diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    index f365988115..a84b628511 100644
    --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    @@ -2,349 +2,17 @@
     
     
     
    -	comment
    -	CoffeeScript Syntax: version 1
    -	fileTypes
    -	
    -		coffee
    -	
    -	name
    -	CoffeeScript
    -	foldingStartMarker
    -	^.*: \{[^}]*
    -	foldingStopMarker
    -	^\s*\}
    -	patterns
    -	
    -		
    -			captures
    -			
    -				1
    -				
    -					name
    -					entity.name.function.coffee
    -				
    -				2
    -				
    -					name
    -					keyword.operator.coffee
    -				
    -				3
    -				
    -					name
    -					variable.parameter.function.coffee
    -				
    -				4
    -				
    -					name
    -					storage.type.function.coffee
    -				
    -			
    -			comment
    -			match stuff like: funcName: => … 
    -			match
    -			([a-zA-Z0-9_?.$*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)
    -			name
    -			meta.function.coffee
    -		
    -		
    -			captures
    -			
    -				1
    -				
    -					name
    -					variable.parameter.function.coffee
    -				
    -				2
    -				
    -					name
    -					storage.type.function.coffee
    -				
    -			
    -			comment
    -			match stuff like: a => … 
    -			match
    -			([a-zA-Z0-9_?., $*]*)\s*(=>)
    -			name
    -			meta.inline.function.coffee
    -		
    -		
    -			captures
    -			
    -				1
    -				
    -					name
    -					keyword.operator.new.coffee
    -				
    -				2
    -				
    -					name
    -					entity.name.type.instance.coffee
    -				
    -			
    -			match
    -			(new)\s+(\w+(?:\.\w*)?)
    -			name
    -			meta.class.instance.constructor
    -		
    -		
    -			match
    -			\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b
    -			name
    -			constant.numeric.coffee
    -		
    -		
    -			begin
    -			'
    -			beginCaptures
    -			
    -				0
    -				
    -					name
    -					punctuation.definition.string.begin.coffee
    -				
    -			
    -			end
    -			'
    -			endCaptures
    -			
    -				0
    -				
    -					name
    -					punctuation.definition.string.end.coffee
    -				
    -			
    -			name
    -			string.quoted.single.coffee
    -			patterns
    -			
    -				
    -					match
    -					\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)
    -					name
    -					constant.character.escape.coffee
    -				
    -			
    -		
    -		
    -			begin
    -			"
    -			beginCaptures
    -			
    -				0
    -				
    -					name
    -					punctuation.definition.string.begin.coffee
    -				
    -			
    -			end
    -			"
    -			endCaptures
    -			
    -				0
    -				
    -					name
    -					punctuation.definition.string.end.coffee
    -				
    -			
    -			name
    -			string.quoted.double.coffee
    -			patterns
    -			
    -				
    -					match
    -					\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)
    -					name
    -					constant.character.escape.coffee
    -				
    -			
    -		
    -		
    -			begin
    -			`
    -			beginCaptures
    -			
    -				0
    -				
    -					name
    -					punctuation.definition.string.begin.coffee
    -				
    -			
    -			end
    -			`
    -			endCaptures
    -			
    -				0
    -				
    -					name
    -					punctuation.definition.string.end.coffee
    -				
    -			
    -			name
    -			string.quoted.script.coffee
    -			patterns
    -			
    -				
    -					match
    -					\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)
    -					name
    -					constant.character.escape.coffee
    -				
    -			
    -		
    -		
    -			captures
    -			
    -				1
    -				
    -					name
    -					punctuation.definition.comment.coffee
    -				
    -			
    -			match
    -			(#).*$\n?
    -			name
    -			comment.line.coffee
    -		
    -		
    -			match
    -			\b(break|by|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|when|while)\b
    -			name
    -			keyword.control.coffee
    -		
    -		
    -			match
    -			\b([a-zA-Z$_]\w*)(\:)\s
    -			name
    -			variable.assignment.coffee
    -			captures
    -			
    -				1
    -				
    -					name
    -					entity.name.function.coffee
    -				
    -				2
    -				
    -					name
    -					keyword.operator.coffee
    -				
    -			
    -		
    -		
    -			match
    -			\b(true|on|yes)\b
    -			name
    -			constant.language.boolean.true.coffee
    -		
    -		
    -			match
    -			\b(false|off|no)\b
    -			name
    -			constant.language.boolean.false.coffee
    -		
    -		
    -			match
    -			\bnull\b
    -			name
    -			constant.language.null.coffee
    -		
    -		
    -			match
    -			\b(super|this|extends)\b
    -			name
    -			variable.language.coffee
    -		
    -		
    -			match
    -			\b(debugger|\\)\b
    -			name
    -			keyword.other.coffee
    -		
    -		
    -			match
    -			!|\$|%|&|\*|\/|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b
    -			name
    -			keyword.operator.coffee
    -		
    -		
    -			match
    -			\b(Infinity|NaN|undefined)\b
    -			name
    -			constant.language.coffee
    -		
    -		
    -			begin
    -			(?<=[=(:]|^|return)\s*(/)(?![/*+{}?])
    -			beginCaptures
    -			
    -				1
    -				
    -					name
    -					punctuation.definition.string.begin.coffee
    -				
    -			
    -			end
    -			(/)[igm]*
    -			endCaptures
    -			
    -				1
    -				
    -					name
    -					punctuation.definition.string.end.coffee
    -				
    -			
    -			name
    -			string.regexp.coffee
    -			patterns
    -			
    -				
    -					match
    -					\\.
    -					name
    -					constant.character.escape.coffee
    -				
    -			
    -		
    -		
    -			match
    -			\;
    -			name
    -			punctuation.terminator.statement.coffee
    -		
    -		
    -			match
    -			,[ |\t]*
    -			name
    -			meta.delimiter.object.comma.coffee
    -		
    -		
    -			match
    -			\.
    -			name
    -			meta.delimiter.method.period.coffee
    -		
    -		
    -			match
    -			\{|\}
    -			name
    -			meta.brace.curly.coffee
    -		
    -		
    -			match
    -			\(|\)
    -			name
    -			meta.brace.round.coffee
    -		
    -		
    -			match
    -			\[|\]
    -			name
    -			meta.brace.square.coffee
    -		
    -	
    -	scopeName
    -	source.coffee
    +	changed
    +	
    +		foldingStartMarker
    +		^.*[:=] \{[^\}]*$
    +		foldingStopMarker
    +		^\s*\}
    +	
    +	deleted
    +	
    +	isDelta
    +	
     	uuid
     	5B520980-A7D5-4E10-8582-1A4C889A8DE5
     
    
    From 41bb6edcd2ec54dd0cbbaf0082a9274d03041298 Mon Sep 17 00:00:00 2001
    From: Matt Lyon 
    Date: Wed, 6 Jan 2010 00:11:20 -0800
    Subject: [PATCH 274/303] next time: don't copy the .tmDelta over the
     .tmLanguage
    
    ---
     .../Syntaxes/CoffeeScript.tmLanguage          | 354 +++++++++++++++++-
     1 file changed, 343 insertions(+), 11 deletions(-)
    
    diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    index a84b628511..af21e2e05c 100644
    --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
    @@ -2,17 +2,349 @@
     
     
     
    -	changed
    -	
    -		foldingStartMarker
    -		^.*[:=] \{[^\}]*$
    -		foldingStopMarker
    -		^\s*\}
    -	
    -	deleted
    -	
    -	isDelta
    -	
    +	comment
    +	CoffeeScript Syntax: version 1
    +	fileTypes
    +	
    +		coffee
    +	
    +	name
    +	CoffeeScript
    +	foldingStartMarker
    +	^.*[:=] \{[^\}]*$
    +	foldingStopMarker
    +	\s*\}
    +	patterns
    +	
    +		
    +			captures
    +			
    +				1
    +				
    +					name
    +					entity.name.function.coffee
    +				
    +				2
    +				
    +					name
    +					keyword.operator.coffee
    +				
    +				3
    +				
    +					name
    +					variable.parameter.function.coffee
    +				
    +				4
    +				
    +					name
    +					storage.type.function.coffee
    +				
    +			
    +			comment
    +			match stuff like: funcName: => … 
    +			match
    +			([a-zA-Z0-9_?.$*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>)
    +			name
    +			meta.function.coffee
    +		
    +		
    +			captures
    +			
    +				1
    +				
    +					name
    +					variable.parameter.function.coffee
    +				
    +				2
    +				
    +					name
    +					storage.type.function.coffee
    +				
    +			
    +			comment
    +			match stuff like: a => … 
    +			match
    +			([a-zA-Z0-9_?., $*]*)\s*(=>)
    +			name
    +			meta.inline.function.coffee
    +		
    +		
    +			captures
    +			
    +				1
    +				
    +					name
    +					keyword.operator.new.coffee
    +				
    +				2
    +				
    +					name
    +					entity.name.type.instance.coffee
    +				
    +			
    +			match
    +			(new)\s+(\w+(?:\.\w*)?)
    +			name
    +			meta.class.instance.constructor
    +		
    +		
    +			match
    +			\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b
    +			name
    +			constant.numeric.coffee
    +		
    +		
    +			begin
    +			'
    +			beginCaptures
    +			
    +				0
    +				
    +					name
    +					punctuation.definition.string.begin.coffee
    +				
    +			
    +			end
    +			'
    +			endCaptures
    +			
    +				0
    +				
    +					name
    +					punctuation.definition.string.end.coffee
    +				
    +			
    +			name
    +			string.quoted.single.coffee
    +			patterns
    +			
    +				
    +					match
    +					\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)
    +					name
    +					constant.character.escape.coffee
    +				
    +			
    +		
    +		
    +			begin
    +			"
    +			beginCaptures
    +			
    +				0
    +				
    +					name
    +					punctuation.definition.string.begin.coffee
    +				
    +			
    +			end
    +			"
    +			endCaptures
    +			
    +				0
    +				
    +					name
    +					punctuation.definition.string.end.coffee
    +				
    +			
    +			name
    +			string.quoted.double.coffee
    +			patterns
    +			
    +				
    +					match
    +					\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)
    +					name
    +					constant.character.escape.coffee
    +				
    +			
    +		
    +		
    +			begin
    +			`
    +			beginCaptures
    +			
    +				0
    +				
    +					name
    +					punctuation.definition.string.begin.coffee
    +				
    +			
    +			end
    +			`
    +			endCaptures
    +			
    +				0
    +				
    +					name
    +					punctuation.definition.string.end.coffee
    +				
    +			
    +			name
    +			string.quoted.script.coffee
    +			patterns
    +			
    +				
    +					match
    +					\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)
    +					name
    +					constant.character.escape.coffee
    +				
    +			
    +		
    +		
    +			captures
    +			
    +				1
    +				
    +					name
    +					punctuation.definition.comment.coffee
    +				
    +			
    +			match
    +			(#).*$\n?
    +			name
    +			comment.line.coffee
    +		
    +		
    +			match
    +			\b(break|by|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|when|while)\b
    +			name
    +			keyword.control.coffee
    +		
    +		
    +			match
    +			\b([a-zA-Z$_]\w*)(\:)\s
    +			name
    +			variable.assignment.coffee
    +			captures
    +			
    +				1
    +				
    +					name
    +					entity.name.function.coffee
    +				
    +				2
    +				
    +					name
    +					keyword.operator.coffee
    +				
    +			
    +		
    +		
    +			match
    +			\b(true|on|yes)\b
    +			name
    +			constant.language.boolean.true.coffee
    +		
    +		
    +			match
    +			\b(false|off|no)\b
    +			name
    +			constant.language.boolean.false.coffee
    +		
    +		
    +			match
    +			\bnull\b
    +			name
    +			constant.language.null.coffee
    +		
    +		
    +			match
    +			\b(super|this|extends)\b
    +			name
    +			variable.language.coffee
    +		
    +		
    +			match
    +			\b(debugger|\\)\b
    +			name
    +			keyword.other.coffee
    +		
    +		
    +			match
    +			!|\$|%|&|\*|\/|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\?|\|\||\:|\*=|(?<!\()/=|%=|\+=|\-=|&=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b
    +			name
    +			keyword.operator.coffee
    +		
    +		
    +			match
    +			\b(Infinity|NaN|undefined)\b
    +			name
    +			constant.language.coffee
    +		
    +		
    +			begin
    +			(?<=[=(:]|^|return)\s*(/)(?![/*+{}?])
    +			beginCaptures
    +			
    +				1
    +				
    +					name
    +					punctuation.definition.string.begin.coffee
    +				
    +			
    +			end
    +			(/)[igm]*
    +			endCaptures
    +			
    +				1
    +				
    +					name
    +					punctuation.definition.string.end.coffee
    +				
    +			
    +			name
    +			string.regexp.coffee
    +			patterns
    +			
    +				
    +					match
    +					\\.
    +					name
    +					constant.character.escape.coffee
    +				
    +			
    +		
    +		
    +			match
    +			\;
    +			name
    +			punctuation.terminator.statement.coffee
    +		
    +		
    +			match
    +			,[ |\t]*
    +			name
    +			meta.delimiter.object.comma.coffee
    +		
    +		
    +			match
    +			\.
    +			name
    +			meta.delimiter.method.period.coffee
    +		
    +		
    +			match
    +			\{|\}
    +			name
    +			meta.brace.curly.coffee
    +		
    +		
    +			match
    +			\(|\)
    +			name
    +			meta.brace.round.coffee
    +		
    +		
    +			match
    +			\[|\]
    +			name
    +			meta.brace.square.coffee
    +		
    +	
    +	scopeName
    +	source.coffee
     	uuid
     	5B520980-A7D5-4E10-8582-1A4C889A8DE5
     
    
    From fbcdc12a9ce404b6500678e8406bbe8afbd03484 Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Wed, 6 Jan 2010 21:21:42 -0500
    Subject: [PATCH 275/303] adding empty return statements -- they return null
    
    ---
     lib/coffee_script/grammar.y                  | 4 +++-
     test/fixtures/execution/test_literals.coffee | 8 +++++++-
     2 files changed, 10 insertions(+), 2 deletions(-)
    
    diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y
    index 8b459ee87c..ea3845ee10 100644
    --- a/lib/coffee_script/grammar.y
    +++ b/lib/coffee_script/grammar.y
    @@ -38,7 +38,8 @@ prechigh
       right    THROW FOR NEW SUPER
       left     EXTENDS
       left     ASSIGN '||=' '&&='
    -  right    RETURN '=>' UNLESS IF ELSE WHILE
    +  right    RETURN
    +  right    '=>' UNLESS IF ELSE WHILE
     preclow
     
     rule
    @@ -124,6 +125,7 @@ rule
       # A return statement.
       Return:
         RETURN Expression                 { result = ReturnNode.new(val[1]) }
    +  | RETURN                            { result = ReturnNode.new(ValueNode.new(Value.new('null'))) }
       ;
     
       # A comment.
    diff --git a/test/fixtures/execution/test_literals.coffee b/test/fixtures/execution/test_literals.coffee
    index 27fec2d36e..6570509ea2 100644
    --- a/test/fixtures/execution/test_literals.coffee
    +++ b/test/fixtures/execution/test_literals.coffee
    @@ -11,4 +11,10 @@ print(!!words.match(regex))
     
     neg: (3 -4)
     
    -print(neg is -1)
    \ No newline at end of file
    +print(neg is -1)
    +
    +
    +func: =>
    +  return if true
    +
    +print(func() is null)
    \ No newline at end of file
    
    From eb9b18376ee13f9dbc8a0a7ad4230539011759dd Mon Sep 17 00:00:00 2001
    From: Jeremy Ashkenas 
    Date: Wed, 6 Jan 2010 22:37:25 -0500
    Subject: [PATCH 276/303] removing the parser.rb from revision control -- it's
     taking up too much space -- you'll have to generate it yourself with rake
     build:parser
    
    ---
     documentation/speed.html | 57 ++++++++++++++++++++++++++++++++++++++++
     1 file changed, 57 insertions(+)
     create mode 100644 documentation/speed.html
    
    diff --git a/documentation/speed.html b/documentation/speed.html
    new file mode 100644
    index 0000000000..e4fcdefcb2
    --- /dev/null
    +++ b/documentation/speed.html
    @@ -0,0 +1,57 @@
    +
    +
    +
    +  
    +  Quickie CoffeeScript Speed Tests
    +  
    +
    +
    +
    +  

    Quickie CoffeeScript Speed Tests

    + + + + + From c49f2d80976890d1a14f21f44661dccd3c2ecef7 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 6 Jan 2010 22:38:03 -0500 Subject: [PATCH 277/303] ignoring the generated parser.rb --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 34de3574ee..1f899cdfe9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ test.coffee parser.output +lib/coffee_script/parser.rb test/fixtures/underscore *.gem \ No newline at end of file From a5c192220059fc7c488d4a98e200803fc40d8659 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 6 Jan 2010 22:41:09 -0500 Subject: [PATCH 278/303] printing a warning when the parser goes missing --- lib/coffee_script/command_line.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index bf7481b051..fc0f5d37af 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -1,7 +1,13 @@ require 'optparse' require 'fileutils' require 'open3' -require File.expand_path(File.dirname(__FILE__) + '/../coffee-script') +begin + require File.expand_path(File.dirname(__FILE__) + '/../coffee-script') +rescue LoadError => e + puts(e.message) + puts("use \"rake build:parser\" to regenerate parser.rb") + exit(1) +end module CoffeeScript From 3e33b4a61854b600a134370b387a0604bba997d5 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 6 Jan 2010 22:57:21 -0500 Subject: [PATCH 279/303] adding a warning about parser.rb to the readme --- README | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README b/README index 79d1679c06..264e9a82ab 100644 --- a/README +++ b/README @@ -36,3 +36,8 @@ The source repository: git://github.com/jashkenas/coffee-script.git + + Warning: + If you're building CoffeeScript from source, the Racc-generated + parser.rb was recently removed from revision control. Use + `rake build:parser` to regenerate it. From cf7079a3791adf3c740f39d53ae6c7d34ef7535f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 6 Jan 2010 23:15:13 -0500 Subject: [PATCH 280/303] fixing the lexer to handle strings that end with a backslash character --- lib/coffee_script/lexer.rb | 6 +++--- test/fixtures/execution/test_literals.coffee | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 742ef132dc..01adeefb9e 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -21,13 +21,13 @@ class Lexer # Token matching regexes. 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 + STRING = /\A(""|''|"(.*?)([^\\]|\\\\)"|'(.*?)([^\\]|\\\\)')/m + JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m OPERATOR = /\A([+\*&|\/\-%=<>:!]+)/ WHITESPACE = /\A([ \t]+)/ COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/ CODE = /\A(=>)/ - REGEX = /\A(\/(.*?)[^\\]\/[imgy]{0,4})/ + REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/ MULTI_DENT = /\A((\n([ \t]*)?)+)/ LAST_DENT = /\n([ \t]*)/ ASSIGNMENT = /\A(:|=)\Z/ diff --git a/test/fixtures/execution/test_literals.coffee b/test/fixtures/execution/test_literals.coffee index 6570509ea2..9d0ef3910c 100644 --- a/test/fixtures/execution/test_literals.coffee +++ b/test/fixtures/execution/test_literals.coffee @@ -17,4 +17,10 @@ print(neg is -1) func: => return if true -print(func() is null) \ No newline at end of file +print(func() is null) + + +str: "\\" +reg: /\\/ + +print(reg(str) and str is '\\') \ No newline at end of file From 9ed8020b84723ed9ee874cae59037041553030ec Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 6 Jan 2010 23:34:56 -0500 Subject: [PATCH 281/303] added the constructor fix for capitalized functions --- lib/coffee_script/nodes.rb | 4 +++- test/fixtures/execution/test_calling_super.coffee | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index f89cd96b02..64463d2964 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -100,7 +100,9 @@ def compile_node(options={}) if node.statement? node.compile(o.merge(:return => true)) else - "#{o[:indent]}return #{node.compile(o)};" + constructor = o[:top] && o[:last_assign] && o[:last_assign][0..0][/[A-Z]/] + prefix = constructor ? "if (#{o[:last_assign]} !== this.constructor) " : '' + "#{o[:indent]}#{prefix}return #{node.compile(o)};" end else ending = node.statement? ? '' : ';' diff --git a/test/fixtures/execution/test_calling_super.coffee b/test/fixtures/execution/test_calling_super.coffee index 8a9d4bc1ff..0dee5757b3 100644 --- a/test/fixtures/execution/test_calling_super.coffee +++ b/test/fixtures/execution/test_calling_super.coffee @@ -6,13 +6,14 @@ FirstChild: => FirstChild extends Base FirstChild.prototype.func: string => super('one/') + string - + SecondChild: => SecondChild extends FirstChild SecondChild.prototype.func: string => super('two/') + string - + ThirdChild: => + this.array: [1, 2, 3] ThirdChild extends SecondChild ThirdChild.prototype.func: string => super('three/') + string From 18c3e79a50afa081298f7ff3d295f7fae6928cb2 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 6 Jan 2010 23:47:36 -0500 Subject: [PATCH 282/303] adding automatic 'return this' for constructors: functions that start with a Capital Letter --- documentation/js/super.js | 8 ++++++-- index.html | 16 ++++++++++++---- lib/coffee_script/nodes.rb | 9 ++++++--- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/documentation/js/super.js b/documentation/js/super.js index ce26399ee7..dc6e099abd 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -6,7 +6,9 @@ return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return this.name = name; + var __a; + __a = this.name = name; + return Snake === this.constructor ? this : __a; }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -16,7 +18,9 @@ return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return this.name = name; + var __a; + __a = this.name = name; + return Horse === this.constructor ? this : __a; }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); diff --git a/index.html b/index.html index dcbabd77a1..d1c3b16894 100644 --- a/index.html +++ b/index.html @@ -1001,7 +1001,9 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return this.name = name; + var __a; + __a = this.name = name; + return Snake === this.constructor ? this : __a; }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -1011,7 +1013,9 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return this.name = name; + var __a; + __a = this.name = name; + return Horse === this.constructor ? this : __a; }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); @@ -1031,7 +1035,9 @@

    Language Reference

    return alert(this.name + " moved " + meters + "m."); }; Snake = function Snake(name) { - return this.name = name; + var __a; + __a = this.name = name; + return Snake === this.constructor ? this : __a; }; Snake.__superClass__ = Animal.prototype; Snake.prototype = new Animal(); @@ -1041,7 +1047,9 @@

    Language Reference

    return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - return this.name = name; + var __a; + __a = this.name = name; + return Horse === this.constructor ? this : __a; }; Horse.__superClass__ = Animal.prototype; Horse.prototype = new Animal(); diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 64463d2964..7ce3fc5b88 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -100,9 +100,12 @@ def compile_node(options={}) if node.statement? node.compile(o.merge(:return => true)) else - constructor = o[:top] && o[:last_assign] && o[:last_assign][0..0][/[A-Z]/] - prefix = constructor ? "if (#{o[:last_assign]} !== this.constructor) " : '' - "#{o[:indent]}#{prefix}return #{node.compile(o)};" + if o[:top] && o[:last_assign] && o[:last_assign][0..0][/[A-Z]/] + temp = o[:scope].free_variable + "#{o[:indent]}#{temp} = #{node.compile(o)};\n#{o[:indent]}return #{o[:last_assign]} === this.constructor ? this : #{temp};" + else + "#{o[:indent]}return #{node.compile(o)};" + end end else ending = node.statement? ? '' : ';' From fab98a22197a552495bdb8dee1fd27a554cf374e Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 7 Jan 2010 09:43:44 -0500 Subject: [PATCH 283/303] mentioning the racc gem in the readme --- README | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README b/README index 264e9a82ab..3659b844f9 100644 --- a/README +++ b/README @@ -37,7 +37,5 @@ The source repository: git://github.com/jashkenas/coffee-script.git - Warning: - If you're building CoffeeScript from source, the Racc-generated - parser.rb was recently removed from revision control. Use - `rake build:parser` to regenerate it. + To build CoffeeScript from source, install the "racc" gem and + run "rake build:parser". Then bin/coffee will work. From c70b6f39b1064fd3e7e911b97a04bb68fc13a2e0 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 7 Jan 2010 20:27:26 -0500 Subject: [PATCH 284/303] weepy's new speedy comprehensions --- documentation/speed.html | 12 ++++++++ lib/coffee_script/nodes.rb | 35 ++++++++++++------------ test/fixtures/generation/each.js | 35 ------------------------ test/fixtures/generation/each_no_wrap.js | 33 ---------------------- test/unit/test_parser.rb | 6 ---- 5 files changed, 29 insertions(+), 92 deletions(-) delete mode 100644 test/fixtures/generation/each.js delete mode 100644 test/fixtures/generation/each_no_wrap.js diff --git a/documentation/speed.html b/documentation/speed.html index e4fcdefcb2..e756437ae4 100644 --- a/documentation/speed.html +++ b/documentation/speed.html @@ -51,6 +51,18 @@

    Quickie CoffeeScript Speed Tests

    __c[__b] = __a[__b]; } }); + + JSLitmus.test('the new comprehensions', function() { + __c = []; __a = arr; + __d = function(num, __b) { + __c.push(num); + }; + if (__a instanceof Array) { + for (__b=0; __b<__a.length; __b++) __d(__a[__b], __b); + } else { + for (__b in __a) { if (__a.hasOwnProperty(__b)) __d(__a[__b], __b); } + } + }); diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7ce3fc5b88..dd79605a88 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -354,7 +354,7 @@ def compile_variables(o) idt = o[:indent] @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable from_val, to_val = @from.compile(o), @to.compile(o) - write("#{idt}#{@from_var} = #{from_val};\n#{idt}#{@to_var} = #{to_val};\n#{idt}") + write("#{@from_var} = #{from_val}; #{@to_var} = #{to_val};\n#{idt}") end def compile_node(o) @@ -625,38 +625,30 @@ def compile_node(o) range = @source.is_a?(ValueNode) && @source.literal.is_a?(RangeNode) && @source.properties.empty? source = range ? @source.literal : @source scope = o[:scope] - name_found = scope.find(@name) index_found = @index && scope.find(@index) + body_dent = o[:indent] + TAB svar = scope.free_variable ivar = range ? name : @index ? @index : scope.free_variable rvar = scope.free_variable unless top_level - tvar = scope.free_variable + fvar = scope.free_variable if range - body_dent = o[:indent] + TAB - var_part, pre_cond, post_cond = '', '', '' index_var = scope.free_variable source_part = source.compile_variables(o) for_part = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++" else index_var = nil - body_dent = o[:indent] + TAB + TAB - source_part = "#{o[:indent]}#{svar} = #{source.compile(o)};\n#{o[:indent]}" - for_part = "#{ivar} in #{svar}" - pre_cond = "\n#{o[:indent] + TAB}if (#{svar}.hasOwnProperty(#{ivar})) {" - var_part = "\n#{body_dent}#{@name} = #{svar}[#{ivar}];" - post_cond = "\n#{o[:indent] + TAB}}" + source_part = "#{svar} = #{source.compile(o)};\n#{o[:indent]}" + for_part = "#{ivar}=0; #{ivar}<#{svar}.length; #{ivar}++" end body = @body - set_result = rvar ? "#{rvar} = [];\n#{o[:indent]}" : '' + set_result = rvar ? "#{o[:indent]}#{rvar} = []; " : o[:indent] return_result = rvar || '' - temp_var = ValueNode.new(LiteralNode.new(tvar)) if top_level body = Expressions.wrap(body) else - body = Expressions.wrap( - AssignNode.new(temp_var, @body.unwrap), - CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [temp_var]) - ) + body = Expressions.wrap(CallNode.new( + ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body.unwrap] + )) end if o[:return] return_result = "return #{return_result}" if o[:return] @@ -668,7 +660,14 @@ def compile_node(o) return_result = "\n#{o[:indent]}#{return_result};" unless top_level body = body.compile(o.merge(:indent => body_dent, :top => true)) - write("#{source_part}#{set_result}for (#{for_part}) {#{pre_cond}#{var_part}\n#{body}#{post_cond}\n#{o[:indent]}}#{return_result}") + vars = range ? @name : "#{@name}, #{ivar}" + func = "#{fvar} = function(#{vars}) {\n#{body}\n#{o[:indent]}};\n#{o[:indent]}" + return write(set_result + source_part + func + "for (#{for_part}) #{fvar}(#{ivar});\n#{o[:indent]}#{return_result}") if range + + call = "#{fvar}(#{svar}[#{ivar}], #{ivar})" + fast = "if (#{svar} instanceof Array) {\n#{o[:indent] + TAB}for (#{for_part}) #{call};\n#{o[:indent]}} else {\n#{o[:indent] + TAB}" + slow = "for (#{ivar} in #{svar}) { if (#{svar}.hasOwnProperty(#{ivar})) #{call}; }\n#{o[:indent]}}\n#{o[:indent]}#{return_result}" + write(set_result + source_part + func + fast + slow) end end diff --git a/test/fixtures/generation/each.js b/test/fixtures/generation/each.js deleted file mode 100644 index 3dfdaa3f77..0000000000 --- a/test/fixtures/generation/each.js +++ /dev/null @@ -1,35 +0,0 @@ -(function(){ - - // The cornerstone, an each implementation. - // Handles objects implementing forEach, arrays, and raw objects. - _.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, i, index, item, key; - index = 0; - try { - if (obj.forEach) { - obj.forEach(iterator, context); - } else if (_.isArray(obj) || _.isArguments(obj)) { - __a = obj; - for (i in __a) { - if (__a.hasOwnProperty(i)) { - item = __a[i]; - iterator.call(context, item, i, obj); - } - } - } else { - __c = _.keys(obj); - for (__d in __c) { - if (__c.hasOwnProperty(__d)) { - key = __c[__d]; - iterator.call(context, obj[key], key, obj); - } - } - } - } catch (e) { - if (e !== breaker) { - throw e; - } - } - return obj; - }; -})(); \ No newline at end of file diff --git a/test/fixtures/generation/each_no_wrap.js b/test/fixtures/generation/each_no_wrap.js deleted file mode 100644 index 40ee9e4cc8..0000000000 --- a/test/fixtures/generation/each_no_wrap.js +++ /dev/null @@ -1,33 +0,0 @@ - -// The cornerstone, an each implementation. -// Handles objects implementing forEach, arrays, and raw objects. -_.each = function each(obj, iterator, context) { - var __a, __b, __c, __d, __e, i, index, item, key; - index = 0; - try { - if (obj.forEach) { - obj.forEach(iterator, context); - } else if (_.isArray(obj) || _.isArguments(obj)) { - __a = obj; - for (i in __a) { - if (__a.hasOwnProperty(i)) { - item = __a[i]; - iterator.call(context, item, i, obj); - } - } - } else { - __c = _.keys(obj); - for (__d in __c) { - if (__c.hasOwnProperty(__d)) { - key = __c[__d]; - iterator.call(context, obj[key], key, obj); - } - } - } - } catch (e) { - if (e !== breaker) { - throw e; - } - } - return obj; -}; \ No newline at end of file diff --git a/test/unit/test_parser.rb b/test/unit/test_parser.rb index dfcc4763ad..ef3feab550 100644 --- a/test/unit/test_parser.rb +++ b/test/unit/test_parser.rb @@ -69,12 +69,6 @@ def test_parsing assert assign.variable.literal == '_' assert assign.value.is_a?(CodeNode) assert assign.value.params == ['obj', 'iterator', 'context'] - assert nodes.compile == File.read('test/fixtures/generation/each.js') - end - - def test_no_wrap - nodes = @par.parse(File.read('test/fixtures/generation/each.coffee')) - assert nodes.compile(:no_wrap => true) == File.read('test/fixtures/generation/each_no_wrap.js') end end From 69908be85c2051feff43523d92e9a3c524bc4f12 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 7 Jan 2010 20:55:30 -0500 Subject: [PATCH 285/303] cleaned up nodes.rb indentation code with an idt method --- lib/coffee_script/nodes.rb | 85 +++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index dd79605a88..24c9130133 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -29,15 +29,20 @@ def write(code) # already been asked to return the result. def compile(o={}) @options = o.dup + @indent = o[:indent] top = self.is_a?(ForNode) ? @options[:top] : @options.delete(:top) closure = statement? && !statement_only? && !top && !@options[:return] closure ? compile_closure(@options) : compile_node(@options) end def compile_closure(o={}) - indent = o[:indent] o[:indent] += TAB - "(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()" + "(function() {\n#{compile_node(o.merge(:return => true))}\n#{idt}})()" + end + + # Quick method for the current indentation level, plus tabs out. + def idt(tabs=0) + @indent + (TAB * tabs) end # Default implementations of the common node methods. @@ -95,6 +100,7 @@ def compile(o={}) def compile_node(options={}) compiled = @expressions.map do |node| o = options.dup + @indent = o[:indent] returns = o.delete(:return) if last?(node) && returns && !node.statement_only? if node.statement? @@ -102,14 +108,14 @@ def compile_node(options={}) else if o[:top] && o[:last_assign] && o[:last_assign][0..0][/[A-Z]/] temp = o[:scope].free_variable - "#{o[:indent]}#{temp} = #{node.compile(o)};\n#{o[:indent]}return #{o[:last_assign]} === this.constructor ? this : #{temp};" + "#{idt}#{temp} = #{node.compile(o)};\n#{idt}return #{o[:last_assign]} === this.constructor ? this : #{temp};" else - "#{o[:indent]}return #{node.compile(o)};" + "#{idt}return #{node.compile(o)};" end end else ending = node.statement? ? '' : ';' - indent = node.statement? ? '' : o[:indent] + indent = node.statement? ? '' : idt "#{indent}#{node.compile(o.merge(:top => true))}#{ending}" end end @@ -119,6 +125,7 @@ def compile_node(options={}) # If this is the top-level Expressions, wrap everything in a safety closure. def compile_root(o={}) indent = o[:no_wrap] ? '' : TAB + @indent = indent o.merge!(:indent => indent, :scope => Scope.new(nil, self)) code = o[:no_wrap] ? compile_node(o) : compile_with_declarations(o) code.gsub!(STRIP_TRAILING_WHITESPACE, '') @@ -128,7 +135,7 @@ def compile_root(o={}) def compile_with_declarations(o={}) code = compile_node(o) decls = '' - decls = "#{o[:indent]}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self) + decls = "#{idt}var #{o[:scope].declared_variables.join(', ')};\n" if o[:scope].declarations?(self) decls + code end @@ -156,7 +163,7 @@ def statement? def compile_node(o) val = CONVERSIONS[@value.to_s] || @value.to_s - indent = statement? ? o[:indent] : '' + indent = statement? ? idt : '' ending = statement? ? ';' : '' write("#{indent}#{val}#{ending}") end @@ -175,7 +182,7 @@ def initialize(expression) def compile_node(o) return write(@expression.compile(o.merge(:return => true))) if @expression.statement? compiled = @expression.compile(o) - write(@expression.statement? ? "#{compiled}\n#{o[:indent]}return null;" : "#{o[:indent]}return #{compiled};") + write(@expression.statement? ? "#{compiled}\n#{idt}return null;" : "#{idt}return #{compiled};") end end @@ -189,7 +196,7 @@ def initialize(lines) end def compile_node(o={}) - delimiter = "\n#{o[:indent]}//" + delimiter = "\n#{idt}//" comment = "#{delimiter}#{@lines.join(delimiter)}" write(comment) end @@ -263,8 +270,8 @@ def initialize(sub_object, super_object) def compile_node(o={}) sub, sup = @sub_object.compile(o), @super_object.compile(o) - "#{o[:indent]}#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" + - "#{sub}.prototype = new #{sup}();\n#{o[:indent]}" + + "#{idt}#{sub}.__superClass__ = #{sup}.prototype;\n#{idt}" + + "#{sub}.prototype = new #{sup}();\n#{idt}" + "#{sub}.prototype.constructor = #{sub};" end @@ -351,7 +358,7 @@ def greater_operator end def compile_variables(o) - idt = o[:indent] + @indent = o[:indent] @from_var, @to_var = o[:scope].free_variable, o[:scope].free_variable from_val, to_val = @from.compile(o), @to.compile(o) write("#{@from_var} = #{from_val}; #{@to_var} = #{to_val};\n#{idt}") @@ -417,7 +424,7 @@ 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)}" - write(o[:return] ? "#{o[:indent]}return (#{val})" : val) + write(o[:return] ? "#{idt}return (#{val})" : val) end def compile_splice(o) @@ -487,7 +494,6 @@ def initialize(params, body) def compile_node(o) shared_scope = o.delete(:shared_scope) - indent = o[:indent] o[:scope] = shared_scope || Scope.new(o[:scope], @body) o[:return] = true o[:top] = true @@ -502,7 +508,7 @@ def compile_node(o) @params.each {|id| o[:scope].parameter(id.to_s) } code = @body.compile_with_declarations(o) name_part = name ? " #{name}" : '' - write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{indent}}") + write("function#{name_part}(#{@params.join(', ')}) {\n#{code}\n#{idt}}") end end @@ -546,7 +552,6 @@ def initialize(properties = []) # AssignNodes get interleaved correctly, with no trailing commas or # commas affixed to comments. TODO: Extract this and add it to ArrayNode. def compile_node(o) - indent = o[:indent] o[:indent] += TAB joins = Hash.new("\n") non_comments = @properties.select {|p| !p.is_a?(CommentNode) } @@ -554,10 +559,10 @@ def compile_node(o) props = @properties.map { |prop| join = joins[prop] join = '' if prop == @properties.last - idt = prop.is_a?(CommentNode) ? '' : o[:indent] - "#{idt}#{prop.compile(o)}#{join}" + indent = prop.is_a?(CommentNode) ? '' : idt(1) + "#{indent}#{prop.compile(o)}#{join}" }.join('') - write("{\n#{props}\n#{indent}}") + write("{\n#{props}\n#{idt}}") end end @@ -570,14 +575,13 @@ def initialize(objects=[]) end def compile_node(o) - indent = o[:indent] o[:indent] += TAB objects = @objects.map { |obj| code = obj.compile(o) obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" : obj == @objects.last ? code : "#{code}, " }.join('') - ending = objects.include?("\n") ? "\n#{indent}]" : ']' + ending = objects.include?("\n") ? "\n#{idt}]" : ']' write("[#{objects}#{ending}") end end @@ -595,12 +599,11 @@ def initialize(condition, body) def compile_node(o) returns = o.delete(:return) - indent = o[:indent] o[:indent] += TAB o[:top] = true cond = @condition.compile(o) - post = returns ? "\n#{indent}return null;" : '' - write("#{indent}while (#{cond}) {\n#{@body.compile(o)}\n#{indent}}#{post}") + post = returns ? "\n#{idt}return null;" : '' + write("#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}") end end @@ -626,7 +629,7 @@ def compile_node(o) source = range ? @source.literal : @source scope = o[:scope] index_found = @index && scope.find(@index) - body_dent = o[:indent] + TAB + body_dent = idt(1) svar = scope.free_variable ivar = range ? name : @index ? @index : scope.free_variable rvar = scope.free_variable unless top_level @@ -637,11 +640,11 @@ def compile_node(o) for_part = "#{index_var}=0, #{source.compile(o.merge(:index => ivar, :step => @step))}, #{index_var}++" else index_var = nil - source_part = "#{svar} = #{source.compile(o)};\n#{o[:indent]}" + source_part = "#{svar} = #{source.compile(o)};\n#{idt}" for_part = "#{ivar}=0; #{ivar}<#{svar}.length; #{ivar}++" end body = @body - set_result = rvar ? "#{o[:indent]}#{rvar} = []; " : o[:indent] + set_result = rvar ? "#{idt}#{rvar} = []; " : idt return_result = rvar || '' if top_level body = Expressions.wrap(body) @@ -658,15 +661,15 @@ def compile_node(o) body = Expressions.wrap(IfNode.new(@filter, @body)) end - return_result = "\n#{o[:indent]}#{return_result};" unless top_level + return_result = "\n#{idt}#{return_result};" unless top_level body = body.compile(o.merge(:indent => body_dent, :top => true)) vars = range ? @name : "#{@name}, #{ivar}" - func = "#{fvar} = function(#{vars}) {\n#{body}\n#{o[:indent]}};\n#{o[:indent]}" - return write(set_result + source_part + func + "for (#{for_part}) #{fvar}(#{ivar});\n#{o[:indent]}#{return_result}") if range + func = "#{fvar} = function(#{vars}) {\n#{body}\n#{idt}};\n#{idt}" + return write(set_result + source_part + func + "for (#{for_part}) #{fvar}(#{ivar});\n#{idt}#{return_result}") if range call = "#{fvar}(#{svar}[#{ivar}], #{ivar})" - fast = "if (#{svar} instanceof Array) {\n#{o[:indent] + TAB}for (#{for_part}) #{call};\n#{o[:indent]}} else {\n#{o[:indent] + TAB}" - slow = "for (#{ivar} in #{svar}) { if (#{svar}.hasOwnProperty(#{ivar})) #{call}; }\n#{o[:indent]}}\n#{o[:indent]}#{return_result}" + fast = "if (#{svar} instanceof Array) {\n#{idt(1)}for (#{for_part}) #{call};\n#{idt}} else {\n#{idt(1)}" + slow = "for (#{ivar} in #{svar}) { if (#{svar}.hasOwnProperty(#{ivar})) #{call}; }\n#{idt}}\n#{idt}#{return_result}" write(set_result + source_part + func + fast + slow) end end @@ -682,13 +685,12 @@ def initialize(try, error, recovery, finally=nil) end def compile_node(o) - indent = o[:indent] o[:indent] += TAB o[:top] = true error_part = @error ? " (#{@error}) " : ' ' - catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{indent}}" - finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{indent}}" - write("#{indent}try {\n#{@try.compile(o)}\n#{indent}}#{catch_part}#{finally_part}") + catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}" + finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{idt}}" + write("#{idt}try {\n#{@try.compile(o)}\n#{idt}}#{catch_part}#{finally_part}") end end @@ -703,7 +705,7 @@ def initialize(expression) end def compile_node(o) - write("#{o[:indent]}throw #{@expression.compile(o)};") + write("#{idt}throw #{@expression.compile(o)};") end end @@ -796,18 +798,17 @@ def compile_node(o) # Compile the IfNode as a regular if-else statement. Flattened chains # force sub-else bodies into statement form. def compile_statement(o) - indent = o[:indent] child = o.delete(:chain_child) cond_o = o.dup cond_o.delete(:return) o[:indent] += TAB o[:top] = true - if_dent = child ? '' : indent - if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{indent}}" + if_dent = child ? '' : idt + if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}" return if_part unless @else_body else_part = chain? ? - " else #{@else_body.compile(o.merge(:indent => indent, :chain_child => true))}" : - " else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{indent}}" + " else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" : + " else {\n#{Expressions.wrap(@else_body).compile(o)}\n#{idt}}" if_part + else_part end From 30dca132bd9b30519d8ad7d2d7681b04974f01cc Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 7 Jan 2010 20:57:23 -0500 Subject: [PATCH 286/303] more node cleaning, using idt() instead of TAB --- lib/coffee_script/nodes.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 24c9130133..d9229959b7 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -36,7 +36,7 @@ def compile(o={}) end def compile_closure(o={}) - o[:indent] += TAB + o[:indent] += idt(1) "(function() {\n#{compile_node(o.merge(:return => true))}\n#{idt}})()" end @@ -497,7 +497,7 @@ def compile_node(o) o[:scope] = shared_scope || Scope.new(o[:scope], @body) o[:return] = true o[:top] = true - o[:indent] += TAB + o[:indent] = idt(1) o.delete(:no_wrap) name = o.delete(:immediate_assign) if @params.last.is_a?(ParamSplatNode) @@ -552,7 +552,7 @@ def initialize(properties = []) # AssignNodes get interleaved correctly, with no trailing commas or # commas affixed to comments. TODO: Extract this and add it to ArrayNode. def compile_node(o) - o[:indent] += TAB + o[:indent] = idt(1) joins = Hash.new("\n") non_comments = @properties.select {|p| !p.is_a?(CommentNode) } non_comments.each {|p| joins[p] = p == non_comments.last ? "\n" : ",\n" } @@ -575,7 +575,7 @@ def initialize(objects=[]) end def compile_node(o) - o[:indent] += TAB + o[:indent] = idt(1) objects = @objects.map { |obj| code = obj.compile(o) obj.is_a?(CommentNode) ? "\n#{code}\n#{o[:indent]}" : @@ -599,7 +599,7 @@ def initialize(condition, body) def compile_node(o) returns = o.delete(:return) - o[:indent] += TAB + o[:indent] = idt(1) o[:top] = true cond = @condition.compile(o) post = returns ? "\n#{idt}return null;" : '' @@ -685,7 +685,7 @@ def initialize(try, error, recovery, finally=nil) end def compile_node(o) - o[:indent] += TAB + o[:indent] = idt(1) o[:top] = true error_part = @error ? " (#{@error}) " : ' ' catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}" @@ -798,13 +798,13 @@ def compile_node(o) # Compile the IfNode as a regular if-else statement. Flattened chains # force sub-else bodies into statement form. def compile_statement(o) - child = o.delete(:chain_child) - cond_o = o.dup + child = o.delete(:chain_child) + cond_o = o.dup cond_o.delete(:return) - o[:indent] += TAB - o[:top] = true - if_dent = child ? '' : idt - if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}" + o[:indent] = idt(1) + o[:top] = true + if_dent = child ? '' : idt + if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}" return if_part unless @else_body else_part = chain? ? " else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" : From d416c184db7d6034df616db1146c890e345a9e01 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Thu, 7 Jan 2010 21:10:25 -0500 Subject: [PATCH 287/303] separating out the --no-wrap and the --globals arguments, which shouldn't be jammed together --- lib/coffee_script/command_line.rb | 6 +++- .../narwhal/coffee-script.coffee | 10 +++---- .../narwhal/lib/coffee-script.js | 30 +++++++++---------- .../narwhal/lib/coffee-script/loader.js | 4 +-- lib/coffee_script/nodes.rb | 3 +- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/lib/coffee_script/command_line.rb b/lib/coffee_script/command_line.rb index fc0f5d37af..30ad1e11a0 100644 --- a/lib/coffee_script/command_line.rb +++ b/lib/coffee_script/command_line.rb @@ -139,6 +139,7 @@ def compile(script, source='error') begin options = {} options[:no_wrap] = true if @options[:no_wrap] + options[:globals] = true if @options[:globals] CoffeeScript.compile(script, options) rescue CoffeeScript::ParseError, SyntaxError => e STDERR.puts "#{source}: #{e.message}" @@ -193,9 +194,12 @@ def parse_options opts.on('-v', '--verbose', 'print at every step of code generation') do |v| ENV['VERBOSE'] = 'true' end - opts.on('-n', '--no-wrap', 'raw output, no safety wrapper or vars') do |n| + opts.on('-n', '--no-wrap', 'raw output, no function safety wrapper') do |n| @options[:no_wrap] = true end + opts.on('-g', '--globals', 'attach all top-level variable as globals') do |n| + @options[:globals] = true + end opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i| install_bundle exit diff --git a/lib/coffee_script/narwhal/coffee-script.coffee b/lib/coffee_script/narwhal/coffee-script.coffee index d687c42b32..d3bc2b338e 100644 --- a/lib/coffee_script/narwhal/coffee-script.coffee +++ b/lib/coffee_script/narwhal/coffee-script.coffee @@ -29,7 +29,7 @@ exports.run: args => while true try system.stdout.write('coffee> ').flush() - result: exports.evalCS(Readline.readline()) + result: exports.evalCS(Readline.readline(), ['--globals']) print(result) if result isnt undefined catch e print(e) @@ -41,15 +41,15 @@ exports.compileFile: path => coffee.stdout.read() # Compile a string of CoffeeScript into JavaScript. -exports.compile: source => - coffee: OS.popen([coffeePath, "--eval", "--no-wrap"]) +exports.compile: source, flags => + coffee: OS.popen([coffeePath, "--eval", "--no-wrap"].concat(flags or [])) coffee.stdin.write(source).flush().close() checkForErrors(coffee) coffee.stdout.read() # Evaluating a string of CoffeeScript first compiles it externally. -exports.evalCS: source => - eval(exports.compile(source)) +exports.evalCS: source, flags => + eval(exports.compile(source, flags)) # Make a factory for the CoffeeScript environment. exports.makeNarwhalFactory: path => diff --git a/lib/coffee_script/narwhal/lib/coffee-script.js b/lib/coffee_script/narwhal/lib/coffee-script.js index 99a2c40c12..7e9fb6322f 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script.js +++ b/lib/coffee_script/narwhal/lib/coffee-script.js @@ -20,25 +20,24 @@ // Run a simple REPL, round-tripping to the CoffeeScript compiler for every // command. exports.run = function run(args) { - var __a, __b, __c, i, path, result; + var __a, __b, i, result; if (args.length) { __a = args; - __b = []; - for (i in __a) { - if (__a.hasOwnProperty(i)) { - path = __a[i]; - exports.evalCS(File.read(path)); - __c = delete args[i]; - __b.push(__c); - } + __b = function(path, i) { + exports.evalCS(File.read(path)); + delete args[i]; + }; + if (__a instanceof Array) { + for (i=0; i<__a.length; i++) __b(__a[i], i); + } else { + for (i in __a) { if (__a.hasOwnProperty(i)) __b(__a[i], i); } } - __b; return true; } while (true) { try { system.stdout.write('coffee> ').flush(); - result = exports.evalCS(Readline.readline()); + result = exports.evalCS(Readline.readline(), ['--globals']); if (result !== undefined) { print(result); } @@ -46,6 +45,7 @@ print(e); } } + return null; }; // Compile a given CoffeeScript file into JavaScript. exports.compileFile = function compileFile(path) { @@ -55,16 +55,16 @@ return coffee.stdout.read(); }; // Compile a string of CoffeeScript into JavaScript. - exports.compile = function compile(source) { + exports.compile = function compile(source, flags) { var coffee; - coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]); + coffee = OS.popen([coffeePath, "--eval", "--no-wrap"].concat(flags || [])); coffee.stdin.write(source).flush().close(); checkForErrors(coffee); return coffee.stdout.read(); }; // Evaluating a string of CoffeeScript first compiles it externally. - exports.evalCS = function evalCS(source) { - return eval(exports.compile(source)); + exports.evalCS = function evalCS(source, flags) { + return eval(exports.compile(source, flags)); }; // Make a factory for the CoffeeScript environment. exports.makeNarwhalFactory = function makeNarwhalFactory(path) { diff --git a/lib/coffee_script/narwhal/lib/coffee-script/loader.js b/lib/coffee_script/narwhal/lib/coffee-script/loader.js index 24b7ef27bb..9fc8354bc9 100644 --- a/lib/coffee_script/narwhal/lib/coffee-script/loader.js +++ b/lib/coffee_script/narwhal/lib/coffee-script/loader.js @@ -8,9 +8,9 @@ // Reload the coffee-script environment from source. reload: function reload(topId, path) { coffeescript = coffeescript || require('coffee-script'); - return (factories[topId] = function() { + return factories[topId] = function() { return coffeescript.makeNarwhalFactory(path); - }); + }; }, // Ensure that the coffee-script environment is loaded. load: function load(topId, path) { diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index d9229959b7..1370299bc0 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -127,7 +127,7 @@ def compile_root(o={}) indent = o[:no_wrap] ? '' : TAB @indent = indent o.merge!(:indent => indent, :scope => Scope.new(nil, self)) - code = o[:no_wrap] ? compile_node(o) : compile_with_declarations(o) + code = o[:globals] ? compile_node(o) : compile_with_declarations(o) code.gsub!(STRIP_TRAILING_WHITESPACE, '') o[:no_wrap] ? code : "(function(){\n#{code}\n})();" end @@ -499,6 +499,7 @@ def compile_node(o) o[:top] = true o[:indent] = idt(1) o.delete(:no_wrap) + o.delete(:globals) name = o.delete(:immediate_assign) if @params.last.is_a?(ParamSplatNode) splat = @params.pop From 8b3926fb0c5830ad4eabca035a313e2b925ca8b6 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 8 Jan 2010 09:35:02 -0500 Subject: [PATCH 288/303] making extends equivalent to the Google Closure version --- lib/coffee_script/nodes.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 1370299bc0..3fc6d6e986 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -269,9 +269,12 @@ def initialize(sub_object, super_object) end def compile_node(o={}) + constructor = o[:scope].free_variable sub, sup = @sub_object.compile(o), @super_object.compile(o) - "#{idt}#{sub}.__superClass__ = #{sup}.prototype;\n#{idt}" + - "#{sub}.prototype = new #{sup}();\n#{idt}" + + "#{idt}#{constructor} = function(){};\n#{idt}" + + "#{constructor}.prototype = #{sup}.prototype;\n#{idt}" + + "#{sub}.__superClass__ = #{sup}.prototype;\n#{idt}" + + "#{sub}.prototype = new #{constructor}();\n#{idt}" + "#{sub}.prototype.constructor = #{sub};" end From 4c3f00cf7792bb5840a0c14bad92c19b1aeaf627 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Fri, 8 Jan 2010 10:54:44 -0500 Subject: [PATCH 289/303] fixing calling super from constructors --- lib/coffee_script/nodes.rb | 4 +++- test/fixtures/execution/test_calling_super.coffee | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 3fc6d6e986..ce7275cad3 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -243,7 +243,9 @@ def compile_node(o) def compile_super(args, o) methname = o[:last_assign] arg_part = args.empty? ? '' : ", #{args}" - "#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})" + meth = o[:proto_assign] ? "#{o[:proto_assign]}.__superClass__.#{methname}" : + "#{methname}.__superClass__.constructor" + "#{meth}.call(this#{arg_part})" end def compile_splat(o) diff --git a/test/fixtures/execution/test_calling_super.coffee b/test/fixtures/execution/test_calling_super.coffee index 0dee5757b3..65de1b0cdf 100644 --- a/test/fixtures/execution/test_calling_super.coffee +++ b/test/fixtures/execution/test_calling_super.coffee @@ -22,3 +22,17 @@ result: (new ThirdChild()).func('four') print(result is 'zero/one/two/three/four') + +TopClass: arg => + this.prop: 'top-' + arg + +SuperClass: arg => + super('super-' + arg) + +SubClass: => + super('sub') + +SuperClass extends TopClass +SubClass extends SuperClass + +print((new SubClass()).prop is 'top-super-sub') \ No newline at end of file From ae58e2ec6ce3c27e6cf39233cdba5966cd5f1af1 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 9 Jan 2010 11:51:32 -0500 Subject: [PATCH 290/303] adding body-less while expressions --- examples/poignant.coffee | 6 +++--- lib/coffee_script/grammar.y | 1 + lib/coffee_script/nodes.rb | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/poignant.coffee b/examples/poignant.coffee index 9714c980a3..9ed24328c8 100644 --- a/examples/poignant.coffee +++ b/examples/poignant.coffee @@ -14,9 +14,9 @@ # end LotteryTicket: { - get_picks: => this.picks - set_picks: nums => this.picks: nums - get_purchase: => this.purchase + get_picks: => this.picks + set_picks: nums => this.picks: nums + get_purchase: => this.purchase set_purchase: amount => this.purchase: amount } diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index ea3845ee10..7c2ee5bb0b 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -340,6 +340,7 @@ rule # The while loop. (there is no do..while). While: WHILE Expression Block { result = WhileNode.new(val[1], val[2]) } + | WHILE Expression { result = WhileNode.new(val[1], nil) } ; # Array comprehensions, including guard and current index. diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index ce7275cad3..1419ff2f42 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -609,6 +609,7 @@ def compile_node(o) o[:top] = true cond = @condition.compile(o) post = returns ? "\n#{idt}return null;" : '' + return write("#{idt}while (#{cond});#{post}") if @body.nil? write("#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}") end end From 7befbddae217ec96bb5d9921d569291ad377f992 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 9 Jan 2010 11:58:50 -0500 Subject: [PATCH 291/303] adding test for body-less while, using null as the body so JSLint doesn't get in a tizzy --- lib/coffee_script/nodes.rb | 2 +- test/fixtures/execution/test_literals.coffee | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 1419ff2f42..fd1d5b1c39 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -609,7 +609,7 @@ def compile_node(o) o[:top] = true cond = @condition.compile(o) post = returns ? "\n#{idt}return null;" : '' - return write("#{idt}while (#{cond});#{post}") if @body.nil? + return write("#{idt}while (#{cond}) null;#{post}") if @body.nil? write("#{idt}while (#{cond}) {\n#{@body.compile(o)}\n#{idt}}#{post}") end end diff --git a/test/fixtures/execution/test_literals.coffee b/test/fixtures/execution/test_literals.coffee index 9d0ef3910c..2262434f5c 100644 --- a/test/fixtures/execution/test_literals.coffee +++ b/test/fixtures/execution/test_literals.coffee @@ -23,4 +23,10 @@ print(func() is null) str: "\\" reg: /\\/ -print(reg(str) and str is '\\') \ No newline at end of file +print(reg(str) and str is '\\') + + +i: 10 +while i -= 1 + +print(i is 0) \ No newline at end of file From 2319affa6118f5cfff1ec4dcf79388617f19fd15 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 9 Jan 2010 12:12:38 -0500 Subject: [PATCH 292/303] allowing chained calls broken up over multiple lines with periods at the front (jQuery-style) --- lib/coffee_script/lexer.rb | 6 ++++-- .../execution/test_chained_calls.coffee | 21 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 01adeefb9e..1b682a54ea 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -28,7 +28,7 @@ class Lexer COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/ CODE = /\A(=>)/ REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/ - MULTI_DENT = /\A((\n([ \t]*)?)+)/ + MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/ LAST_DENT = /\n([ \t]*)/ ASSIGNMENT = /\A(:|=)\Z/ @@ -139,7 +139,9 @@ def indent_token return false unless indent = @chunk[MULTI_DENT, 1] @line += indent.scan(MULTILINER).size @i += indent.size - return suppress_newlines(indent) if last_value.to_s.match(NO_NEWLINE) && last_value != "=>" + next_character = @chunk[MULTI_DENT, 4] + no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && last_value != "=>") + return suppress_newlines(indent) if no_newlines size = indent.scan(LAST_DENT).last.last.length return newline_token(indent) if size == @indent if size > @indent diff --git a/test/fixtures/execution/test_chained_calls.coffee b/test/fixtures/execution/test_chained_calls.coffee index 4cf76ec3a9..d48b50db82 100644 --- a/test/fixtures/execution/test_chained_calls.coffee +++ b/test/fixtures/execution/test_chained_calls.coffee @@ -2,4 +2,23 @@ identity_wrap: x => => x result: identity_wrap(identity_wrap(true))()() -print(result) \ No newline at end of file +print(result) + + +str: 'god' + +result: str. + split(''). + reverse(). + reverse(). + reverse() + +print(result.join('') is 'dog') + +result: str + .split('') + .reverse() + .reverse() + .reverse() + +print(result.join('') is 'dog') \ No newline at end of file From 8e3922b6c6ce4201ec51620b542aa48f5a5fa215 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 9 Jan 2010 13:25:44 -0500 Subject: [PATCH 293/303] allowing comments in the middle of switch statements --- lib/coffee_script/grammar.y | 2 +- lib/coffee_script/nodes.rb | 11 +++++++++-- test/fixtures/execution/test_switch.coffee | 6 +++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 7c2ee5bb0b..5bacc2aac6 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -385,7 +385,7 @@ rule LEADING_WHEN Expression Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } | LEADING_WHEN Expression Block Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) } - | Comment + | Comment Terminator When { result = val[2].add_comment(val[0]) } ; # The most basic form of "if". diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index fd1d5b1c39..ec209f1c2c 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -769,6 +769,11 @@ def <<(else_body) self end + def add_comment(comment) + @comment = comment + self + end + def force_statement @tags[:statement] = true self @@ -795,7 +800,7 @@ def chain? # The IfNode only compiles into a statement if either of the bodies needs # to be a statement. def statement? - @is_statement ||= !!(@tags[:statement] || @body.statement? || (@else_body && @else_body.statement?)) + @is_statement ||= !!(@comment || @tags[:statement] || @body.statement? || (@else_body && @else_body.statement?)) end def compile_node(o) @@ -811,7 +816,9 @@ def compile_statement(o) o[:indent] = idt(1) o[:top] = true if_dent = child ? '' : idt - if_part = "#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}" + com_dent = child ? idt : '' + prefix = @comment ? @comment.compile(cond_o) + "\n#{com_dent}" : '' + if_part = "#{prefix}#{if_dent}if (#{@condition.compile(cond_o)}) {\n#{Expressions.wrap(@body).compile(o)}\n#{idt}}" return if_part unless @else_body else_part = chain? ? " else #{@else_body.compile(o.merge(:indent => idt, :chain_child => true))}" : diff --git a/test/fixtures/execution/test_switch.coffee b/test/fixtures/execution/test_switch.coffee index dad4335896..9bdbf0f011 100644 --- a/test/fixtures/execution/test_switch.coffee +++ b/test/fixtures/execution/test_switch.coffee @@ -7,7 +7,11 @@ result: switch num true false when 10 then true + + + # Mid-switch comment with whitespace + # and multi line when 11 then false else false - + print(result) From 9498ef9fe49839a2c79dc0e61f413bc0ade4a147 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 10 Jan 2010 12:21:06 -0500 Subject: [PATCH 294/303] ignoring 'presentation' folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1f899cdfe9..346237661e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +presentation test.coffee parser.output lib/coffee_script/parser.rb From 8ea75290b5e3db4c785d42ae6b1121b36740be7f Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 10 Jan 2010 13:18:44 -0500 Subject: [PATCH 295/303] better indentation for compile_closure --- lib/coffee_script/nodes.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index ec209f1c2c..f2c4bd02e9 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -36,8 +36,9 @@ def compile(o={}) end def compile_closure(o={}) - o[:indent] += idt(1) - "(function() {\n#{compile_node(o.merge(:return => true))}\n#{idt}})()" + indent = o[:indent] + @indent = (o[:indent] = idt(1)) + "(function() {\n#{compile_node(o.merge(:return => true))}\n#{indent}})()" end # Quick method for the current indentation level, plus tabs out. From 13fc8aea04364b97ae1975d9497008d9babb54f4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 10 Jan 2010 14:45:44 -0500 Subject: [PATCH 296/303] removing object comprehensions from CoffeeScript, they were necessitating the generation of too much ugly JS --- coffee-script.gemspec | 4 +- .../coffee/object_comprehensions.coffee | 3 - documentation/index.html.erb | 15 +- documentation/js/array_comprehensions.js | 36 ++-- documentation/js/expressions_comprehension.js | 14 +- documentation/js/object_comprehensions.js | 20 -- documentation/js/overview.js | 14 +- documentation/js/range_comprehensions.js | 7 +- documentation/js/super.js | 16 +- index.html | 187 +++++++----------- lib/coffee-script.rb | 2 +- lib/coffee_script/nodes.rb | 11 +- package.json | 2 +- .../execution/test_array_comprehension.coffee | 8 - 14 files changed, 128 insertions(+), 211 deletions(-) delete mode 100644 documentation/coffee/object_comprehensions.coffee delete mode 100644 documentation/js/object_comprehensions.js diff --git a/coffee-script.gemspec b/coffee-script.gemspec index ebf191b694..b1114071ac 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.1' # Keep version in sync with coffee-script.rb - s.date = '2010-1-5' + s.version = '0.2.2' # Keep version in sync with coffee-script.rb + s.date = '2010-1-10' s.homepage = "http://jashkenas.github.com/coffee-script/" s.summary = "The CoffeeScript Compiler" diff --git a/documentation/coffee/object_comprehensions.coffee b/documentation/coffee/object_comprehensions.coffee deleted file mode 100644 index 8b637082b2..0000000000 --- a/documentation/coffee/object_comprehensions.coffee +++ /dev/null @@ -1,3 +0,0 @@ -years_old: {max: 10, ida: 9, tim: 11} - -ages: child + " is " + age for age, child in years_old \ No newline at end of file diff --git a/documentation/index.html.erb b/documentation/index.html.erb index 9a927bf304..d50bc725e6 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -51,7 +51,7 @@

    Latest Version: - 0.2.1 + 0.2.2

    Table of Contents

    @@ -237,7 +237,7 @@ coffee --print app/scripts/*.coffee > concatenation.js

    You can use newlines to break up your expression into smaller pieces, - as long as CoffeeScript can tell that the line hasn't finished + as long as CoffeeScript can tell that the line hasn't finished (similar to how Ruby handles it). For example, if the line ends in an operator, dot, or keyword.

    @@ -571,6 +571,17 @@ coffee --print app/scripts/*.coffee > concatenation.js

    Change Log

    +

    + 0.2.2 + The "splat" symbol has been changed from a prefix asterisk *, to + a postfix ellipsis .... Added JavaScript's in operator, + empty return statements, and empty while loops. + Constructor functions that start with capital letters now include a + safety check to make sure that the new instance of the object is returned. + The extends keyword now functions identically to goog.inherits + in Google's Closure Library. +

    +

    0.2.1 Arguments objects are now converted into real arrays when referenced. diff --git a/documentation/js/array_comprehensions.js b/documentation/js/array_comprehensions.js index 089b9fcf35..c65579059a 100644 --- a/documentation/js/array_comprehensions.js +++ b/documentation/js/array_comprehensions.js @@ -1,32 +1,24 @@ (function(){ - var __a, __b, __c, __d, __e, __f, __g, __h, __i, __j, food, lunch, roid, roid2; + var __a, __b, __c, __d, __e, __f, __g, lunch; // Eat lunch. lunch = (function() { - __a = ['toast', 'cheese', 'wine']; - __c = []; - for (__b in __a) { - if (__a.hasOwnProperty(__b)) { - food = __a[__b]; - __d = eat(food); - __c.push(__d); - } + __c = []; __a = ['toast', 'cheese', 'wine']; + for (__b=0; __b<__a.length; __b++) { + food = __a[__b]; + __c.push(eat(food)); } return __c; })(); // Naive collision detection. - __e = asteroids; - for (__f in __e) { - if (__e.hasOwnProperty(__f)) { - roid = __e[__f]; - __h = asteroids; - for (__i in __h) { - if (__h.hasOwnProperty(__i)) { - roid2 = __h[__i]; - if (roid !== roid2) { - if (roid.overlaps(roid2)) { - roid.explode(); - } - } + __d = asteroids; + for (__e=0; __e<__d.length; __e++) { + roid = __d[__e]; + __f = asteroids; + for (__g=0; __g<__f.length; __g++) { + roid2 = __f[__g]; + if (roid !== roid2) { + if (roid.overlaps(roid2)) { + roid.explode(); } } } diff --git a/documentation/js/expressions_comprehension.js b/documentation/js/expressions_comprehension.js index 5dea5cf044..7cdcbe7e5c 100644 --- a/documentation/js/expressions_comprehension.js +++ b/documentation/js/expressions_comprehension.js @@ -1,15 +1,11 @@ (function(){ - var __a, __b, __c, globals, name, property; + var __a, __b, globals, name; // The first ten global properties. globals = ((function() { - __a = window; - __b = []; - for (name in __a) { - if (__a.hasOwnProperty(name)) { - property = __a[name]; - __c = name; - __b.push(__c); - } + __b = []; __a = window; + for (name=0; name<__a.length; name++) { + property = __a[name]; + __b.push(name); } return __b; })()).slice(0, 10); diff --git a/documentation/js/object_comprehensions.js b/documentation/js/object_comprehensions.js deleted file mode 100644 index 757ad863c6..0000000000 --- a/documentation/js/object_comprehensions.js +++ /dev/null @@ -1,20 +0,0 @@ -(function(){ - var __a, __b, __c, age, ages, child, years_old; - years_old = { - max: 10, - ida: 9, - tim: 11 - }; - ages = (function() { - __a = years_old; - __b = []; - for (child in __a) { - if (__a.hasOwnProperty(child)) { - age = __a[child]; - __c = child + " is " + age; - __b.push(__c); - } - } - return __b; - })(); -})(); \ No newline at end of file diff --git a/documentation/js/overview.js b/documentation/js/overview.js index e3832187ee..cdb3d0a061 100644 --- a/documentation/js/overview.js +++ b/documentation/js/overview.js @@ -1,5 +1,5 @@ (function(){ - var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, race, square; + var __a, __b, __c, cubed_list, list, math, number, opposite_day, race, square; // Assignment: number = 42; opposite_day = true; @@ -33,14 +33,10 @@ } // Array comprehensions: cubed_list = (function() { - __a = list; - __c = []; - for (__b in __a) { - if (__a.hasOwnProperty(__b)) { - num = __a[__b]; - __d = math.cube(num); - __c.push(__d); - } + __c = []; __a = list; + for (__b=0; __b<__a.length; __b++) { + num = __a[__b]; + __c.push(math.cube(num)); } return __c; })(); diff --git a/documentation/js/range_comprehensions.js b/documentation/js/range_comprehensions.js index 7457b7f4a8..c2563558bc 100644 --- a/documentation/js/range_comprehensions.js +++ b/documentation/js/range_comprehensions.js @@ -1,8 +1,7 @@ (function(){ - var __a, __b, __c, __d, __e, dozen_eggs, i; - __d = 0; - __e = eggs.length; - for (__c=0, i=__d; (__d <= __e ? i < __e : i > __e); (__d <= __e ? i += 12 : i -= 12), __c++) { + 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)); } diff --git a/documentation/js/super.js b/documentation/js/super.js index dc6e099abd..4bc9b92d4c 100644 --- a/documentation/js/super.js +++ b/documentation/js/super.js @@ -1,5 +1,5 @@ (function(){ - var Animal, Horse, Snake, sam, tom; + var Animal, Horse, Snake, __a, __b, sam, tom; Animal = function Animal() { }; Animal.prototype.move = function move(meters) { @@ -10,20 +10,24 @@ __a = this.name = name; return Snake === this.constructor ? this : __a; }; + __a = function(){}; + __a.prototype = Animal.prototype; Snake.__superClass__ = Animal.prototype; - Snake.prototype = new Animal(); + Snake.prototype = new __a(); Snake.prototype.constructor = Snake; Snake.prototype.move = function move() { alert("Slithering..."); return Snake.__superClass__.move.call(this, 5); }; Horse = function Horse(name) { - var __a; - __a = this.name = name; - return Horse === this.constructor ? this : __a; + var __b; + __b = this.name = name; + return Horse === this.constructor ? this : __b; }; + __b = function(){}; + __b.prototype = Animal.prototype; Horse.__superClass__ = Animal.prototype; - Horse.prototype = new Animal(); + Horse.prototype = new __b(); Horse.prototype.constructor = Horse; Horse.prototype.move = function move() { alert("Galloping..."); diff --git a/index.html b/index.html index d1c3b16894..fe1bbc093c 100644 --- a/index.html +++ b/index.html @@ -37,7 +37,7 @@

    CoffeeScript

    Latest Version: - 0.2.1 + 0.2.2

    Table of Contents

    @@ -103,7 +103,7 @@

    Mini Overview

    # Array comprehensions: cubed_list: math.cube(num) for num in list -
    var __a, __b, __c, __d, cubed_list, list, math, num, number, opposite_day, race, square;
    +
    var __a, __b, __c, cubed_list, list, math, number, opposite_day, race, square;
     // Assignment:
     number = 42;
     opposite_day = true;
    @@ -137,18 +137,14 @@ 

    Mini Overview

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

    +

    Array Slicing and Splicing with Ranges @@ -897,31 +841,23 @@

    Language Reference

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

    - 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 @@

    Language Reference

    # 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 @@ 

    Language Reference

    } 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 af21e2e05c..034d410e11 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 5bacc2aac6..0b270cfab7 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 1b682a54ea..9e75beaca0 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -12,14 +12,14 @@ class Lexer "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 718c4843f5..02ce470760 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -153,6 +153,10 @@ class LiteralNode < Node attr_reader :value + def self.wrap(string) + self.new(Value.new(string)) + end + def initialize(value) @value = value end @@ -384,7 +388,7 @@ def compile_node(o) # 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 @@ def initialize(body, source, name, index=nil) @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 @@ def compile_node(o) 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 @@ def compile_node(o) 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 @@ def compile_node(o) 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 @@ def compile_node(o) 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 2469b407f5..5663c33527 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 2262434f5c..89271965f4 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 From ad18378f7eb5a2df3b85ee89eba4e87ceaaffada Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 10 Jan 2010 16:16:59 -0500 Subject: [PATCH 298/303] adding '::' as shorthand for '.prototype.' --- documentation/coffee/super.coffee | 6 ++--- documentation/index.html.erb | 18 ++++++++++---- examples/code.coffee | 6 ++--- examples/underscore.coffee | 14 +++++------ index.html | 24 +++++++++++++------ .../Syntaxes/CoffeeScript.tmLanguage | 6 ++--- lib/coffee_script/grammar.y | 3 ++- lib/coffee_script/lexer.rb | 1 + lib/coffee_script/nodes.rb | 9 +++---- .../execution/test_calling_super.coffee | 8 +++---- 10 files changed, 59 insertions(+), 36 deletions(-) diff --git a/documentation/coffee/super.coffee b/documentation/coffee/super.coffee index cc28c84d90..a64eff39b2 100644 --- a/documentation/coffee/super.coffee +++ b/documentation/coffee/super.coffee @@ -1,16 +1,16 @@ Animal: => -Animal.prototype.move: meters => +Animal::move: meters => alert(this.name + " moved " + meters + "m.") Snake: name => this.name: name Snake extends Animal -Snake.prototype.move: => +Snake::move: => alert("Slithering...") super(5) Horse: name => this.name: name Horse extends Animal -Horse.prototype.move: => +Horse::move: => alert("Galloping...") super(45) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index a396ce3aff..c80b7757da 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -467,9 +467,9 @@ coffee --print app/scripts/*.coffee > concatenation.js 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. CoffeeScript provides extends - to help with prototype setup, and converts - super() calls into calls against the immediate ancestor's - method of the same name. + to help with prototype setup, :: for quick access to an + object's prototype, and converts super() calls into calls against + the immediate ancestor's method of the same name.

    <%= code_for('super', true) %> @@ -575,11 +575,21 @@ coffee --print app/scripts/*.coffee > concatenation.js

    0.2.2 + When performing a comprehension over an object, use ino, instead + of in, which helps us generate smaller, more efficient code at + compile time. +
    + Added :: as a shorthand for saying .prototype. +
    The "splat" symbol has been changed from a prefix asterisk *, to - a postfix ellipsis .... Added JavaScript's in operator, + a postfix ellipsis ... +
    + Added JavaScript's in operator, empty return statements, and empty while loops. +
    Constructor functions that start with capital letters now include a safety check to make sure that the new instance of the object is returned. +
    The extends keyword now functions identically to goog.inherits in Google's Closure Library.

    diff --git a/examples/code.coffee b/examples/code.coffee index ca78218c55..4917d0feee 100644 --- a/examples/code.coffee +++ b/examples/code.coffee @@ -141,18 +141,18 @@ aliquam erat volutpat. Ut wisi enim ad." # Inheritance and calling super. Animal: => -Animal.prototype.move: meters => +Animal::move: meters => alert(this.name + " moved " + meters + "m.") Snake: name => this.name: name Snake extends Animal -Snake.prototype.move: => +Snake::move: => alert('Slithering...') super(5) Horse: name => this.name: name Horse extends Animal -Horse.prototype.move: => +Horse::move: => alert('Galloping...') super(45) diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 31881a34c8..9678122764 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -39,11 +39,11 @@ # Create quick reference variables for speed access to core prototypes. - slice: Array.prototype.slice - unshift: Array.prototype.unshift - toString: Object.prototype.toString - hasOwnProperty: Object.prototype.hasOwnProperty - propertyIsEnumerable: Object.prototype.propertyIsEnumerable + slice: Array::slice + unshift: Array::unshift + toString: Object::toString + hasOwnProperty: Object::hasOwnProperty + propertyIsEnumerable: Object::propertyIsEnumerable # Current version. @@ -585,10 +585,10 @@ # Start chaining a wrapped Underscore object. - wrapper.prototype.chain: => + wrapper::chain: => this._chain: true this # Extracts the result from a wrapped and chained object. - wrapper.prototype.value: => this._wrapped + wrapper::value: => this._wrapped diff --git a/index.html b/index.html index 3f284d6792..8c72bffc1d 100644 --- a/index.html +++ b/index.html @@ -973,23 +973,23 @@

    Language Reference

    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. CoffeeScript provides extends - to help with prototype setup, and converts - super() calls into calls against the immediate ancestor's - method of the same name. + to help with prototype setup, :: for quick access to an + object's prototype, and converts super() calls into calls against + the immediate ancestor's method of the same name.

    Animal: =>
    -Animal.prototype.move: meters =>
    +Animal::move: meters =>
       alert(this.name + " moved " + meters + "m.")
     
     Snake: name => this.name: name
     Snake extends Animal
    -Snake.prototype.move: =>
    +Snake::move: =>
       alert("Slithering...")
       super(5)
     
     Horse: name => this.name: name
     Horse extends Animal
    -Horse.prototype.move: =>
    +Horse::move: =>
       alert("Galloping...")
       super(45)
     
    @@ -1265,11 +1265,21 @@ 

    Change Log

    0.2.2 + When performing a comprehension over an object, use ino, instead + of in, which helps us generate smaller, more efficient code at + compile time. +
    + Added :: as a shorthand for saying .prototype. +
    The "splat" symbol has been changed from a prefix asterisk *, to - a postfix ellipsis .... Added JavaScript's in operator, + a postfix ellipsis ... +
    + Added JavaScript's in operator, empty return statements, and empty while loops. +
    Constructor functions that start with capital letters now include a safety check to make sure that the new instance of the object is returned. +
    The extends keyword now functions identically to goog.inherits in Google's Closure Library.

    diff --git a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage index 034d410e11..a50aafaea9 100644 --- a/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +++ b/lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage @@ -43,7 +43,7 @@ comment match stuff like: funcName: => … match - ([a-zA-Z0-9_?.$*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) + ([a-zA-Z0-9_?.$:*]*)\s*(=|:)\s*([\w,\s]*?)\s*(=>) name meta.function.coffee @@ -64,7 +64,7 @@ comment match stuff like: a => … match - ([a-zA-Z0-9_?., $*]*)\s*(=>) + ([a-zA-Z0-9_?., $:*]*)\s*(=>) name meta.inline.function.coffee @@ -214,7 +214,7 @@ match - \b([a-zA-Z$_](\w|\$)*)(\:)\s + \b([a-zA-Z$_](\w|\$|:)*)(\:)\s name variable.assignment.coffee captures diff --git a/lib/coffee_script/grammar.y b/lib/coffee_script/grammar.y index 0b270cfab7..125673515f 100644 --- a/lib/coffee_script/grammar.y +++ b/lib/coffee_script/grammar.y @@ -4,7 +4,7 @@ class Parser token IF ELSE UNLESS token NUMBER STRING REGEX token TRUE FALSE YES NO ON OFF -token IDENTIFIER PROPERTY_ACCESS +token IDENTIFIER PROPERTY_ACCESS PROTOTYPE_ACCESS token CODE PARAM NEW RETURN token TRY CATCH FINALLY THROW token BREAK CONTINUE @@ -234,6 +234,7 @@ rule # Accessing into an object or array, through dot or index notation. Accessor: PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) } + | PROTOTYPE_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], true) } | Index { result = val[0] } | Range { result = SliceNode.new(val[0]) } ; diff --git a/lib/coffee_script/lexer.rb b/lib/coffee_script/lexer.rb index 9e75beaca0..a629be280b 100644 --- a/lib/coffee_script/lexer.rb +++ b/lib/coffee_script/lexer.rb @@ -88,6 +88,7 @@ def identifier_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] = :PROTOTYPE_ACCESS if tag == :IDENTIFIER && last_value == '::' token(tag, identifier) @i += identifier.length end diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 02ce470760..dbb5add3ea 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -324,12 +324,13 @@ def compile_node(o) class AccessorNode < Node attr_reader :name - def initialize(name) - @name = name + def initialize(name, prototype=false) + @name, @prototype = name, prototype end def compile_node(o) - write(".#{@name}") + proto = @prototype ? "prototype." : '' + write(".#{proto}#{@name}") end end @@ -416,7 +417,7 @@ def compile_node(o) # Setting the value of a local variable, or the value of an object property. class AssignNode < Node PROTO_ASSIGN = /\A(\S+)\.prototype/ - LEADING_DOT = /\A\./ + LEADING_DOT = /\A\.(prototype\.)?/ attr_reader :variable, :value, :context diff --git a/test/fixtures/execution/test_calling_super.coffee b/test/fixtures/execution/test_calling_super.coffee index 65de1b0cdf..33a59bd3e1 100644 --- a/test/fixtures/execution/test_calling_super.coffee +++ b/test/fixtures/execution/test_calling_super.coffee @@ -1,21 +1,21 @@ Base: => -Base.prototype.func: string => +Base::func: string => 'zero/' + string FirstChild: => FirstChild extends Base -FirstChild.prototype.func: string => +FirstChild::func: string => super('one/') + string SecondChild: => SecondChild extends FirstChild -SecondChild.prototype.func: string => +SecondChild::func: string => super('two/') + string ThirdChild: => this.array: [1, 2, 3] ThirdChild extends SecondChild -ThirdChild.prototype.func: string => +ThirdChild::func: string => super('three/') + string result: (new ThirdChild()).func('four') From c265b7d5d6dac6fcac432d73ef3c9dff029fe446 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 10 Jan 2010 16:39:38 -0500 Subject: [PATCH 299/303] updating comprehension speed test with the new normal comprehensions --- documentation/speed.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/documentation/speed.html b/documentation/speed.html index e756437ae4..52d99650b6 100644 --- a/documentation/speed.html +++ b/documentation/speed.html @@ -52,7 +52,7 @@

    Quickie CoffeeScript Speed Tests

    } }); - JSLitmus.test('the new comprehensions', function() { + JSLitmus.test('weepy\'s comprehensions', function() { __c = []; __a = arr; __d = function(num, __b) { __c.push(num); @@ -63,6 +63,14 @@

    Quickie CoffeeScript Speed Tests

    for (__b in __a) { if (__a.hasOwnProperty(__b)) __d(__a[__b], __b); } } }); + + JSLitmus.test('CoffeeScript 0.2.2 comprehensions', function() { + __c = []; __a = arr; + for (__b=0; __b<__a.length; __b++) { + num = __a[__b]; + __c.push(num); + } + }); From de74bce2cd73314c3867815098f39163bf236eb9 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 10 Jan 2010 17:14:20 -0500 Subject: [PATCH 300/303] more docs for 0.2.2 --- documentation/index.html.erb | 47 +++++++++++++++++++++++------------- index.html | 47 +++++++++++++++++++++++------------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/documentation/index.html.erb b/documentation/index.html.erb index c80b7757da..f3670c4c32 100644 --- a/documentation/index.html.erb +++ b/documentation/index.html.erb @@ -93,9 +93,13 @@

    For a longer CoffeeScript example, check out Underscore.coffee, a port - of Underscore.js - to CoffeeScript, which, when compiled, can pass the complete Underscore test suite. - Or, clone the source and take a look in the + 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 + 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. + Additional examples are included in the source repository, inside the examples folder.

    @@ -188,9 +192,15 @@ gem install coffee-script
    + + + + @@ -436,6 +446,11 @@ coffee --print app/scripts/*.coffee > concatenation.js below.

    <%= code_for('expressions', 'eldest') %> +

    + Even though functions will always return their final value, it's both possible + and encouraged to return early from a function body writing out the explicit + return (return value), when you know that you're done. +

    Because variable declarations occur at the top of scope, assignment can be used within expressions, even for variables that haven't been seen before: @@ -466,9 +481,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. CoffeeScript provides extends + set the prototype chain. +

    +

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

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

    Contributing

    - Here's a wish list of things that would be wonderful to have in - CoffeeScript: + Here's a wish list of things that would be wonderful to have contributed:

    • - A clean, safe syntax for manipulating the prototype chain, and performing - inheritance. extends and super are the start of this, but - aren't a complete answer. -
    • -
    • - A CoffeeScript version of the compiler, perhaps using Alessandro Warth's - OMeta. + + A CoffeeScript version of the compiler. +
    • Test cases for any syntax errors you encounter that you think CoffeeScript diff --git a/index.html b/index.html index 8c72bffc1d..ea074d6082 100644 --- a/index.html +++ b/index.html @@ -190,9 +190,13 @@

      Mini Overview

      For a longer CoffeeScript example, check out Underscore.coffee, a port - of Underscore.js - to CoffeeScript, which, when compiled, can pass the complete Underscore test suite. - Or, clone the source and take a look in the + 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 + 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. + Additional examples are included in the source repository, inside the examples folder.

      @@ -285,9 +289,15 @@

      Installation and Usage

    + + + + @@ -894,6 +904,11 @@

    Language Reference

    }; eldest = 24 > 21 ? "Liz" : "Ike"; ;alert(eldest);'>run: eldest
    +

    + Even though functions will always return their final value, it's both possible + and encouraged to return early from a function body writing out the explicit + return (return value), when you know that you're done. +

    Because variable declarations occur at the top of scope, assignment can be used within expressions, even for variables that haven't been seen before: @@ -972,9 +987,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. CoffeeScript provides extends + set the prototype chain. +

    +

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

    Animal: =>
    @@ -1228,19 +1246,14 @@ 

    Resources

    Contributing

    - Here's a wish list of things that would be wonderful to have in - CoffeeScript: + Here's a wish list of things that would be wonderful to have contributed:

    • - A clean, safe syntax for manipulating the prototype chain, and performing - inheritance. extends and super are the start of this, but - aren't a complete answer. -
    • -
    • - A CoffeeScript version of the compiler, perhaps using Alessandro Warth's - OMeta. + + A CoffeeScript version of the compiler. +
    • Test cases for any syntax errors you encounter that you think CoffeeScript From 3e987de4a8c0c0ebc1878d1b5042c79238625356 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 10 Jan 2010 17:23:07 -0500 Subject: [PATCH 301/303] rebuilding underscore.coffee docs, adding a build task -- need to remember to copy over nicer headers --- Rakefile | 5 + documentation/underscore.html | 339 ++++++++++++++++++---------------- examples/underscore.coffee | 2 +- index.html | 110 +++++------ 4 files changed, 236 insertions(+), 220 deletions(-) diff --git a/Rakefile b/Rakefile index 1086cefdad..8409bfad28 100644 --- a/Rakefile +++ b/Rakefile @@ -29,6 +29,11 @@ namespace :build do sh "sudo mv coffeescript.yaml /usr/local/lib/ruby/gems/1.8/gems/ultraviolet-0.10.2/syntax/coffeescript.syntax" end + desc "Rebuild the Underscore.coffee documentation page" + task :underscore do + sh "uv -s coffeescript -t idle -h examples/underscore.coffee > documentation/underscore.html" + end + end desc "Build the documentation page" diff --git a/documentation/underscore.html b/documentation/underscore.html index b502cf222b..7ad08ae84a 100644 --- a/documentation/underscore.html +++ b/documentation/underscore.html @@ -14,7 +14,6 @@ font-size: 12px; } - @@ -31,56 +30,56 @@ 11 # ------------------------- Baseline setup --------------------------------- 12 13 # Establish the root object, "window" in the browser, or "global" on the server. - 14 root: this + 14 root: this 15 16 17 # Save the previous value of the "_" variable. - 18 previousUnderscore: root._ + 18 previousUnderscore: root._ 19 20 21 # If Underscore is called as a function, it returns a wrapped object that 22 # can be used OO-style. This wrapper holds altered versions of all the 23 # underscore functions. Wrapped objects may be chained. - 24 wrapper: obj => - 25 this._wrapped: obj + 24 wrapper: obj => + 25 this._wrapped: obj 26 this 27 28 29 # Establish the object that gets thrown to break out of a loop iteration. - 30 breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration + 30 breaker: if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration 31 32 - 33 # Create a safe reference to the Underscore object for reference below. - 34 _: root._: obj => new wrapper(obj) + 33 # Create a safe reference to the Underscore object forreference below. + 34 _: root._: obj => new wrapper(obj) 35 36 37 # Export the Underscore object for CommonJS. - 38 if typeof(exports) != 'undefined' then exports._: _ + 38 if typeof(exports) != 'undefined' then exports._: _ 39 40 41 # Create quick reference variables for speed access to core prototypes. - 42 slice: Array.prototype.slice - 43 unshift: Array.prototype.unshift - 44 toString: Object.prototype.toString - 45 hasOwnProperty: Object.prototype.hasOwnProperty - 46 propertyIsEnumerable: Object.prototype.propertyIsEnumerable + 42 slice: Array::slice + 43 unshift: Array::unshift + 44 toString: Object::toString + 45 hasOwnProperty: Object::hasOwnProperty + 46 propertyIsEnumerable: Object::propertyIsEnumerable 47 48 49 # Current version. - 50 _.VERSION: '0.5.3' + 50 _.VERSION: '0.5.5' 51 52 53 # ------------------------ Collection Functions: --------------------------- 54 55 # The cornerstone, an each implementation. 56 # Handles objects implementing forEach, arrays, and raw objects. - 57 _.each: obj, iterator, context => - 58 index: 0 + 57 _.each: obj, iterator, context => + 58 index: 0 59 try 60 return obj.forEach(iterator, context) if obj.forEach 61 if _.isArray(obj) or _.isArguments(obj) 62 return iterator.call(context, obj[i], i, obj) for i in [0...obj.length] - 63 iterator.call(context, val, key, obj) for val, key in obj + 63 iterator.call(context, val, key, obj) for key, val ino obj 64 catch e 65 throw e if e isnt breaker 66 obj @@ -88,9 +87,9 @@ 68 69 # Return the results of applying the iterator to each element. Use JavaScript 70 # 1.6's version of map, if possible. - 71 _.map: obj, iterator, context => + 71 _.map: obj, iterator, context => 72 return obj.map(iterator, context) if (obj and _.isFunction(obj.map)) - 73 results: [] + 73 results: [] 74 _.each(obj) value, index, list => 75 results.push(iterator.call(context, value, index, list)) 76 results @@ -98,45 +97,45 @@ 78 79 # Reduce builds up a single result from a list of values. Also known as 80 # inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. - 81 _.reduce: obj, memo, iterator, context => + 81 _.reduce: obj, memo, iterator, context => 82 return obj.reduce(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduce)) 83 _.each(obj) value, index, list => - 84 memo: iterator.call(context, memo, value, index, list) + 84 memo: iterator.call(context, memo, value, index, list) 85 memo 86 87 88 # The right-associative version of reduce, also known as foldr. Uses 89 # JavaScript 1.8's version of reduceRight, if available. - 90 _.reduceRight: obj, memo, iterator, context => + 90 _.reduceRight: obj, memo, iterator, context => 91 return obj.reduceRight(_.bind(iterator, context), memo) if (obj and _.isFunction(obj.reduceRight)) 92 _.each(_.clone(_.toArray(obj)).reverse()) value, index => - 93 memo: iterator.call(context, memo, value, index, obj) + 93 memo: iterator.call(context, memo, value, index, obj) 94 memo 95 96 97 # Return the first value which passes a truth test. - 98 _.detect: obj, iterator, context => - 99 result: null + 98 _.detect: obj, iterator, context => + 99 result: null 100 _.each(obj) value, index, list => 101 if iterator.call(context, value, index, list) - 102 result: value + 102 result: value 103 _.breakLoop() 104 result 105 106 107 # Return all the elements that pass a truth test. Use JavaScript 1.6's 108 # filter(), if it exists. - 109 _.select: obj, iterator, context => + 109 _.select: obj, iterator, context => 110 if obj and _.isFunction(obj.filter) then return obj.filter(iterator, context) - 111 results: [] + 111 results: [] 112 _.each(obj) value, index, list => 113 results.push(value) if iterator.call(context, value, index, list) 114 results 115 116 117 # Return all the elements for which a truth test fails. - 118 _.reject: obj, iterator, context => - 119 results: [] + 118 _.reject: obj, iterator, context => + 119 results: [] 120 _.each(obj) value, index, list => 121 results.push(value) if not iterator.call(context, value, index, list) 122 results @@ -144,89 +143,89 @@ 124 125 # Determine whether all of the elements match a truth test. Delegate to 126 # JavaScript 1.6's every(), if it is present. - 127 _.all: obj, iterator, context => + 127 _.all: obj, iterator, context => 128 iterator ||= _.identity 129 return obj.every(iterator, context) if obj and _.isFunction(obj.every) - 130 result: true + 130 result: true 131 _.each(obj) value, index, list => - 132 _.breakLoop() unless (result: result and iterator.call(context, value, index, list)) + 132 _.breakLoop() unless (result: result and iterator.call(context, value, index, list)) 133 result 134 135 136 # Determine if at least one element in the object matches a truth test. Use 137 # JavaScript 1.6's some(), if it exists. - 138 _.any: obj, iterator, context => + 138 _.any: obj, iterator, context => 139 iterator ||= _.identity 140 return obj.some(iterator, context) if obj and _.isFunction(obj.some) - 141 result: false + 141 result: false 142 _.each(obj) value, index, list => - 143 _.breakLoop() if (result: iterator.call(context, value, index, list)) + 143 _.breakLoop() if (result: iterator.call(context, value, index, list)) 144 result 145 146 147 # Determine if a given value is included in the array or object, 148 # based on '==='. - 149 _.include: obj, target => + 149 _.include: obj, target => 150 return _.indexOf(obj, target) isnt -1 if _.isArray(obj) - 151 for val in obj + 151 for key, val ino obj 152 return true if val is target 153 false 154 155 156 # Invoke a method with arguments on every item in a collection. - 157 _.invoke: obj, method => - 158 args: _.rest(arguments, 2) + 157 _.invoke: obj, method => + 158 args: _.rest(arguments, 2) 159 (if method then val[method] else val).apply(val, args) for val in obj 160 161 162 # Convenience version of a common use case of map: fetching a property. - 163 _.pluck: obj, key => + 163 _.pluck: obj, key => 164 _.map(obj, (val => val[key])) 165 166 167 # Return the maximum item or (item-based computation). - 168 _.max: obj, iterator, context => + 168 _.max: obj, iterator, context => 169 return Math.max.apply(Math, obj) if not iterator and _.isArray(obj) - 170 result: {computed: -Infinity} + 170 result: {computed: -Infinity} 171 _.each(obj) value, index, list => - 172 computed: if iterator then iterator.call(context, value, index, list) else value - 173 computed >= result.computed and (result: {value: value, computed: computed}) + 172 computed: if iterator then iterator.call(context, value, index, list) else value + 173 computed >= result.computed and (result: {value: value, computed: computed}) 174 result.value 175 176 177 # Return the minimum element (or element-based computation). - 178 _.min: obj, iterator, context => + 178 _.min: obj, iterator, context => 179 return Math.min.apply(Math, obj) if not iterator and _.isArray(obj) - 180 result: {computed: Infinity} + 180 result: {computed: Infinity} 181 _.each(obj) value, index, list => - 182 computed: if iterator then iterator.call(context, value, index, list) else value - 183 computed < result.computed and (result: {value: value, computed: computed}) + 182 computed: if iterator then iterator.call(context, value, index, list) else value + 183 computed < result.computed and (result: {value: value, computed: computed}) 184 result.value 185 186 187 # Sort the object's values by a criteria produced by an iterator. - 188 _.sortBy: obj, iterator, context => + 188 _.sortBy: obj, iterator, context => 189 _.pluck(((_.map(obj) value, index, list => - 190 {value: value, criteria: iterator.call(context, value, index, list)} + 190 {value: value, criteria: iterator.call(context, value, index, list)} 191 ).sort() left, right => - 192 a: left.criteria; b: right.criteria + 192 a: left.criteria; b: right.criteria 193 if a < b then -1 else if a > b then 1 else 0 194 ), 'value') 195 196 197 # Use a comparator function to figure out at what index an object should 198 # be inserted so as to maintain order. Uses binary search. - 199 _.sortedIndex: array, obj, iterator => + 199 _.sortedIndex: array, obj, iterator => 200 iterator ||= _.identity - 201 low: 0; high: array.length + 201 low: 0; high: array.length 202 while low < high - 203 mid: (low + high) >> 1 - 204 if iterator(array[mid]) < iterator(obj) then low: mid + 1 else high: mid + 203 mid: (low + high) >> 1 + 204 if iterator(array[mid]) < iterator(obj) then low: mid + 1 else high: mid 205 low 206 207 208 # Convert anything iterable into a real, live array. - 209 _.toArray: iterable => + 209 _.toArray: iterable => 210 return [] if (!iterable) 211 return iterable.toArray() if (iterable.toArray) 212 return iterable if (_.isArray(iterable)) @@ -235,7 +234,7 @@ 215 216 217 # Return the number of elements in an object. - 218 _.size: obj => _.toArray(obj).length + 218 _.size: obj => _.toArray(obj).length 219 220 221 # -------------------------- Array Functions: ------------------------------ @@ -243,7 +242,7 @@ 223 # Get the first element of an array. Passing "n" will return the first N 224 # values in the array. Aliased as "head". The "guard" check allows it to work 225 # with _.map. - 226 _.first: array, n, guard => + 226 _.first: array, n, guard => 227 if n and not guard then slice.call(array, 0, n) else array[0] 228 229 @@ -251,20 +250,20 @@ 231 # Especially useful on the arguments object. Passing an "index" will return 232 # the rest of the values in the array from that index onward. The "guard" 233 # check allows it to work with _.map. - 234 _.rest: array, index, guard => + 234 _.rest: array, index, guard => 235 slice.call(array, if _.isUndefined(index) or guard then 1 else index) 236 237 238 # Get the last element of an array. - 239 _.last: array => array[array.length - 1] + 239 _.last: array => array[array.length - 1] 240 241 242 # Trim out all falsy values from an array. - 243 _.compact: array => array[i] for i in [0...array.length] when array[i] + 243 _.compact: array => array[i] for i in [0...array.length] when array[i] 244 245 246 # Return a completely flattened version of an array. - 247 _.flatten: array => + 247 _.flatten: array => 248 _.reduce(array, []) memo, value => 249 return memo.concat(_.flatten(value)) if _.isArray(value) 250 memo.push(value) @@ -272,15 +271,15 @@ 252 253 254 # Return a version of the array that does not contain the specified value(s). - 255 _.without: array => - 256 values: _.rest(arguments) + 255 _.without: array => + 256 values: _.rest(arguments) 257 val for val in _.toArray(array) when not _.include(values, val) 258 259 260 # Produce a duplicate-free version of the array. If the array has already 261 # been sorted, you have the option of using a faster algorithm. - 262 _.uniq: array, isSorted => - 263 memo: [] + 262 _.uniq: array, isSorted => + 263 memo: [] 264 for el, i in _.toArray(array) 265 memo.push(el) if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el)) 266 memo @@ -288,8 +287,8 @@ 268 269 # Produce an array that contains every item shared between all the 270 # passed-in arrays. - 271 _.intersect: array => - 272 rest: _.rest(arguments) + 271 _.intersect: array => + 272 rest: _.rest(arguments) 273 _.select(_.uniq(array)) item => 274 _.all(rest) other => 275 _.indexOf(other, item) >= 0 @@ -297,10 +296,10 @@ 277 278 # Zip together multiple lists into a single array -- elements that share 279 # an index go together. - 280 _.zip: => - 281 args: _.toArray(arguments) - 282 length: _.max(_.pluck(args, 'length')) - 283 results: new Array(length) + 280 _.zip: => + 281 args: _.toArray(arguments) + 282 length: _.max(_.pluck(args, 'length')) + 283 results: new Array(length) 284 for i in [0...length] 285 results[i]: _.pluck(args, String(i)) 286 results @@ -309,9 +308,9 @@ 289 # If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), 290 # we need this function. Return the position of the first occurence of an 291 # item in an array, or -1 if the item is not included in the array. - 292 _.indexOf: array, item => + 292 _.indexOf: array, item => 293 return array.indexOf(item) if array.indexOf - 294 i: 0; l: array.length + 294 i: 0; l: array.length 295 while l - i 296 if array[i] is item then return i else i++ 297 -1 @@ -319,9 +318,9 @@ 299 300 # Provide JavaScript 1.6's lastIndexOf, delegating to the native function, 301 # if possible. - 302 _.lastIndexOf: array, item => + 302 _.lastIndexOf: array, item => 303 return array.lastIndexOf(item) if array.lastIndexOf - 304 i: array.length + 304 i: array.length 305 while i 306 if array[i] is item then return i else i-- 307 -1 @@ -330,16 +329,16 @@ 310 # Generate an integer Array containing an arithmetic progression. A port of 311 # the native Python range() function. See: 312 # http://docs.python.org/library/functions.html#range - 313 _.range: start, stop, step => - 314 a: _.toArray(arguments) - 315 solo: a.length <= 1 - 316 i: start: if solo then 0 else a[0]; - 317 stop: if solo then a[0] else a[1]; - 318 step: a[2] or 1 - 319 len: Math.ceil((stop - start) / step) + 313 _.range: start, stop, step => + 314 a: _.toArray(arguments) + 315 solo: a.length <= 1 + 316 i: start: if solo then 0 else a[0]; + 317 stop: if solo then a[0] else a[1]; + 318 step: a[2] or 1 + 319 len: Math.ceil((stop - start) / step) 320 return [] if len <= 0 - 321 range: new Array(len) - 322 idx: 0 + 321 range: new Array(len) + 322 idx: 0 323 while true 324 return range if (if step > 0 then i - stop else stop - i) >= 0 325 range[idx]: i @@ -351,94 +350,94 @@ 331 332 # Create a function bound to a given object (assigning 'this', and arguments, 333 # optionally). Binding with arguments is also known as 'curry'. - 334 _.bind: func, obj => - 335 args: _.rest(arguments, 2) + 334 _.bind: func, obj => + 335 args: _.rest(arguments, 2) 336 => func.apply(obj or root, args.concat(_.toArray(arguments))) 337 338 339 # Bind all of an object's methods to that object. Useful for ensuring that 340 # all callbacks defined on an object belong to it. - 341 _.bindAll: obj => - 342 funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj) + 341 _.bindAll: obj => + 342 funcs: if arguments.length > 1 then _.rest(arguments) else _.functions(obj) 343 _.each(funcs, (f => obj[f]: _.bind(obj[f], obj))) 344 obj 345 346 347 # Delays a function for the given number of milliseconds, and then calls 348 # it with the arguments supplied. - 349 _.delay: func, wait => - 350 args: _.rest(arguments, 2) + 349 _.delay: func, wait => + 350 args: _.rest(arguments, 2) 351 setTimeout((=> func.apply(func, args)), wait) 352 353 354 # Defers a function, scheduling it to run after the current call stack has 355 # cleared. - 356 _.defer: func => + 356 _.defer: func => 357 _.delay.apply(_, [func, 1].concat(_.rest(arguments))) 358 359 360 # Returns the first function passed as an argument to the second, 361 # allowing you to adjust arguments, run code before and after, and 362 # conditionally execute the original function. - 363 _.wrap: func, wrapper => + 363 _.wrap: func, wrapper => 364 => wrapper.apply(wrapper, [func].concat(_.toArray(arguments))) 365 366 367 # Returns a function that is the composition of a list of functions, each 368 # consuming the return value of the function that follows. - 369 _.compose: => - 370 funcs: _.toArray(arguments) + 369 _.compose: => + 370 funcs: _.toArray(arguments) 371 => - 372 args: _.toArray(arguments) + 372 args: _.toArray(arguments) 373 for i in [(funcs.length - 1)..0] - 374 args: [funcs[i].apply(this, args)] + 374 args: [funcs[i].apply(this, args)] 375 args[0] 376 377 378 # ------------------------- Object Functions: ---------------------------- 379 380 # Retrieve the names of an object's properties. - 381 _.keys: obj => + 381 _.keys: obj => 382 return _.range(0, obj.length) if _.isArray(obj) - 383 key for val, key in obj + 383 key for key, val ino obj 384 385 386 # Retrieve the values of an object's properties. - 387 _.values: obj => + 387 _.values: obj => 388 _.map(obj, _.identity) 389 390 391 # Return a sorted list of the function names available in Underscore. - 392 _.functions: obj => + 392 _.functions: obj => 393 _.select(_.keys(obj), key => _.isFunction(obj[key])).sort() 394 395 396 # Extend a given object with all of the properties in a source object. - 397 _.extend: destination, source => - 398 for val, key in source + 397 _.extend: destination, source => + 398 for key, val ino source 399 destination[key]: val 400 destination 401 402 403 # Create a (shallow-cloned) duplicate of an object. - 404 _.clone: obj => + 404 _.clone: obj => 405 return obj.slice(0) if _.isArray(obj) 406 _.extend({}, obj) 407 408 409 # Invokes interceptor with the obj, and then returns obj. 410 # The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain. - 411 _.tap: obj, interceptor => + 411 _.tap: obj, interceptor => 412 interceptor(obj) 413 obj 414 415 416 # Perform a deep comparison to check if two objects are equal. - 417 _.isEqual: a, b => + 417 _.isEqual: a, b => 418 # Check object identity. 419 return true if a is b 420 # Different types? - 421 atype: typeof(a); btype: typeof(b) + 421 atype: typeof(a); btype: typeof(b) 422 return false if atype isnt btype 423 # Basic equality test (watch out for coercions). 424 return true if `a == b` @@ -461,7 +460,7 @@ 441 # Check for different array lengths before comparing contents. 442 return false if a.length and (a.length isnt b.length) 443 # Nothing else worked, deep compare the contents. - 444 aKeys: _.keys(a); bKeys: _.keys(b) + 444 aKeys: _.keys(a); bKeys: _.keys(b) 445 # Different object sizes? 446 return false if aKeys.length isnt bKeys.length 447 # Recursive comparison of contents. @@ -470,81 +469,81 @@ 450 451 452 # Is a given array or object empty? - 453 _.isEmpty: obj => _.keys(obj).length is 0 + 453 _.isEmpty: obj => _.keys(obj).length is 0 454 455 456 # Is a given value a DOM element? - 457 _.isElement: obj => obj and obj.nodeType is 1 + 457 _.isElement: obj => obj and obj.nodeType is 1 458 459 460 # Is a given value an array? - 461 _.isArray: obj => !!(obj and obj.concat and obj.unshift) + 461 _.isArray: obj => !!(obj and obj.concat and obj.unshift) 462 463 464 # Is a given variable an arguments object? - 465 _.isArguments: obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length') + 465 _.isArguments: obj => obj and _.isNumber(obj.length) and !_.isArray(obj) and !propertyIsEnumerable.call(obj, 'length') 466 467 468 # Is the given value a function? - 469 _.isFunction: obj => !!(obj and obj.constructor and obj.call and obj.apply) + 469 _.isFunction: obj => !!(obj and obj.constructor and obj.call and obj.apply) 470 471 472 # Is the given value a string? - 473 _.isString: obj => !!(obj is '' or (obj and obj.charCodeAt and obj.substr)) + 473 _.isString: obj => !!(obj is '' or (obj and obj.charCodeAt and obj.substr)) 474 475 476 # Is a given value a number? - 477 _.isNumber: obj => toString.call(obj) is '[object Number]' + 477 _.isNumber: obj => toString.call(obj) is '[object Number]' 478 479 480 # Is a given value a Date? - 481 _.isDate: obj => !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear) + 481 _.isDate: obj => !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear) 482 483 484 # Is the given value a regular expression? - 485 _.isRegExp: obj => !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false)) + 485 _.isRegExp: obj => !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false)) 486 487 488 # Is the given value NaN -- this one is interesting. NaN != NaN, and 489 # isNaN(undefined) == true, so we make sure it's a number first. - 490 _.isNaN: obj => _.isNumber(obj) and window.isNaN(obj) + 490 _.isNaN: obj => _.isNumber(obj) and window.isNaN(obj) 491 492 493 # Is a given value equal to null? - 494 _.isNull: obj => obj is null + 494 _.isNull: obj => obj is null 495 496 497 # Is a given variable undefined? - 498 _.isUndefined: obj => typeof obj is 'undefined' + 498 _.isUndefined: obj => typeof obj is 'undefined' 499 500 501 # -------------------------- Utility Functions: -------------------------- 502 503 # Run Underscore.js in noConflict mode, returning the '_' variable to its 504 # previous owner. Returns a reference to the Underscore object. - 505 _.noConflict: => - 506 root._: previousUnderscore + 505 _.noConflict: => + 506 root._: previousUnderscore 507 this 508 509 510 # Keep the identity function around for default iterators. - 511 _.identity: value => value + 511 _.identity: value => value 512 513 514 # Break out of the middle of an iteration. - 515 _.breakLoop: => throw breaker + 515 _.breakLoop: => throw breaker 516 517 518 # Generate a unique integer id (unique within the entire client session). 519 # Useful for temporary DOM ids. - 520 idCounter: 0 - 521 _.uniqueId: prefix => + 520 idCounter: 0 + 521 _.uniqueId: prefix => 522 (prefix or '') + idCounter++ 523 524 525 # JavaScript templating a-la ERB, pilfered from John Resig's 526 # "Secrets of the JavaScript Ninja", page 83. - 527 _.template: str, data => + 527 _.template: str, data => 528 `var fn = new Function('obj', 529 'var p=[],print=function(){p.push.apply(p,arguments);};' + 530 'with(obj){p.push(\'' + @@ -562,55 +561,67 @@ 542 543 # ------------------------------- Aliases ---------------------------------- 544 - 545 _.forEach: _.each - 546 _.foldl: _.inject: _.reduce - 547 _.foldr: _.reduceRight - 548 _.filter: _.select - 549 _.every: _.all - 550 _.some: _.any - 551 _.head: _.first - 552 _.tail: _.rest - 553 _.methods: _.functions + 545 _.forEach: _.each + 546 _.foldl: _.inject: _.reduce + 547 _.foldr: _.reduceRight + 548 _.filter: _.select + 549 _.every: _.all + 550 _.some: _.any + 551 _.head: _.first + 552 _.tail: _.rest + 553 _.methods: _.functions 554 555 556 # /*------------------------ Setup the OOP Wrapper: --------------------------*/ 557 558 # Helper function to continue chaining intermediate results. - 559 result: obj, chain => + 559 result: obj, chain => 560 if chain then _(obj).chain() else obj 561 562 563 # Add all of the Underscore functions to the wrapper object. 564 _.each(_.functions(_)) name => - 565 method: _[name] + 565 method: _[name] 566 wrapper.prototype[name]: => - 567 unshift.call(arguments, this._wrapped) - 568 result(method.apply(_, arguments), this._chain) - 569 + 567 args: _.toArray(arguments) + 568 unshift.call(args, this._wrapped) + 569 result(method.apply(_, args), this._chain) 570 - 571 # Add all mutator Array functions to the wrapper. - 572 _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']) name => - 573 method: Array.prototype[name] - 574 wrapper.prototype[name]: => - 575 method.apply(this._wrapped, arguments) - 576 result(this._wrapped, this._chain) - 577 + 571 + 572 # Add all mutator Array functions to the wrapper. + 573 _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']) name => + 574 method: Array.prototype[name] + 575 wrapper.prototype[name]: => + 576 method.apply(this._wrapped, arguments) + 577 result(this._wrapped, this._chain) 578 - 579 # Add all accessor Array functions to the wrapper. - 580 _.each(['concat', 'join', 'slice']) name => - 581 method: Array.prototype[name] - 582 wrapper.prototype[name]: => - 583 result(method.apply(this._wrapped, arguments), this._chain) - 584 + 579 + 580 # Add all accessor Array functions to the wrapper. + 581 _.each(['concat', 'join', 'slice']) name => + 582 method: Array.prototype[name] + 583 wrapper.prototype[name]: => + 584 result(method.apply(this._wrapped, arguments), this._chain) 585 - 586 # Start chaining a wrapped Underscore object. - 587 wrapper.prototype.chain: => - 588 this._chain: true - 589 this - 590 + 586 + 587 # Start chaining a wrapped Underscore object. + 588 wrapper::chain: => + 589 this._chain: true + 590 this 591 - 592 # Extracts the result from a wrapped and chained object. - 593 wrapper.prototype.value: => this._wrapped -
    + 592 + 593 # Extracts the result from a wrapped and chained object. + 594 wrapper::value: => this._wrapped +

    + + Valid XHTML 1.0 Strict + + + Valid CSS! + +

    diff --git a/examples/underscore.coffee b/examples/underscore.coffee index 9678122764..8da8cc587b 100644 --- a/examples/underscore.coffee +++ b/examples/underscore.coffee @@ -47,7 +47,7 @@ # Current version. - _.VERSION: '0.5.3' + _.VERSION: '0.5.5' # ------------------------ Collection Functions: --------------------------- diff --git a/index.html b/index.html index ea074d6082..189bf2f779 100644 --- a/index.html +++ b/index.html @@ -75,34 +75,34 @@

    Mini Overview

    CoffeeScript on the left, compiled JavaScript output on the right.

    # Assignment:
    -number: 42
    -opposite_day: true
    +number: 42
    +opposite_day: true
     
     # Conditions:
    -number: -42 if opposite_day
    +number: -42 if opposite_day
     
     # Functions:
     square: x => x * x
     
     # Arrays:
    -list: [1, 2, 3, 4, 5]
    +list: [1, 2, 3, 4, 5]
     
     # Objects:
    -math: {
    -  root:   Math.sqrt
    -  square: square
    -  cube:   x => x * square(x)
    +math: {
    +  root:   Math.sqrt
    +  square: square
    +  cube:   x => x * square(x)
     }
     
     # Splats:
    -race: winner, runners... =>
    +race: winner, runners... =>
       print(winner, runners)
     
     # Existence:
     alert("I knew it!") if elvis?
     
     # Array comprehensions:
    -cubed_list: math.cube(num) for num in list
    +cubed_list: math.cube(num) for num in list
     
    var __a, __b, __c, cubed_list, list, math, num, number, opposite_day, race, square;
     // Assignment:
     number = 42;
    @@ -379,8 +379,8 @@ 

    Language Reference

    JSON. Equal signs are only needed for mathy things.

    -
    greeting: "Hello CoffeeScript"
    -difficulty: 0.5
    +    
    greeting: "Hello CoffeeScript"
    +difficulty: 0.5
     
    var difficulty, greeting;
     greeting = "Hello CoffeeScript";
     difficulty = 0.5;
    @@ -401,15 +401,15 @@ 

    Language Reference

    assigning local variables, and can be moved around freely. You can mix and match the two styles.

    -
    song: ["do", "re", "mi", "fa", "so"]
    +    
    song: ["do", "re", "mi", "fa", "so"]
     
    -ages: {
    -  max: 10
    -  ida: 9
    -  tim: 11
    +ages: {
    +  max: 10
    +  ida: 9
    +  tim: 11
     }
     
    -matrix: [
    +matrix: [
       1, 0, 1
       0, 0, 1
       1, 1, 0
    @@ -438,11 +438,11 @@ 

    Language Reference

    are properly declared within lexical scope — you never need to write var yourself.

    -
    num: 1
    +    
    num: 1
     change_numbers: =>
    -  new_num: -1
    -  num: 10
    -new_num: change_numbers()
    +  new_num: -1
    +  num: 10
    +new_num: change_numbers()
     
    var change_numbers, new_num, num;
     num = 1;
     change_numbers = function change_numbers() {
    @@ -489,13 +489,13 @@ 

    Language Reference

    CoffeeScript will compile if statements using the ternary operator when possible, to make it easier to use the result as an expression.

    -
    mood: greatly_improved if singing
    +    
    mood: greatly_improved if singing
     
     if happy and knows_it
       claps_hands()
       cha_cha_cha()
     
    -date: if friday then sue else jill
    +date: if friday then sue else jill
     
     expensive ||= do_the_math()
     
    var date, mood;
    @@ -524,7 +524,7 @@ 

    Language Reference

    a variable is null or undefined, which makes it analogous to Ruby's nil?

    -
    solipsism: true if mind? and not world?
    +    
    solipsism: true if mind? and not world?
     
    var solipsism;
     if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) {
       solipsism = true;
    @@ -561,7 +561,7 @@ 

    Language Reference

    launch() if ignition is on
     
    -volume: 10 if band isnt spinal_tap
    +volume: 10 if band isnt spinal_tap
     
     let_the_wild_rumpus_begin() unless answer is no
     
    @@ -586,14 +586,14 @@ 

    Language Reference

    splats ..., both for function definition as well as invocation, making variable arguments a little bit more palatable.

    -
    gold: silver: the_field: "unknown"
    +    
    gold: silver: the_field: "unknown"
     
    -medalists: first, second, rest... =>
    -  gold:       first
    -  silver:     second
    -  the_field:  rest
    +medalists: first, second, rest... =>
    +  gold:       first
    +  silver:     second
    +  the_field:  rest
     
    -contenders: [
    +contenders: [
       "Michael Phelps"
       "Liu Xiang"
       "Yao Ming"
    @@ -698,7 +698,7 @@ 

    Language Reference

    would use a loop, each/forEach, map, or select/filter.

    # Eat lunch.
    -lunch: eat(food) for food in ['toast', 'cheese', 'wine']
    +lunch: eat(food) for food in ['toast', 'cheese', 'wine']
     
     # Naive collision detection.
     for roid in asteroids
    @@ -735,11 +735,11 @@ 

    Language Reference

    end of your comprehension. (The long line-breaking "for" definitions in the compiled JS below allow ranges to count downwards, as well as upwards).

    -
    countdown: num for num in [10..1]
    +    
    countdown: num for num in [10..1]
     
     egg_delivery: =>
       for i in [0...eggs.length] by 12
    -    dozen_eggs: eggs[i...i+12]
    +    dozen_eggs: eggs[i...i+12]
         deliver(new egg_carton(dozen))
     
    var __a, __b, __c, __d, __e, countdown, egg_delivery, num;
     countdown = (function() {
    @@ -785,9 +785,9 @@ 

    Language Reference

    an object. Use ino to signal comprehension over an object instead of an array.

    -
    years_old: {max: 10, ida: 9, tim: 11}
    +    
    years_old: {max: 10, ida: 9, tim: 11}
     
    -ages: child + " is " + age for child, age ino years_old
    +ages: child + " is " + age for child, age ino years_old
     
    var __a, __b, age, ages, child, years_old;
     years_old = {
       max: 10,
    @@ -831,11 +831,11 @@ 

    Language Reference

    the slice, and the second is the index of the last one. Three dots signify a range that excludes the end.

    -
    numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    +    
    numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     
    -three_to_six: numbers[3..6]
    +three_to_six: numbers[3..6]
     
    -numbers_copy: numbers[0...numbers.length]
    +numbers_copy: numbers[0...numbers.length]
     
     
    var numbers, numbers_copy, three_to_six;
     numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    @@ -850,7 +850,7 @@ 

    Language Reference

    The same syntax can be used with assignment to replace a segment of an array with new values (to splice it).

    -
    numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    +    
    numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     
     numbers[3..6]: [-3, -4, -5, -6]
     
    @@ -880,7 +880,7 @@ 

    Language Reference

    else "C" -eldest: if 24 > 21 then "Liz" else "Ike" +eldest: if 24 > 21 then "Liz" else "Ike"
    var eldest, grade;
     grade = function grade(student) {
       if (student.excellent_work) {
    @@ -913,7 +913,7 @@ 

    Language Reference

    Because variable declarations occur at the top of scope, assignment can be used within expressions, even for variables that haven't been seen before:

    -
    six: (one: 1) + (two: 2) + (three: 3)
    +    
    six: (one: 1) + (two: 2) + (three: 3)
     
    var one, six, three, two;
     six = (one = 1) + (two = 2) + (three = 3);
     
    -o, --output [DIR]-i, --interactive + Launch an interactive CoffeeScript session. + Requires Narwhal. +
    -r, --run + Compile and execute the CoffeeScripts without saving the intermediate + JavaScript. Requires Narwhal. +
    -o, --output [DIR] Write out all compiled JavaScript files into the specified directory. -l, --lint If the jsl (JavaScript Lint) command is installed, use it - to check the compilation of a CoffeeScript file. (Handy in + to check the compilation of a CoffeeScript file. (Handy in conjunction with --watch)
    -i, --interactive - Launch an interactive CoffeeScript session. + Launch an interactive CoffeeScript session. Requires Narwhal.
    -i, --interactive - Launch an interactive CoffeeScript session. + Launch an interactive CoffeeScript session. Requires Narwhal.
    -e, --eval Compile and print a little snippet of CoffeeScript directly from the - command line (or from stdin). For example:
    coffee-script -e "square: x => x * x." + command line (or from stdin). For example:
    coffee -e "square: x => x * x."
    -e, --eval Compile and print a little snippet of CoffeeScript directly from the - command line (or from stdin). For example:
    coffee-script -e "square: x => x * x." + command line (or from stdin). For example:
    coffee -e "square: x => x * x."
    -r, --run - Compile and execute the CoffeeScripts without saving the intermediate + Compile and execute scripts without saving the intermediate JavaScript. Requires Narwhal.
    -e, --eval Compile and print a little snippet of CoffeeScript directly from the - command line (or from stdin). For example:
    coffee -e "square: x => x * x." + command line (or from stdin). For example:
    coffee -e "square: x => x * x"
    -r, --run - Compile and execute the CoffeeScripts without saving the intermediate + Compile and execute scripts without saving the intermediate JavaScript. Requires Narwhal.
    -e, --eval Compile and print a little snippet of CoffeeScript directly from the - command line (or from stdin). For example:
    coffee -e "square: x => x * x." + command line (or from stdin). For example:
    coffee -e "square: x => x * x"
    -n, --no-wrap - Compile the JavaScript without the top-level function safety wrapper - or var declarations, for situations where you want to add every - variable to global scope. + Compile the JavaScript without the top-level function safety wrapper. + (Used for CoffeeScript as a Narwhal module.) +
    -g, --globals + Suppress all variable declarations at the top-level, effectively adding + those variables to the global scope. (Used by the REPL.)
    -n, --no-wrap - Compile the JavaScript without the top-level function safety wrapper - or var declarations, for situations where you want to add every - variable to global scope. + Compile the JavaScript without the top-level function safety wrapper. + (Used for CoffeeScript as a Narwhal module.) +
    -g, --globals + Suppress all variable declarations at the top-level, effectively adding + those variables to the global scope. (Used by the REPL.)