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 @@
commentmatch stuff like: a => … match
- ([a-zA-Z_?.$]*)\s*(=>)
+ ([a-zA-Z_?., $]*)\s*(=>)namemeta.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.
+
+ 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 ? "" : ''
+ "
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.
+
+
-
+ 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.
+
+ 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.
+
+ 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.
+
+ Objects and Arrays
+ Object and Array literals look very similar. When you spread out
+ each assignment on a separate line, the commas are optional.
+
+ 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.
+
var num =1;
+varchange_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()
+
+ 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.
+
+ 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().
+
+ 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 %2is0.
+
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.
+
+ 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
+ ).
+
+ 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().
+
+ 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)\bnamevariable.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 %2is0.
-
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 @@