merging in master

This commit is contained in:
Jeremy Ashkenas
2009-12-26 09:59:06 -08:00
91 changed files with 1190 additions and 613 deletions

View File

@@ -5,7 +5,7 @@
<key>name</key>
<string>comments</string>
<key>scope</key>
<string>source.cs</string>
<string>source.coffee</string>
<key>settings</key>
<dict>
<key>shellVariables</key>

View File

@@ -6,8 +6,7 @@
<string>CoffeeScript Syntax: version 1</string>
<key>fileTypes</key>
<array>
<string>cs</string>
<string>coffeescript</string>
<string>coffee</string>
</array>
<key>name</key>
<string>CoffeeScript</string>
@@ -19,22 +18,22 @@
<key>1</key>
<dict>
<key>name</key>
<string>entity.name.function.cs</string>
<string>entity.name.function.coffee</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>keyword.operator.cs</string>
<string>keyword.operator.coffee</string>
</dict>
<key>3</key>
<dict>
<key>name</key>
<string>variable.parameter.function.cs</string>
<string>variable.parameter.function.coffee</string>
</dict>
<key>4</key>
<dict>
<key>name</key>
<string>storage.type.function.cs</string>
<string>storage.type.function.coffee</string>
</dict>
</dict>
<key>comment</key>
@@ -42,7 +41,7 @@
<key>match</key>
<string>([a-zA-Z_?.$]*)\s*(=|:)\s*([\w,\s]*?)\s*(=&gt;)</string>
<key>name</key>
<string>meta.function.cs</string>
<string>meta.function.coffee</string>
</dict>
<dict>
<key>captures</key>
@@ -50,12 +49,12 @@
<key>1</key>
<dict>
<key>name</key>
<string>variable.parameter.function.cs</string>
<string>variable.parameter.function.coffee</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>storage.type.function.cs</string>
<string>storage.type.function.coffee</string>
</dict>
</dict>
<key>comment</key>
@@ -63,7 +62,7 @@
<key>match</key>
<string>([a-zA-Z_?., $]*)\s*(=&gt;)</string>
<key>name</key>
<string>meta.inline.function.cs</string>
<string>meta.inline.function.coffee</string>
</dict>
<dict>
<key>captures</key>
@@ -71,12 +70,12 @@
<key>1</key>
<dict>
<key>name</key>
<string>keyword.operator.new.cs</string>
<string>keyword.operator.new.coffee</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>entity.name.type.instance.cs</string>
<string>entity.name.type.instance.coffee</string>
</dict>
</dict>
<key>match</key>
@@ -88,7 +87,7 @@
<key>match</key>
<string>\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?))\b</string>
<key>name</key>
<string>constant.numeric.cs</string>
<string>constant.numeric.coffee</string>
</dict>
<dict>
<key>begin</key>
@@ -98,7 +97,7 @@
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.cs</string>
<string>punctuation.definition.string.begin.coffee</string>
</dict>
</dict>
<key>end</key>
@@ -108,18 +107,18 @@
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.cs</string>
<string>punctuation.definition.string.end.coffee</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.single.cs</string>
<string>string.quoted.single.coffee</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)</string>
<key>name</key>
<string>constant.character.escape.cs</string>
<string>constant.character.escape.coffee</string>
</dict>
</array>
</dict>
@@ -131,7 +130,7 @@
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.cs</string>
<string>punctuation.definition.string.begin.coffee</string>
</dict>
</dict>
<key>end</key>
@@ -141,18 +140,18 @@
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.cs</string>
<string>punctuation.definition.string.end.coffee</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.double.cs</string>
<string>string.quoted.double.coffee</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)</string>
<key>name</key>
<string>constant.character.escape.cs</string>
<string>constant.character.escape.coffee</string>
</dict>
</array>
</dict>
@@ -164,7 +163,7 @@
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.cs</string>
<string>punctuation.definition.string.begin.coffee</string>
</dict>
</dict>
<key>end</key>
@@ -174,18 +173,18 @@
<key>0</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.cs</string>
<string>punctuation.definition.string.end.coffee</string>
</dict>
</dict>
<key>name</key>
<string>string.quoted.script.cs</string>
<string>string.quoted.script.coffee</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\(x\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)</string>
<key>name</key>
<string>constant.character.escape.cs</string>
<string>constant.character.escape.coffee</string>
</dict>
</array>
</dict>
@@ -195,61 +194,61 @@
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.comment.cs</string>
<string>punctuation.definition.comment.coffee</string>
</dict>
</dict>
<key>match</key>
<string>(#).*$\n?</string>
<key>name</key>
<string>comment.line.cs</string>
<string>comment.line.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\b(break|when|catch|continue|else|finally|for|if|return|switch|then|throw|try|unless|while)\b</string>
<key>name</key>
<string>keyword.control.cs</string>
<string>keyword.control.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\b(true|on|yes)\b</string>
<key>name</key>
<string>constant.language.boolean.true.cs</string>
<string>constant.language.boolean.true.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\b(false|off|no)\b</string>
<key>name</key>
<string>constant.language.boolean.false.cs</string>
<string>constant.language.boolean.false.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\bnull\b</string>
<key>name</key>
<string>constant.language.null.cs</string>
<string>constant.language.null.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\b(super|this)\b</string>
<string>\b(super|this|extends)\b</string>
<key>name</key>
<string>variable.language.cs</string>
<string>variable.language.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\b(debugger)\b</string>
<string>\b(debugger|\\)\b</string>
<key>name</key>
<string>keyword.other.cs</string>
<string>keyword.other.coffee</string>
</dict>
<dict>
<key>match</key>
<string>!|\$|%|&amp;|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|&lt;=|&gt;=|&lt;&lt;=|&gt;&gt;=|&gt;&gt;&gt;=|&lt;&gt;|&lt;|&gt;|!|&amp;&amp;|\?|\|\||\:|\*=|(?&lt;!\()/=|%=|\+=|\-=|&amp;=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|aint|not)\b</string>
<string>!|\$|%|&amp;|\*|\-\-|\-|\+\+|\+|~|===|==|=|!=|!==|&lt;=|&gt;=|&lt;&lt;=|&gt;&gt;=|&gt;&gt;&gt;=|&lt;&gt;|&lt;|&gt;|!|&amp;&amp;|\?|\|\||\:|\*=|(?&lt;!\()/=|%=|\+=|\-=|&amp;=|\^=|\b(in|instanceof|new|delete|typeof|and|or|is|isnt|not)\b</string>
<key>name</key>
<string>keyword.operator.cs</string>
<string>keyword.operator.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\b(Infinity|NaN|undefined)\b</string>
<key>name</key>
<string>constant.language.cs</string>
<string>constant.language.coffee</string>
</dict>
<dict>
<key>begin</key>
@@ -259,7 +258,7 @@
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.begin.cs</string>
<string>punctuation.definition.string.begin.coffee</string>
</dict>
</dict>
<key>end</key>
@@ -269,18 +268,18 @@
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.string.end.cs</string>
<string>punctuation.definition.string.end.coffee</string>
</dict>
</dict>
<key>name</key>
<string>string.regexp.cs</string>
<string>string.regexp.coffee</string>
<key>patterns</key>
<array>
<dict>
<key>match</key>
<string>\\.</string>
<key>name</key>
<string>constant.character.escape.cs</string>
<string>constant.character.escape.coffee</string>
</dict>
</array>
</dict>
@@ -288,41 +287,41 @@
<key>match</key>
<string>\;</string>
<key>name</key>
<string>punctuation.terminator.statement.cs</string>
<string>punctuation.terminator.statement.coffee</string>
</dict>
<dict>
<key>match</key>
<string>,[ |\t]*</string>
<key>name</key>
<string>meta.delimiter.object.comma.cs</string>
<string>meta.delimiter.object.comma.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\.</string>
<key>name</key>
<string>meta.delimiter.method.period.cs</string>
<string>meta.delimiter.method.period.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\{|\}</string>
<key>name</key>
<string>meta.brace.curly.cs</string>
<string>meta.brace.curly.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\(|\)</string>
<key>name</key>
<string>meta.brace.round.cs</string>
<string>meta.brace.round.coffee</string>
</dict>
<dict>
<key>match</key>
<string>\[|\]</string>
<key>name</key>
<string>meta.brace.square.cs</string>
<string>meta.brace.square.coffee</string>
</dict>
</array>
<key>scopeName</key>
<string>source.cs</string>
<string>source.coffee</string>
<key>uuid</key>
<string>5B520980-A7D5-4E10-8582-1A4C889A8DE5</string>
</dict>

View File

@@ -5,15 +5,15 @@ require File.expand_path(File.dirname(__FILE__) + '/../coffee-script')
module CoffeeScript
# The CommandLine handles all of the functionality of the `coffee-script`
# The CommandLine handles all of the functionality of the `coffee`
# utility.
class CommandLine
BANNER = <<-EOS
coffee-script compiles CoffeeScript source files into JavaScript.
coffee compiles CoffeeScript source files into JavaScript.
Usage:
coffee-script path/to/script.cs
coffee path/to/script.coffee
EOS
# Seconds to pause between checks for changed source files.
@@ -23,8 +23,10 @@ Usage:
def initialize
@mtimes = {}
parse_options
return launch_repl if @options[:interactive]
return eval_scriptlet if @options[:eval]
check_sources
return run_scripts if @options[:run]
@sources.each {|source| compile_javascript(source) }
watch_coffee_scripts if @options[:watch]
end
@@ -100,17 +102,36 @@ Usage:
puts js
end
# Use Narwhal to run an interactive CoffeeScript session.
def launch_repl
exec "narwhal lib/coffee_script/narwhal/js/launcher.js"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed to use the interactive REPL."
exit(1)
end
# Use Narwhal to compile and execute CoffeeScripts.
def run_scripts
sources = @sources.join(' ')
exec "narwhal lib/coffee_script/narwhal/js/launcher.js #{sources}"
rescue Errno::ENOENT
puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
exit(1)
end
# Print the tokens that the lexer generates from a source script.
def tokens(script)
puts Lexer.new.tokenize(script).inspect
end
# Compile a single source file to JavaScript.
def compile(script, source='')
def compile(script, source='error')
begin
CoffeeScript.compile(script)
rescue CoffeeScript::ParseError => e
STDERR.puts e.message(source)
options = {}
options[:no_wrap] = true if @options[:no_wrap]
CoffeeScript.compile(script, options)
rescue CoffeeScript::ParseError, SyntaxError => e
STDERR.puts "#{source}: #{e.message}"
exit(1) unless @options[:watch]
nil
end
@@ -134,6 +155,12 @@ Usage:
def parse_options
@options = {}
@option_parser = OptionParser.new do |opts|
opts.on('-i', '--interactive', 'run a CoffeeScript REPL (requires Narwhal)') do |i|
@options[:interactive] = true
end
opts.on('-r', '--run', 'compile and run a script (requires Narwhal)') do |r|
@options[:run] = true
end
opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
@options[:output] = d
FileUtils.mkdir_p(d) unless File.exists?(d)
@@ -147,7 +174,7 @@ Usage:
opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l|
@options[:lint] = true
end
opts.on('-e', '--eval', 'eval a little scriptlet directly from the cli') do |e|
opts.on('-e', '--eval', '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|
@@ -156,12 +183,15 @@ Usage:
opts.on('-v', '--verbose', 'print at every step of code generation') do |v|
ENV['VERBOSE'] = 'true'
end
opts.on('-n', '--no-wrap', 'raw output, no safety wrapper or vars') do |n|
@options[:no_wrap] = true
end
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
install_bundle
exit
end
opts.on_tail('--version', 'display coffee-script version') do
puts "coffee-script version #{CoffeeScript::VERSION}"
opts.on_tail('--version', 'display CoffeeScript version') do
puts "CoffeeScript version #{CoffeeScript::VERSION}"
exit
end
opts.on_tail('-h', '--help', 'display this help message') do

View File

@@ -11,7 +11,7 @@ token BREAK CONTINUE
token FOR IN WHILE
token SWITCH WHEN
token DELETE INSTANCEOF TYPEOF
token SUPER
token SUPER EXTENDS
token NEWLINE
token COMMENT
token JS
@@ -25,21 +25,21 @@ prechigh
left '<<' '>>' '>>>'
left '&' '|' '^'
left '<=' '<' '>' '>='
right '==' '!=' IS AINT
right '==' '!=' IS ISNT
left '&&' '||' AND OR
right '-=' '+=' '/=' '*='
right '-=' '+=' '/=' '*=' '%='
right DELETE INSTANCEOF TYPEOF
left "."
right THROW FOR IN WHILE NEW
left UNLESS IF ELSE
left ":" '||:' '&&:'
right RETURN INDENT
left '.'
right THROW FOR IN WHILE NEW SUPER
left UNLESS IF ELSE EXTENDS
left ASSIGN '||=' '&&='
right RETURN
left OUTDENT
preclow
# We expect 4 shift/reduce errors for optional syntax.
# We expect 3 shift/reduce errors for optional syntax.
# There used to be 252 -- greatly improved.
expect 4
expect 3
rule
@@ -66,11 +66,11 @@ rule
# The parts that are natural JavaScript expressions.
PureExpression:
Literal
| Value
Value
| Call
| Code
| Operation
| Range
;
# We have to take extra care to convert these statements into expressions.
@@ -83,6 +83,7 @@ rule
| While
| For
| Switch
| Extends
| Comment
;
@@ -126,12 +127,13 @@ rule
# Assignment to a variable.
Assign:
Value ":" Expression { result = AssignNode.new(val[0], val[2]) }
Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) }
;
# Assignment within an object literal.
AssignObj:
IDENTIFIER ":" Expression { result = AssignNode.new(val[0], val[2], :object) }
IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
| STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
| Comment { result = val[0] }
;
@@ -154,10 +156,10 @@ rule
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
| NOT Expression { result = OpNode.new(val[0], val[1]) }
| '~' Expression { result = OpNode.new(val[0], val[1]) }
| '--' Expression { result = OpNode.new(val[0], val[1]) }
| '++' Expression { result = OpNode.new(val[0], val[1]) }
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
| '--' Expression { result = OpNode.new(val[0], val[1]) }
| '++' Expression { result = OpNode.new(val[0], val[1]) }
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
@@ -182,7 +184,7 @@ rule
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression AINT Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
@@ -193,8 +195,9 @@ 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]) }
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
@@ -222,6 +225,7 @@ rule
# Expressions that can be treated as values.
Value:
IDENTIFIER { result = ValueNode.new(val[0]) }
| Literal { result = ValueNode.new(val[0]) }
| Array { result = ValueNode.new(val[0]) }
| Object { result = ValueNode.new(val[0]) }
| Parenthetical { result = ValueNode.new(val[0]) }
@@ -233,7 +237,7 @@ rule
Accessor:
PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
| Index { result = val[0] }
| Slice { result = val[0] }
| Range { result = SliceNode.new(val[0]) }
;
# Indexing into an object or array.
@@ -241,11 +245,6 @@ rule
"[" 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]) }
@@ -266,9 +265,15 @@ rule
| 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 "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
| Invocation "(" ArgList ")" { result = CallNode.new(val[0], val[2]) }
;
# Calling super.
@@ -276,6 +281,12 @@ rule
SUPER "(" ArgList ")" { result = CallNode.new(:super, val[2]) }
;
# The range literal.
Range:
"[" Value "." "." Value "]" { result = RangeNode.new(val[1], val[4]) }
| "[" Value "." "." "." Value "]" { result = RangeNode.new(val[1], val[5], true) }
;
# The array literal.
Array:
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
@@ -318,19 +329,23 @@ rule
;
# Array comprehensions, including guard and current index.
# Looks a little confusing, check nodes.rb for the arguments to ForNode.
For:
Expression FOR IDENTIFIER
IN PureExpression { result = ForNode.new(val[0], val[4], val[2], nil) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN PureExpression { result = ForNode.new(val[0], val[6], val[2], nil, val[4]) }
| Expression FOR IDENTIFIER
IN PureExpression
IF Expression { result = ForNode.new(val[0], val[4], val[2], val[6]) }
| Expression FOR
IDENTIFIER "," IDENTIFIER
IN PureExpression
IF Expression { result = ForNode.new(val[0], val[6], val[2], val[8], val[4]) }
Expression FOR
ForVariables ForSource { result = ForNode.new(val[0], val[3][0], val[2][0], val[3][1], val[2][1]) }
;
# An array comprehension has variables for the current element and index.
ForVariables:
IDENTIFIER { result = val }
| IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] }
;
# The source of the array comprehension can optionally be filtered.
ForSource:
IN PureExpression "." { result = [val[1]] }
| IN PureExpression
IF Expression "." { result = [val[1], val[3]] }
;
# Switch/When blocks.

View File

@@ -8,13 +8,13 @@ module CoffeeScript
# The list of keywords passed verbatim to the parser.
KEYWORDS = ["if", "else", "then", "unless",
"true", "false", "yes", "no", "on", "off",
"and", "or", "is", "aint", "not",
"and", "or", "is", "isnt", "not",
"new", "return",
"try", "catch", "finally", "throw",
"break", "continue",
"for", "in", "while",
"switch", "when",
"super",
"super", "extends",
"delete", "instanceof", "typeof"]
# Token matching regexes.
@@ -41,6 +41,9 @@ module CoffeeScript
# Tokens that always constitute the end of an expression.
EXP_END = ['}', ')', ']']
# Assignment tokens.
ASSIGN = [':', '=']
# Scan by attempting to match tokens one character at a time. Slow and steady.
def tokenize(code)
@code = code.chomp # Cleanup code by remove extra line breaks
@@ -76,7 +79,7 @@ module CoffeeScript
# Keywords are special identifiers tagged with their own name, 'if' will result
# in an [:IF, "if"] token
tag = KEYWORDS.include?(identifier) ? identifier.upcase.to_sym : :IDENTIFIER
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.'
@tokens[-1][0] = :PROPERTY_ACCESS if tag == :IDENTIFIER && last_value == '.' && !(@tokens[-2][1] == '.')
token(tag, identifier)
@i += identifier.length
end
@@ -150,11 +153,13 @@ module CoffeeScript
# We treat all other single characters as a token. Eg.: ( ) , . !
# Multi-character operators are also literal tokens, so that Racc can assign
# the proper order of operations. Multiple newlines get merged together.
# Use a trailing \ to escape newlines.
def literal_token
value = @chunk[NEWLINE, 1]
if value
@line += value.length
token("\n", "\n") unless last_value == "\n"
token("\n", "\n") unless ["\n", "\\"].include?(last_value)
@tokens.pop if last_value == "\\"
return @i += value.length
end
value = @chunk[OPERATOR, 1]
@@ -162,7 +167,8 @@ module CoffeeScript
value ||= @chunk[0,1]
skip_following_newlines if EXP_START.include?(value)
remove_leading_newlines if EXP_END.include?(value)
token(value, value)
tag = ASSIGN.include?(value) ? :ASSIGN : value
token(tag, value)
@i += value.length
end

View File

@@ -0,0 +1,59 @@
# This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee
# Executes the `coffee` Ruby program to convert from CoffeeScript
# to Javascript. Eventually this will hopefully happen entirely within JS.
# Require external dependencies.
OS: require('os')
File: require('file')
Readline: require('readline')
# The path to the CoffeeScript Compiler.
coffeePath: File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee')
# Our general-purpose error handler.
checkForErrors: coffeeProcess =>
return true if coffeeProcess.wait() is 0
system.stderr.print(coffeeProcess.stderr.read())
throw new Error("CoffeeScript compile error").
# Run a simple REPL, round-tripping to the CoffeeScript compiler for every
# command.
exports.run: args =>
args.shift()
return require(File.absolute(args[0])) if args.length
while true
try
system.stdout.write('coffee> ').flush()
result: exports.evalCS(Readline.readline())
print(result) if result isnt undefined
catch e
print(e)...
# Compile a given CoffeeScript file into JavaScript.
exports.compileFile: path =>
coffee: OS.popen([coffeePath, "--print", "--no-wrap", path])
checkForErrors(coffee)
coffee.stdout.read().
# Compile a string of CoffeeScript into JavaScript.
exports.compile: source =>
coffee: OS.popen([coffeePath, "--eval", "--no-wrap"])
coffee.stdin.write(source).flush().close()
checkForErrors(coffee)
coffee.stdout.read().
# Evaluating a string of CoffeeScript first compiles it externally.
exports.evalCS: source =>
eval(exports.compile(source)).
# Make a factory for the CoffeeScript environment.
exports.makeNarwhalFactory: path =>
code: exports.compileFile(path)
factoryText: "function(require,exports,module,system,print){" + code + "/**/\n}"
if system.engine is "rhino"
Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null)
else
# eval requires parenthesis, but parenthesis break compileFunction.
eval("(" + factoryText + ")")..

View File

@@ -0,0 +1,69 @@
(function(){
var File, OS, Readline, checkForErrors, coffeePath;
// This (javascript) file is generated from lib/coffee_script/narwhal/coffee-script.coffee Executes the `coffee` Ruby program to convert from CoffeeScript
// to Javascript. Eventually this will hopefully happen entirely within JS. Require external dependencies.
OS = require('os');
File = require('file');
Readline = require('readline');
// The path to the CoffeeScript Compiler.
coffeePath = File.path(module.path).dirname().dirname().dirname().dirname().dirname().join('bin', 'coffee');
// Our general-purpose error handler.
checkForErrors = function(coffeeProcess) {
if (coffeeProcess.wait() === 0) {
return true;
}
system.stderr.print(coffeeProcess.stderr.read());
throw new Error("CoffeeScript compile error");
};
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every
// command.
exports.run = function(args) {
var result;
args.shift();
if (args.length) {
return require(File.absolute(args[0]));
}
while (true) {
try {
system.stdout.write('coffee> ').flush();
result = exports.evalCS(Readline.readline());
if (result !== undefined) {
print(result);
}
} catch (e) {
print(e);
}
}
};
// Compile a given CoffeeScript file into JavaScript.
exports.compileFile = function(path) {
var coffee;
coffee = OS.popen([coffeePath, "--print", "--no-wrap", path]);
checkForErrors(coffee);
return coffee.stdout.read();
};
// Compile a string of CoffeeScript into JavaScript.
exports.compile = function(source) {
var coffee;
coffee = OS.popen([coffeePath, "--eval", "--no-wrap"]);
coffee.stdin.write(source).flush().close();
checkForErrors(coffee);
return coffee.stdout.read();
};
// Evaluating a string of CoffeeScript first compiles it externally.
exports.evalCS = function(source) {
return eval(exports.compile(source));
};
// Make a factory for the CoffeeScript environment.
exports.makeNarwhalFactory = function(path) {
var code, factoryText;
code = exports.compileFile(path);
factoryText = "function(require,exports,module,system,print){" + code + "/**/\n}";
if (system.engine === "rhino") {
return Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(global, factoryText, path, 0, null);
} else {
// eval requires parenthesis, but parenthesis break compileFunction.
return eval("(" + factoryText + ")");
}
};
})();

View File

@@ -0,0 +1,3 @@
(function(){
require("coffee-script").run(system.args);
})();

View File

@@ -0,0 +1,20 @@
(function(){
var coffeescript, factories, loader;
// This (javascript) file is generated from lib/coffee_script/narwhal/loader.coffee
coffeescript = null;
factories = {
};
loader = {
// Reload the coffee-script environment from source.
reload: function(topId, path) {
coffeescript = coffeescript || require('coffee-script');
factories[topId] = coffeescript.makeNarwhalFactory(path);
return factories[topId];
},
// Ensure that the coffee-script environment is loaded.
load: function(topId, path) {
return factories[topId] = factories[topId] || this.reload(topId, path);
}
};
require.loader.loaders.unshift([".coffee", loader]);
})();

View File

@@ -0,0 +1 @@
require("coffee-script").run(system.args)

View File

@@ -0,0 +1,19 @@
# This (javascript) file is generated from lib/coffee_script/narwhal/loader.coffee
coffeescript: null
factories: {}
loader: {
# Reload the coffee-script environment from source.
reload: topId, path =>
coffeescript ||= require('coffee-script')
factories[topId]: coffeescript.makeNarwhalFactory(path).
# Ensure that the coffee-script environment is loaded.
load: topId, path =>
factories[topId] ||= this.reload(topId, path).
}
require.loader.loaders.unshift([".coffee", loader])

View File

@@ -75,17 +75,20 @@ module CoffeeScript
end
# If this is the top-level Expressions, wrap everything in a safety closure.
def root_compile
code = compile(:indent => TAB, :scope => Scope.new)
def root_compile(o={})
indent = o[:no_wrap] ? '' : TAB
code = compile(o.merge(:indent => indent, :scope => Scope.new), o[:no_wrap] ? nil : :code)
code.gsub!(STRIP_TRAILING_WHITESPACE, '')
"(function(){\n#{code}\n})();"
o[:no_wrap] ? code : "(function(){\n#{code}\n})();"
end
# The extra fancy is to handle pushing down returns and assignments
# recursively to the final lines of inner statements.
def compile(options={})
return root_compile unless options[:scope]
code = @expressions.map { |node|
# Variables first defined within the Expressions body have their
# declarations pushed up to the top scope.
def compile(options={}, parent=nil)
return root_compile(options) unless options[:scope]
compiled = @expressions.map do |node|
o = super(options)
if last?(node) && (o[:return] || o[:assign])
if o[:return]
@@ -98,14 +101,17 @@ module CoffeeScript
if node.statement? || node.custom_assign?
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
else
"#{o[:indent]}#{AssignNode.new(ValueNode.new(LiteralNode.new(o[:assign])), node).compile(o)};"
"#{o[:indent]}#{AssignNode.new(o[:assign], node).compile(o)};"
end
end
else
o.delete(:return) and o.delete(:assign)
"#{o[:indent]}#{node.compile(o)}#{node.line_ending}"
end
}.join("\n")
end
scope = options[:scope]
declarations = scope.any_declared? && parent == :code ? "#{options[:indent]}var #{scope.declared_variables.join(', ')};\n" : ''
code = declarations + compiled.join("\n")
write(code)
end
end
@@ -209,10 +215,29 @@ module CoffeeScript
def compile_super(args, o)
methname = o[:last_assign].sub(LEADING_DOT, '')
"this.constructor.prototype.#{methname}.call(this, #{args})"
arg_part = args.empty? ? '' : ", #{args}"
"#{o[:proto_assign]}.__superClass__.#{methname}.call(this#{arg_part})"
end
end
# Node to extend an object's prototype with an ancestor object.
# After goog.inherits from the Closure Library.
class ExtendsNode < Node
attr_reader :sub_object, :super_object
def initialize(sub_object, super_object)
@sub_object, @super_object = sub_object, super_object
end
def compile(o={})
sub, sup = @sub_object.compile(o), @super_object.compile(o)
"#{sub}.__superClass__ = #{sup}.prototype;\n#{o[:indent]}" +
"#{sub}.prototype = new #{sup}();\n#{o[:indent]}" +
"#{sub}.prototype.constructor = #{sub}"
end
end
# A value, indexed or dotted into, or vanilla.
class ValueNode < Node
attr_reader :literal, :properties, :last
@@ -280,27 +305,57 @@ module CoffeeScript
end
end
# A range literal. Ranges can be used to extract portions (slices) of arrays,
# or to specify a range for array comprehensions.
# RangeNodes get expanded into the equivalent array, if not used to index
# a slice or define an array comprehension.
class RangeNode
attr_reader :from, :to
def initialize(from, to, exclusive=false)
@from, @to, @exclusive = from, to, exclusive
end
def exclusive?
@exclusive
end
# TODO -- figure out if we can detect if a range runs negative.
def downward?
end
# TODO -- figure out if we can expand ranges into the corresponding array,
# as an expression, reliably.
def compile(o={})
write()
end
end
# An array slice literal. Unlike JavaScript's Array#slice, the second parameter
# specifies the index of the end of the slice (just like the first parameter)
# is the index of the beginning.
class SliceNode < Node
attr_reader :from, :to
attr_reader :range
def initialize(from, to)
@from, @to = from, to
def initialize(range)
@range = range
end
def compile(o={})
o = super(o)
write(".slice(#{@from.compile(o)}, #{@to.compile(o)} + 1)")
o = super(o)
from = @range.from.compile(o)
to = @range.to.compile(o)
plus_part = @range.exclusive? ? '' : ' + 1'
write(".slice(#{from}, #{to}#{plus_part})")
end
end
# Setting the value of a local variable, or the value of an object property.
class AssignNode < Node
LEADING_VAR = /\Avar\s+/
PROTO_ASSIGN = /\A(\S+)\.prototype/
statement
custom_return
attr_reader :variable, :value, :context
@@ -315,18 +370,16 @@ module CoffeeScript
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
o = o.merge(:assign => name, :last_assign => last)
name = @variable.compile(o)
last = @variable.last.to_s
proto = name[PROTO_ASSIGN, 1]
o = o.merge(:assign => @variable, :last_assign => last, :proto_assign => proto)
postfix = o[:return] ? ";\n#{o[:indent]}return #{name}" : ''
return write("#{@variable}: #{@value.compile(o)}") if @context == :object
return write("#{name}: #{@value.compile(o)}") if @context == :object
return write("#{name} = #{@value.compile(o)}#{postfix}") if @variable.properties? && !@value.custom_assign?
defined = o[:scope].find(name)
def_part = defined || @variable.properties? ? "" : "var #{name};\n#{o[:indent]}"
return write(def_part + @value.compile(o)) if @value.custom_assign?
def_part = defined ? name : "var #{name}"
val_part = @value.compile(o).sub(LEADING_VAR, '')
write("#{def_part} = #{val_part}#{postfix}")
o[:scope].find(name) unless @variable.properties?
return write(@value.compile(o)) if @value.custom_assign?
write("#{name} = #{@value.compile(o)}#{postfix}")
end
end
@@ -339,10 +392,10 @@ module CoffeeScript
'and' => '&&',
'or' => '||',
'is' => '===',
"aint" => "!==",
'not' => '!',
"isnt" => "!==",
'not' => '!'
}
CONDITIONALS = ['||:', '&&:']
CONDITIONALS = ['||=', '&&=']
PREFIX_OPERATORS = ['typeof', 'delete']
attr_reader :operator, :first, :second
@@ -393,8 +446,9 @@ module CoffeeScript
indent = o[:indent]
o[:indent] += TAB
o.delete(:assign)
@params.each {|id| o[:scope].find(id.to_s) }
code = @body.compile(o)
o.delete(:no_wrap)
@params.each {|id| o[:scope].parameter(id.to_s) }
code = @body.compile(o, :code)
write("function(#{@params.join(', ')}) {\n#{code}\n#{indent}}")
end
end
@@ -455,6 +509,7 @@ module CoffeeScript
def compile(o={})
o = super(o)
o.delete(:return)
indent = o[:indent] + TAB
cond = @condition.compile(o.merge(:no_paren => true))
write("while (#{cond}) {\n#{@body.compile(o.merge(:indent => indent))}\n#{o[:indent]}}")
@@ -482,32 +537,38 @@ module CoffeeScript
def compile(o={})
o = super(o)
scope = o[:scope]
name_found = scope.find(@name)
index_found = @index && scope.find(@index)
svar = scope.free_variable
ivar = scope.free_variable
lvar = scope.free_variable
name_part = name_found ? @name : "var #{@name}"
index_name = @index ? (index_found ? @index : "var #{@index}") : nil
source_part = "var #{svar} = #{@source.compile(o)};"
for_part = "var #{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
var_part = "\n#{o[:indent] + TAB}#{name_part} = #{svar}[#{ivar}];\n"
index_part = @index ? "#{o[:indent] + TAB}#{index_name} = #{ivar};\n" : ''
range = @source.is_a?(RangeNode)
scope = o[:scope]
name_found = scope.find(@name)
index_found = @index && scope.find(@index)
svar = scope.free_variable
ivar = range ? name : scope.free_variable
lvar = scope.free_variable
rvar = scope.free_variable
index_name = @index ? @index : nil
if range
source_part = ''
operator = @source.exclusive? ? '<' : '<='
index_var = scope.free_variable
for_part = "#{index_var}=0, #{ivar}=#{@source.from.compile(o)}, #{lvar}=#{@source.to.compile(o)}; #{ivar}#{operator}#{lvar}; #{ivar}++, #{index_var}++"
var_part = ''
index_part = ''
else
index_var = nil
source_part = "#{svar} = #{@source.compile(o)};\n#{o[:indent]}"
for_part = "#{ivar}=0, #{lvar}=#{svar}.length; #{ivar}<#{lvar}; #{ivar}++"
var_part = "\n#{o[:indent] + TAB}#{@name} = #{svar}[#{ivar}];"
index_part = @index ? "\n#{o[:indent] + TAB}#{index_name} = #{ivar};" : ''
end
body = @body
suffix = ';'
set_result = "#{rvar} = [];\n#{o[:indent]}"
save_result = "#{rvar}[#{index_var || ivar}] = "
return_result = rvar
set_result = ''
save_result = ''
return_result = ''
body = @body
suffix = ';'
if o[:return] || o[:assign]
rvar = scope.free_variable
set_result = "var #{rvar} = [];\n#{o[:indent]}"
save_result += "#{rvar}[#{ivar}] = "
return_result = rvar
return_result = "#{o[:assign]} = #{return_result};" if o[:assign]
return_result = "return #{return_result};" if o[:return]
return_result = "\n#{o[:indent]}#{return_result}"
return_result = "#{o[:assign].compile(o)} = #{return_result}" if o[:assign]
return_result = "return #{return_result}" if o[:return]
if @filter
body = CallNode.new(ValueNode.new(LiteralNode.new(rvar), [AccessorNode.new('push')]), [@body])
body = IfNode.new(@filter, body, nil, :statement => true)
@@ -518,9 +579,10 @@ module CoffeeScript
body = IfNode.new(@filter, @body)
end
return_result = "\n#{o[:indent]}#{return_result};"
indent = o[:indent] + TAB
body = body.compile(o.merge(:indent => indent))
write("#{source_part}\n#{o[:indent]}#{set_result}for (#{for_part}) {#{var_part}#{index_part}#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}")
write("#{source_part}#{set_result}for (#{for_part}) {#{var_part}#{index_part}\n#{indent}#{save_result}#{body}#{suffix}\n#{o[:indent]}}#{return_result}")
end
end
@@ -567,7 +629,9 @@ module CoffeeScript
end
end
# An extra set of parenthesis, supplied by the script source.
# An extra set of parentheses, supplied by the script source.
# You can't wrap parentheses around bits that get compiled into JS statements,
# unfortunately.
class ParentheticalNode < Node
attr_reader :expressions
@@ -576,7 +640,7 @@ module CoffeeScript
end
def statement?
@expressions.statement?
@expressions.unwrap.statement?
end
def custom_assign?
@@ -588,10 +652,11 @@ module CoffeeScript
end
def compile(o={})
raise SyntaxError, "parentheses can't be wrapped around a statement" if statement?
o = super(o)
compiled = @expressions.compile(o)
compiled = compiled[0...-1] if compiled[-1..-1] == ';'
write(o[:no_paren] || statement? ? compiled : "(#{compiled})")
write(o[:no_paren] ? compiled : "(#{compiled})")
end
end

View File

@@ -9,9 +9,9 @@ module CoffeeScript
@token_id, @value, @stack = token_id, value, stack
end
def message(source_file=nil)
def message
line = @value.respond_to?(:line) ? @value.line : "END"
line_part = source_file ? "#{source_file}:#{line}:" : "line #{line}:"
line_part = "line #{line}:"
id_part = @token_id != @value.inspect ? ", unexpected #{@token_id.downcase}" : ""
"#{line_part} syntax error for '#{@value.to_s}'#{id_part}"
end

View File

@@ -5,23 +5,29 @@ module CoffeeScript
# whether a variable has been seen before or if it needs to be declared.
class Scope
attr_reader :parent, :temp_variable
attr_reader :parent, :variables, :temp_variable
# Initialize a scope with its parent, for lookups up the chain.
def initialize(parent=nil)
@parent = parent
@variables = {}
@temp_variable = @parent ? @parent.temp_variable : 'a'
@temp_variable = @parent ? @parent.temp_variable : '__a'
end
# Look up a variable in lexical scope, or declare it if not found.
def find(name, remote=false)
found = check(name, remote)
return found if found || remote
@variables[name.to_sym] = true
@variables[name.to_sym] = :var
found
end
# Define a local variable as originating from a parameter in current scope
# -- no var required.
def parameter(name)
@variables[name.to_sym] = :param
end
# Just check to see if a variable has already been declared.
def check(name, remote=false)
return true if @variables[name.to_sym]
@@ -36,10 +42,19 @@ module CoffeeScript
# Find an available, short, name for a compiler-generated variable.
def free_variable
@temp_variable.succ! while check(@temp_variable)
@variables[@temp_variable.to_sym] = true
@variables[@temp_variable.to_sym] = :var
@temp_variable.dup
end
def any_declared?
!declared_variables.empty?
end
# Return the list of variables first declared in current scope.
def declared_variables
@variables.select {|k, v| v == :var }.map {|pair| pair[0].to_s }.sort
end
end
end