The Great Purge. Removing the Ruby compiler, and all of its accoutrements. bin/coffee is now CoffeeScript-in-CoffeeScript

This commit is contained in:
Jeremy Ashkenas 2010-02-17 00:33:55 -05:00
parent edf5f4947e
commit 6446e0004c
16 changed files with 10 additions and 5206 deletions

View File

@ -3,7 +3,7 @@ coffee: require 'coffee-script'
# Run a CoffeeScript through our node/coffee interpreter.
run: (args) ->
proc: process.createChildProcess 'bin/node_coffee', args
proc: process.createChildProcess 'bin/coffee', args
proc.addListener 'error', (err) -> if err then puts err

14
README
View File

@ -23,7 +23,7 @@
CoffeeScript is a little language that compiles into JavaScript.
Install the compiler:
gem install coffee-script
... to be determined ...
Compile a script:
coffee /path/to/script.coffee
@ -38,14 +38,4 @@
The source repository:
git://github.com/jashkenas/coffee-script.git
Warning: A new version of the compiler, written entirely in CoffeeScript,
is in the works. Check out /src for the details. To try it out, use
bin/node_coffee. To have CoffeeScript recompile itself, run:
build compiler
To rebuild the Jison parser (takes about 30 seconds), run:
build grammar

View File

@ -1,5 +1,7 @@
#!/usr/bin/env ruby
#!/usr/bin/env node --
require "#{File.dirname(__FILE__)}/../lib/coffee_script/command_line.rb"
process.mixin(require('sys'));
CoffeeScript::CommandLine.new
require.paths.unshift('./lib/coffee_script');
require('command_line').run();

View File

@ -1,7 +0,0 @@
#!/usr/bin/env node --
process.mixin(require('sys'));
require.paths.unshift('./lib/coffee_script');
require('command_line').run();

View File

@ -1,27 +0,0 @@
Gem::Specification.new do |s|
s.name = 'coffee-script'
s.version = '0.3.2' # Keep version in sync with coffee-script.rb
s.date = '2010-2-8'
s.homepage = "http://jashkenas.github.com/coffee-script/"
s.summary = "The CoffeeScript Compiler"
s.description = <<-EOS
CoffeeScript is a little language that compiles into JavaScript. Think
of it as JavaScript's less ostentatious kid brother -- the same genes,
roughly the same height, but a different sense of style. Apart from a
handful of bonus goodies, statements in CoffeeScript correspond
one-to-one with their equivalent in JavaScript, it's just another
way of saying it.
EOS
s.authors = ['Jeremy Ashkenas']
s.email = 'jashkenas@gmail.com'
s.rubyforge_project = 'coffee-script'
s.has_rdoc = false
s.require_paths = ['lib']
s.executables = ['coffee']
s.files = Dir['bin/*', 'examples/*', 'extras/**/*', 'lib/**/*',
'coffee-script.gemspec', 'LICENSE', 'README', 'package.json']
end

View File

@ -1,8 +1,8 @@
This folder includes rough cuts of CoffeeScript syntax highlighters for
TextMate and Vim. Improvements to their lexing ability are always welcome.
To install the TextMate bundle, run `bin/coffee --install-bundle`, or drop it
into "~/Library/Application Support/TextMate/Bundles".
To install the TextMate bundle, drop it into:
~/Library/Application Support/TextMate/Bundles
To install the Vim highlighter, copy "coffee.vim" into the "syntax" directory of
your vim72, and enable it in either of the following two ways:

View File

@ -1,21 +0,0 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
require "coffee_script/lexer"
require "coffee_script/parser"
require "coffee_script/nodes"
require "coffee_script/value"
require "coffee_script/scope"
require "coffee_script/rewriter"
require "coffee_script/parse_error"
# Namespace for all CoffeeScript internal classes.
module CoffeeScript
VERSION = '0.3.2' # Keep in sync with the gemspec.
# Compile a script (String or IO) to JavaScript.
def self.compile(script, options={})
script = script.read if script.respond_to?(:read)
Parser.new.parse(script).compile(options)
end
end

View File

@ -1,235 +0,0 @@
require 'optparse'
require 'fileutils'
require 'open3'
begin
require File.expand_path(File.dirname(__FILE__) + '/../coffee-script')
rescue LoadError => e
puts(e.message)
puts("use \"rake build:parser\" to regenerate parser.rb")
exit(1)
end
module CoffeeScript
# The CommandLine handles all of the functionality of the `coffee`
# utility.
class CommandLine
BANNER = <<-EOS
coffee compiles CoffeeScript source files into JavaScript.
Usage:
coffee path/to/script.coffee
EOS
# Seconds to pause between checks for changed source files.
WATCH_INTERVAL = 0.5
# Path to the root of the CoffeeScript install.
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
# Commands to execute CoffeeScripts.
RUNNERS = {
:node => "node #{ROOT}/lib/coffee_script/runner.js",
:narwhal => "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
}
# Run the CommandLine off the contents of ARGV.
def initialize
@mtimes = {}
parse_options
return launch_repl if @options[:interactive]
return eval_scriptlet if @options[:eval]
check_sources
return run_scripts if @options[:run]
@sources.each {|source| compile_javascript(source) }
watch_coffee_scripts if @options[:watch]
end
# The "--help" usage message.
def usage
puts "\n#{@option_parser}\n"
exit
end
private
# Compiles (or partially compiles) the source CoffeeScript file, returning
# the desired JS, tokens, or lint results.
def compile_javascript(source)
script = File.read(source)
return tokens(script) if @options[:tokens]
js = compile(script, source)
return unless js
return puts(js) if @options[:print]
return lint(js) if @options[:lint]
File.open(path_for(source), 'w+') {|f| f.write(js) }
end
# Spins up a watcher thread to keep track of the modification times of the
# source files, recompiling them whenever they're saved.
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
Signal.trap("INT") { watch_thread.kill }
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) }
if missing
STDERR.puts("File not found: '#{missing}'")
exit(1)
end
end
# 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
# Eval a little piece of CoffeeScript directly from the command line.
def eval_scriptlet
script = STDIN.tty? ? @sources.join(' ') : STDIN.read
return tokens(script) if @options[:tokens]
js = compile(script)
return lint(js) if @options[:lint]
puts js
end
# Use Node.js or Narwhal to run an interactive CoffeeScript session.
def launch_repl
exec "#{RUNNERS[@options[:runner]]}"
rescue Errno::ENOENT
puts "Error: #{@options[:runner]} must be installed to use the interactive REPL."
exit(1)
end
# Use Node.js or Narwhal to compile and execute CoffeeScripts.
def run_scripts
sources = @sources.join(' ')
exec "#{RUNNERS[@options[:runner]]} #{sources}"
rescue Errno::ENOENT
puts "Error: #{@options[:runner]} must be installed in order to execute scripts."
exit(1)
end
# Print the tokens that the lexer generates from a source script.
def tokens(script)
puts Lexer.new.tokenize(script).inspect
end
# Compile a single source file to JavaScript.
def compile(script, source='error')
begin
options = {}
options[:no_wrap] = true if @options[:no_wrap]
options[:globals] = true if @options[:globals]
CoffeeScript.compile(script, options)
rescue CoffeeScript::ParseError => e
STDERR.puts "#{source}: #{e.message}"
exit(1) unless @options[:watch]
nil
end
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
# Install the CoffeeScript TextMate bundle to ~/Library.
def install_bundle
bundle_dir = File.expand_path('~/Library/Application Support/TextMate/Bundles/')
FileUtils.cp_r("#{ROOT}/extras/CoffeeScript.tmbundle", bundle_dir)
end
# Use OptionParser for all the options.
def parse_options
@options = {:runner => :node}
@option_parser = OptionParser.new do |opts|
opts.on('-i', '--interactive', 'run an interactive CoffeeScript REPL') do |i|
@options[:interactive] = true
end
opts.on('-r', '--run', 'compile and run a CoffeeScript') do |r|
@options[:run] = true
end
opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
@options[:output] = d
FileUtils.mkdir_p(d) unless File.exists?(d)
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
opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l|
@options[:lint] = true
end
opts.on('-e', '--eval', 'compile a cli scriptlet or read from stdin') do |e|
@options[:eval] = true
end
opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t|
@options[:tokens] = true
end
opts.on('-v', '--verbose', 'print at every step of code generation') do |v|
ENV['VERBOSE'] = 'true'
end
opts.on('-n', '--no-wrap', 'raw output, no function safety wrapper') do |n|
@options[:no_wrap] = true
end
opts.on('-g', '--globals', 'attach all top-level variables as globals') do |n|
@options[:globals] = true
end
opts.on_tail('--narwhal', 'use Narwhal instead of Node.js') do |n|
@options[:runner] = :narwhal
end
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
install_bundle
exit
end
opts.on_tail('--version', 'display CoffeeScript version') do
puts "CoffeeScript version #{CoffeeScript::VERSION}"
exit
end
opts.on_tail('-h', '--help', 'display this help message') do
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

View File

@ -1,483 +0,0 @@
class Parser
# Declare terminal tokens produced by the lexer.
token IF ELSE UNLESS
token NUMBER STRING REGEX
token TRUE FALSE YES NO ON OFF
token IDENTIFIER PROPERTY_ACCESS PROTOTYPE_ACCESS SOAK_ACCESS
token CODE PARAM_START PARAM PARAM_END NEW RETURN
token CALL_START CALL_END INDEX_START INDEX_END
token TRY CATCH FINALLY THROW
token BREAK CONTINUE
token FOR IN OF BY WHEN WHILE
token SWITCH LEADING_WHEN
token DELETE INSTANCEOF TYPEOF
token SUPER EXTENDS
token ASSIGN RETURN
token NEWLINE
token COMMENT
token JS
token INDENT OUTDENT
# Declare order of operations.
prechigh
left '?'
nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
left '*' '/' '%' '.'
left '+' '-'
left '<<' '>>' '>>>' '&' '|' '^'
left '<=' '<' '>' '>='
right DELETE INSTANCEOF TYPEOF
right '==' '!=' IS ISNT
left '&&' '||' AND OR
right '-=' '+=' '/=' '*=' '%=' '||=' '&&=' '?='
right INDENT
left OUTDENT
right WHEN LEADING_WHEN IN OF BY
right THROW FOR NEW SUPER
left EXTENDS
right ASSIGN RETURN
right '->' '=>' UNLESS IF ELSE WHILE
preclow
rule
# All parsing will end in this rule, being the trunk of the AST.
Root:
/* nothing */ { result = Expressions.new }
| Terminator { result = Expressions.new }
| Expressions { result = val[0] }
| Block Terminator { result = val[0] }
;
# Any list of expressions or method body, seperated by line breaks or semis.
Expressions:
Expression { result = Expressions.wrap(val) }
| Expressions Terminator Expression { result = val[0] << val[2] }
| Expressions Terminator { result = val[0] }
;
# All types of expressions in our language. The basic unit of CoffeeScript
# is the expression.
Expression:
Value
| Call
| Code
| Operation
| Assign
| If
| Try
| Throw
| Return
| While
| For
| Switch
| Extends
| Splat
| Existence
| Comment
;
# A block of expressions. Note that the Rewriter will convert some postfix
# forms into blocks for us, by altering the token stream.
Block:
INDENT Expressions OUTDENT { result = val[1] }
| INDENT OUTDENT { result = Expressions.new }
;
# Tokens that can terminate an expression.
Terminator:
"\n"
| ";"
;
# All hard-coded values. These can be printed straight to JavaScript.
Literal:
NUMBER { result = LiteralNode.new(val[0]) }
| STRING { result = LiteralNode.new(val[0]) }
| JS { result = LiteralNode.new(val[0]) }
| REGEX { result = LiteralNode.new(val[0]) }
| BREAK { result = LiteralNode.new(val[0]) }
| CONTINUE { result = LiteralNode.new(val[0]) }
| TRUE { result = LiteralNode.new(Value.new(true)) }
| FALSE { result = LiteralNode.new(Value.new(false)) }
| YES { result = LiteralNode.new(Value.new(true)) }
| NO { result = LiteralNode.new(Value.new(false)) }
| ON { result = LiteralNode.new(Value.new(true)) }
| OFF { result = LiteralNode.new(Value.new(false)) }
;
# Assignment to a variable (or index).
Assign:
Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) }
;
# Assignment within an object literal (can be quoted).
AssignObj:
IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
| STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
| NUMBER ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
| Comment { result = val[0] }
;
# A return statement.
Return:
RETURN Expression { result = ReturnNode.new(val[1]) }
| RETURN { result = ReturnNode.new(ValueNode.new(Value.new('null'))) }
;
# 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
Operation:
'!' Expression { result = OpNode.new(val[0], val[1]) }
| '!!' Expression { result = OpNode.new(val[0], val[1]) }
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
| '+' Expression = UPLUS { result = OpNode.new(val[0], val[1]) }
| NOT Expression { result = OpNode.new(val[0], val[1]) }
| '~' Expression { result = OpNode.new(val[0], val[1]) }
| '--' Expression { result = OpNode.new(val[0], val[1]) }
| '++' Expression { result = OpNode.new(val[0], val[1]) }
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '?=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) }
;
# The existence operator.
Existence:
Expression '?' { result = ExistenceNode.new(val[0]) }
;
# Function definition.
Code:
PARAM_START ParamList PARAM_END
FuncGlyph Block { result = CodeNode.new(val[1], val[4], val[3]) }
| FuncGlyph Block { result = CodeNode.new([], val[1], val[0]) }
;
# The symbols to signify functions, and bound functions.
FuncGlyph:
'->' { result = :func }
| '=>' { result = :boundfunc }
;
# The parameters to a function definition.
ParamList:
Param { result = val }
| ParamList "," Param { result = val[0] << val[2] }
;
# A Parameter (or ParamSplat) in a function definition.
Param:
PARAM
| PARAM "." "." "." { result = SplatNode.new(val[0]) }
;
# A regular splat.
Splat:
Expression "." "." "." { result = SplatNode.new(val[0]) }
;
# Expressions that can be treated as values.
Value:
IDENTIFIER { result = ValueNode.new(val[0]) }
| Literal { result = ValueNode.new(val[0]) }
| Array { result = ValueNode.new(val[0]) }
| Object { result = ValueNode.new(val[0]) }
| Parenthetical { result = ValueNode.new(val[0]) }
| Range { result = ValueNode.new(val[0]) }
| This { result = ValueNode.new(val[0]) }
| Value Accessor { result = val[0] << val[1] }
| 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]) }
| PROTOTYPE_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :prototype) }
| SOAK_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :soak) }
| Index { result = val[0] }
| Slice { result = SliceNode.new(val[0]) }
;
# Indexing into an object or array.
Index:
INDEX_START Expression INDEX_END { result = IndexNode.new(val[1]) }
;
# 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 }
| AssignList "," AssignObj { result = val[0] << val[2] }
| AssignList Terminator AssignObj { result = val[0] << val[2] }
| AssignList ","
Terminator AssignObj { result = val[0] << val[3] }
| INDENT AssignList OUTDENT { result = val[1] }
;
# 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] }
;
# Extending an object's prototype.
Extends:
Value EXTENDS Value { result = ExtendsNode.new(val[0], val[2]) }
;
# A generic function invocation.
Invocation:
Value Arguments { result = CallNode.new(val[0], val[1]) }
| Invocation Arguments { result = CallNode.new(val[0], val[1]) }
;
# The list of arguments to a function invocation.
Arguments:
CALL_START ArgList CALL_END { result = val[1] }
;
# Calling super.
Super:
SUPER CALL_START ArgList CALL_END { result = CallNode.new(Value.new('super'), val[2]) }
;
# This references, either naked or to a property.
This:
'@' { result = ThisNode.new }
| '@' IDENTIFIER { result = ThisNode.new(val[1]) }
;
# The range literal.
Range:
"[" Expression
"." "." Expression "]" { result = RangeNode.new(val[1], val[4]) }
| "[" Expression
"." "." "." Expression "]" { result = RangeNode.new(val[1], val[5], true) }
;
# The slice literal.
Slice:
INDEX_START Expression "." "."
Expression INDEX_END { result = RangeNode.new(val[1], val[4]) }
| INDEX_START Expression "." "." "."
Expression INDEX_END { result = RangeNode.new(val[1], val[5], true) }
;
# The array literal.
Array:
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
;
# A list of arguments to a method call, or as the contents of an array.
ArgList:
/* nothing */ { result = [] }
| Expression { result = val }
| INDENT Expression { result = [val[1]] }
| ArgList "," Expression { result = val[0] << val[2] }
| ArgList Terminator Expression { result = val[0] << val[2] }
| ArgList "," Terminator Expression { result = val[0] << val[3] }
| ArgList "," INDENT Expression { result = val[0] << val[3] }
| ArgList OUTDENT { result = val[0] }
;
# Just simple, comma-separated, required arguments (no fancy syntax).
SimpleArgs:
Expression { result = val[0] }
| SimpleArgs "," Expression { result = ([val[0]] << val[2]).flatten }
;
# Try/catch/finally exception handling blocks.
Try:
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
| TRY Block FINALLY Block { result = TryNode.new(val[1], nil, nil, val[3]) }
| TRY Block Catch
FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) }
;
# A catch clause.
Catch:
CATCH IDENTIFIER Block { result = [val[1], val[2]] }
;
# Throw an exception.
Throw:
THROW Expression { result = ThrowNode.new(val[1]) }
;
# Parenthetical expressions.
Parenthetical:
"(" Expression ")" { result = ParentheticalNode.new(val[1], val[0].line) }
;
# The while loop. (there is no do..while).
While:
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
| WHILE Expression { result = WhileNode.new(val[1], nil) }
| Expression WHILE Expression { result = WhileNode.new(val[2], Expressions.wrap(val[0])) }
;
# Array comprehensions, including guard and current index.
# Looks a little confusing, check nodes.rb for the arguments to ForNode.
For:
Expression FOR
ForVariables ForSource { result = ForNode.new(val[0], val[3], val[2][0], val[2][1]) }
| FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2], val[1][0], val[1][1]) }
;
# An array comprehension has variables for the current element and index.
ForVariables:
IDENTIFIER { result = val }
| IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] }
;
# The source of the array comprehension can optionally be filtered.
ForSource:
IN Expression { result = {:source => val[1]} }
| OF Expression { result = {:source => val[1], :object => true} }
| ForSource
WHEN Expression { result = val[0].merge(:filter => val[2]) }
| ForSource
BY Expression { result = val[0].merge(:step => val[2]) }
;
# Switch/When blocks.
Switch:
SWITCH Expression INDENT
Whens OUTDENT { result = val[3].rewrite_condition(val[1]) }
| SWITCH Expression INDENT
Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
;
# The inner list of whens.
Whens:
When { result = val[0] }
| Whens When { result = val[0] << val[1] }
;
# An individual when.
When:
LEADING_WHEN SimpleArgs Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| LEADING_WHEN SimpleArgs Block
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
| Comment Terminator When { result = val[2].add_comment(val[0]) }
;
# The most basic form of "if".
IfBlock:
IF Expression Block { result = IfNode.new(val[1], val[2]) }
;
# An elsif portion of an if-else block.
ElsIf:
ELSE IfBlock { result = val[1].force_statement }
;
# 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
/* nothing */ { result = nil }
| ELSE Block { 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:
IfBlock IfEnd { result = val[0].add_else(val[1]) }
| Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) }
| Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) }
;
end
---- header
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
---- footer
end

View File

@ -1,273 +0,0 @@
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", "yes", "no", "on", "off",
"and", "or", "is", "isnt", "not",
"new", "return",
"try", "catch", "finally", "throw",
"break", "continue",
"for", "in", "of", "by", "where", "while",
"delete", "instanceof", "typeof",
"switch", "when",
"super", "extends"]
# 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
HEREDOC = /\A("{6}|'{6}|"{3}\n?(.*?)\n?([ \t]*)"{3}|'{3}\n?(.*?)\n?([ \t]*)'{3})/m
JS = /\A(``|`(.*?)([^\\]|\\\\)`)/m
OPERATOR = /\A([+\*&|\/\-%=<>:!?]+)/
WHITESPACE = /\A([ \t]+)/
COMMENT = /\A(((\n?[ \t]*)?#.*$)+)/
CODE = /\A((-|=)>)/
REGEX = /\A(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
MULTI_DENT = /\A((\n([ \t]*))+)(\.)?/
LAST_DENT = /\n([ \t]*)/
ASSIGNMENT = /\A(:|=)\Z/
# Token cleaning regexes.
JS_CLEANER = /(\A`|`\Z)/
MULTILINER = /\n/
STRING_NEWLINES = /\n[ \t]*/
COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/
NO_NEWLINE = /\A([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)\Z/
HEREDOC_INDENT = /^[ \t]+/
# Tokens which a regular expression will never immediately follow, but which
# a division operator might.
# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
NOT_REGEX = [
:IDENTIFIER, :NUMBER, :REGEX, :STRING,
')', '++', '--', ']', '}',
:FALSE, :NULL, :TRUE
]
# Tokens which could legitimately be invoked or indexed.
CALLABLE = [:IDENTIFIER, :SUPER, ')', ']', '}', :STRING]
# Scan by attempting to match tokens one character at a time. Slow and steady.
def tokenize(code)
@code = code.chomp # Cleanup code by remove extra line breaks
@i = 0 # Current character position we're parsing
@line = 1 # The current line.
@indent = 0 # The current indent level.
@indents = [] # The stack of all indent levels we are currently within.
@tokens = [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
@spaced = nil # The last value that has a space following it.
while @i < @code.length
@chunk = @code[@i..-1]
extract_next_token
end
puts "original stream: #{@tokens.inspect}" if ENV['VERBOSE']
close_indentation
Rewriter.new.rewrite(@tokens)
end
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed.
def extract_next_token
return if identifier_token
return if number_token
return if heredoc_token
return if string_token
return if js_token
return if regex_token
return if indent_token
return if comment_token
return if whitespace_token
return literal_token
end
# Tokenizers ==========================================================
# Matches identifying literals: variables, keywords, method names, etc.
def identifier_token
return false unless identifier = @chunk[IDENTIFIER, 1]
# Keywords are special identifiers tagged with their own name,
# 'if' will result in an [:IF, "if"] token.
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
tag = :LEADING_WHEN if tag == :WHEN && [:OUTDENT, :INDENT, "\n"].include?(last_tag)
@tokens[-1][0] = :PROTOTYPE_ACCESS if tag == :IDENTIFIER && last_value == '::'
if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2] && @tokens[-2][1] == '.')
if @tokens[-2][0] == "?"
@tokens[-1][0] = :SOAK_ACCESS
@tokens.delete_at(-2)
else
@tokens[-1][0] = :PROPERTY_ACCESS
end
end
token(tag, identifier)
@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(STRING_NEWLINES, " \\\n")
token(:STRING, escaped)
@line += string.count("\n")
@i += string.length
end
# Matches heredocs, adjusting indentation to the correct level.
def heredoc_token
return false unless match = @chunk.match(HEREDOC)
doc = match[2] || match[4]
indent = doc.scan(HEREDOC_INDENT).min
doc.gsub!(/^#{indent}/, "")
doc.gsub!("\n", "\\n")
doc.gsub!('"', '\\"')
token(:STRING, "\"#{doc}\"")
@line += match[1].count("\n")
@i += match[1].length
end
# Matches interpolated JavaScript.
def js_token
return false unless script = @chunk[JS, 1]
token(:JS, script.gsub(JS_CLEANER, ''))
@i += script.length
end
# Matches regular expression literals.
def regex_token
return false unless regex = @chunk[REGEX, 1]
return false if NOT_REGEX.include?(last_tag)
token(:REGEX, regex)
@i += regex.length
end
# Matches and consumes comments.
def comment_token
return false unless comment = @chunk[COMMENT, 1]
@line += comment.scan(MULTILINER).length
token(:COMMENT, comment.gsub(COMMENT_CLEANER, '').split(MULTILINER))
token("\n", "\n")
@i += comment.length
end
# Record tokens for indentation differing from the previous line.
def indent_token
return false unless indent = @chunk[MULTI_DENT, 1]
@line += indent.scan(MULTILINER).size
@i += indent.size
next_character = @chunk[MULTI_DENT, 4]
prev = @tokens[-2]
no_newlines = next_character == '.' || (last_value.to_s.match(NO_NEWLINE) && prev && prev[0] != '.' && !last_value.match(CODE))
return suppress_newlines(indent) if no_newlines
size = indent.scan(LAST_DENT).last.last.length
return newline_token(indent) if size == @indent
if size > @indent
token(:INDENT, size - @indent)
@indents << (size - @indent)
else
outdent_token(@indent - size)
end
@indent = size
end
# Record an oudent token or tokens, if we're moving back inwards past
# multiple recorded indents.
def outdent_token(move_out)
while move_out > 0 && !@indents.empty?
last_indent = @indents.pop
token(:OUTDENT, last_indent)
move_out -= last_indent
end
token("\n", "\n")
end
# Matches and consumes non-meaningful whitespace.
def whitespace_token
return false unless whitespace = @chunk[WHITESPACE, 1]
@spaced = last_value
@i += whitespace.length
end
# Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
def newline_token(newlines)
token("\n", "\n") unless last_value == "\n"
true
end
# Tokens to explicitly escape newlines are removed once their job is done.
def suppress_newlines(newlines)
@tokens.pop if last_value == "\\"
true
end
# We treat all other single characters as a token. Eg.: ( ) , . !
# Multi-character operators are also literal tokens, so that Racc can assign
# the proper order of operations.
def literal_token
value = @chunk[OPERATOR, 1]
tag_parameters if value && value.match(CODE)
value ||= @chunk[0,1]
tag = value.match(ASSIGNMENT) ? :ASSIGN : value
if !@spaced.equal?(last_value) && CALLABLE.include?(last_tag)
tag = :CALL_START if value == '('
tag = :INDEX_START if value == '['
end
token(tag, value)
@i += value.length
end
# Helpers ==========================================================
# Add a token to the results, taking note of the line number.
def token(tag, value)
@tokens << [tag, Value.new(value, @line)]
end
# Peek at the previous token's value.
def last_value
@tokens.last && @tokens.last[1]
end
# Peek at the previous token's tag.
def last_tag
@tokens.last && @tokens.last[0]
end
# A source of ambiguity in our grammar was parameter lists in function
# definitions (as opposed to argument lists in function calls). Tag
# parameter identifiers in order to avoid this. Also, parameter lists can
# make use of splats.
def tag_parameters
return if last_tag != ')'
i = 0
loop do
i -= 1
tok = @tokens[i]
return if !tok
case tok[0]
when :IDENTIFIER then tok[0] = :PARAM
when ')' then tok[0] = :PARAM_END
when '(' then return tok[0] = :PARAM_START
end
end
end
# Close up all remaining open blocks. IF the first token is an indent,
# axe it.
def close_indentation
outdent_token(@indent)
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
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
TOKEN_MAP = {
'INDENT' => 'indent',
'OUTDENT' => 'outdent',
"\n" => 'newline'
}
def initialize(token_id, value, stack=nil, message=nil)
@token_id, @value, @stack, @message = token_id, value, stack, message
end
def message
line = @value.respond_to?(:line) ? @value.line : "END"
line_part = "line #{line}:"
id_part = @token_id != @value.to_s ? " unexpected #{@token_id.to_s.downcase}" : ""
val_part = @message || "for #{TOKEN_MAP[@value.to_s] || "'#{@value}'"}"
"#{line_part} syntax error, #{val_part}#{id_part}"
end
alias_method :inspect, :message
end
end

File diff suppressed because it is too large Load Diff

View File

@ -1,289 +0,0 @@
module CoffeeScript
# In order to keep the grammar simple, the stream of tokens that the Lexer
# emits is rewritten by the Rewriter, smoothing out ambiguities, mis-nested
# indentation, and single-line flavors of expressions.
class Rewriter
# Tokens that must be balanced.
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT],
[:PARAM_START, :PARAM_END], [:CALL_START, :CALL_END], [:INDEX_START, :INDEX_END]]
# Tokens that signal the start of a balanced pair.
EXPRESSION_START = BALANCED_PAIRS.map {|pair| pair.first }
# Tokens that signal the end of a balanced pair.
EXPRESSION_TAIL = BALANCED_PAIRS.map {|pair| pair.last }
# Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = [:CATCH, :WHEN, :ELSE, :FINALLY] + EXPRESSION_TAIL
# Tokens pairs that, in immediate succession, indicate an implicit call.
IMPLICIT_FUNC = [:IDENTIFIER, :SUPER, ')', :CALL_END, ']', :INDEX_END]
IMPLICIT_END = [:IF, :UNLESS, :FOR, :WHILE, "\n", ';', :OUTDENT]
IMPLICIT_CALL = [:IDENTIFIER, :NUMBER, :STRING, :JS, :REGEX, :NEW, :PARAM_START,
:TRY, :DELETE, :TYPEOF, :SWITCH,
:TRUE, :FALSE, :YES, :NO, :ON, :OFF, '!', '!!', :NOT,
'@', '->', '=>', '[', '(', '{']
# The inverse mappings of token pairs we're trying to fix up.
INVERSES = BALANCED_PAIRS.inject({}) do |memo, pair|
memo[pair.first] = pair.last
memo[pair.last] = pair.first
memo
end
# Single-line flavors of block expressions that have unclosed endings.
# The grammar can't disambiguate them, so we insert the implicit indentation.
SINGLE_LINERS = [:ELSE, "->", "=>", :TRY, :FINALLY, :THEN]
SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN, :PARAM_START]
# Rewrite the token stream in multiple passes, one logical filter at
# a time. This could certainly be changed into a single pass through the
# stream, with a big ol' efficient switch, but it's much nicer like this.
def rewrite(tokens)
@tokens = tokens
adjust_comments
remove_leading_newlines
remove_mid_expression_newlines
move_commas_outside_outdents
close_open_calls_and_indexes
add_implicit_parentheses
add_implicit_indentation
ensure_balance(*BALANCED_PAIRS)
rewrite_closing_parens
@tokens
end
# Rewrite the token stream, looking one token ahead and behind.
# Allow the return value of the block to tell us how many tokens to move
# forwards (or backwards) in the stream, to make sure we don't miss anything
# as the stream changes length under our feet.
def scan_tokens
i = 0
loop do
break unless @tokens[i]
move = yield(@tokens[i - 1], @tokens[i], @tokens[i + 1], i)
i += move
end
end
# Massage newlines and indentations so that comments don't have to be
# correctly indented, or appear on their own line.
def adjust_comments
scan_tokens do |prev, token, post, i|
next 1 unless token[0] == :COMMENT
before, after = @tokens[i - 2], @tokens[i + 2]
if before && after &&
((before[0] == :INDENT && after[0] == :OUTDENT) ||
(before[0] == :OUTDENT && after[0] == :INDENT)) &&
before[1] == after[1]
@tokens.delete_at(i + 2)
@tokens.delete_at(i - 2)
next 0
elsif prev[0] == "\n" && [:INDENT].include?(after[0])
@tokens.delete_at(i + 2)
@tokens[i - 1] = after
next 1
elsif !["\n", :INDENT, :OUTDENT].include?(prev[0])
@tokens.insert(i, ["\n", Value.new("\n", token[1].line)])
next 2
else
next 1
end
end
end
# Leading newlines would introduce an ambiguity in the grammar, so we
# dispatch them here.
def remove_leading_newlines
@tokens.shift if @tokens[0][0] == "\n"
end
# Some blocks occur in the middle of expressions -- when we're expecting
# this, remove their trailing newlines.
def remove_mid_expression_newlines
scan_tokens do |prev, token, post, i|
next 1 unless post && EXPRESSION_CLOSE.include?(post[0]) && token[0] == "\n"
@tokens.delete_at(i)
next 0
end
end
# Make sure that we don't accidentally break trailing commas, which need
# to go on the outside of expression closers.
def move_commas_outside_outdents
scan_tokens do |prev, token, post, i|
if token[0] == :OUTDENT && prev[0] == ','
@tokens.delete_at(i)
@tokens.insert(i - 1, token)
end
next 1
end
end
# We've tagged the opening parenthesis of a method call, and the opening
# bracket of an indexing operation. Match them with their close.
def close_open_calls_and_indexes
parens, brackets = [0], [0]
scan_tokens do |prev, token, post, i|
case token[0]
when :CALL_START then parens.push(0)
when :INDEX_START then brackets.push(0)
when '(' then parens[-1] += 1
when '[' then brackets[-1] += 1
when ')'
if parens.last == 0
parens.pop
token[0] = :CALL_END
else
parens[-1] -= 1
end
when ']'
if brackets.last == 0
brackets.pop
token[0] = :INDEX_END
else
brackets[-1] -= 1
end
end
next 1
end
end
# Methods may be optionally called without parentheses, for simple cases.
# Insert the implicit parentheses here, so that the parser doesn't have to
# deal with them.
def add_implicit_parentheses
stack = [0]
scan_tokens do |prev, token, post, i|
stack.push(0) if token[0] == :INDENT
if token[0] == :OUTDENT
last = stack.pop
stack[-1] += last
end
if stack.last > 0 && (IMPLICIT_END.include?(token[0]) || post.nil?)
idx = token[0] == :OUTDENT ? i + 1 : i
stack.last.times { @tokens.insert(idx, [:CALL_END, Value.new(')', token[1].line)]) }
size, stack[-1] = stack[-1] + 1, 0
next size
end
next 1 unless IMPLICIT_FUNC.include?(prev[0]) && IMPLICIT_CALL.include?(token[0])
@tokens.insert(i, [:CALL_START, Value.new('(', token[1].line)])
stack[-1] += 1
next 2
end
end
# Because our grammar is LALR(1), it can't handle some single-line
# expressions that lack ending delimiters. Use the lexer to add the implicit
# blocks, so it doesn't need to.
# ')' can close a single-line block, but we need to make sure it's balanced.
def add_implicit_indentation
scan_tokens do |prev, token, post, i|
next 1 unless SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT &&
!(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks.
starter = token[0]
line = token[1].line
@tokens.insert(i + 1, [:INDENT, Value.new(2, line)])
idx = i + 1
parens = 0
loop do
idx += 1
tok = @tokens[idx]
if (!tok || SINGLE_CLOSERS.include?(tok[0]) ||
(tok[0] == ')' && parens == 0)) &&
!(starter == :ELSE && tok[0] == :ELSE)
insertion = @tokens[idx - 1][0] == "," ? idx - 1 : idx
@tokens.insert(insertion, [:OUTDENT, Value.new(2, line)])
break
end
parens += 1 if tok[0] == '('
parens -= 1 if tok[0] == ')'
end
next 1 unless token[0] == :THEN
@tokens.delete_at(i)
next 0
end
end
# Ensure that all listed pairs of tokens are correctly balanced throughout
# the course of the token stream.
def ensure_balance(*pairs)
puts "\nbefore ensure_balance: #{@tokens.inspect}" if ENV['VERBOSE']
levels, lines = Hash.new(0), Hash.new
scan_tokens do |prev, token, post, i|
pairs.each do |pair|
open, close = *pair
levels[open] += 1 if token[0] == open
levels[open] -= 1 if token[0] == close
lines[token[0]] = token[1].line
raise ParseError.new(token[0], token[1], nil) if levels[open] < 0
end
next 1
end
unclosed = levels.detect {|k, v| v > 0 }
sym = unclosed && unclosed[0]
raise ParseError.new(sym, Value.new(sym, lines[sym]), nil, "unclosed '#{sym}'") if unclosed
end
# We'd like to support syntax like this:
# el.click((event) ->
# el.hide())
# In order to accomplish this, move outdents that follow closing parens
# inwards, safely. The steps to accomplish this are:
#
# 1. Check that all paired tokens are balanced and in order.
# 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
# to the stack. If you see an ')' or OUTDENT, pop the stack and replace
# it with the inverse of what we've just popped.
# 3. Keep track of "debt" for tokens that we fake, to make sure we end
# up balanced in the end.
#
def rewrite_closing_parens
verbose = ENV['VERBOSE']
stack, debt = [], Hash.new(0)
stack_stats = lambda { "stack: #{stack.inspect} debt: #{debt.inspect}\n\n" }
puts "rewrite_closing_original: #{@tokens.inspect}" if verbose
scan_tokens do |prev, token, post, i|
tag, inv = token[0], INVERSES[token[0]]
# Push openers onto the stack.
if EXPRESSION_START.include?(tag)
stack.push(token)
puts "pushing #{tag} #{stack_stats[]}" if verbose
next 1
# The end of an expression, check stack and debt for a pair.
elsif EXPRESSION_TAIL.include?(tag)
puts @tokens[i..-1].inspect if verbose
# If the tag is already in our debt, swallow it.
if debt[inv] > 0
debt[inv] -= 1
@tokens.delete_at(i)
puts "tag in debt #{tag} #{stack_stats[]}" if verbose
next 0
else
# Pop the stack of open delimiters.
match = stack.pop
mtag = match[0]
# Continue onwards if it's the expected tag.
if tag == INVERSES[mtag]
puts "expected tag #{tag} #{stack_stats[]}" if verbose
next 1
else
# Unexpected close, insert correct close, adding to the debt.
debt[mtag] += 1
puts "unexpected #{tag}, replacing with #{INVERSES[mtag]} #{stack_stats[]}" if verbose
val = mtag == :INDENT ? match[1] : INVERSES[mtag]
@tokens.insert(i, [INVERSES[mtag], Value.new(val, token[1].line)])
next 1
end
end
else
# Uninteresting token:
next 1
end
end
end
end
end

View File

@ -1,95 +0,0 @@
module CoffeeScript
# 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
attr_reader :parent, :expressions, :function, :variables, :temp_variable
# Initialize a scope with its parent, for lookups up the chain,
# as well as the Expressions body where it should declare its variables,
# and the function that it wraps.
def initialize(parent, expressions, function)
@parent, @expressions, @function = parent, expressions, function
@variables = {}
@temp_variable = @parent ? @parent.temp_variable.dup : '__a'
end
# Look up a variable in lexical scope, or declare it if not found.
def find(name, remote=false)
found = check(name)
return found if found || remote
@variables[name.to_sym] = :var
found
end
# Define a local variable as originating from a parameter in current scope
# -- no var required.
def parameter(name)
@variables[name.to_sym] = :param
end
# Just check to see if a variable has already been declared.
def check(name)
return true if @variables[name.to_sym]
!!(@parent && @parent.check(name))
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.to_sym] = :var
Value.new(@temp_variable.dup)
end
# Ensure that an assignment is made at the top of scope (or top-level
# scope, if requested).
def assign(name, value, top=false)
return @parent.assign(name, value, top) if top && @parent
@variables[name.to_sym] = Value.new(value)
end
# Does this scope reference any variables that need to be declared in the
# given function body?
def declarations?(body)
!declared_variables.empty? && body == @expressions
end
# Does this scope reference any assignments that need to be declared at the
# top of the given function body?
def assignments?(body)
!assigned_variables.empty? && body == @expressions
end
# Return the list of variables first declared in current scope.
def declared_variables
@variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort
end
# Return the list of variables that are supposed to be assigned at the top
# of scope.
def assigned_variables
@variables.select {|k, v| v.is_a?(Value) }.sort_by {|pair| pair[0].to_s }
end
def compiled_declarations
declared_variables.join(', ')
end
def compiled_assignments
assigned_variables.map {|name, val| "#{name} = #{val}"}.join(', ')
end
def inspect
"<Scope:#{__id__} #{@variables.inspect}>"
end
end
end

View File

@ -1,64 +0,0 @@
module CoffeeScript
# Instead of producing raw Ruby objects, the Lexer produces values of this
# class, wrapping native objects tagged with line number information.
# Values masquerade as both strings and nodes -- being used both as nodes in
# the AST, and as literally-interpolated values in the generated code.
class Value
attr_reader :value, :line
def initialize(value, line=nil)
@value, @line = value, line
end
def to_str
@value.to_s
end
alias_method :to_s, :to_str
def to_sym
to_str.to_sym
end
def compile(o={})
to_s
end
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
def match(regex)
@value.match(regex)
end
def children
[]
end
def statement_only?
false
end
def contains?
false
end
end
end