mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
merging node into master -- you can now pass the --narwhal flag to use narwhal instead. All tests are executing successfully against both Node.js and Narwhal/Rhino backends
This commit is contained in:
9
Rakefile
9
Rakefile
@@ -17,9 +17,14 @@ namespace :build do
|
||||
sh "racc #{args[:racc_args]} -o lib/coffee_script/parser.rb lib/coffee_script/grammar.y"
|
||||
end
|
||||
|
||||
desc "Compile the Narwhal interface for --interactive and --run"
|
||||
desc "Compile the Narwhal interface"
|
||||
task :narwhal do
|
||||
sh "bin/coffee lib/coffee_script/narwhal/*.coffee -o lib/coffee_script/narwhal"
|
||||
sh "bin/coffee src/narwhal/*.coffee -o lib/coffee_script/narwhal"
|
||||
end
|
||||
|
||||
desc "Continually compile the CoffeeScript/Node.js components with --watch"
|
||||
task :node do
|
||||
sh "bin/coffee -w src/*.coffee -o lib/coffee_script/"
|
||||
end
|
||||
|
||||
desc "Compile and install the Ultraviolet syntax highlighter"
|
||||
|
||||
@@ -118,7 +118,7 @@ gem install coffee-script</pre>
|
||||
Installing the gem provides the <tt>coffee</tt> command, which can
|
||||
be used to compile CoffeeScript <tt>.coffee</tt> files into JavaScript, as
|
||||
well as debug them. In conjunction with
|
||||
<a href="http://narwhaljs.org/">Narwhal</a>, the <tt>coffee</tt>
|
||||
<a href="http://nodejs.org/">Node.js</a>, the <tt>coffee</tt>
|
||||
command also provides direct evaluation and an interactive REPL.
|
||||
When compiling to JavaScript, <tt>coffee</tt> writes the output
|
||||
as <tt>.js</tt> files in the same directory by default, but output
|
||||
@@ -130,14 +130,14 @@ gem install coffee-script</pre>
|
||||
<td width="25%"><code>-i, --interactive</code></td>
|
||||
<td>
|
||||
Launch an interactive CoffeeScript session.
|
||||
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
|
||||
Requires <a href="http://nodejs.org/">Node.js</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-r, --run</code></td>
|
||||
<td>
|
||||
Compile and execute scripts without saving the intermediate
|
||||
JavaScript. Requires <a href="http://narwhaljs.org/">Narwhal</a>.
|
||||
JavaScript. Requires <a href="http://nodejs.org/">Node.js</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -194,7 +194,7 @@ gem install coffee-script</pre>
|
||||
<td><code>-n, --no-wrap</code></td>
|
||||
<td>
|
||||
Compile the JavaScript without the top-level function safety wrapper.
|
||||
(Used for CoffeeScript as a Narwhal module.)
|
||||
(Used for CoffeeScript as a Node.js module.)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -11,6 +11,6 @@ index: (list, target) ->
|
||||
if val < target then low: mid + 1 else high: mid
|
||||
return -1
|
||||
|
||||
print(2 is index([10, 20, 30, 40, 50], 30))
|
||||
print(4 is index([-97, 35, 67, 88, 1200], 1200))
|
||||
print(0 is index([0, 45, 70], 0))
|
||||
puts 2 is index([10, 20, 30, 40, 50], 30)
|
||||
puts 4 is index([-97, 35, 67, 88, 1200], 1200)
|
||||
puts 0 is index([0, 45, 70], 0)
|
||||
@@ -8,6 +8,6 @@ runtime: (N) ->
|
||||
t: n - 1 + sum / n
|
||||
t
|
||||
|
||||
print(runtime(3) is 2.6666666666666665)
|
||||
print(runtime(5) is 7.4)
|
||||
print(runtime(8) is 16.92142857142857)
|
||||
puts runtime(3) is 2.6666666666666665
|
||||
puts runtime(5) is 7.4
|
||||
puts runtime(8) is 16.92142857142857
|
||||
|
||||
@@ -26,9 +26,9 @@ match_star: (c, regexp, text) ->
|
||||
return false unless text and (text[0] is c or c is '.')
|
||||
text: text.slice(1)
|
||||
|
||||
print(match("ex", "some text"))
|
||||
print(match("s..t", "spit"))
|
||||
print(match("^..t", "buttercup"))
|
||||
print(match("i..$", "cherries"))
|
||||
print(match("o*m", "vrooooommm!"))
|
||||
print(match("^hel*o$", "hellllllo"))
|
||||
puts match("ex", "some text")
|
||||
puts match("s..t", "spit")
|
||||
puts match("^..t", "buttercup")
|
||||
puts match("i..$", "cherries")
|
||||
puts match("o*m", "vrooooommm!")
|
||||
puts match("^hel*o$", "hellllllo")
|
||||
@@ -19,7 +19,7 @@ binary_search: (items, value) ->
|
||||
|
||||
|
||||
# Test the function.
|
||||
print(2 is binary_search([10, 20, 30, 40, 50], 30))
|
||||
print(4 is binary_search([-97, 35, 67, 88, 1200], 1200))
|
||||
print(0 is binary_search([0, 45, 70], 0))
|
||||
print(-1 is binary_search([0, 45, 70], 10))
|
||||
puts(2 is binary_search([10, 20, 30, 40, 50], 30))
|
||||
puts(4 is binary_search([-97, 35, 67, 88, 1200], 1200))
|
||||
puts(0 is binary_search([0, 45, 70], 0))
|
||||
puts(-1 is binary_search([0, 45, 70], 10))
|
||||
@@ -7,5 +7,5 @@ bubble_sort: (list) ->
|
||||
|
||||
|
||||
# Test the function.
|
||||
print(bubble_sort([3, 2, 1]).join(' ') is '1 2 3')
|
||||
print(bubble_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
|
||||
puts(bubble_sort([3, 2, 1]).join(' ') is '1 2 3')
|
||||
puts(bubble_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
|
||||
@@ -91,16 +91,16 @@ LinkedList::toString: -> this.toArray().toString()
|
||||
list: new LinkedList()
|
||||
|
||||
list.add("Hi")
|
||||
print(list.size() is 1)
|
||||
print(list.item(0) is "Hi")
|
||||
print(list.item(1) is null)
|
||||
puts(list.size() is 1)
|
||||
puts(list.item(0) is "Hi")
|
||||
puts(list.item(1) is null)
|
||||
|
||||
list: new LinkedList()
|
||||
list.add("zero").add("one").add("two")
|
||||
print(list.size() is 3)
|
||||
print(list.item(2) is "two")
|
||||
print(list.remove(1) is "one")
|
||||
print(list.item(0) is "zero")
|
||||
print(list.item(1) is "two")
|
||||
print(list.size() is 2)
|
||||
print(list.item(-10) is null)
|
||||
puts(list.size() is 3)
|
||||
puts(list.item(2) is "two")
|
||||
puts(list.remove(1) is "one")
|
||||
puts(list.item(0) is "zero")
|
||||
puts(list.item(1) is "two")
|
||||
puts(list.size() is 2)
|
||||
puts(list.item(-10) is null)
|
||||
|
||||
@@ -31,6 +31,6 @@ is_valid_identifier: (identifier) ->
|
||||
|
||||
|
||||
# Tests.
|
||||
print(is_valid_identifier("49927398716") is true)
|
||||
print(is_valid_identifier("4408041234567893") is true)
|
||||
print(is_valid_identifier("4408041234567890") is false)
|
||||
puts(is_valid_identifier("49927398716") is true)
|
||||
puts(is_valid_identifier("4408041234567893") is true)
|
||||
puts(is_valid_identifier("4408041234567890") is false)
|
||||
|
||||
@@ -15,5 +15,5 @@ merge_sort: (list) ->
|
||||
|
||||
|
||||
# Test the function.
|
||||
print(merge_sort([3, 2, 1]).join(' ') is '1 2 3')
|
||||
print(merge_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
|
||||
puts(merge_sort([3, 2, 1]).join(' ') is '1 2 3')
|
||||
puts(merge_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
|
||||
@@ -9,7 +9,7 @@ selection_sort: (list) ->
|
||||
min: i
|
||||
|
||||
# Check the rest of the array to see if anything is smaller.
|
||||
(min: j if list[j] < list[min]) for j in [i+1...len]
|
||||
(min: j if list[j] < list[min]) for j in [(i+1)...len]
|
||||
|
||||
# Swap if a smaller item has been found.
|
||||
[list[i], list[min]]: [list[min], list[i]] if i isnt min
|
||||
@@ -19,5 +19,5 @@ selection_sort: (list) ->
|
||||
|
||||
|
||||
# Test the function.
|
||||
print(selection_sort([3, 2, 1]).join(' ') is '1 2 3')
|
||||
print(selection_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
|
||||
puts(selection_sort([3, 2, 1]).join(' ') is '1 2 3')
|
||||
puts(selection_sort([9, 2, 7, 0, 1]).join(' ') is '0 1 2 7 9')
|
||||
@@ -215,7 +215,7 @@ gem install coffee-script</pre>
|
||||
Installing the gem provides the <tt>coffee</tt> command, which can
|
||||
be used to compile CoffeeScript <tt>.coffee</tt> files into JavaScript, as
|
||||
well as debug them. In conjunction with
|
||||
<a href="http://narwhaljs.org/">Narwhal</a>, the <tt>coffee</tt>
|
||||
<a href="http://nodejs.org/">Node.js</a>, the <tt>coffee</tt>
|
||||
command also provides direct evaluation and an interactive REPL.
|
||||
When compiling to JavaScript, <tt>coffee</tt> writes the output
|
||||
as <tt>.js</tt> files in the same directory by default, but output
|
||||
@@ -227,14 +227,14 @@ gem install coffee-script</pre>
|
||||
<td width="25%"><code>-i, --interactive</code></td>
|
||||
<td>
|
||||
Launch an interactive CoffeeScript session.
|
||||
Requires <a href="http://narwhaljs.org/">Narwhal</a>.
|
||||
Requires <a href="http://nodejs.org/">Node.js</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>-r, --run</code></td>
|
||||
<td>
|
||||
Compile and execute scripts without saving the intermediate
|
||||
JavaScript. Requires <a href="http://narwhaljs.org/">Narwhal</a>.
|
||||
JavaScript. Requires <a href="http://nodejs.org/">Node.js</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -291,7 +291,7 @@ gem install coffee-script</pre>
|
||||
<td><code>-n, --no-wrap</code></td>
|
||||
<td>
|
||||
Compile the JavaScript without the top-level function safety wrapper.
|
||||
(Used for CoffeeScript as a Narwhal module.)
|
||||
(Used for CoffeeScript as a Node.js module.)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
50
lib/coffee_script/coffee-script.js
Normal file
50
lib/coffee_script/coffee-script.js
Normal file
@@ -0,0 +1,50 @@
|
||||
(function(){
|
||||
var compiler, path;
|
||||
// Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript.
|
||||
path = require('path');
|
||||
// The path to the CoffeeScript executable.
|
||||
compiler = path.normalize(path.dirname(__filename) + '/../../bin/coffee');
|
||||
// Compile a string over stdin, with global variables, for the REPL.
|
||||
exports.compile = function compile(code, callback) {
|
||||
var coffee, js;
|
||||
js = '';
|
||||
coffee = process.createChildProcess(compiler, ['--eval', '--no-wrap', '--globals']);
|
||||
coffee.addListener('output', function(results) {
|
||||
if ((typeof results !== "undefined" && results !== null)) {
|
||||
return js += results;
|
||||
}
|
||||
});
|
||||
coffee.addListener('exit', function() {
|
||||
return callback(js);
|
||||
});
|
||||
coffee.write(code);
|
||||
return coffee.close();
|
||||
};
|
||||
// Compile a list of CoffeeScript files on disk.
|
||||
exports.compile_files = function compile_files(paths, callback) {
|
||||
var coffee, exit_ran, js;
|
||||
js = '';
|
||||
coffee = process.createChildProcess(compiler, ['--print'].concat(paths));
|
||||
coffee.addListener('output', function(results) {
|
||||
if ((typeof results !== "undefined" && results !== null)) {
|
||||
return js += results;
|
||||
}
|
||||
});
|
||||
// NB: we have to add a mutex to make sure it doesn't get called twice.
|
||||
exit_ran = false;
|
||||
coffee.addListener('exit', function() {
|
||||
if (exit_ran) {
|
||||
return null;
|
||||
}
|
||||
exit_ran = true;
|
||||
return callback(js);
|
||||
});
|
||||
return coffee.addListener('error', function(message) {
|
||||
if (!(message)) {
|
||||
return null;
|
||||
}
|
||||
puts(message);
|
||||
throw new Error("CoffeeScript compile error");
|
||||
});
|
||||
};
|
||||
})();
|
||||
@@ -28,8 +28,11 @@ Usage:
|
||||
# Path to the root of the CoffeeScript install.
|
||||
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
|
||||
|
||||
# Command to execute in Narwhal
|
||||
LAUNCHER = "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
|
||||
# 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
|
||||
@@ -114,20 +117,20 @@ Usage:
|
||||
puts js
|
||||
end
|
||||
|
||||
# Use Narwhal to run an interactive CoffeeScript session.
|
||||
# Use Node.js or Narwhal to run an interactive CoffeeScript session.
|
||||
def launch_repl
|
||||
exec "#{LAUNCHER}"
|
||||
exec "#{RUNNERS[@options[:runner]]}"
|
||||
rescue Errno::ENOENT
|
||||
puts "Error: Narwhal must be installed to use the interactive REPL."
|
||||
puts "Error: #{@options[:runner]} must be installed to use the interactive REPL."
|
||||
exit(1)
|
||||
end
|
||||
|
||||
# Use Narwhal to compile and execute CoffeeScripts.
|
||||
# Use Node.js or Narwhal to compile and execute CoffeeScripts.
|
||||
def run_scripts
|
||||
sources = @sources.join(' ')
|
||||
exec "#{LAUNCHER} #{sources}"
|
||||
exec "#{RUNNERS[@options[:runner]]} #{sources}"
|
||||
rescue Errno::ENOENT
|
||||
puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
|
||||
puts "Error: #{@options[:runner]} must be installed in order to execute scripts."
|
||||
exit(1)
|
||||
end
|
||||
|
||||
@@ -166,12 +169,12 @@ Usage:
|
||||
|
||||
# Use OptionParser for all the options.
|
||||
def parse_options
|
||||
@options = {}
|
||||
@options = {:runner => :node}
|
||||
@option_parser = OptionParser.new do |opts|
|
||||
opts.on('-i', '--interactive', 'run a CoffeeScript REPL (requires Narwhal)') do |i|
|
||||
opts.on('-i', '--interactive', 'run an interactive CoffeeScript REPL') do |i|
|
||||
@options[:interactive] = true
|
||||
end
|
||||
opts.on('-r', '--run', 'compile and run a script (requires Narwhal)') do |r|
|
||||
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|
|
||||
@@ -202,6 +205,9 @@ Usage:
|
||||
opts.on('-g', '--globals', 'attach all top-level variable 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
|
||||
|
||||
@@ -13,6 +13,7 @@ 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
|
||||
@@ -20,24 +21,20 @@ token INDENT OUTDENT
|
||||
|
||||
# Declare order of operations.
|
||||
prechigh
|
||||
left '?'
|
||||
nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
|
||||
left '*' '/' '%'
|
||||
left '*' '/' '%' '?' '.'
|
||||
left '+' '-'
|
||||
left '<<' '>>' '>>>'
|
||||
left '&' '|' '^'
|
||||
left '<<' '>>' '>>>' '&' '|' '^'
|
||||
left '<=' '<' '>' '>='
|
||||
right '==' '!=' IS ISNT
|
||||
left '&&' '||' AND OR
|
||||
right '-=' '+=' '/=' '*=' '%='
|
||||
right '-=' '+=' '/=' '*=' '%=' '||=' '&&=' '?='
|
||||
right DELETE INSTANCEOF TYPEOF
|
||||
left '.'
|
||||
right INDENT
|
||||
left OUTDENT
|
||||
right WHEN LEADING_WHEN IN OF BY
|
||||
right THROW FOR NEW SUPER
|
||||
left EXTENDS
|
||||
left '||=' '&&=' '?='
|
||||
right ASSIGN RETURN
|
||||
right '->' '=>' UNLESS IF ELSE WHILE
|
||||
preclow
|
||||
|
||||
362
lib/coffee_script/lexer.js
Normal file
362
lib/coffee_script/lexer.js
Normal file
@@ -0,0 +1,362 @@
|
||||
(function(){
|
||||
var ASSIGNMENT, CALLABLE, CODE, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, KEYWORDS, LAST_DENT, LAST_DENTS, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex, sys;
|
||||
sys = require('sys');
|
||||
Rewriter = require('./rewriter').Rewriter;
|
||||
// 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.
|
||||
exports.Lexer = (lex = function lex() { });
|
||||
// Constants ============================================================
|
||||
// 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", "arguments", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "of", "by", "where", "while", "delete", "instanceof", "typeof", "switch", "when", "super", "extends"];
|
||||
// Token matching regexes.
|
||||
IDENTIFIER = /^([a-zA-Z$_](\w|\$)*)/;
|
||||
NUMBER = /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i;
|
||||
STRING = /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/;
|
||||
HEREDOC = /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/;
|
||||
JS = /^(``|`([\s\S]*?)([^\\]|\\\\)`)/;
|
||||
OPERATOR = /^([+\*&|\/\-%=<>:!?]+)/;
|
||||
WHITESPACE = /^([ \t]+)/;
|
||||
COMMENT = /^(((\n?[ \t]*)?#.*$)+)/;
|
||||
CODE = /^((-|=)>)/;
|
||||
REGEX = /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/;
|
||||
MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
|
||||
LAST_DENTS = /\n([ \t]*)/g;
|
||||
LAST_DENT = /\n([ \t]*)/;
|
||||
ASSIGNMENT = /^(:|=)$/;
|
||||
// Token cleaning regexes.
|
||||
JS_CLEANER = /(^`|`$)/g;
|
||||
MULTILINER = /\n/g;
|
||||
STRING_NEWLINES = /\n[ \t]*/g;
|
||||
COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/mg;
|
||||
NO_NEWLINE = /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/;
|
||||
HEREDOC_INDENT = /^[ \t]+/g;
|
||||
// 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.
|
||||
lex.prototype.tokenize = function tokenize(code) {
|
||||
this.code = code;
|
||||
// Cleanup code by remove extra line breaks, TODO: chomp
|
||||
this.i = 0;
|
||||
// Current character position we're parsing
|
||||
this.line = 1;
|
||||
// The current line.
|
||||
this.indent = 0;
|
||||
// The current indent level.
|
||||
this.indents = [];
|
||||
// The stack of all indent levels we are currently within.
|
||||
this.tokens = [];
|
||||
// Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
|
||||
this.spaced = null;
|
||||
// The last token that has a space following it.
|
||||
while (this.i < this.code.length) {
|
||||
this.chunk = this.code.slice(this.i);
|
||||
this.extract_next_token();
|
||||
}
|
||||
// sys.puts "original stream: " + this.tokens if process.ENV['VERBOSE']
|
||||
this.close_indentation();
|
||||
return (new Rewriter()).rewrite(this.tokens);
|
||||
};
|
||||
// At every position, run through this list of attempted matches,
|
||||
// short-circuiting if any of them succeed.
|
||||
lex.prototype.extract_next_token = function extract_next_token() {
|
||||
if (this.identifier_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.number_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.heredoc_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.string_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.js_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.regex_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.indent_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.comment_token()) {
|
||||
return null;
|
||||
}
|
||||
if (this.whitespace_token()) {
|
||||
return null;
|
||||
}
|
||||
return this.literal_token();
|
||||
};
|
||||
// Tokenizers ==========================================================
|
||||
// Matches identifying literals: variables, keywords, method names, etc.
|
||||
lex.prototype.identifier_token = function identifier_token() {
|
||||
var id, tag;
|
||||
if (!((id = this.match(IDENTIFIER, 1)))) {
|
||||
return false;
|
||||
}
|
||||
// Keywords are special identifiers tagged with their own name,
|
||||
// 'if' will result in an ['IF', "if"] token.
|
||||
tag = KEYWORDS.indexOf(id) >= 0 ? id.toUpperCase() : 'IDENTIFIER';
|
||||
if (tag === 'WHEN' && (this.tag() === 'OUTDENT' || this.tag() === 'INDENT')) {
|
||||
tag = 'LEADING_WHEN';
|
||||
}
|
||||
if (tag === 'IDENTIFIER' && this.value() === '::') {
|
||||
this.tag(-1, 'PROTOTYPE_ACCESS');
|
||||
}
|
||||
if (tag === 'IDENTIFIER' && this.value() === '.' && !(this.value(-2) === '.')) {
|
||||
if (this.tag(-2) === '?') {
|
||||
this.tag(-1, 'SOAK_ACCESS');
|
||||
this.tokens.splice(-2, 1);
|
||||
} else {
|
||||
this.tag(-1, 'PROPERTY_ACCESS');
|
||||
}
|
||||
}
|
||||
this.token(tag, id);
|
||||
this.i += id.length;
|
||||
return true;
|
||||
};
|
||||
// Matches numbers, including decimals, hex, and exponential notation.
|
||||
lex.prototype.number_token = function number_token() {
|
||||
var number;
|
||||
if (!((number = this.match(NUMBER, 1)))) {
|
||||
return false;
|
||||
}
|
||||
this.token('NUMBER', number);
|
||||
this.i += number.length;
|
||||
return true;
|
||||
};
|
||||
// Matches strings, including multi-line strings.
|
||||
lex.prototype.string_token = function string_token() {
|
||||
var escaped, string;
|
||||
if (!((string = this.match(STRING, 1)))) {
|
||||
return false;
|
||||
}
|
||||
escaped = string.replace(STRING_NEWLINES, " \\\n");
|
||||
this.token('STRING', escaped);
|
||||
this.line += this.count(string, "\n");
|
||||
this.i += string.length;
|
||||
return true;
|
||||
};
|
||||
// Matches heredocs, adjusting indentation to the correct level.
|
||||
lex.prototype.heredoc_token = function heredoc_token() {
|
||||
var doc, indent, match;
|
||||
if (!((match = this.chunk.match(HEREDOC)))) {
|
||||
return false;
|
||||
}
|
||||
doc = match[2] || match[4];
|
||||
indent = doc.match(HEREDOC_INDENT).sort()[0];
|
||||
doc = doc.replace(new RegExp("^" + indent, 'g'), '').replace(MULTILINER, "\\n").replace('"', '\\"');
|
||||
this.token('STRING', '"' + doc + '"');
|
||||
this.line += this.count(match[1], "\n");
|
||||
this.i += match[1].length;
|
||||
return true;
|
||||
};
|
||||
// Matches interpolated JavaScript.
|
||||
lex.prototype.js_token = function js_token() {
|
||||
var script;
|
||||
if (!((script = this.match(JS, 1)))) {
|
||||
return false;
|
||||
}
|
||||
this.token('JS', script.replace(JS_CLEANER, ''));
|
||||
this.i += script.length;
|
||||
return true;
|
||||
};
|
||||
// Matches regular expression literals.
|
||||
lex.prototype.regex_token = function regex_token() {
|
||||
var regex;
|
||||
if (!((regex = this.match(REGEX, 1)))) {
|
||||
return false;
|
||||
}
|
||||
if (NOT_REGEX.indexOf(this.tag()) >= 0) {
|
||||
return false;
|
||||
}
|
||||
this.token('REGEX', regex);
|
||||
this.i += regex.length;
|
||||
return true;
|
||||
};
|
||||
// Matches and conumes comments.
|
||||
lex.prototype.comment_token = function comment_token() {
|
||||
var comment;
|
||||
if (!((comment = this.match(COMMENT, 1)))) {
|
||||
return false;
|
||||
}
|
||||
this.line += comment.match(MULTILINER).length;
|
||||
this.token('COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER));
|
||||
this.token("\n", "\n");
|
||||
this.i += comment.length;
|
||||
return true;
|
||||
};
|
||||
// Record tokens for indentation differing from the previous line.
|
||||
lex.prototype.indent_token = function indent_token() {
|
||||
var diff, indent, next_character, no_newlines, size;
|
||||
if (!((indent = this.match(MULTI_DENT, 1)))) {
|
||||
return false;
|
||||
}
|
||||
this.line += indent.match(MULTILINER).length;
|
||||
this.i += indent.length;
|
||||
next_character = this.chunk.match(MULTI_DENT)[4];
|
||||
no_newlines = next_character === '.' || (this.value().match(NO_NEWLINE) && this.tokens[this.tokens.length - 2][0] !== '.' && !this.value().match(CODE));
|
||||
if (no_newlines) {
|
||||
return this.suppress_newlines(indent);
|
||||
}
|
||||
size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
|
||||
if (size === this.indent) {
|
||||
return this.newline_token(indent);
|
||||
}
|
||||
if (size > this.indent) {
|
||||
diff = size - this.indent;
|
||||
this.token('INDENT', diff);
|
||||
this.indents.push(diff);
|
||||
} else {
|
||||
this.outdent_token(this.indent - size);
|
||||
}
|
||||
this.indent = size;
|
||||
return true;
|
||||
};
|
||||
// Record an oudent token or tokens, if we're moving back inwards past
|
||||
// multiple recorded indents.
|
||||
lex.prototype.outdent_token = function outdent_token(move_out) {
|
||||
var last_indent;
|
||||
while (move_out > 0 && this.indents.length) {
|
||||
last_indent = this.indents.pop();
|
||||
this.token('OUTDENT', last_indent);
|
||||
move_out -= last_indent;
|
||||
}
|
||||
this.token("\n", "\n");
|
||||
return true;
|
||||
};
|
||||
// Matches and consumes non-meaningful whitespace.
|
||||
lex.prototype.whitespace_token = function whitespace_token() {
|
||||
var space;
|
||||
if (!((space = this.match(WHITESPACE, 1)))) {
|
||||
return false;
|
||||
}
|
||||
this.spaced = this.value();
|
||||
this.i += space.length;
|
||||
return true;
|
||||
};
|
||||
// Multiple newlines get merged together.
|
||||
// Use a trailing \ to escape newlines.
|
||||
lex.prototype.newline_token = function newline_token(newlines) {
|
||||
if (!(this.value() === "\n")) {
|
||||
this.token("\n", "\n");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Tokens to explicitly escape newlines are removed once their job is done.
|
||||
lex.prototype.suppress_newlines = function suppress_newlines(newlines) {
|
||||
if (this.value() === "\\") {
|
||||
this.tokens.pop();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// 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.
|
||||
lex.prototype.literal_token = function literal_token() {
|
||||
var match, tag, value;
|
||||
match = this.chunk.match(OPERATOR);
|
||||
value = match && match[1];
|
||||
if (value && value.match(CODE)) {
|
||||
this.tag_parameters();
|
||||
}
|
||||
value = value || this.chunk.substr(0, 1);
|
||||
tag = value.match(ASSIGNMENT) ? 'ASSIGN' : value;
|
||||
if (this.value() !== this.spaced && CALLABLE.indexOf(this.tag()) >= 0) {
|
||||
if (value === '(') {
|
||||
tag = 'CALL_START';
|
||||
}
|
||||
if (value === '[') {
|
||||
tag = 'INDEX_START';
|
||||
}
|
||||
}
|
||||
this.token(tag, value);
|
||||
this.i += value.length;
|
||||
return true;
|
||||
};
|
||||
// Helpers =============================================================
|
||||
// Add a token to the results, taking note of the line number.
|
||||
lex.prototype.token = function token(tag, value) {
|
||||
return this.tokens.push([tag, value]);
|
||||
// this.tokens.push([tag, Value.new(value, @line)])
|
||||
};
|
||||
// Look at a tag in the current token stream.
|
||||
lex.prototype.tag = function tag(index, tag) {
|
||||
var tok;
|
||||
if (!((tok = this.tokens[this.tokens.length - (index || 1)]))) {
|
||||
return null;
|
||||
}
|
||||
if ((typeof tag !== "undefined" && tag !== null)) {
|
||||
return (tok[0] = tag);
|
||||
}
|
||||
return tok[0];
|
||||
};
|
||||
// Look at a value in the current token stream.
|
||||
lex.prototype.value = function value(index, val) {
|
||||
var tok;
|
||||
if (!((tok = this.tokens[this.tokens.length - (index || 1)]))) {
|
||||
return null;
|
||||
}
|
||||
if ((typeof val !== "undefined" && val !== null)) {
|
||||
return (tok[1] = val);
|
||||
}
|
||||
return tok[1];
|
||||
};
|
||||
// Count the occurences of a character in a string.
|
||||
lex.prototype.count = function count(string, char) {
|
||||
var num, pos;
|
||||
num = 0;
|
||||
pos = string.indexOf(char);
|
||||
while (pos !== -1) {
|
||||
count += 1;
|
||||
pos = string.indexOf(char, pos + 1);
|
||||
}
|
||||
return count;
|
||||
};
|
||||
// Attempt to match a string against the current chunk, returning the indexed
|
||||
// match.
|
||||
lex.prototype.match = function match(regex, index) {
|
||||
var m;
|
||||
if (!((m = this.chunk.match(regex)))) {
|
||||
return false;
|
||||
}
|
||||
return m ? m[index] : false;
|
||||
};
|
||||
// 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.
|
||||
lex.prototype.tag_parameters = function tag_parameters() {
|
||||
var i, tok;
|
||||
if (this.tag() !== ')') {
|
||||
return null;
|
||||
}
|
||||
i = 0;
|
||||
while (true) {
|
||||
i += 1;
|
||||
tok = this.tokens[this.tokens.length - i];
|
||||
if (!tok) {
|
||||
return null;
|
||||
}
|
||||
if (tok[0] === 'IDENTIFIER') {
|
||||
tok[0] = 'PARAM';
|
||||
} else if (tok[0] === ')') {
|
||||
tok[0] = 'PARAM_END';
|
||||
} else if (tok[0] === '(') {
|
||||
return (tok[0] = 'PARAM_START');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Close up all remaining open blocks. IF the first token is an indent,
|
||||
// axe it.
|
||||
lex.prototype.close_indentation = function close_indentation() {
|
||||
return this.outdent_token(this.indent);
|
||||
};
|
||||
})();
|
||||
@@ -228,8 +228,7 @@ module CoffeeScript
|
||||
|
||||
# Helpers ==========================================================
|
||||
|
||||
# Add a token to the results, taking note of the line number, and
|
||||
# immediately-preceding comment.
|
||||
# Add a token to the results, taking note of the line number.
|
||||
def token(tag, value)
|
||||
@tokens << [tag, Value.new(value, @line)]
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(function(){
|
||||
var File, OS, Readline, checkForErrors, coffeePath, factories, loader;
|
||||
var File, OS, Readline, checkForErrors, coffeePath, factories, loader, puts;
|
||||
// The Narwhal-compatibility wrapper for CoffeeScript.
|
||||
// Require external dependencies.
|
||||
OS = require('os');
|
||||
@@ -15,6 +15,8 @@
|
||||
system.stderr.print(coffeeProcess.stderr.read());
|
||||
throw new Error("CoffeeScript compile error");
|
||||
};
|
||||
// Alias print to "puts", for Node.js compatibility:
|
||||
puts = print;
|
||||
// Run a simple REPL, round-tripping to the CoffeeScript compiler for every
|
||||
// command.
|
||||
exports.run = function run(args) {
|
||||
|
||||
34
lib/coffee_script/nodes.js
Normal file
34
lib/coffee_script/nodes.js
Normal file
@@ -0,0 +1,34 @@
|
||||
(function(){
|
||||
exports.Node = function Node() {
|
||||
var __a;
|
||||
var arguments = Array.prototype.slice.call(arguments, 0);
|
||||
__a = this.values = arguments;
|
||||
return Node === this.constructor ? this : __a;
|
||||
};
|
||||
exports.Expressions = exports.Node;
|
||||
exports.LiteralNode = exports.Node;
|
||||
exports.ReturnNode = exports.Node;
|
||||
exports.CommentNode = exports.Node;
|
||||
exports.CallNode = exports.Node;
|
||||
exports.ExtendsNode = exports.Node;
|
||||
exports.ValueNode = exports.Node;
|
||||
exports.AccessorNode = exports.Node;
|
||||
exports.IndexNode = exports.Node;
|
||||
exports.RangeNode = exports.Node;
|
||||
exports.SliceNode = exports.Node;
|
||||
exports.AssignNode = exports.Node;
|
||||
exports.OpNode = exports.Node;
|
||||
exports.CodeNode = exports.Node;
|
||||
exports.SplatNode = exports.Node;
|
||||
exports.ObjectNode = exports.Node;
|
||||
exports.ArrayNode = exports.Node;
|
||||
exports.PushNode = exports.Node;
|
||||
exports.ClosureNode = exports.Node;
|
||||
exports.WhileNode = exports.Node;
|
||||
exports.ForNode = exports.Node;
|
||||
exports.TryNode = exports.Node;
|
||||
exports.ThrowNode = exports.Node;
|
||||
exports.ExistenceNode = exports.Node;
|
||||
exports.ParentheticalNode = exports.Node;
|
||||
exports.IfNode = exports.Node;
|
||||
})();
|
||||
620
lib/coffee_script/parser.js
Normal file
620
lib/coffee_script/parser.js
Normal file
@@ -0,0 +1,620 @@
|
||||
(function(){
|
||||
var Parser, __a, __b, __c, __d, __e, __f, bnf, grammar, name, non_terminal, o, operators, option, parser, part, tokens, unwrap;
|
||||
var __hasProp = Object.prototype.hasOwnProperty;
|
||||
Parser = require('jison').Parser;
|
||||
// DSL ===================================================================
|
||||
// Detect functions: [
|
||||
unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/;
|
||||
// Quickie DSL for Jison access.
|
||||
o = function o(pattern_string, func) {
|
||||
var match;
|
||||
if (func) {
|
||||
func = (match = (func + "").match(unwrap)) ? match[1] : '(' + func + '())';
|
||||
return [pattern_string, '$$ = ' + func + ';'];
|
||||
} else {
|
||||
return [pattern_string, '$$ = $1;'];
|
||||
}
|
||||
};
|
||||
// Precedence ===========================================================
|
||||
operators = [["left", '?'], ["right", 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'], ["right", 'THROW', 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["left", '||=', '&&=', '?='], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']];
|
||||
// Grammar ==============================================================
|
||||
grammar = {
|
||||
// All parsing will end in this rule, being the trunk of the AST.
|
||||
Root: [o("", function() {
|
||||
return new Expressions();
|
||||
}), o("Terminator", function() {
|
||||
return new Expressions();
|
||||
}), o("Expressions"), o("Block Terminator")
|
||||
],
|
||||
// Any list of expressions or method body, seperated by line breaks or semis.
|
||||
Expressions: [o("Expression", function() {
|
||||
return Expressions.wrap([$1]);
|
||||
}), o("Expressions Terminator Expression", function() {
|
||||
return $1.push($3);
|
||||
}), o("Expressions Terminator")
|
||||
],
|
||||
// All types of expressions in our language. The basic unit of CoffeeScript
|
||||
// is the expression.
|
||||
Expression: [o("Value"), o("Call"), o("Code"), o("Operation"), o("Assign"), o("If"), o("Try"), o("Throw"), o("Return"), o("While"), o("For"), o("Switch"), o("Extends"), o("Splat"), o("Existence"), o("Comment")],
|
||||
// A block of expressions. Note that the Rewriter will convert some postfix
|
||||
// forms into blocks for us, by altering the token stream.
|
||||
Block: [o("INDENT Expressions OUTDENT", function() {
|
||||
return $2;
|
||||
}), o("INDENT OUTDENT", function() {
|
||||
return new Expressions();
|
||||
})
|
||||
],
|
||||
// Tokens that can terminate an expression.
|
||||
Terminator: [o("\n"), o(";")],
|
||||
// All hard-coded values. These can be printed straight to JavaScript.
|
||||
Literal: [o("NUMBER", function() {
|
||||
return new LiteralNode($1);
|
||||
}), o("STRING", function() {
|
||||
return new LiteralNode($1);
|
||||
}), o("JS", function() {
|
||||
return new LiteralNode($1);
|
||||
}), o("REGEX", function() {
|
||||
return new LiteralNode($1);
|
||||
}), o("BREAK", function() {
|
||||
return new LiteralNode($1);
|
||||
}), o("CONTINUE", function() {
|
||||
return new LiteralNode($1);
|
||||
}), o("ARGUMENTS", function() {
|
||||
return new LiteralNode($1);
|
||||
}), o("TRUE", function() {
|
||||
return new LiteralNode(true);
|
||||
}), o("FALSE", function() {
|
||||
return new LiteralNode(false);
|
||||
}), o("YES", function() {
|
||||
return new LiteralNode(true);
|
||||
}), o("NO", function() {
|
||||
return new LiteralNode(false);
|
||||
}), o("ON", function() {
|
||||
return new LiteralNode(true);
|
||||
}), o("OFF", function() {
|
||||
return new LiteralNode(false);
|
||||
})
|
||||
],
|
||||
// Assignment to a variable (or index).
|
||||
Assign: [o("Value ASSIGN Expression", function() {
|
||||
return new AssignNode($1, $3);
|
||||
})
|
||||
],
|
||||
// Assignment within an object literal (can be quoted).
|
||||
AssignObj: [o("IDENTIFIER ASSIGN Expression", function() {
|
||||
return new AssignNode(new ValueNode($1), $3, 'object');
|
||||
}), o("STRING ASSIGN Expression", function() {
|
||||
return new AssignNode(new ValueNode(new LiteralNode($1)), $3, 'object');
|
||||
}), o("Comment")
|
||||
],
|
||||
// A return statement.
|
||||
Return: [o("RETURN Expression", function() {
|
||||
return new ReturnNode($2);
|
||||
}), o("RETURN", function() {
|
||||
return new ReturnNode(new ValueNode(new LiteralNode('null')));
|
||||
})
|
||||
],
|
||||
// A comment.
|
||||
Comment: [o("COMMENT", function() {
|
||||
return new CommentNode($1);
|
||||
})
|
||||
],
|
||||
// Arithmetic and logical operators
|
||||
// For Ruby's Operator precedence, see: [
|
||||
// https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
|
||||
Operation: [o("! Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("!! Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("- Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("+ Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("NOT Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("~ Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("-- Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("++ Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("DELETE Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("TYPEOF Expression", function() {
|
||||
return new OpNode($1, $2);
|
||||
}), o("Expression --", function() {
|
||||
return new OpNode($2, $1, null, true);
|
||||
}), o("Expression ++", function() {
|
||||
return new OpNode($2, $1, null, true);
|
||||
}), o("Expression * Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression / Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression % Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression + Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression - Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression << Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression >> Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression >>> Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression & Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression | Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression ^ Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression <= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression < Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression > Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression >= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression == Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression != Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression IS Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression ISNT Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression && Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression || Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression AND Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression OR Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression ? Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression -= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression += Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression /= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression *= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression %= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression ||= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression &&= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression ?= Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression INSTANCEOF Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
}), o("Expression IN Expression", function() {
|
||||
return new OpNode($2, $1, $3);
|
||||
})
|
||||
],
|
||||
// Try abbreviated expressions to make the grammar build faster:
|
||||
// UnaryOp: [
|
||||
// o "!"
|
||||
// o "!!"
|
||||
// o "NOT"
|
||||
// o "~"
|
||||
// o "--"
|
||||
// o "++"
|
||||
// o "DELETE"
|
||||
// o "TYPEOF"
|
||||
// ]
|
||||
//
|
||||
// BinaryOp: [
|
||||
// o "*"
|
||||
// o "/"
|
||||
// o "%"
|
||||
// o "+"
|
||||
// o "-"
|
||||
// o "<<"
|
||||
// o ">>"
|
||||
// o ">>>"
|
||||
// o "&"
|
||||
// o "|"
|
||||
// o "^"
|
||||
// o "<="
|
||||
// o "<"
|
||||
// o ">"
|
||||
// o ">="
|
||||
// o "=="
|
||||
// o "!="
|
||||
// o "IS"
|
||||
// o "ISNT"
|
||||
// o "&&"
|
||||
// o "||"
|
||||
// o "AND"
|
||||
// o "OR"
|
||||
// o "?"
|
||||
// o "-="
|
||||
// o "+="
|
||||
// o "/="
|
||||
// o "*="
|
||||
// o "%="
|
||||
// o "||="
|
||||
// o "&&="
|
||||
// o "?="
|
||||
// o "INSTANCEOF"
|
||||
// o "IN"
|
||||
// ]
|
||||
//
|
||||
// Operation: [
|
||||
// o "Expression BinaryOp Expression", -> new OpNode($2, $1, $3)
|
||||
// o "UnaryOp Expression", -> new OpNode($1, $2)
|
||||
// ]
|
||||
// The existence operator.
|
||||
Existence: [o("Expression ?", function() {
|
||||
return new ExistenceNode($1);
|
||||
})
|
||||
],
|
||||
// Function definition.
|
||||
Code: [o("PARAM_START ParamList PARAM_END FuncGlyph Block", function() {
|
||||
return new CodeNode($2, $5, $4);
|
||||
}), o("FuncGlyph Block", function() {
|
||||
return new CodeNode([], $2, $1);
|
||||
})
|
||||
],
|
||||
// The symbols to signify functions, and bound functions.
|
||||
FuncGlyph: [o("->", function() {
|
||||
return 'func';
|
||||
}), o("=>", function() {
|
||||
return 'boundfunc';
|
||||
})
|
||||
],
|
||||
// The parameters to a function definition.
|
||||
ParamList: [o("Param", function() {
|
||||
return [$1];
|
||||
}), o("ParamList , Param", function() {
|
||||
return $1.push($3);
|
||||
})
|
||||
],
|
||||
// A Parameter (or ParamSplat) in a function definition.
|
||||
Param: [o("PARAM"), o("PARAM . . .", function() {
|
||||
return new SplatNode($1);
|
||||
})
|
||||
],
|
||||
// A regular splat.
|
||||
Splat: [o("Expression . . .", function() {
|
||||
return new SplatNode($1);
|
||||
})
|
||||
],
|
||||
// Expressions that can be treated as values.
|
||||
Value: [o("IDENTIFIER", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Literal", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Array", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Object", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Parenthetical", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Range", function() {
|
||||
return new ValueNode($1);
|
||||
}), o("Value Accessor", function() {
|
||||
return $1.push($2);
|
||||
}), o("Invocation Accessor", function() {
|
||||
return new ValueNode($1, [$2]);
|
||||
})
|
||||
],
|
||||
// Accessing into an object or array, through dot or index notation.
|
||||
Accessor: [o("PROPERTY_ACCESS IDENTIFIER", function() {
|
||||
return new AccessorNode($2);
|
||||
}), o("PROTOTYPE_ACCESS IDENTIFIER", function() {
|
||||
return new AccessorNode($2, 'prototype');
|
||||
}), o("SOAK_ACCESS IDENTIFIER", function() {
|
||||
return new AccessorNode($2, 'soak');
|
||||
}), o("Index"), o("Slice", function() {
|
||||
return new SliceNode($1);
|
||||
})
|
||||
],
|
||||
// Indexing into an object or array.
|
||||
Index: [o("INDEX_START Expression INDEX_END", function() {
|
||||
return new IndexNode($2);
|
||||
})
|
||||
],
|
||||
// An object literal.
|
||||
Object: [o("{ AssignList }", function() {
|
||||
return new ObjectNode($2);
|
||||
})
|
||||
],
|
||||
// Assignment within an object literal (comma or newline separated).
|
||||
AssignList: [o("", function() {
|
||||
return [];
|
||||
}), o("AssignObj", function() {
|
||||
return [$1];
|
||||
}), o("AssignList , AssignObj", function() {
|
||||
return $1.push($3);
|
||||
}), o("AssignList Terminator AssignObj", function() {
|
||||
return $1.push($3);
|
||||
}), o("AssignList , Terminator AssignObj", function() {
|
||||
return $1.push($4);
|
||||
}), o("INDENT AssignList OUTDENT", function() {
|
||||
return $2;
|
||||
})
|
||||
],
|
||||
// All flavors of function call (instantiation, super, and regular).
|
||||
Call: [o("Invocation", function() {
|
||||
return $1;
|
||||
}), o("NEW Invocation", function() {
|
||||
return $2.new_instance();
|
||||
}), o("Super", function() {
|
||||
return $1;
|
||||
})
|
||||
],
|
||||
// Extending an object's prototype.
|
||||
Extends: [o("Value EXTENDS Value", function() {
|
||||
return new ExtendsNode($1, $3);
|
||||
})
|
||||
],
|
||||
// A generic function invocation.
|
||||
Invocation: [o("Value Arguments", function() {
|
||||
return new CallNode($1, $2);
|
||||
}), o("Invocation Arguments", function() {
|
||||
return new CallNode($1, $2);
|
||||
})
|
||||
],
|
||||
// The list of arguments to a function invocation.
|
||||
Arguments: [o("CALL_START ArgList CALL_END", function() {
|
||||
return $2;
|
||||
})
|
||||
],
|
||||
// Calling super.
|
||||
Super: [o("SUPER CALL_START ArgList CALL_END", function() {
|
||||
return new CallNode('super', $3);
|
||||
})
|
||||
],
|
||||
// The range literal.
|
||||
Range: [o("[ Expression . . Expression ]", function() {
|
||||
return new RangeNode($2, $5);
|
||||
}), o("[ Expression . . . Expression ]", function() {
|
||||
return new RangeNode($2, $6, true);
|
||||
})
|
||||
],
|
||||
// The slice literal.
|
||||
Slice: [o("INDEX_START Expression . . Expression INDEX_END", function() {
|
||||
return new RangeNode($2, $5);
|
||||
}), o("INDEX_START Expression . . . Expression INDEX_END", function() {
|
||||
return new RangeNode($2, $6, true);
|
||||
})
|
||||
],
|
||||
// The array literal.
|
||||
Array: [o("[ ArgList ]", function() {
|
||||
return new ArrayNode($2);
|
||||
})
|
||||
],
|
||||
// A list of arguments to a method call, or as the contents of an array.
|
||||
ArgList: [o("", function() {
|
||||
return [];
|
||||
}), o("Expression", function() {
|
||||
return val;
|
||||
}), o("INDENT Expression", function() {
|
||||
return [$2];
|
||||
}), o("ArgList , Expression", function() {
|
||||
return $1.push($3);
|
||||
}), o("ArgList Terminator Expression", function() {
|
||||
return $1.push($3);
|
||||
}), o("ArgList , Terminator Expression", function() {
|
||||
return $1.push($4);
|
||||
}), o("ArgList , INDENT Expression", function() {
|
||||
return $1.push($4);
|
||||
}), o("ArgList OUTDENT", function() {
|
||||
return $1;
|
||||
})
|
||||
],
|
||||
// Just simple, comma-separated, required arguments (no fancy syntax).
|
||||
SimpleArgs: [o("Expression", function() {
|
||||
return $1;
|
||||
}), o("SimpleArgs , Expression", function() {
|
||||
return ([$1].push($3)).reduce(function(a, b) {
|
||||
return a.concat(b);
|
||||
});
|
||||
})
|
||||
],
|
||||
// Try/catch/finally exception handling blocks.
|
||||
Try: [o("TRY Block Catch", function() {
|
||||
return new TryNode($2, $3[0], $3[1]);
|
||||
}), o("TRY Block FINALLY Block", function() {
|
||||
return new TryNode($2, nil, nil, $4);
|
||||
}), o("TRY Block Catch FINALLY Block", function() {
|
||||
return new TryNode($2, $3[0], $3[1], $5);
|
||||
})
|
||||
],
|
||||
// A catch clause.
|
||||
Catch: [o("CATCH IDENTIFIER Block", function() {
|
||||
return [$2, $3];
|
||||
})
|
||||
],
|
||||
// Throw an exception.
|
||||
Throw: [o("THROW Expression", function() {
|
||||
return new ThrowNode($2);
|
||||
})
|
||||
],
|
||||
// Parenthetical expressions.
|
||||
Parenthetical: [o("( Expression )", function() {
|
||||
return new ParentheticalNode($2);
|
||||
})
|
||||
],
|
||||
// The while loop. (there is no do..while).
|
||||
While: [o("WHILE Expression Block", function() {
|
||||
return new WhileNode($2, $3);
|
||||
}), o("WHILE Expression", function() {
|
||||
return new WhileNode($2, nil);
|
||||
}), o("Expression WHILE Expression", function() {
|
||||
return new WhileNode($3, Expressions.wrap($1));
|
||||
})
|
||||
],
|
||||
// Array comprehensions, including guard and current index.
|
||||
// Looks a little confusing, check nodes.rb for the arguments to ForNode.
|
||||
For: [o("Expression FOR ForVariables ForSource", function() {
|
||||
return new ForNode($1, $4, $3[0], $3[1]);
|
||||
}), o("FOR ForVariables ForSource Block", function() {
|
||||
return new ForNode($4, $3, $2[0], $2[1]);
|
||||
})
|
||||
],
|
||||
// An array comprehension has variables for the current element and index.
|
||||
ForVariables: [o("IDENTIFIER", function() {
|
||||
return [$1];
|
||||
}), o("IDENTIFIER , IDENTIFIER", function() {
|
||||
return [$1, $3];
|
||||
})
|
||||
],
|
||||
// The source of the array comprehension can optionally be filtered.
|
||||
ForSource: [o("IN Expression", function() {
|
||||
return {
|
||||
source: $2
|
||||
};
|
||||
}), o("OF Expression", function() {
|
||||
return {
|
||||
source: $2,
|
||||
object: true
|
||||
};
|
||||
}), o("ForSource WHEN Expression", function() {
|
||||
$1.filter = $3;
|
||||
return $1;
|
||||
}), o("ForSource BY Expression", function() {
|
||||
$1.step = $3;
|
||||
return $1;
|
||||
})
|
||||
],
|
||||
// Switch/When blocks.
|
||||
Switch: [o("SWITCH Expression INDENT Whens OUTDENT", function() {
|
||||
return $4.rewrite_condition($2);
|
||||
}), o("SWITCH Expression INDENT Whens ELSE Block OUTDENT", function() {
|
||||
return $4.rewrite_condition($2).add_else($6);
|
||||
})
|
||||
],
|
||||
// The inner list of whens.
|
||||
Whens: [o("When", function() {
|
||||
return $1;
|
||||
}), o("Whens When", function() {
|
||||
return $1.push($2);
|
||||
})
|
||||
],
|
||||
// An individual when.
|
||||
When: [o("LEADING_WHEN SimpleArgs Block", function() {
|
||||
return new IfNode($2, $3, nil, {
|
||||
statement: true
|
||||
});
|
||||
}), o("LEADING_WHEN SimpleArgs Block Terminator", function() {
|
||||
return new IfNode($2, $3, nil, {
|
||||
statement: true
|
||||
});
|
||||
}), o("Comment Terminator When", function() {
|
||||
return $3.add_comment($1);
|
||||
})
|
||||
],
|
||||
// The most basic form of "if".
|
||||
IfBlock: [o("IF Expression Block", function() {
|
||||
return new IfNode($2, $3);
|
||||
})
|
||||
],
|
||||
// An elsif portion of an if-else block.
|
||||
ElsIf: [o("ELSE IfBlock", function() {
|
||||
return $2.force_statement();
|
||||
})
|
||||
],
|
||||
// Multiple elsifs can be chained together.
|
||||
ElsIfs: [o("ElsIf", function() {
|
||||
return $1;
|
||||
}), o("ElsIfs ElsIf", function() {
|
||||
return $1.add_else($2);
|
||||
})
|
||||
],
|
||||
// Terminating else bodies are strictly optional.
|
||||
ElseBody: [o("", function() {
|
||||
return null;
|
||||
}), o("ELSE Block", function() {
|
||||
return $2;
|
||||
})
|
||||
],
|
||||
// All the alternatives for ending an if-else block.
|
||||
IfEnd: [o("ElseBody", function() {
|
||||
return $1;
|
||||
}), o("ElsIfs ElseBody", function() {
|
||||
return $1.add_else($2);
|
||||
})
|
||||
],
|
||||
// The full complement of if blocks, including postfix one-liner ifs and unlesses.
|
||||
If: [o("IfBlock IfEnd", function() {
|
||||
return $1.add_else($2);
|
||||
}), o("Expression IF Expression", function() {
|
||||
return new IfNode($3, Expressions.wrap($1), nil, {
|
||||
statement: true
|
||||
});
|
||||
}), o("Expression UNLESS Expression", function() {
|
||||
return new IfNode($3, Expressions.wrap($1), nil, {
|
||||
statement: true,
|
||||
invert: true
|
||||
});
|
||||
})
|
||||
]
|
||||
};
|
||||
// Helpers ==============================================================
|
||||
// Make the Jison parser.
|
||||
bnf = {
|
||||
};
|
||||
tokens = [];
|
||||
__a = grammar;
|
||||
for (name in __a) {
|
||||
non_terminal = __a[name];
|
||||
if (__hasProp.call(__a, name)) {
|
||||
bnf[name] = (function() {
|
||||
__b = []; __c = non_terminal;
|
||||
for (__d = 0; __d < __c.length; __d++) {
|
||||
option = __c[__d];
|
||||
__b.push((function() {
|
||||
__e = option[0].split(" ");
|
||||
for (__f = 0; __f < __e.length; __f++) {
|
||||
part = __e[__f];
|
||||
!grammar[part] ? tokens.push(part) : null;
|
||||
}
|
||||
name === "Root" ? (option[1] = "return " + option[1]) : null;
|
||||
return option;
|
||||
}).call(this));
|
||||
}
|
||||
return __b;
|
||||
}).call(this);
|
||||
}
|
||||
}
|
||||
tokens = tokens.join(" ");
|
||||
parser = new Parser({
|
||||
tokens: tokens,
|
||||
bnf: bnf,
|
||||
operators: operators
|
||||
}, {
|
||||
debug: false
|
||||
});
|
||||
// Thin wrapper around the real lexer
|
||||
parser.lexer = {
|
||||
lex: function lex() {
|
||||
var token;
|
||||
token = this.tokens[this.pos] || [""];
|
||||
this.pos += 1;
|
||||
// this.yylineno: token and token[1] and token[1][1]
|
||||
this.yytext = token[1];
|
||||
return token[0];
|
||||
},
|
||||
setInput: function setInput(tokens) {
|
||||
this.tokens = tokens;
|
||||
return this.pos = 0;
|
||||
},
|
||||
upcomingInput: function upcomingInput() {
|
||||
return "";
|
||||
},
|
||||
showPosition: function showPosition() {
|
||||
return this.pos;
|
||||
}
|
||||
};
|
||||
exports.Parser = function Parser() { };
|
||||
exports.Parser.prototype.parse = function parse(tokens) {
|
||||
return parser.parse(tokens);
|
||||
};
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
33
lib/coffee_script/repl.js
Normal file
33
lib/coffee_script/repl.js
Normal file
@@ -0,0 +1,33 @@
|
||||
(function(){
|
||||
var coffee, prompt, quit, readline, run;
|
||||
// A CoffeeScript port/version of the Node.js REPL.
|
||||
// Required modules.
|
||||
coffee = require('./coffee-script');
|
||||
process.mixin(require('sys'));
|
||||
// Shortcut variables.
|
||||
prompt = 'coffee> ';
|
||||
quit = function quit() {
|
||||
return process.stdio.close();
|
||||
};
|
||||
// The main REPL function. Called everytime a line of code is entered.
|
||||
readline = function readline(code) {
|
||||
return coffee.compile(code, run);
|
||||
};
|
||||
// Attempt to evaluate the command. If there's an exception, print it.
|
||||
run = function run(js) {
|
||||
var val;
|
||||
try {
|
||||
val = eval(js);
|
||||
if (val !== undefined) {
|
||||
p(val);
|
||||
}
|
||||
} catch (err) {
|
||||
puts(err.stack || err.toString());
|
||||
}
|
||||
return print(prompt);
|
||||
};
|
||||
// Start up the REPL.
|
||||
process.stdio.open();
|
||||
process.stdio.addListener('data', readline);
|
||||
print(prompt);
|
||||
})();
|
||||
377
lib/coffee_script/rewriter.js
Normal file
377
lib/coffee_script/rewriter.js
Normal file
@@ -0,0 +1,377 @@
|
||||
(function(){
|
||||
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, SINGLE_CLOSERS, SINGLE_LINERS, __a, __b, __c, __d, __e, __f, __g, __h, pair, re;
|
||||
var __hasProp = Object.prototype.hasOwnProperty;
|
||||
// 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.
|
||||
exports.Rewriter = (re = function re() { });
|
||||
// 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 = (function() {
|
||||
__a = []; __b = BALANCED_PAIRS;
|
||||
for (__c = 0; __c < __b.length; __c++) {
|
||||
pair = __b[__c];
|
||||
__a.push(pair[0]);
|
||||
}
|
||||
return __a;
|
||||
}).call(this);
|
||||
// Tokens that signal the end of a balanced pair.
|
||||
EXPRESSION_TAIL = (function() {
|
||||
__d = []; __e = BALANCED_PAIRS;
|
||||
for (__f = 0; __f < __e.length; __f++) {
|
||||
pair = __e[__f];
|
||||
__d.push(pair[1]);
|
||||
}
|
||||
return __d;
|
||||
}).call(this);
|
||||
// Tokens that indicate the close of a clause of an expression.
|
||||
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(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', 'ARGUMENTS', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', '->', '=>', '[', '(', '{'];
|
||||
// The inverse mappings of token pairs we're trying to fix up.
|
||||
INVERSES = {
|
||||
};
|
||||
__g = BALANCED_PAIRS;
|
||||
for (__h = 0; __h < __g.length; __h++) {
|
||||
pair = __g[__h];
|
||||
INVERSES[pair[0]] = pair[1];
|
||||
INVERSES[pair[1]] = pair[0];
|
||||
}
|
||||
// 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.
|
||||
re.prototype.rewrite = function rewrite(tokens) {
|
||||
this.tokens = tokens;
|
||||
this.adjust_comments();
|
||||
this.remove_leading_newlines();
|
||||
this.remove_mid_expression_newlines();
|
||||
this.move_commas_outside_outdents();
|
||||
this.close_open_calls_and_indexes();
|
||||
this.add_implicit_parentheses();
|
||||
this.add_implicit_indentation();
|
||||
this.ensure_balance(BALANCED_PAIRS);
|
||||
this.rewrite_closing_parens();
|
||||
return this.tokens;
|
||||
};
|
||||
// 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.
|
||||
re.prototype.scan_tokens = function scan_tokens(yield) {
|
||||
var i, move;
|
||||
i = 0;
|
||||
while (true) {
|
||||
if (!(this.tokens[i])) {
|
||||
break;
|
||||
}
|
||||
move = yield(this.tokens[i - 1], this.tokens[i], this.tokens[i + 1], i);
|
||||
i += move;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Massage newlines and indentations so that comments don't have to be
|
||||
// correctly indented, or appear on their own line.
|
||||
re.prototype.adjust_comments = function adjust_comments() {
|
||||
return this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
var after, before;
|
||||
if (!(token[0] === 'COMMENT')) {
|
||||
return 1;
|
||||
}
|
||||
before = this.tokens[i - 2];
|
||||
after = this.tokens[i + 2];
|
||||
if (before && after && ((before[0] === 'INDENT' && after[0] === 'OUTDENT') || (before[0] === 'OUTDENT' && after[0] === 'INDENT')) && before[1] === after[1]) {
|
||||
this.tokens.splice(i + 2, 1);
|
||||
this.tokens.splice(i - 2, 1);
|
||||
return 0;
|
||||
} else if (prev[0] === "\n" && after[0] === 'INDENT') {
|
||||
this.tokens.splice(i + 2, 1);
|
||||
this.tokens[i - 1] = after;
|
||||
return 1;
|
||||
} else if (prev[0] !== "\n" && prev[0] !== 'INDENT' && prev[0] !== 'OUTDENT') {
|
||||
this.tokens.splice(i, 0, ["\n", "\n"]);
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
// Leading newlines would introduce an ambiguity in the grammar, so we
|
||||
// dispatch them here.
|
||||
re.prototype.remove_leading_newlines = function remove_leading_newlines() {
|
||||
if (this.tokens[0][0] === "\n") {
|
||||
return this.tokens.shift();
|
||||
}
|
||||
};
|
||||
// Some blocks occur in the middle of expressions -- when we're expecting
|
||||
// this, remove their trailing newlines.
|
||||
re.prototype.remove_mid_expression_newlines = function remove_mid_expression_newlines() {
|
||||
return this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
if (!(post && EXPRESSION_CLOSE.indexOf(post[0]) >= 0 && token[0] === "\n")) {
|
||||
return 1;
|
||||
}
|
||||
this.tokens.splice(i, 1);
|
||||
return 0;
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
// Make sure that we don't accidentally break trailing commas, which need
|
||||
// to go on the outside of expression closers.
|
||||
re.prototype.move_commas_outside_outdents = function move_commas_outside_outdents() {
|
||||
return this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
if (token[0] === 'OUTDENT' && prev[0] === ',') {
|
||||
this.tokens.splice(i, 1, token);
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
// We've tagged the opening parenthesis of a method call, and the opening
|
||||
// bracket of an indexing operation. Match them with their close.
|
||||
re.prototype.close_open_calls_and_indexes = function close_open_calls_and_indexes() {
|
||||
var brackets, parens;
|
||||
parens = [0];
|
||||
brackets = [0];
|
||||
return this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
if (token[0] === 'CALL_START') {
|
||||
parens.push(0);
|
||||
} else if (token[0] === 'INDEX_START') {
|
||||
brackets.push(0);
|
||||
} else if (token[0] === '(') {
|
||||
parens[parens.length - 1] += 1;
|
||||
} else if (token[0] === '[') {
|
||||
brackets[brackets.length - 1] += 1;
|
||||
} else if (token[0] === ')') {
|
||||
if (parens[parens.length - 1] === 0) {
|
||||
parens.pop();
|
||||
token[0] = 'CALL_END';
|
||||
} else {
|
||||
parens[parens.length - 1] -= 1;
|
||||
}
|
||||
} else if (token[0] === ']') {
|
||||
if (brackets[brackets.length - 1] === 0) {
|
||||
brackets.pop();
|
||||
token[0] = 'INDEX_END';
|
||||
} else {
|
||||
brackets[brackets.length - 1] -= 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
// 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.
|
||||
re.prototype.add_implicit_parentheses = function add_implicit_parentheses() {
|
||||
var stack;
|
||||
stack = [0];
|
||||
return this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
var __i, __j, __k, __l, idx, last, size, tmp;
|
||||
if (token[0] === 'INDENT') {
|
||||
stack.push(0);
|
||||
}
|
||||
if (token[0] === 'OUTDENT') {
|
||||
last = stack.pop();
|
||||
stack[stack.length - 1] += last;
|
||||
}
|
||||
if (stack[stack.length - 1] > 0 && (IMPLICIT_END.indexOf(token[0]) >= 0 || (typeof !post !== "undefined" && !post !== null))) {
|
||||
idx = token[0] === 'OUTDENT' ? i + 1 : i;
|
||||
__k = 0; __l = stack[stack.length - 1];
|
||||
for (__j=0, tmp=__k; (__k <= __l ? tmp < __l : tmp > __l); (__k <= __l ? tmp += 1 : tmp -= 1), __j++) {
|
||||
this.tokens.splice(idx, 0, ['CALL_END', ')']);
|
||||
}
|
||||
size = stack[stack.length - 1] + 1;
|
||||
stack[stack.length - 1] = 0;
|
||||
return size;
|
||||
}
|
||||
if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(token[0]) >= 0)) {
|
||||
return 1;
|
||||
}
|
||||
this.tokens.splice(i, 0, ['CALL_START', '(']);
|
||||
stack[stack.length - 1] += 1;
|
||||
return 2;
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
// 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.
|
||||
re.prototype.add_implicit_indentation = function add_implicit_indentation() {
|
||||
return this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
var idx, insertion, parens, starter, tok;
|
||||
if (!(SINGLE_LINERS.indexOf(token[0]) >= 0 && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) {
|
||||
return 1;
|
||||
}
|
||||
starter = token[0];
|
||||
this.tokens.splice(i + 1, 0, ['INDENT', 2]);
|
||||
idx = i + 1;
|
||||
parens = 0;
|
||||
while (true) {
|
||||
idx += 1;
|
||||
tok = this.tokens[idx];
|
||||
if ((!tok || SINGLE_CLOSERS.indexOf(tok[0]) >= 0 || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) {
|
||||
insertion = this.tokens[idx - 1][0] === "," ? idx - 1 : idx;
|
||||
this.tokens.splice(insertion, 0, ['OUTDENT', 2]);
|
||||
break;
|
||||
}
|
||||
if (tok[0] === '(') {
|
||||
parens += 1;
|
||||
}
|
||||
if (tok[0] === ')') {
|
||||
parens -= 1;
|
||||
}
|
||||
}
|
||||
if (!(token[0] === 'THEN')) {
|
||||
return 1;
|
||||
}
|
||||
this.tokens.splice(i, 1);
|
||||
return 0;
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
// Ensure that all listed pairs of tokens are correctly balanced throughout
|
||||
// the course of the token stream.
|
||||
re.prototype.ensure_balance = function ensure_balance(pairs) {
|
||||
var __i, __j, key, levels, unclosed, value;
|
||||
levels = {
|
||||
};
|
||||
this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
var __i, __j, __k, close, open;
|
||||
__i = pairs;
|
||||
for (__j = 0; __j < __i.length; __j++) {
|
||||
pair = __i[__j];
|
||||
__k = pair;
|
||||
open = __k[0];
|
||||
close = __k[1];
|
||||
levels[open] = levels[open] || 0;
|
||||
if (token[0] === open) {
|
||||
levels[open] += 1;
|
||||
}
|
||||
if (token[0] === close) {
|
||||
levels[open] -= 1;
|
||||
}
|
||||
if (levels[open] < 0) {
|
||||
throw "too many " + token[1];
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
unclosed = (function() {
|
||||
__i = []; __j = levels;
|
||||
for (key in __j) {
|
||||
value = __j[key];
|
||||
if (__hasProp.call(__j, key)) {
|
||||
if (value > 0) {
|
||||
__i.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return __i;
|
||||
}).call(this);
|
||||
if (unclosed.length) {
|
||||
throw "unclosed " + unclosed[0];
|
||||
}
|
||||
};
|
||||
// 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.
|
||||
re.prototype.rewrite_closing_parens = function rewrite_closing_parens() {
|
||||
var __i, debt, key, stack, val;
|
||||
stack = [];
|
||||
debt = {
|
||||
};
|
||||
__i = INVERSES;
|
||||
for (key in __i) {
|
||||
val = __i[key];
|
||||
if (__hasProp.call(__i, key)) {
|
||||
((debt[key] = 0));
|
||||
}
|
||||
}
|
||||
return this.scan_tokens((function(__this) {
|
||||
var __func = function(prev, token, post, i) {
|
||||
var inv, match, mtag, tag;
|
||||
tag = token[0];
|
||||
inv = INVERSES[token[0]];
|
||||
// Push openers onto the stack.
|
||||
if (EXPRESSION_START.indexOf(tag) >= 0) {
|
||||
stack.push(token);
|
||||
return 1;
|
||||
// The end of an expression, check stack and debt for a pair.
|
||||
} else if (EXPRESSION_TAIL.indexOf(tag) >= 0) {
|
||||
// If the tag is already in our debt, swallow it.
|
||||
if (debt[inv] > 0) {
|
||||
debt[inv] -= 1;
|
||||
this.tokens.splice(i, 1);
|
||||
return 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]) {
|
||||
return 1;
|
||||
} else {
|
||||
// Unexpected close, insert correct close, adding to the debt.
|
||||
debt[mtag] += 1;
|
||||
val = mtag === 'INDENT' ? match[1] : INVERSES[mtag];
|
||||
this.tokens.splice(i, 0, [INVERSES[mtag], val]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
return (function() {
|
||||
return __func.apply(__this, arguments);
|
||||
});
|
||||
})(this));
|
||||
};
|
||||
})();
|
||||
@@ -151,6 +151,30 @@ module CoffeeScript
|
||||
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.
|
||||
@@ -183,30 +207,6 @@ module CoffeeScript
|
||||
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
|
||||
|
||||
# Ensure that all listed pairs of tokens are correctly balanced throughout
|
||||
# the course of the token stream.
|
||||
def ensure_balance(*pairs)
|
||||
|
||||
11
lib/coffee_script/runner.js
Normal file
11
lib/coffee_script/runner.js
Normal file
@@ -0,0 +1,11 @@
|
||||
(function(){
|
||||
var coffee, paths;
|
||||
// Quickie script to compile and run all the files given as arguments.
|
||||
process.mixin(require('sys'));
|
||||
coffee = require('./coffee-script');
|
||||
paths = process.ARGV;
|
||||
paths = paths.slice(2, paths.length);
|
||||
paths.length ? coffee.compile_files(paths, function(js) {
|
||||
return eval(js);
|
||||
}) : require('./repl');
|
||||
})();
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "coffee-script",
|
||||
"lib": "lib/coffee_script/narwhal",
|
||||
"preload": ["narwhal"],
|
||||
"description": "Unfancy JavaScript",
|
||||
"keywords": ["javascript", "language"],
|
||||
"author": "Jeremy Ashkenas",
|
||||
|
||||
45
src/coffee-script.coffee
Normal file
45
src/coffee-script.coffee
Normal file
@@ -0,0 +1,45 @@
|
||||
# Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript.
|
||||
path: require('path')
|
||||
|
||||
# The path to the CoffeeScript executable.
|
||||
compiler: path.normalize(path.dirname(__filename) + '/../../bin/coffee')
|
||||
|
||||
|
||||
# Compile a string over stdin, with global variables, for the REPL.
|
||||
exports.compile: (code, callback) ->
|
||||
js: ''
|
||||
coffee: process.createChildProcess compiler, ['--eval', '--no-wrap', '--globals']
|
||||
|
||||
coffee.addListener 'output', (results) ->
|
||||
js += results if results?
|
||||
|
||||
coffee.addListener 'exit', ->
|
||||
callback(js)
|
||||
|
||||
coffee.write(code)
|
||||
coffee.close()
|
||||
|
||||
|
||||
# Compile a list of CoffeeScript files on disk.
|
||||
exports.compile_files: (paths, callback) ->
|
||||
js: ''
|
||||
coffee: process.createChildProcess compiler, ['--print'].concat(paths)
|
||||
|
||||
coffee.addListener 'output', (results) ->
|
||||
js += results if results?
|
||||
|
||||
# NB: we have to add a mutex to make sure it doesn't get called twice.
|
||||
exit_ran: false
|
||||
coffee.addListener 'exit', ->
|
||||
return if exit_ran
|
||||
exit_ran: true
|
||||
callback(js)
|
||||
|
||||
coffee.addListener 'error', (message) ->
|
||||
return unless message
|
||||
puts message
|
||||
throw new Error "CoffeeScript compile error"
|
||||
|
||||
|
||||
|
||||
|
||||
281
src/lexer.coffee
Normal file
281
src/lexer.coffee
Normal file
@@ -0,0 +1,281 @@
|
||||
sys: require 'sys'
|
||||
Rewriter: require('./rewriter').Rewriter
|
||||
|
||||
# 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.
|
||||
exports.Lexer: lex: ->
|
||||
|
||||
# Constants ============================================================
|
||||
|
||||
# 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", "arguments",
|
||||
"try", "catch", "finally", "throw",
|
||||
"break", "continue",
|
||||
"for", "in", "of", "by", "where", "while",
|
||||
"delete", "instanceof", "typeof",
|
||||
"switch", "when",
|
||||
"super", "extends"
|
||||
]
|
||||
|
||||
# Token matching regexes.
|
||||
IDENTIFIER : /^([a-zA-Z$_](\w|\$)*)/
|
||||
NUMBER : /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i
|
||||
STRING : /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/
|
||||
HEREDOC : /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/
|
||||
JS : /^(``|`([\s\S]*?)([^\\]|\\\\)`)/
|
||||
OPERATOR : /^([+\*&|\/\-%=<>:!?]+)/
|
||||
WHITESPACE : /^([ \t]+)/
|
||||
COMMENT : /^(((\n?[ \t]*)?#.*$)+)/
|
||||
CODE : /^((-|=)>)/
|
||||
REGEX : /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/
|
||||
MULTI_DENT : /^((\n([ \t]*))+)(\.)?/
|
||||
LAST_DENTS : /\n([ \t]*)/g
|
||||
LAST_DENT : /\n([ \t]*)/
|
||||
ASSIGNMENT : /^(:|=)$/
|
||||
|
||||
# Token cleaning regexes.
|
||||
JS_CLEANER : /(^`|`$)/g
|
||||
MULTILINER : /\n/g
|
||||
STRING_NEWLINES : /\n[ \t]*/g
|
||||
COMMENT_CLEANER : /(^[ \t]*#|\n[ \t]*$)/mg
|
||||
NO_NEWLINE : /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
|
||||
HEREDOC_INDENT : /^[ \t]+/g
|
||||
|
||||
# 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.
|
||||
lex::tokenize: (code) ->
|
||||
this.code : code # Cleanup code by remove extra line breaks, TODO: chomp
|
||||
this.i : 0 # Current character position we're parsing
|
||||
this.line : 1 # The current line.
|
||||
this.indent : 0 # The current indent level.
|
||||
this.indents : [] # The stack of all indent levels we are currently within.
|
||||
this.tokens : [] # Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
|
||||
this.spaced : null # The last token that has a space following it.
|
||||
while this.i < this.code.length
|
||||
this.chunk: this.code.slice(this.i)
|
||||
this.extract_next_token()
|
||||
# sys.puts "original stream: " + this.tokens if process.ENV['VERBOSE']
|
||||
this.close_indentation()
|
||||
(new Rewriter()).rewrite this.tokens
|
||||
|
||||
# At every position, run through this list of attempted matches,
|
||||
# short-circuiting if any of them succeed.
|
||||
lex::extract_next_token: ->
|
||||
return if this.identifier_token()
|
||||
return if this.number_token()
|
||||
return if this.heredoc_token()
|
||||
return if this.string_token()
|
||||
return if this.js_token()
|
||||
return if this.regex_token()
|
||||
return if this.indent_token()
|
||||
return if this.comment_token()
|
||||
return if this.whitespace_token()
|
||||
return this.literal_token()
|
||||
|
||||
# Tokenizers ==========================================================
|
||||
|
||||
# Matches identifying literals: variables, keywords, method names, etc.
|
||||
lex::identifier_token: ->
|
||||
return false unless id: this.match IDENTIFIER, 1
|
||||
# Keywords are special identifiers tagged with their own name,
|
||||
# 'if' will result in an ['IF', "if"] token.
|
||||
tag: if KEYWORDS.indexOf(id) >= 0 then id.toUpperCase() else 'IDENTIFIER'
|
||||
tag: 'LEADING_WHEN' if tag is 'WHEN' and (this.tag() is 'OUTDENT' or this.tag() is 'INDENT')
|
||||
this.tag(-1, 'PROTOTYPE_ACCESS') if tag is 'IDENTIFIER' and this.value() is '::'
|
||||
if tag is 'IDENTIFIER' and this.value() is '.' and !(this.value(-2) is '.')
|
||||
if this.tag(-2) is '?'
|
||||
this.tag(-1, 'SOAK_ACCESS')
|
||||
this.tokens.splice(-2, 1)
|
||||
else
|
||||
this.tag(-1, 'PROPERTY_ACCESS')
|
||||
this.token(tag, id)
|
||||
this.i += id.length
|
||||
true
|
||||
|
||||
# Matches numbers, including decimals, hex, and exponential notation.
|
||||
lex::number_token: ->
|
||||
return false unless number: this.match NUMBER, 1
|
||||
this.token 'NUMBER', number
|
||||
this.i += number.length
|
||||
true
|
||||
|
||||
# Matches strings, including multi-line strings.
|
||||
lex::string_token: ->
|
||||
return false unless string: this.match STRING, 1
|
||||
escaped: string.replace STRING_NEWLINES, " \\\n"
|
||||
this.token 'STRING', escaped
|
||||
this.line += this.count string, "\n"
|
||||
this.i += string.length
|
||||
true
|
||||
|
||||
# Matches heredocs, adjusting indentation to the correct level.
|
||||
lex::heredoc_token: ->
|
||||
return false unless match = this.chunk.match(HEREDOC)
|
||||
doc: match[2] or match[4]
|
||||
indent: doc.match(HEREDOC_INDENT).sort()[0]
|
||||
doc: doc.replace(new RegExp("^" + indent, 'g'), '')
|
||||
.replace(MULTILINER, "\\n")
|
||||
.replace('"', '\\"')
|
||||
this.token 'STRING', '"' + doc + '"'
|
||||
this.line += this.count match[1], "\n"
|
||||
this.i += match[1].length
|
||||
true
|
||||
|
||||
# Matches interpolated JavaScript.
|
||||
lex::js_token: ->
|
||||
return false unless script: this.match JS, 1
|
||||
this.token 'JS', script.replace(JS_CLEANER, '')
|
||||
this.i += script.length
|
||||
true
|
||||
|
||||
# Matches regular expression literals.
|
||||
lex::regex_token: ->
|
||||
return false unless regex: this.match REGEX, 1
|
||||
return false if NOT_REGEX.indexOf(this.tag()) >= 0
|
||||
this.token 'REGEX', regex
|
||||
this.i += regex.length
|
||||
true
|
||||
|
||||
# Matches and conumes comments.
|
||||
lex::comment_token: ->
|
||||
return false unless comment: this.match COMMENT, 1
|
||||
this.line += comment.match(MULTILINER).length
|
||||
this.token 'COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER)
|
||||
this.token "\n", "\n"
|
||||
this.i += comment.length
|
||||
true
|
||||
|
||||
# Record tokens for indentation differing from the previous line.
|
||||
lex::indent_token: ->
|
||||
return false unless indent: this.match MULTI_DENT, 1
|
||||
this.line += indent.match(MULTILINER).length
|
||||
this.i += indent.length
|
||||
next_character: this.chunk.match(MULTI_DENT)[4]
|
||||
no_newlines: next_character is '.' or (this.value().match(NO_NEWLINE) and this.tokens[this.tokens.length - 2][0] isnt '.' and not this.value().match(CODE))
|
||||
return this.suppress_newlines(indent) if no_newlines
|
||||
size: indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
|
||||
return this.newline_token(indent) if size is this.indent
|
||||
if size > this.indent
|
||||
diff: size - this.indent
|
||||
this.token 'INDENT', diff
|
||||
this.indents.push diff
|
||||
else
|
||||
this.outdent_token this.indent - size
|
||||
this.indent: size
|
||||
true
|
||||
|
||||
# Record an oudent token or tokens, if we're moving back inwards past
|
||||
# multiple recorded indents.
|
||||
lex::outdent_token: (move_out) ->
|
||||
while move_out > 0 and this.indents.length
|
||||
last_indent: this.indents.pop()
|
||||
this.token 'OUTDENT', last_indent
|
||||
move_out -= last_indent
|
||||
this.token "\n", "\n"
|
||||
true
|
||||
|
||||
# Matches and consumes non-meaningful whitespace.
|
||||
lex::whitespace_token: ->
|
||||
return false unless space: this.match WHITESPACE, 1
|
||||
this.spaced: this.value()
|
||||
this.i += space.length
|
||||
true
|
||||
|
||||
# Multiple newlines get merged together.
|
||||
# Use a trailing \ to escape newlines.
|
||||
lex::newline_token: (newlines) ->
|
||||
this.token "\n", "\n" unless this.value() is "\n"
|
||||
true
|
||||
|
||||
# Tokens to explicitly escape newlines are removed once their job is done.
|
||||
lex::suppress_newlines: (newlines) ->
|
||||
this.tokens.pop() if this.value() is "\\"
|
||||
true
|
||||
|
||||
# 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.
|
||||
lex::literal_token: ->
|
||||
match: this.chunk.match(OPERATOR)
|
||||
value: match and match[1]
|
||||
this.tag_parameters() if value and value.match(CODE)
|
||||
value ||= this.chunk.substr(0, 1)
|
||||
tag: if value.match(ASSIGNMENT) then 'ASSIGN' else value
|
||||
if this.value() isnt this.spaced and CALLABLE.indexOf(this.tag()) >= 0
|
||||
tag: 'CALL_START' if value is '('
|
||||
tag: 'INDEX_START' if value is '['
|
||||
this.token tag, value
|
||||
this.i += value.length
|
||||
true
|
||||
|
||||
# Helpers =============================================================
|
||||
|
||||
# Add a token to the results, taking note of the line number.
|
||||
lex::token: (tag, value) ->
|
||||
this.tokens.push([tag, value])
|
||||
# this.tokens.push([tag, Value.new(value, @line)])
|
||||
|
||||
# Look at a tag in the current token stream.
|
||||
lex::tag: (index, tag) ->
|
||||
return unless tok: this.tokens[this.tokens.length - (index || 1)]
|
||||
return tok[0]: tag if tag?
|
||||
tok[0]
|
||||
|
||||
# Look at a value in the current token stream.
|
||||
lex::value: (index, val) ->
|
||||
return unless tok: this.tokens[this.tokens.length - (index || 1)]
|
||||
return tok[1]: val if val?
|
||||
tok[1]
|
||||
|
||||
# Count the occurences of a character in a string.
|
||||
lex::count: (string, char) ->
|
||||
num: 0
|
||||
pos: string.indexOf(char)
|
||||
while pos isnt -1
|
||||
count += 1
|
||||
pos: string.indexOf(char, pos + 1)
|
||||
count
|
||||
|
||||
# Attempt to match a string against the current chunk, returning the indexed
|
||||
# match.
|
||||
lex::match: (regex, index) ->
|
||||
return false unless m: this.chunk.match(regex)
|
||||
if m then m[index] else false
|
||||
|
||||
# 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.
|
||||
lex::tag_parameters: ->
|
||||
return if this.tag() isnt ')'
|
||||
i: 0
|
||||
while true
|
||||
i += 1
|
||||
tok: this.tokens[this.tokens.length - i]
|
||||
return if not tok
|
||||
switch tok[0]
|
||||
when 'IDENTIFIER' then tok[0]: 'PARAM'
|
||||
when ')' then tok[0]: 'PARAM_END'
|
||||
when '(' then return tok[0]: 'PARAM_START'
|
||||
true
|
||||
|
||||
# Close up all remaining open blocks. IF the first token is an indent,
|
||||
# axe it.
|
||||
lex::close_indentation: ->
|
||||
this.outdent_token(this.indent)
|
||||
@@ -14,6 +14,9 @@ checkForErrors: (coffeeProcess) ->
|
||||
system.stderr.print coffeeProcess.stderr.read()
|
||||
throw new Error "CoffeeScript compile error"
|
||||
|
||||
# Alias print to "puts", for Node.js compatibility:
|
||||
puts: print
|
||||
|
||||
# Run a simple REPL, round-tripping to the CoffeeScript compiler for every
|
||||
# command.
|
||||
exports.run: (args) ->
|
||||
30
src/nodes.coffee
Normal file
30
src/nodes.coffee
Normal file
@@ -0,0 +1,30 @@
|
||||
exports.Node: -> this.values: arguments
|
||||
|
||||
exports.Expressions : exports.Node
|
||||
exports.LiteralNode : exports.Node
|
||||
exports.ReturnNode : exports.Node
|
||||
exports.CommentNode : exports.Node
|
||||
exports.CallNode : exports.Node
|
||||
exports.ExtendsNode : exports.Node
|
||||
exports.ValueNode : exports.Node
|
||||
exports.AccessorNode : exports.Node
|
||||
exports.IndexNode : exports.Node
|
||||
exports.RangeNode : exports.Node
|
||||
exports.SliceNode : exports.Node
|
||||
exports.AssignNode : exports.Node
|
||||
exports.OpNode : exports.Node
|
||||
exports.CodeNode : exports.Node
|
||||
exports.SplatNode : exports.Node
|
||||
exports.ObjectNode : exports.Node
|
||||
exports.ArrayNode : exports.Node
|
||||
exports.PushNode : exports.Node
|
||||
exports.ClosureNode : exports.Node
|
||||
exports.WhileNode : exports.Node
|
||||
exports.ForNode : exports.Node
|
||||
exports.TryNode : exports.Node
|
||||
exports.ThrowNode : exports.Node
|
||||
exports.ExistenceNode : exports.Node
|
||||
exports.ParentheticalNode : exports.Node
|
||||
exports.IfNode : exports.Node
|
||||
|
||||
|
||||
528
src/parser.coffee
Normal file
528
src/parser.coffee
Normal file
@@ -0,0 +1,528 @@
|
||||
Parser: require('jison').Parser
|
||||
|
||||
# DSL ===================================================================
|
||||
|
||||
# Detect functions: [
|
||||
unwrap: /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/
|
||||
|
||||
# Quickie DSL for Jison access.
|
||||
o: (pattern_string, func) ->
|
||||
if func
|
||||
func: if match: (func + "").match(unwrap) then match[1] else '(' + func + '())'
|
||||
[pattern_string, '$$ = ' + func + ';']
|
||||
else
|
||||
[pattern_string, '$$ = $1;']
|
||||
|
||||
# Precedence ===========================================================
|
||||
|
||||
operators: [
|
||||
["left", '?']
|
||||
["right", 'NOT', '!', '!!', '~', '++', '--']
|
||||
["left", '*', '/', '%']
|
||||
["left", '+', '-']
|
||||
["left", '<<', '>>', '>>>']
|
||||
["left", '&', '|', '^']
|
||||
["left", '<=', '<', '>', '>=']
|
||||
["right", '==', '!=', 'IS', 'ISNT']
|
||||
["left", '&&', '||', 'AND', 'OR']
|
||||
["right", '-=', '+=', '/=', '*=', '%=']
|
||||
["right", 'DELETE', 'INSTANCEOF', 'TYPEOF']
|
||||
["left", '.']
|
||||
["right", 'INDENT']
|
||||
["left", 'OUTDENT']
|
||||
["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY']
|
||||
["right", 'THROW', 'FOR', 'NEW', 'SUPER']
|
||||
["left", 'EXTENDS']
|
||||
["left", '||=', '&&=', '?=']
|
||||
["right", 'ASSIGN', 'RETURN']
|
||||
["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']
|
||||
]
|
||||
|
||||
# Grammar ==============================================================
|
||||
|
||||
grammar: {
|
||||
|
||||
# All parsing will end in this rule, being the trunk of the AST.
|
||||
Root: [
|
||||
o "", -> new Expressions()
|
||||
o "Terminator", -> new Expressions()
|
||||
o "Expressions"
|
||||
o "Block Terminator"
|
||||
]
|
||||
|
||||
# Any list of expressions or method body, seperated by line breaks or semis.
|
||||
Expressions: [
|
||||
o "Expression", -> Expressions.wrap([$1])
|
||||
o "Expressions Terminator Expression", -> $1.push($3)
|
||||
o "Expressions Terminator"
|
||||
]
|
||||
|
||||
# All types of expressions in our language. The basic unit of CoffeeScript
|
||||
# is the expression.
|
||||
Expression: [
|
||||
o "Value"
|
||||
o "Call"
|
||||
o "Code"
|
||||
o "Operation"
|
||||
o "Assign"
|
||||
o "If"
|
||||
o "Try"
|
||||
o "Throw"
|
||||
o "Return"
|
||||
o "While"
|
||||
o "For"
|
||||
o "Switch"
|
||||
o "Extends"
|
||||
o "Splat"
|
||||
o "Existence"
|
||||
o "Comment"
|
||||
]
|
||||
|
||||
# A block of expressions. Note that the Rewriter will convert some postfix
|
||||
# forms into blocks for us, by altering the token stream.
|
||||
Block: [
|
||||
o "INDENT Expressions OUTDENT", -> $2
|
||||
o "INDENT OUTDENT", -> new Expressions()
|
||||
]
|
||||
|
||||
# Tokens that can terminate an expression.
|
||||
Terminator: [
|
||||
o "\n"
|
||||
o ";"
|
||||
]
|
||||
|
||||
# All hard-coded values. These can be printed straight to JavaScript.
|
||||
Literal: [
|
||||
o "NUMBER", -> new LiteralNode($1)
|
||||
o "STRING", -> new LiteralNode($1)
|
||||
o "JS", -> new LiteralNode($1)
|
||||
o "REGEX", -> new LiteralNode($1)
|
||||
o "BREAK", -> new LiteralNode($1)
|
||||
o "CONTINUE", -> new LiteralNode($1)
|
||||
o "ARGUMENTS", -> new LiteralNode($1)
|
||||
o "TRUE", -> new LiteralNode(true)
|
||||
o "FALSE", -> new LiteralNode(false)
|
||||
o "YES", -> new LiteralNode(true)
|
||||
o "NO", -> new LiteralNode(false)
|
||||
o "ON", -> new LiteralNode(true)
|
||||
o "OFF", -> new LiteralNode(false)
|
||||
]
|
||||
|
||||
# Assignment to a variable (or index).
|
||||
Assign: [
|
||||
o "Value ASSIGN Expression", -> new AssignNode($1, $3)
|
||||
]
|
||||
|
||||
# Assignment within an object literal (can be quoted).
|
||||
AssignObj: [
|
||||
o "IDENTIFIER ASSIGN Expression", -> new AssignNode(new ValueNode($1), $3, 'object')
|
||||
o "STRING ASSIGN Expression", -> new AssignNode(new ValueNode(new LiteralNode($1)), $3, 'object')
|
||||
o "Comment"
|
||||
]
|
||||
|
||||
# A return statement.
|
||||
Return: [
|
||||
o "RETURN Expression", -> new ReturnNode($2)
|
||||
o "RETURN", -> new ReturnNode(new ValueNode(new LiteralNode('null')))
|
||||
]
|
||||
|
||||
# A comment.
|
||||
Comment: [
|
||||
o "COMMENT", -> new CommentNode($1)
|
||||
]
|
||||
|
||||
# Arithmetic and logical operators
|
||||
# For Ruby's Operator precedence, see: [
|
||||
# https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
|
||||
Operation: [
|
||||
o "! Expression", -> new OpNode($1, $2)
|
||||
o "!! Expression", -> new OpNode($1, $2)
|
||||
o "- Expression", -> new OpNode($1, $2)
|
||||
o "+ Expression", -> new OpNode($1, $2)
|
||||
o "NOT Expression", -> new OpNode($1, $2)
|
||||
o "~ Expression", -> new OpNode($1, $2)
|
||||
o "-- Expression", -> new OpNode($1, $2)
|
||||
o "++ Expression", -> new OpNode($1, $2)
|
||||
o "DELETE Expression", -> new OpNode($1, $2)
|
||||
o "TYPEOF Expression", -> new OpNode($1, $2)
|
||||
o "Expression --", -> new OpNode($2, $1, null, true)
|
||||
o "Expression ++", -> new OpNode($2, $1, null, true)
|
||||
|
||||
o "Expression * Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression / Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression % Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression + Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression - Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression << Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression >> Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression >>> Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression & Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression | Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression ^ Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression <= Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression < Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression > Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression >= Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression == Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression != Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression IS Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression ISNT Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression && Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression || Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression AND Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression OR Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression ? Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression -= Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression += Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression /= Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression *= Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression %= Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression ||= Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression &&= Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression ?= Expression", -> new OpNode($2, $1, $3)
|
||||
|
||||
o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3)
|
||||
o "Expression IN Expression", -> new OpNode($2, $1, $3)
|
||||
]
|
||||
|
||||
# Try abbreviated expressions to make the grammar build faster:
|
||||
|
||||
# UnaryOp: [
|
||||
# o "!"
|
||||
# o "!!"
|
||||
# o "NOT"
|
||||
# o "~"
|
||||
# o "--"
|
||||
# o "++"
|
||||
# o "DELETE"
|
||||
# o "TYPEOF"
|
||||
# ]
|
||||
#
|
||||
# BinaryOp: [
|
||||
# o "*"
|
||||
# o "/"
|
||||
# o "%"
|
||||
# o "+"
|
||||
# o "-"
|
||||
# o "<<"
|
||||
# o ">>"
|
||||
# o ">>>"
|
||||
# o "&"
|
||||
# o "|"
|
||||
# o "^"
|
||||
# o "<="
|
||||
# o "<"
|
||||
# o ">"
|
||||
# o ">="
|
||||
# o "=="
|
||||
# o "!="
|
||||
# o "IS"
|
||||
# o "ISNT"
|
||||
# o "&&"
|
||||
# o "||"
|
||||
# o "AND"
|
||||
# o "OR"
|
||||
# o "?"
|
||||
# o "-="
|
||||
# o "+="
|
||||
# o "/="
|
||||
# o "*="
|
||||
# o "%="
|
||||
# o "||="
|
||||
# o "&&="
|
||||
# o "?="
|
||||
# o "INSTANCEOF"
|
||||
# o "IN"
|
||||
# ]
|
||||
#
|
||||
# Operation: [
|
||||
# o "Expression BinaryOp Expression", -> new OpNode($2, $1, $3)
|
||||
# o "UnaryOp Expression", -> new OpNode($1, $2)
|
||||
# ]
|
||||
|
||||
# The existence operator.
|
||||
Existence: [
|
||||
o "Expression ?", -> new ExistenceNode($1)
|
||||
]
|
||||
|
||||
# Function definition.
|
||||
Code: [
|
||||
o "PARAM_START ParamList PARAM_END FuncGlyph Block", -> new CodeNode($2, $5, $4)
|
||||
o "FuncGlyph Block", -> new CodeNode([], $2, $1)
|
||||
]
|
||||
|
||||
# The symbols to signify functions, and bound functions.
|
||||
FuncGlyph: [
|
||||
o "->", -> 'func'
|
||||
o "=>", -> 'boundfunc'
|
||||
]
|
||||
|
||||
# The parameters to a function definition.
|
||||
ParamList: [
|
||||
o "Param", -> [$1]
|
||||
o "ParamList , Param", -> $1.push($3)
|
||||
]
|
||||
|
||||
# A Parameter (or ParamSplat) in a function definition.
|
||||
Param: [
|
||||
o "PARAM"
|
||||
o "PARAM . . .", -> new SplatNode($1)
|
||||
]
|
||||
|
||||
# A regular splat.
|
||||
Splat: [
|
||||
o "Expression . . .", -> new SplatNode($1)
|
||||
]
|
||||
|
||||
# Expressions that can be treated as values.
|
||||
Value: [
|
||||
o "IDENTIFIER", -> new ValueNode($1)
|
||||
o "Literal", -> new ValueNode($1)
|
||||
o "Array", -> new ValueNode($1)
|
||||
o "Object", -> new ValueNode($1)
|
||||
o "Parenthetical", -> new ValueNode($1)
|
||||
o "Range", -> new ValueNode($1)
|
||||
o "Value Accessor", -> $1.push($2)
|
||||
o "Invocation Accessor", -> new ValueNode($1, [$2])
|
||||
]
|
||||
|
||||
# Accessing into an object or array, through dot or index notation.
|
||||
Accessor: [
|
||||
o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode($2)
|
||||
o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode($2, 'prototype')
|
||||
o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode($2, 'soak')
|
||||
o "Index"
|
||||
o "Slice", -> new SliceNode($1)
|
||||
]
|
||||
|
||||
# Indexing into an object or array.
|
||||
Index: [
|
||||
o "INDEX_START Expression INDEX_END", -> new IndexNode($2)
|
||||
]
|
||||
|
||||
# An object literal.
|
||||
Object: [
|
||||
o "{ AssignList }", -> new ObjectNode($2)
|
||||
]
|
||||
|
||||
# Assignment within an object literal (comma or newline separated).
|
||||
AssignList: [
|
||||
o "", -> []
|
||||
o "AssignObj", -> [$1]
|
||||
o "AssignList , AssignObj", -> $1.push $3
|
||||
o "AssignList Terminator AssignObj", -> $1.push $3
|
||||
o "AssignList , Terminator AssignObj", -> $1.push $4
|
||||
o "INDENT AssignList OUTDENT", -> $2
|
||||
]
|
||||
|
||||
# All flavors of function call (instantiation, super, and regular).
|
||||
Call: [
|
||||
o "Invocation", -> $1
|
||||
o "NEW Invocation", -> $2.new_instance()
|
||||
o "Super", -> $1
|
||||
]
|
||||
|
||||
# Extending an object's prototype.
|
||||
Extends: [
|
||||
o "Value EXTENDS Value", -> new ExtendsNode($1, $3)
|
||||
]
|
||||
|
||||
# A generic function invocation.
|
||||
Invocation: [
|
||||
o "Value Arguments", -> new CallNode($1, $2)
|
||||
o "Invocation Arguments", -> new CallNode($1, $2)
|
||||
]
|
||||
|
||||
# The list of arguments to a function invocation.
|
||||
Arguments: [
|
||||
o "CALL_START ArgList CALL_END", -> $2
|
||||
]
|
||||
|
||||
# Calling super.
|
||||
Super: [
|
||||
o "SUPER CALL_START ArgList CALL_END", -> new CallNode('super', $3)
|
||||
]
|
||||
|
||||
# The range literal.
|
||||
Range: [
|
||||
o "[ Expression . . Expression ]", -> new RangeNode($2, $5)
|
||||
o "[ Expression . . . Expression ]", -> new RangeNode($2, $6, true)
|
||||
]
|
||||
|
||||
# The slice literal.
|
||||
Slice: [
|
||||
o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode($2, $5)
|
||||
o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode($2, $6, true)
|
||||
]
|
||||
|
||||
# The array literal.
|
||||
Array: [
|
||||
o "[ ArgList ]", -> new ArrayNode($2)
|
||||
]
|
||||
|
||||
# A list of arguments to a method call, or as the contents of an array.
|
||||
ArgList: [
|
||||
o "", -> []
|
||||
o "Expression", -> val
|
||||
o "INDENT Expression", -> [$2]
|
||||
o "ArgList , Expression", -> $1.push $3
|
||||
o "ArgList Terminator Expression", -> $1.push $3
|
||||
o "ArgList , Terminator Expression", -> $1.push $4
|
||||
o "ArgList , INDENT Expression", -> $1.push $4
|
||||
o "ArgList OUTDENT", -> $1
|
||||
]
|
||||
|
||||
# Just simple, comma-separated, required arguments (no fancy syntax).
|
||||
SimpleArgs: [
|
||||
o "Expression", -> $1
|
||||
o "SimpleArgs , Expression", ->
|
||||
([$1].push($3)).reduce (a, b) -> a.concat(b)
|
||||
]
|
||||
|
||||
# Try/catch/finally exception handling blocks.
|
||||
Try: [
|
||||
o "TRY Block Catch", -> new TryNode($2, $3[0], $3[1])
|
||||
o "TRY Block FINALLY Block", -> new TryNode($2, nil, nil, $4)
|
||||
o "TRY Block Catch FINALLY Block", -> new TryNode($2, $3[0], $3[1], $5)
|
||||
]
|
||||
|
||||
# A catch clause.
|
||||
Catch: [
|
||||
o "CATCH IDENTIFIER Block", -> [$2, $3]
|
||||
]
|
||||
|
||||
# Throw an exception.
|
||||
Throw: [
|
||||
o "THROW Expression", -> new ThrowNode($2)
|
||||
]
|
||||
|
||||
# Parenthetical expressions.
|
||||
Parenthetical: [
|
||||
o "( Expression )", -> new ParentheticalNode($2)
|
||||
]
|
||||
|
||||
# The while loop. (there is no do..while).
|
||||
While: [
|
||||
o "WHILE Expression Block", -> new WhileNode($2, $3)
|
||||
o "WHILE Expression", -> new WhileNode($2, nil)
|
||||
o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap($1))
|
||||
]
|
||||
|
||||
# Array comprehensions, including guard and current index.
|
||||
# Looks a little confusing, check nodes.rb for the arguments to ForNode.
|
||||
For: [
|
||||
o "Expression FOR ForVariables ForSource", -> new ForNode($1, $4, $3[0], $3[1])
|
||||
o "FOR ForVariables ForSource Block", -> new ForNode($4, $3, $2[0], $2[1])
|
||||
]
|
||||
|
||||
# An array comprehension has variables for the current element and index.
|
||||
ForVariables: [
|
||||
o "IDENTIFIER", -> [$1]
|
||||
o "IDENTIFIER , IDENTIFIER", -> [$1, $3]
|
||||
]
|
||||
|
||||
# The source of the array comprehension can optionally be filtered.
|
||||
ForSource: [
|
||||
o "IN Expression", -> {source: $2}
|
||||
o "OF Expression", -> {source: $2, object: true}
|
||||
o "ForSource WHEN Expression", -> $1.filter: $3; $1
|
||||
o "ForSource BY Expression", -> $1.step: $3; $1
|
||||
]
|
||||
|
||||
# Switch/When blocks.
|
||||
Switch: [
|
||||
o "SWITCH Expression INDENT Whens OUTDENT", -> $4.rewrite_condition($2)
|
||||
o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6)
|
||||
]
|
||||
|
||||
# The inner list of whens.
|
||||
Whens: [
|
||||
o "When", -> $1
|
||||
o "Whens When", -> $1.push $2
|
||||
]
|
||||
|
||||
# An individual when.
|
||||
When: [
|
||||
o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, nil, {statement: true})
|
||||
o "LEADING_WHEN SimpleArgs Block Terminator", -> new IfNode($2, $3, nil, {statement: true})
|
||||
o "Comment Terminator When", -> $3.add_comment($1)
|
||||
]
|
||||
|
||||
# The most basic form of "if".
|
||||
IfBlock: [
|
||||
o "IF Expression Block", -> new IfNode($2, $3)
|
||||
]
|
||||
|
||||
# An elsif portion of an if-else block.
|
||||
ElsIf: [
|
||||
o "ELSE IfBlock", -> $2.force_statement()
|
||||
]
|
||||
|
||||
# Multiple elsifs can be chained together.
|
||||
ElsIfs: [
|
||||
o "ElsIf", -> $1
|
||||
o "ElsIfs ElsIf", -> $1.add_else($2)
|
||||
]
|
||||
|
||||
# Terminating else bodies are strictly optional.
|
||||
ElseBody: [
|
||||
o "", -> null
|
||||
o "ELSE Block", -> $2
|
||||
]
|
||||
|
||||
# All the alternatives for ending an if-else block.
|
||||
IfEnd: [
|
||||
o "ElseBody", -> $1
|
||||
o "ElsIfs ElseBody", -> $1.add_else($2)
|
||||
]
|
||||
|
||||
# The full complement of if blocks, including postfix one-liner ifs and unlesses.
|
||||
If: [
|
||||
o "IfBlock IfEnd", -> $1.add_else($2)
|
||||
o "Expression IF Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true})
|
||||
o "Expression UNLESS Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true, invert: true})
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
# Helpers ==============================================================
|
||||
|
||||
# Make the Jison parser.
|
||||
bnf: {}
|
||||
tokens: []
|
||||
for name, non_terminal of grammar
|
||||
bnf[name]: for option in non_terminal
|
||||
for part in option[0].split(" ")
|
||||
if !grammar[part]
|
||||
tokens.push(part)
|
||||
if name == "Root"
|
||||
option[1] = "return " + option[1]
|
||||
option
|
||||
tokens: tokens.join(" ")
|
||||
parser: new Parser({tokens: tokens, bnf: bnf, operators: operators}, {debug: false})
|
||||
|
||||
# Thin wrapper around the real lexer
|
||||
parser.lexer: {
|
||||
lex: ->
|
||||
token: this.tokens[this.pos] or [""]
|
||||
this.pos += 1
|
||||
# this.yylineno: token and token[1] and token[1][1]
|
||||
this.yytext: token[1]
|
||||
token[0]
|
||||
setInput: (tokens) ->
|
||||
this.tokens = tokens
|
||||
this.pos = 0
|
||||
upcomingInput: -> ""
|
||||
showPosition: -> this.pos
|
||||
}
|
||||
|
||||
exports.Parser: ->
|
||||
|
||||
exports.Parser::parse: (tokens) -> parser.parse(tokens)
|
||||
26
src/repl.coffee
Normal file
26
src/repl.coffee
Normal file
@@ -0,0 +1,26 @@
|
||||
# A CoffeeScript port/version of the Node.js REPL.
|
||||
|
||||
# Required modules.
|
||||
coffee: require './coffee-script'
|
||||
process.mixin require 'sys'
|
||||
|
||||
# Shortcut variables.
|
||||
prompt: 'coffee> '
|
||||
quit: -> process.stdio.close()
|
||||
|
||||
# The main REPL function. Called everytime a line of code is entered.
|
||||
readline: (code) -> coffee.compile code, run
|
||||
|
||||
# Attempt to evaluate the command. If there's an exception, print it.
|
||||
run: (js) ->
|
||||
try
|
||||
val: eval(js)
|
||||
p val if val isnt undefined
|
||||
catch err
|
||||
puts err.stack or err.toString()
|
||||
print prompt
|
||||
|
||||
# Start up the REPL.
|
||||
process.stdio.open()
|
||||
process.stdio.addListener 'data', readline
|
||||
print prompt
|
||||
244
src/rewriter.coffee
Normal file
244
src/rewriter.coffee
Normal file
@@ -0,0 +1,244 @@
|
||||
# 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.
|
||||
exports.Rewriter: re: ->
|
||||
|
||||
# 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: pair[0] for pair in BALANCED_PAIRS
|
||||
|
||||
# Tokens that signal the end of a balanced pair.
|
||||
EXPRESSION_TAIL: pair[1] for pair in BALANCED_PAIRS
|
||||
|
||||
# Tokens that indicate the close of a clause of an expression.
|
||||
EXPRESSION_CLOSE: ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(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', 'ARGUMENTS',
|
||||
'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT',
|
||||
'->', '=>', '[', '(', '{']
|
||||
|
||||
# The inverse mappings of token pairs we're trying to fix up.
|
||||
INVERSES: {}
|
||||
for pair in BALANCED_PAIRS
|
||||
INVERSES[pair[0]]: pair[1]
|
||||
INVERSES[pair[1]]: pair[0]
|
||||
|
||||
# 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.
|
||||
re::rewrite: (tokens) ->
|
||||
this.tokens: tokens
|
||||
this.adjust_comments()
|
||||
this.remove_leading_newlines()
|
||||
this.remove_mid_expression_newlines()
|
||||
this.move_commas_outside_outdents()
|
||||
this.close_open_calls_and_indexes()
|
||||
this.add_implicit_parentheses()
|
||||
this.add_implicit_indentation()
|
||||
this.ensure_balance(BALANCED_PAIRS)
|
||||
this.rewrite_closing_parens()
|
||||
this.tokens
|
||||
|
||||
# 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.
|
||||
re::scan_tokens: (yield) ->
|
||||
i: 0
|
||||
while true
|
||||
break unless this.tokens[i]
|
||||
move: yield(this.tokens[i - 1], this.tokens[i], this.tokens[i + 1], i)
|
||||
i += move
|
||||
true
|
||||
|
||||
# Massage newlines and indentations so that comments don't have to be
|
||||
# correctly indented, or appear on their own line.
|
||||
re::adjust_comments: ->
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
return 1 unless token[0] is 'COMMENT'
|
||||
before: this.tokens[i - 2]
|
||||
after: this.tokens[i + 2]
|
||||
if before and after and
|
||||
((before[0] is 'INDENT' and after[0] is 'OUTDENT') or
|
||||
(before[0] is 'OUTDENT' and after[0] is 'INDENT')) and
|
||||
before[1] is after[1]
|
||||
this.tokens.splice(i + 2, 1)
|
||||
this.tokens.splice(i - 2, 1)
|
||||
return 0
|
||||
else if prev[0] is "\n" and after[0] is 'INDENT'
|
||||
this.tokens.splice(i + 2, 1)
|
||||
this.tokens[i - 1]: after
|
||||
return 1
|
||||
else if prev[0] isnt "\n" and prev[0] isnt 'INDENT' and prev[0] isnt 'OUTDENT'
|
||||
this.tokens.splice(i, 0, ["\n", "\n"])
|
||||
return 2
|
||||
else
|
||||
return 1
|
||||
|
||||
# Leading newlines would introduce an ambiguity in the grammar, so we
|
||||
# dispatch them here.
|
||||
re::remove_leading_newlines: ->
|
||||
this.tokens.shift() if this.tokens[0][0] is "\n"
|
||||
|
||||
# Some blocks occur in the middle of expressions -- when we're expecting
|
||||
# this, remove their trailing newlines.
|
||||
re::remove_mid_expression_newlines: ->
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
return 1 unless post and EXPRESSION_CLOSE.indexOf(post[0]) >= 0 and token[0] is "\n"
|
||||
this.tokens.splice(i, 1)
|
||||
return 0
|
||||
|
||||
# Make sure that we don't accidentally break trailing commas, which need
|
||||
# to go on the outside of expression closers.
|
||||
re::move_commas_outside_outdents: ->
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
this.tokens.splice(i, 1, token) if token[0] is 'OUTDENT' and prev[0] is ','
|
||||
return 1
|
||||
|
||||
# We've tagged the opening parenthesis of a method call, and the opening
|
||||
# bracket of an indexing operation. Match them with their close.
|
||||
re::close_open_calls_and_indexes: ->
|
||||
parens: [0]
|
||||
brackets: [0]
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
switch token[0]
|
||||
when 'CALL_START' then parens.push(0)
|
||||
when 'INDEX_START' then brackets.push(0)
|
||||
when '(' then parens[parens.length - 1] += 1
|
||||
when '[' then brackets[brackets.length - 1] += 1
|
||||
when ')'
|
||||
if parens[parens.length - 1] is 0
|
||||
parens.pop()
|
||||
token[0]: 'CALL_END'
|
||||
else
|
||||
parens[parens.length - 1] -= 1
|
||||
when ']'
|
||||
if brackets[brackets.length - 1] == 0
|
||||
brackets.pop()
|
||||
token[0]: 'INDEX_END'
|
||||
else
|
||||
brackets[brackets.length - 1] -= 1
|
||||
return 1
|
||||
|
||||
# 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.
|
||||
re::add_implicit_parentheses: ->
|
||||
stack: [0]
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
stack.push(0) if token[0] is 'INDENT'
|
||||
if token[0] is 'OUTDENT'
|
||||
last: stack.pop()
|
||||
stack[stack.length - 1] += last
|
||||
if stack[stack.length - 1] > 0 and (IMPLICIT_END.indexOf(token[0]) >= 0 or !post?)
|
||||
idx: if token[0] is 'OUTDENT' then i + 1 else i
|
||||
for tmp in [0...stack[stack.length - 1]]
|
||||
this.tokens.splice(idx, 0, ['CALL_END', ')'])
|
||||
size: stack[stack.length - 1] + 1
|
||||
stack[stack.length - 1]: 0
|
||||
return size
|
||||
return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(token[0]) >= 0
|
||||
this.tokens.splice(i, 0, ['CALL_START', '('])
|
||||
stack[stack.length - 1] += 1
|
||||
return 2
|
||||
|
||||
# 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.
|
||||
re::add_implicit_indentation: ->
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
return 1 unless SINGLE_LINERS.indexOf(token[0]) >= 0 and post[0] isnt 'INDENT' and
|
||||
not (token[0] is 'ELSE' and post[0] is 'IF')
|
||||
starter: token[0]
|
||||
this.tokens.splice(i + 1, 0, ['INDENT', 2])
|
||||
idx: i + 1
|
||||
parens: 0
|
||||
while true
|
||||
idx += 1
|
||||
tok: this.tokens[idx]
|
||||
if (not tok or SINGLE_CLOSERS.indexOf(tok[0]) >= 0 or
|
||||
(tok[0] is ')' && parens is 0)) and
|
||||
not (starter is 'ELSE' and tok[0] is 'ELSE')
|
||||
insertion: if this.tokens[idx - 1][0] is "," then idx - 1 else idx
|
||||
this.tokens.splice(insertion, 0, ['OUTDENT', 2])
|
||||
break
|
||||
parens += 1 if tok[0] is '('
|
||||
parens -= 1 if tok[0] is ')'
|
||||
return 1 unless token[0] is 'THEN'
|
||||
this.tokens.splice(i, 1)
|
||||
return 0
|
||||
|
||||
# Ensure that all listed pairs of tokens are correctly balanced throughout
|
||||
# the course of the token stream.
|
||||
re::ensure_balance: (pairs) ->
|
||||
levels: {}
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
for pair in pairs
|
||||
[open, close]: pair
|
||||
levels[open] ||= 0
|
||||
levels[open] += 1 if token[0] is open
|
||||
levels[open] -= 1 if token[0] is close
|
||||
throw "too many " + token[1] if levels[open] < 0
|
||||
return 1
|
||||
unclosed: key for key, value of levels when value > 0
|
||||
throw "unclosed " + unclosed[0] if unclosed.length
|
||||
|
||||
# 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.
|
||||
#
|
||||
re::rewrite_closing_parens: ->
|
||||
stack: []
|
||||
debt: {}
|
||||
(debt[key]: 0) for key, val of INVERSES
|
||||
this.scan_tokens (prev, token, post, i) =>
|
||||
tag: token[0]
|
||||
inv: INVERSES[token[0]]
|
||||
# Push openers onto the stack.
|
||||
if EXPRESSION_START.indexOf(tag) >= 0
|
||||
stack.push(token)
|
||||
return 1
|
||||
# The end of an expression, check stack and debt for a pair.
|
||||
else if EXPRESSION_TAIL.indexOf(tag) >= 0
|
||||
# If the tag is already in our debt, swallow it.
|
||||
if debt[inv] > 0
|
||||
debt[inv] -= 1
|
||||
this.tokens.splice(i, 1)
|
||||
return 0
|
||||
else
|
||||
# Pop the stack of open delimiters.
|
||||
match: stack.pop()
|
||||
mtag: match[0]
|
||||
# Continue onwards if it's the expected tag.
|
||||
if tag is INVERSES[mtag]
|
||||
return 1
|
||||
else
|
||||
# Unexpected close, insert correct close, adding to the debt.
|
||||
debt[mtag] += 1
|
||||
val: if mtag is 'INDENT' then match[1] else INVERSES[mtag]
|
||||
this.tokens.splice(i, 0, [INVERSES[mtag], val])
|
||||
return 1
|
||||
else
|
||||
return 1
|
||||
12
src/runner.coffee
Normal file
12
src/runner.coffee
Normal file
@@ -0,0 +1,12 @@
|
||||
# Quickie script to compile and run all the files given as arguments.
|
||||
|
||||
process.mixin require 'sys'
|
||||
coffee: require './coffee-script'
|
||||
|
||||
paths: process.ARGV
|
||||
paths: paths[2...paths.length]
|
||||
|
||||
if paths.length
|
||||
coffee.compile_files paths, (js) -> eval(js)
|
||||
else
|
||||
require './repl'
|
||||
12
test/fixtures/execution/test_arguments.coffee
vendored
12
test/fixtures/execution/test_arguments.coffee
vendored
@@ -4,12 +4,12 @@ area: (x, y, x1, y1) ->
|
||||
x: y: 10
|
||||
x1: y1: 20
|
||||
|
||||
print area(x, y, x1, y1) is 100
|
||||
puts area(x, y, x1, y1) is 100
|
||||
|
||||
print(area(x, y,
|
||||
puts(area(x, y,
|
||||
x1, y1) is 100)
|
||||
|
||||
print(area(
|
||||
puts(area(
|
||||
x
|
||||
y
|
||||
x1
|
||||
@@ -19,7 +19,7 @@ print(area(
|
||||
|
||||
# Arguments are turned into arrays.
|
||||
curried: ->
|
||||
print area.apply(this, arguments.concat(20, 20)) is 100
|
||||
puts area.apply(this, arguments.concat(20, 20)) is 100
|
||||
|
||||
curried 10, 10
|
||||
|
||||
@@ -29,9 +29,9 @@ func: ->
|
||||
arguments: 25
|
||||
arguments
|
||||
|
||||
print func(100) is 25
|
||||
puts func(100) is 25
|
||||
|
||||
|
||||
# Arguments can be accessed as a property.
|
||||
this.arguments: 10
|
||||
print @arguments is 10
|
||||
puts @arguments is 10
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
nums: n * n for n in [1, 2, 3] when n % 2 isnt 0
|
||||
results: n * 2 for n in nums
|
||||
|
||||
print results.join(',') is '2,18'
|
||||
puts results.join(',') is '2,18'
|
||||
|
||||
|
||||
obj: {one: 1, two: 2, three: 3}
|
||||
names: prop + '!' for prop of obj
|
||||
odds: prop + '!' for prop, value of obj when value % 2 isnt 0
|
||||
|
||||
print names.join(' ') is "one! two! three!"
|
||||
print odds.join(' ') is "one! three!"
|
||||
puts names.join(' ') is "one! two! three!"
|
||||
puts odds.join(' ') is "one! three!"
|
||||
|
||||
|
||||
evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
|
||||
@@ -17,12 +17,12 @@ evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0
|
||||
num -= 2
|
||||
num * -1
|
||||
|
||||
print evens.join(', ') is '4, 6, 8'
|
||||
puts evens.join(', ') is '4, 6, 8'
|
||||
|
||||
|
||||
# Make sure that the "in" operator still works.
|
||||
|
||||
print 2 in evens
|
||||
puts 2 in evens
|
||||
|
||||
|
||||
# When functions are being defined within the body of a comprehension, make
|
||||
@@ -37,12 +37,12 @@ for method in methods
|
||||
obj[name]: ->
|
||||
"I'm " + name
|
||||
|
||||
print obj.one() is "I'm one"
|
||||
print obj.two() is "I'm two"
|
||||
print obj.three() is "I'm three"
|
||||
puts obj.one() is "I'm one"
|
||||
puts obj.two() is "I'm two"
|
||||
puts obj.three() is "I'm three"
|
||||
|
||||
|
||||
# Steps should work for array comprehensions.
|
||||
|
||||
array: [0..10]
|
||||
print num % 2 is 0 for num in array by 2
|
||||
puts num % 2 is 0 for num in array by 2
|
||||
|
||||
@@ -7,7 +7,7 @@ catch error
|
||||
|
||||
result2: try nonexistent * missing catch error then true
|
||||
|
||||
print result is true and result2 is true
|
||||
puts result is true and result2 is true
|
||||
|
||||
|
||||
# Assign to conditional.
|
||||
@@ -16,8 +16,8 @@ get_x: -> 10
|
||||
|
||||
if x: get_x() then 100
|
||||
|
||||
print x is 10
|
||||
puts x is 10
|
||||
|
||||
x: if get_x() then 100
|
||||
|
||||
print x is 100
|
||||
puts x is 100
|
||||
2
test/fixtures/execution/test_blocks.coffee
vendored
2
test/fixtures/execution/test_blocks.coffee
vendored
@@ -1,4 +1,4 @@
|
||||
results: [1, 2, 3].map (x) ->
|
||||
x * x
|
||||
|
||||
print results.join(' ') is '1 4 9'
|
||||
puts results.join(' ') is '1 4 9'
|
||||
@@ -20,7 +20,7 @@ ThirdChild::func: (string) ->
|
||||
|
||||
result: (new ThirdChild()).func 'four'
|
||||
|
||||
print result is 'zero/one/two/three/four'
|
||||
puts result is 'zero/one/two/three/four'
|
||||
|
||||
|
||||
TopClass: (arg) ->
|
||||
@@ -35,4 +35,4 @@ SubClass: ->
|
||||
SuperClass extends TopClass
|
||||
SubClass extends SuperClass
|
||||
|
||||
print((new SubClass()).prop is 'top-super-sub')
|
||||
puts((new SubClass()).prop is 'top-super-sub')
|
||||
@@ -3,7 +3,7 @@ identity_wrap: (x) ->
|
||||
|
||||
result: identity_wrap(identity_wrap(true))()()
|
||||
|
||||
print result
|
||||
puts result
|
||||
|
||||
|
||||
str: 'god'
|
||||
@@ -14,7 +14,7 @@ result: str.
|
||||
reverse().
|
||||
reverse()
|
||||
|
||||
print result.join('') is 'dog'
|
||||
puts result.join('') is 'dog'
|
||||
|
||||
result: str
|
||||
.split('')
|
||||
@@ -22,4 +22,4 @@ result: str
|
||||
.reverse()
|
||||
.reverse()
|
||||
|
||||
print result.join('') is 'dog'
|
||||
puts result.join('') is 'dog'
|
||||
@@ -3,26 +3,26 @@ b: -2
|
||||
|
||||
[a, b]: [b, a]
|
||||
|
||||
print a is -2
|
||||
print b is -1
|
||||
puts a is -2
|
||||
puts b is -1
|
||||
|
||||
|
||||
arr: [1, 2, 3]
|
||||
|
||||
[a, b, c]: arr
|
||||
|
||||
print a is 1
|
||||
print b is 2
|
||||
print c is 3
|
||||
puts a is 1
|
||||
puts b is 2
|
||||
puts c is 3
|
||||
|
||||
|
||||
obj: {x: 10, y: 20, z: 30}
|
||||
|
||||
{x: a, y: b, z: c}: obj
|
||||
|
||||
print a is 10
|
||||
print b is 20
|
||||
print c is 30
|
||||
puts a is 10
|
||||
puts b is 20
|
||||
puts c is 30
|
||||
|
||||
|
||||
person: {
|
||||
@@ -42,8 +42,8 @@ person: {
|
||||
|
||||
{name: a, family: {brother: {addresses: [one, {city: b}]}}}: person
|
||||
|
||||
print a is "Bob"
|
||||
print b is "Moquasset NY, 10021"
|
||||
puts a is "Bob"
|
||||
puts b is "Moquasset NY, 10021"
|
||||
|
||||
|
||||
test: {
|
||||
@@ -59,4 +59,4 @@ test: {
|
||||
|
||||
{person: {address: [ignore, addr...]}}: test
|
||||
|
||||
print addr.join(', ') is "Street 101, Apt 101, City 101"
|
||||
puts addr.join(', ') is "Street 101, Apt 101, City 101"
|
||||
@@ -26,4 +26,4 @@ func: ->
|
||||
|
||||
c.single: c.list[1..1][0]
|
||||
|
||||
print func() is '-'
|
||||
puts func() is '-'
|
||||
|
||||
26
test/fixtures/execution/test_existence.coffee
vendored
26
test/fixtures/execution/test_existence.coffee
vendored
@@ -1,8 +1,8 @@
|
||||
print(if my_special_variable? then false else true)
|
||||
puts(if my_special_variable? then false else true)
|
||||
|
||||
my_special_variable: false
|
||||
|
||||
print(if my_special_variable? then true else false)
|
||||
puts(if my_special_variable? then true else false)
|
||||
|
||||
|
||||
# Existential assignment.
|
||||
@@ -12,7 +12,7 @@ a: null
|
||||
a ?= 10
|
||||
b ?= 10
|
||||
|
||||
print a is 10 and b is 10
|
||||
puts a is 10 and b is 10
|
||||
|
||||
|
||||
# The existential operator.
|
||||
@@ -20,7 +20,7 @@ print a is 10 and b is 10
|
||||
z: null
|
||||
x: z ? "EX"
|
||||
|
||||
print z is null and x is "EX"
|
||||
puts z is null and x is "EX"
|
||||
|
||||
|
||||
# Only evaluate once.
|
||||
@@ -30,7 +30,7 @@ get_next_node: ->
|
||||
throw "up" if counter
|
||||
counter++
|
||||
|
||||
print(if get_next_node()? then true else false)
|
||||
puts(if get_next_node()? then true else false)
|
||||
|
||||
|
||||
# Existence chains, soaking up undefined properties:
|
||||
@@ -39,19 +39,19 @@ obj: {
|
||||
prop: "hello"
|
||||
}
|
||||
|
||||
print obj?.prop is "hello"
|
||||
puts obj?.prop is "hello"
|
||||
|
||||
print obj.prop?.length is 5
|
||||
puts obj.prop?.length is 5
|
||||
|
||||
print obj?.prop?.non?.existent?.property is undefined
|
||||
puts obj?.prop?.non?.existent?.property is undefined
|
||||
|
||||
|
||||
# Soaks and caches method calls as well.
|
||||
|
||||
arr: ["--", "----"]
|
||||
|
||||
print arr.pop()?.length is 4
|
||||
print arr.pop()?.length is 2
|
||||
print arr.pop()?.length is undefined
|
||||
print arr[0]?.length is undefined
|
||||
print arr.pop()?.length?.non?.existent()?.property is undefined
|
||||
puts arr.pop()?.length is 4
|
||||
puts arr.pop()?.length is 2
|
||||
puts arr.pop()?.length is undefined
|
||||
puts arr[0]?.length is undefined
|
||||
puts arr.pop()?.length?.non?.existent()?.property is undefined
|
||||
|
||||
@@ -9,7 +9,7 @@ findit: (items) ->
|
||||
for item in items
|
||||
return item if item is "bacon"
|
||||
|
||||
print findit(items) is "bacon"
|
||||
puts findit(items) is "bacon"
|
||||
|
||||
|
||||
# When when a closure wrapper is generated for expression conversion, make sure
|
||||
@@ -26,5 +26,5 @@ obj: {
|
||||
this.num
|
||||
}
|
||||
|
||||
print obj.num is obj.func()
|
||||
print obj.num is obj.result
|
||||
puts obj.num is obj.func()
|
||||
puts obj.num is obj.result
|
||||
@@ -7,10 +7,10 @@ result: if a
|
||||
if d
|
||||
true
|
||||
|
||||
print result
|
||||
puts result
|
||||
|
||||
|
||||
first: if false then false else second: if false then false else true
|
||||
|
||||
print first
|
||||
print second
|
||||
puts first
|
||||
puts second
|
||||
36
test/fixtures/execution/test_functions.coffee
vendored
36
test/fixtures/execution/test_functions.coffee
vendored
@@ -2,11 +2,11 @@ x: 1
|
||||
y: {}
|
||||
y.x: -> 3
|
||||
|
||||
print x is 1
|
||||
print typeof(y.x) is 'function'
|
||||
print y.x instanceof Function
|
||||
print y.x() is 3
|
||||
print y.x.name is 'x'
|
||||
puts x is 1
|
||||
puts typeof(y.x) is 'function'
|
||||
puts y.x instanceof Function
|
||||
puts y.x() is 3
|
||||
puts y.x.name is 'x'
|
||||
|
||||
|
||||
# The empty function should not cause a syntax error.
|
||||
@@ -17,10 +17,10 @@ obj: {
|
||||
name: "Fred"
|
||||
|
||||
bound: ->
|
||||
(=> print(this.name is "Fred"))()
|
||||
(=> puts(this.name is "Fred"))()
|
||||
|
||||
unbound: ->
|
||||
(-> print(!this.name?))()
|
||||
(-> puts(!this.name?))()
|
||||
}
|
||||
|
||||
obj.unbound()
|
||||
@@ -44,18 +44,18 @@ Math: {
|
||||
FastAdd: memoize (a, b) -> a + b
|
||||
}
|
||||
|
||||
print Math.Add(5, 5) is 10
|
||||
print Math.AnonymousAdd(10, 10) is 20
|
||||
print Math.FastAdd(20, 20) is 40
|
||||
puts Math.Add(5, 5) is 10
|
||||
puts Math.AnonymousAdd(10, 10) is 20
|
||||
puts Math.FastAdd(20, 20) is 40
|
||||
|
||||
|
||||
# Parens are optional on simple function calls.
|
||||
print 100 > 1 if 1 > 0
|
||||
print true unless false
|
||||
print true for i in [1..3]
|
||||
puts 100 > 1 if 1 > 0
|
||||
puts true unless false
|
||||
puts true for i in [1..3]
|
||||
|
||||
print_func: (f) -> print(f())
|
||||
print_func -> true
|
||||
puts_func: (f) -> puts(f())
|
||||
puts_func -> true
|
||||
|
||||
# Optional parens can be used in a nested fashion.
|
||||
call: (func) -> func()
|
||||
@@ -64,7 +64,7 @@ result: call ->
|
||||
inner: call ->
|
||||
Math.Add(5, 5)
|
||||
|
||||
print result is 10
|
||||
puts result is 10
|
||||
|
||||
|
||||
# And even with strange things like this:
|
||||
@@ -72,8 +72,8 @@ print result is 10
|
||||
funcs: [(x) -> x, (x) -> x * x]
|
||||
result: funcs[1] 5
|
||||
|
||||
print result is 25
|
||||
puts result is 25
|
||||
|
||||
result: ("hello".slice) 3
|
||||
|
||||
print result is 'lo'
|
||||
puts result is 'lo'
|
||||
@@ -18,4 +18,4 @@ switch 'string'
|
||||
code()
|
||||
# comment
|
||||
|
||||
print func()
|
||||
puts func()
|
||||
|
||||
12
test/fixtures/execution/test_heredocs.coffee
vendored
12
test/fixtures/execution/test_heredocs.coffee
vendored
@@ -3,7 +3,7 @@ a: """
|
||||
on two lines
|
||||
"""
|
||||
|
||||
print a is "basic heredoc\non two lines"
|
||||
puts a is "basic heredoc\non two lines"
|
||||
|
||||
|
||||
a: '''
|
||||
@@ -12,12 +12,12 @@ a: '''
|
||||
c
|
||||
'''
|
||||
|
||||
print a is "a\n \"b\nc"
|
||||
puts a is "a\n \"b\nc"
|
||||
|
||||
|
||||
a: '''one-liner'''
|
||||
|
||||
print a is 'one-liner'
|
||||
puts a is 'one-liner'
|
||||
|
||||
|
||||
a: """
|
||||
@@ -25,7 +25,7 @@ a: """
|
||||
here
|
||||
"""
|
||||
|
||||
print a is "out\nhere"
|
||||
puts a is "out\nhere"
|
||||
|
||||
|
||||
a: '''
|
||||
@@ -34,7 +34,7 @@ a: '''
|
||||
c
|
||||
'''
|
||||
|
||||
print a is " a\n b\nc"
|
||||
puts a is " a\n b\nc"
|
||||
|
||||
a: '''
|
||||
a
|
||||
@@ -43,4 +43,4 @@ a
|
||||
b c
|
||||
'''
|
||||
|
||||
print a is "a\n\n\nb c"
|
||||
puts a is "a\n\n\nb c"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
num: 1 + 2 + (a: 3)
|
||||
|
||||
print num is 6
|
||||
puts num is 6
|
||||
|
||||
|
||||
result: if true
|
||||
false
|
||||
other: "result"
|
||||
|
||||
print result is "result" and other is "result"
|
||||
puts result is "result" and other is "result"
|
||||
16
test/fixtures/execution/test_literals.coffee
vendored
16
test/fixtures/execution/test_literals.coffee
vendored
@@ -1,40 +1,40 @@
|
||||
a: [(x) -> x, (x) -> x * x]
|
||||
|
||||
print a.length is 2
|
||||
puts a.length is 2
|
||||
|
||||
|
||||
regex: /match/i
|
||||
words: "I think there is a match in here."
|
||||
|
||||
print !!words.match(regex)
|
||||
puts !!words.match(regex)
|
||||
|
||||
|
||||
neg: (3 -4)
|
||||
|
||||
print neg is -1
|
||||
puts neg is -1
|
||||
|
||||
|
||||
func: ->
|
||||
return if true
|
||||
|
||||
print func() is null
|
||||
puts func() is null
|
||||
|
||||
|
||||
str: "\\"
|
||||
reg: /\\/
|
||||
|
||||
print reg(str) and str is '\\'
|
||||
puts reg(str) and str is '\\'
|
||||
|
||||
|
||||
i: 10
|
||||
while i -= 1
|
||||
|
||||
print i is 0
|
||||
puts i is 0
|
||||
|
||||
|
||||
money$: 'dollars'
|
||||
|
||||
print money$ is 'dollars'
|
||||
puts money$ is 'dollars'
|
||||
|
||||
|
||||
bob: {
|
||||
@@ -45,4 +45,4 @@ bob: {
|
||||
@greet "Hello"
|
||||
}
|
||||
|
||||
print bob.hello() is "Hello Bob"
|
||||
puts bob.hello() is "Hello Bob"
|
||||
|
||||
@@ -6,6 +6,6 @@ multi_liner:
|
||||
single_liner:
|
||||
[x, y] for y in [3..5] for x in [3..5]
|
||||
|
||||
print multi_liner.length is single_liner.length
|
||||
print 5 is multi_liner[2][2][1]
|
||||
print 5 is single_liner[2][2][1]
|
||||
puts multi_liner.length is single_liner.length
|
||||
puts 5 is multi_liner[2][2][1]
|
||||
puts 5 is single_liner[2][2][1]
|
||||
|
||||
@@ -3,4 +3,4 @@ six:
|
||||
2 +
|
||||
3
|
||||
|
||||
print six is 6
|
||||
puts six is 6
|
||||
10
test/fixtures/execution/test_operations.coffee
vendored
10
test/fixtures/execution/test_operations.coffee
vendored
@@ -1,12 +1,12 @@
|
||||
# CoffeeScript's operations should be chainable, like Python's.
|
||||
|
||||
print 500 > 50 > 5 > -5
|
||||
puts 500 > 50 > 5 > -5
|
||||
|
||||
print true is not false is true is not false
|
||||
puts true is not false is true is not false
|
||||
|
||||
print 10 < 20 > 10
|
||||
puts 10 < 20 > 10
|
||||
|
||||
print 50 > 10 > 5 is parseInt('5', 10)
|
||||
puts 50 > 10 > 5 is parseInt('5', 10)
|
||||
|
||||
|
||||
# Make sure that each argument is only evaluated once, even if used
|
||||
@@ -15,4 +15,4 @@ print 50 > 10 > 5 is parseInt('5', 10)
|
||||
i: 0
|
||||
func: -> i++
|
||||
|
||||
print 1 > func() < 1
|
||||
puts 1 > func() < 1
|
||||
|
||||
@@ -5,16 +5,16 @@ negs: negs[0..2]
|
||||
|
||||
result: nums.concat(negs).join(', ')
|
||||
|
||||
print result is '3, 6, 9, -20, -19, -18'
|
||||
puts result is '3, 6, 9, -20, -19, -18'
|
||||
|
||||
# Ensure that ranges are safe. This used to infinite loop:
|
||||
j = 5
|
||||
result: for j in [j..(j+3)]
|
||||
j
|
||||
|
||||
print result.join(' ') is '5 6 7 8'
|
||||
puts result.join(' ') is '5 6 7 8'
|
||||
|
||||
# With range comprehensions, you can loop in steps.
|
||||
results: x for x in [0..25] by 5
|
||||
|
||||
print results.join(' ') is '0 5 10 15 20 25'
|
||||
puts results.join(' ') is '0 5 10 15 20 25'
|
||||
@@ -5,7 +5,12 @@ b: array[2...4]
|
||||
|
||||
result: a.concat(b).join(' ')
|
||||
|
||||
print result is "7 8 9 2 3"
|
||||
puts result is "7 8 9 2 3"
|
||||
|
||||
|
||||
countdown: [10..1].join(' ')
|
||||
print countdown is "10 9 8 7 6 5 4 3 2 1"
|
||||
puts countdown is "10 9 8 7 6 5 4 3 2 1"
|
||||
|
||||
|
||||
array: [(1+5)..1+9]
|
||||
puts array.join(' ') is "6 7 8 9 10"
|
||||
10
test/fixtures/execution/test_splats.coffee
vendored
10
test/fixtures/execution/test_splats.coffee
vendored
@@ -3,7 +3,7 @@ func: (first, second, rest...) ->
|
||||
|
||||
result: func 1, 2, 3, 4, 5
|
||||
|
||||
print result is "3 4 5"
|
||||
puts result is "3 4 5"
|
||||
|
||||
|
||||
gold: silver: bronze: the_field: null
|
||||
@@ -29,7 +29,7 @@ contenders: [
|
||||
|
||||
medalists "Mighty Mouse", contenders...
|
||||
|
||||
print gold is "Mighty Mouse"
|
||||
print silver is "Michael Phelps"
|
||||
print bronze is "Liu Xiang"
|
||||
print the_field.length is 8
|
||||
puts gold is "Mighty Mouse"
|
||||
puts silver is "Michael Phelps"
|
||||
puts bronze is "Liu Xiang"
|
||||
puts the_field.length is 8
|
||||
2
test/fixtures/execution/test_splices.coffee
vendored
2
test/fixtures/execution/test_splices.coffee
vendored
@@ -2,4 +2,4 @@ array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
array[5..10]: [0, 0, 0]
|
||||
|
||||
print array.join(' ') is '0 1 2 3 4 0 0 0'
|
||||
puts array.join(' ') is '0 1 2 3 4 0 0 0'
|
||||
10
test/fixtures/execution/test_switch.coffee
vendored
10
test/fixtures/execution/test_switch.coffee
vendored
@@ -14,7 +14,7 @@ result: switch num
|
||||
when 11 then false
|
||||
else false
|
||||
|
||||
print result
|
||||
puts result
|
||||
|
||||
func: (num) ->
|
||||
switch num
|
||||
@@ -24,7 +24,7 @@ func: (num) ->
|
||||
false
|
||||
else false
|
||||
|
||||
print func(2)
|
||||
print func(6)
|
||||
print !func(3)
|
||||
print !func(8)
|
||||
puts func(2)
|
||||
puts func(6)
|
||||
puts !func(3)
|
||||
puts !func(8)
|
||||
|
||||
6
test/fixtures/execution/test_while.coffee
vendored
6
test/fixtures/execution/test_while.coffee
vendored
@@ -1,17 +1,17 @@
|
||||
i: 100
|
||||
while i -= 1
|
||||
|
||||
print i is 0
|
||||
puts i is 0
|
||||
|
||||
|
||||
i: 5
|
||||
list: while i -= 1
|
||||
i * 2
|
||||
|
||||
print list.join(' ') is "8 6 4 2"
|
||||
puts list.join(' ') is "8 6 4 2"
|
||||
|
||||
|
||||
i: 5
|
||||
list: (i * 3 while i -= 1)
|
||||
|
||||
print list.join(' ') is "12 9 6 3"
|
||||
puts list.join(' ') is "12 9 6 3"
|
||||
@@ -3,14 +3,14 @@
|
||||
result: while sunny?
|
||||
go_outside()
|
||||
|
||||
print(3 + try
|
||||
puts(3 + try
|
||||
nonexistent.no_way
|
||||
catch error
|
||||
print(error)
|
||||
puts(error)
|
||||
3
|
||||
)
|
||||
|
||||
func: (x) ->
|
||||
return throw x
|
||||
|
||||
print(x * x for x in [1..100])
|
||||
puts(x * x for x in [1..100])
|
||||
@@ -19,6 +19,13 @@ class ExecutionTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
# Test all of the code examples under Narwhal as well.
|
||||
def test_execution_with_narwhal
|
||||
(`bin/coffee -r --narwhal #{SOURCES.join(' ')}`).split("\n").each do |line|
|
||||
assert line == "true"
|
||||
end
|
||||
end
|
||||
|
||||
def test_lintless_tests
|
||||
no_warnings `bin/coffee -l test/fixtures/*/*.coffee`
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user