coffee-script 0.3.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +2 -2
- data/README.md +15 -0
- data/lib/coffee-script.rb +1 -21
- data/lib/coffee_script.rb +31 -0
- metadata +30 -46
- data/README +0 -41
- data/bin/coffee +0 -5
- data/coffee-script.gemspec +0 -27
- data/examples/blocks.coffee +0 -57
- data/examples/code.coffee +0 -173
- data/examples/poignant.coffee +0 -186
- data/examples/potion.coffee +0 -205
- data/examples/underscore.coffee +0 -603
- data/extras/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences +0 -24
- data/extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +0 -361
- data/extras/CoffeeScript.tmbundle/info.plist +0 -10
- data/extras/EXTRAS +0 -20
- data/extras/coffee.vim +0 -111
- data/lib/coffee_script/coffee-script.js +0 -50
- data/lib/coffee_script/command_line.rb +0 -235
- data/lib/coffee_script/grammar.y +0 -481
- data/lib/coffee_script/lexer.js +0 -363
- data/lib/coffee_script/lexer.rb +0 -272
- data/lib/coffee_script/narwhal/coffee-script.js +0 -96
- data/lib/coffee_script/nodes.js +0 -443
- data/lib/coffee_script/nodes.rb +0 -1050
- data/lib/coffee_script/parse_error.rb +0 -29
- data/lib/coffee_script/parser.js +0 -477
- data/lib/coffee_script/parser.rb +0 -2611
- data/lib/coffee_script/repl.js +0 -33
- data/lib/coffee_script/rewriter.js +0 -377
- data/lib/coffee_script/rewriter.rb +0 -289
- data/lib/coffee_script/runner.js +0 -11
- data/lib/coffee_script/scope.js +0 -73
- data/lib/coffee_script/scope.rb +0 -91
- data/lib/coffee_script/value.rb +0 -64
- data/package.json +0 -8
@@ -1,50 +0,0 @@
|
|
1
|
-
(function(){
|
2
|
-
var compiler, path;
|
3
|
-
// Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript.
|
4
|
-
path = require('path');
|
5
|
-
// The path to the CoffeeScript executable.
|
6
|
-
compiler = path.normalize(path.dirname(__filename) + '/../../bin/coffee');
|
7
|
-
// Compile a string over stdin, with global variables, for the REPL.
|
8
|
-
exports.compile = function compile(code, callback) {
|
9
|
-
var coffee, js;
|
10
|
-
js = '';
|
11
|
-
coffee = process.createChildProcess(compiler, ['--eval', '--no-wrap', '--globals']);
|
12
|
-
coffee.addListener('output', function(results) {
|
13
|
-
if ((typeof results !== "undefined" && results !== null)) {
|
14
|
-
return js += results;
|
15
|
-
}
|
16
|
-
});
|
17
|
-
coffee.addListener('exit', function() {
|
18
|
-
return callback(js);
|
19
|
-
});
|
20
|
-
coffee.write(code);
|
21
|
-
return coffee.close();
|
22
|
-
};
|
23
|
-
// Compile a list of CoffeeScript files on disk.
|
24
|
-
exports.compile_files = function compile_files(paths, callback) {
|
25
|
-
var coffee, exit_ran, js;
|
26
|
-
js = '';
|
27
|
-
coffee = process.createChildProcess(compiler, ['--print'].concat(paths));
|
28
|
-
coffee.addListener('output', function(results) {
|
29
|
-
if ((typeof results !== "undefined" && results !== null)) {
|
30
|
-
return js += results;
|
31
|
-
}
|
32
|
-
});
|
33
|
-
// NB: we have to add a mutex to make sure it doesn't get called twice.
|
34
|
-
exit_ran = false;
|
35
|
-
coffee.addListener('exit', function() {
|
36
|
-
if (exit_ran) {
|
37
|
-
return null;
|
38
|
-
}
|
39
|
-
exit_ran = true;
|
40
|
-
return callback(js);
|
41
|
-
});
|
42
|
-
return coffee.addListener('error', function(message) {
|
43
|
-
if (!(message)) {
|
44
|
-
return null;
|
45
|
-
}
|
46
|
-
puts(message);
|
47
|
-
throw new Error("CoffeeScript compile error");
|
48
|
-
});
|
49
|
-
};
|
50
|
-
})();
|
@@ -1,235 +0,0 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
require 'fileutils'
|
3
|
-
require 'open3'
|
4
|
-
begin
|
5
|
-
require File.expand_path(File.dirname(__FILE__) + '/../coffee-script')
|
6
|
-
rescue LoadError => e
|
7
|
-
puts(e.message)
|
8
|
-
puts("use \"rake build:parser\" to regenerate parser.rb")
|
9
|
-
exit(1)
|
10
|
-
end
|
11
|
-
|
12
|
-
module CoffeeScript
|
13
|
-
|
14
|
-
# The CommandLine handles all of the functionality of the `coffee`
|
15
|
-
# utility.
|
16
|
-
class CommandLine
|
17
|
-
|
18
|
-
BANNER = <<-EOS
|
19
|
-
coffee compiles CoffeeScript source files into JavaScript.
|
20
|
-
|
21
|
-
Usage:
|
22
|
-
coffee path/to/script.coffee
|
23
|
-
EOS
|
24
|
-
|
25
|
-
# Seconds to pause between checks for changed source files.
|
26
|
-
WATCH_INTERVAL = 0.5
|
27
|
-
|
28
|
-
# Path to the root of the CoffeeScript install.
|
29
|
-
ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
|
30
|
-
|
31
|
-
# Commands to execute CoffeeScripts.
|
32
|
-
RUNNERS = {
|
33
|
-
:node => "node #{ROOT}/lib/coffee_script/runner.js",
|
34
|
-
:narwhal => "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
|
35
|
-
}
|
36
|
-
|
37
|
-
# Run the CommandLine off the contents of ARGV.
|
38
|
-
def initialize
|
39
|
-
@mtimes = {}
|
40
|
-
parse_options
|
41
|
-
return launch_repl if @options[:interactive]
|
42
|
-
return eval_scriptlet if @options[:eval]
|
43
|
-
check_sources
|
44
|
-
return run_scripts if @options[:run]
|
45
|
-
@sources.each {|source| compile_javascript(source) }
|
46
|
-
watch_coffee_scripts if @options[:watch]
|
47
|
-
end
|
48
|
-
|
49
|
-
# The "--help" usage message.
|
50
|
-
def usage
|
51
|
-
puts "\n#{@option_parser}\n"
|
52
|
-
exit
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
# Compiles (or partially compiles) the source CoffeeScript file, returning
|
59
|
-
# the desired JS, tokens, or lint results.
|
60
|
-
def compile_javascript(source)
|
61
|
-
script = File.read(source)
|
62
|
-
return tokens(script) if @options[:tokens]
|
63
|
-
js = compile(script, source)
|
64
|
-
return unless js
|
65
|
-
return puts(js) if @options[:print]
|
66
|
-
return lint(js) if @options[:lint]
|
67
|
-
File.open(path_for(source), 'w+') {|f| f.write(js) }
|
68
|
-
end
|
69
|
-
|
70
|
-
# Spins up a watcher thread to keep track of the modification times of the
|
71
|
-
# source files, recompiling them whenever they're saved.
|
72
|
-
def watch_coffee_scripts
|
73
|
-
watch_thread = Thread.start do
|
74
|
-
loop do
|
75
|
-
@sources.each do |source|
|
76
|
-
mtime = File.stat(source).mtime
|
77
|
-
@mtimes[source] ||= mtime
|
78
|
-
if mtime > @mtimes[source]
|
79
|
-
@mtimes[source] = mtime
|
80
|
-
compile_javascript(source)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
sleep WATCH_INTERVAL
|
84
|
-
end
|
85
|
-
end
|
86
|
-
Signal.trap("INT") { watch_thread.kill }
|
87
|
-
watch_thread.join
|
88
|
-
end
|
89
|
-
|
90
|
-
# Ensure that all of the source files exist.
|
91
|
-
def check_sources
|
92
|
-
usage if @sources.empty?
|
93
|
-
missing = @sources.detect {|s| !File.exists?(s) }
|
94
|
-
if missing
|
95
|
-
STDERR.puts("File not found: '#{missing}'")
|
96
|
-
exit(1)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Pipe compiled JS through JSLint (requires a working 'jsl' command).
|
101
|
-
def lint(js)
|
102
|
-
stdin, stdout, stderr = Open3.popen3('jsl -nologo -stdin')
|
103
|
-
stdin.write(js)
|
104
|
-
stdin.close
|
105
|
-
puts stdout.read.tr("\n", '')
|
106
|
-
errs = stderr.read.chomp
|
107
|
-
puts errs unless errs.empty?
|
108
|
-
stdout.close and stderr.close
|
109
|
-
end
|
110
|
-
|
111
|
-
# Eval a little piece of CoffeeScript directly from the command line.
|
112
|
-
def eval_scriptlet
|
113
|
-
script = STDIN.tty? ? @sources.join(' ') : STDIN.read
|
114
|
-
return tokens(script) if @options[:tokens]
|
115
|
-
js = compile(script)
|
116
|
-
return lint(js) if @options[:lint]
|
117
|
-
puts js
|
118
|
-
end
|
119
|
-
|
120
|
-
# Use Node.js or Narwhal to run an interactive CoffeeScript session.
|
121
|
-
def launch_repl
|
122
|
-
exec "#{RUNNERS[@options[:runner]]}"
|
123
|
-
rescue Errno::ENOENT
|
124
|
-
puts "Error: #{@options[:runner]} must be installed to use the interactive REPL."
|
125
|
-
exit(1)
|
126
|
-
end
|
127
|
-
|
128
|
-
# Use Node.js or Narwhal to compile and execute CoffeeScripts.
|
129
|
-
def run_scripts
|
130
|
-
sources = @sources.join(' ')
|
131
|
-
exec "#{RUNNERS[@options[:runner]]} #{sources}"
|
132
|
-
rescue Errno::ENOENT
|
133
|
-
puts "Error: #{@options[:runner]} must be installed in order to execute scripts."
|
134
|
-
exit(1)
|
135
|
-
end
|
136
|
-
|
137
|
-
# Print the tokens that the lexer generates from a source script.
|
138
|
-
def tokens(script)
|
139
|
-
puts Lexer.new.tokenize(script).inspect
|
140
|
-
end
|
141
|
-
|
142
|
-
# Compile a single source file to JavaScript.
|
143
|
-
def compile(script, source='error')
|
144
|
-
begin
|
145
|
-
options = {}
|
146
|
-
options[:no_wrap] = true if @options[:no_wrap]
|
147
|
-
options[:globals] = true if @options[:globals]
|
148
|
-
CoffeeScript.compile(script, options)
|
149
|
-
rescue CoffeeScript::ParseError => e
|
150
|
-
STDERR.puts "#{source}: #{e.message}"
|
151
|
-
exit(1) unless @options[:watch]
|
152
|
-
nil
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# Write out JavaScript alongside CoffeeScript unless an output directory
|
157
|
-
# is specified.
|
158
|
-
def path_for(source)
|
159
|
-
filename = File.basename(source, File.extname(source)) + '.js'
|
160
|
-
dir = @options[:output] || File.dirname(source)
|
161
|
-
File.join(dir, filename)
|
162
|
-
end
|
163
|
-
|
164
|
-
# Install the CoffeeScript TextMate bundle to ~/Library.
|
165
|
-
def install_bundle
|
166
|
-
bundle_dir = File.expand_path('~/Library/Application Support/TextMate/Bundles/')
|
167
|
-
FileUtils.cp_r("#{ROOT}/extras/CoffeeScript.tmbundle", bundle_dir)
|
168
|
-
end
|
169
|
-
|
170
|
-
# Use OptionParser for all the options.
|
171
|
-
def parse_options
|
172
|
-
@options = {:runner => :node}
|
173
|
-
@option_parser = OptionParser.new do |opts|
|
174
|
-
opts.on('-i', '--interactive', 'run an interactive CoffeeScript REPL') do |i|
|
175
|
-
@options[:interactive] = true
|
176
|
-
end
|
177
|
-
opts.on('-r', '--run', 'compile and run a CoffeeScript') do |r|
|
178
|
-
@options[:run] = true
|
179
|
-
end
|
180
|
-
opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
|
181
|
-
@options[:output] = d
|
182
|
-
FileUtils.mkdir_p(d) unless File.exists?(d)
|
183
|
-
end
|
184
|
-
opts.on('-w', '--watch', 'watch scripts for changes, and recompile') do |w|
|
185
|
-
@options[:watch] = true
|
186
|
-
end
|
187
|
-
opts.on('-p', '--print', 'print the compiled JavaScript to stdout') do |d|
|
188
|
-
@options[:print] = true
|
189
|
-
end
|
190
|
-
opts.on('-l', '--lint', 'pipe the compiled JavaScript through JSLint') do |l|
|
191
|
-
@options[:lint] = true
|
192
|
-
end
|
193
|
-
opts.on('-e', '--eval', 'compile a cli scriptlet or read from stdin') do |e|
|
194
|
-
@options[:eval] = true
|
195
|
-
end
|
196
|
-
opts.on('-t', '--tokens', 'print the tokens that the lexer produces') do |t|
|
197
|
-
@options[:tokens] = true
|
198
|
-
end
|
199
|
-
opts.on('-v', '--verbose', 'print at every step of code generation') do |v|
|
200
|
-
ENV['VERBOSE'] = 'true'
|
201
|
-
end
|
202
|
-
opts.on('-n', '--no-wrap', 'raw output, no function safety wrapper') do |n|
|
203
|
-
@options[:no_wrap] = true
|
204
|
-
end
|
205
|
-
opts.on('-g', '--globals', 'attach all top-level variable as globals') do |n|
|
206
|
-
@options[:globals] = true
|
207
|
-
end
|
208
|
-
opts.on_tail('--narwhal', 'use Narwhal instead of Node.js') do |n|
|
209
|
-
@options[:runner] = :narwhal
|
210
|
-
end
|
211
|
-
opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
|
212
|
-
install_bundle
|
213
|
-
exit
|
214
|
-
end
|
215
|
-
opts.on_tail('--version', 'display CoffeeScript version') do
|
216
|
-
puts "CoffeeScript version #{CoffeeScript::VERSION}"
|
217
|
-
exit
|
218
|
-
end
|
219
|
-
opts.on_tail('-h', '--help', 'display this help message') do
|
220
|
-
usage
|
221
|
-
end
|
222
|
-
end
|
223
|
-
@option_parser.banner = BANNER
|
224
|
-
begin
|
225
|
-
@option_parser.parse!(ARGV)
|
226
|
-
rescue OptionParser::InvalidOption => e
|
227
|
-
puts e.message
|
228
|
-
exit(1)
|
229
|
-
end
|
230
|
-
@sources = ARGV
|
231
|
-
end
|
232
|
-
|
233
|
-
end
|
234
|
-
|
235
|
-
end
|
data/lib/coffee_script/grammar.y
DELETED
@@ -1,481 +0,0 @@
|
|
1
|
-
class Parser
|
2
|
-
|
3
|
-
# Declare terminal tokens produced by the lexer.
|
4
|
-
token IF ELSE UNLESS
|
5
|
-
token NUMBER STRING REGEX
|
6
|
-
token TRUE FALSE YES NO ON OFF
|
7
|
-
token IDENTIFIER PROPERTY_ACCESS PROTOTYPE_ACCESS SOAK_ACCESS
|
8
|
-
token CODE PARAM_START PARAM PARAM_END NEW RETURN
|
9
|
-
token CALL_START CALL_END INDEX_START INDEX_END
|
10
|
-
token TRY CATCH FINALLY THROW
|
11
|
-
token BREAK CONTINUE
|
12
|
-
token FOR IN OF BY WHEN WHILE
|
13
|
-
token SWITCH LEADING_WHEN
|
14
|
-
token DELETE INSTANCEOF TYPEOF
|
15
|
-
token SUPER EXTENDS
|
16
|
-
token ASSIGN RETURN
|
17
|
-
token NEWLINE
|
18
|
-
token COMMENT
|
19
|
-
token JS
|
20
|
-
token INDENT OUTDENT
|
21
|
-
|
22
|
-
# Declare order of operations.
|
23
|
-
prechigh
|
24
|
-
nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
|
25
|
-
left '*' '/' '%' '?' '.'
|
26
|
-
left '+' '-'
|
27
|
-
left '<<' '>>' '>>>' '&' '|' '^'
|
28
|
-
left '<=' '<' '>' '>='
|
29
|
-
right '==' '!=' IS ISNT
|
30
|
-
left '&&' '||' AND OR
|
31
|
-
right '-=' '+=' '/=' '*=' '%=' '||=' '&&=' '?='
|
32
|
-
right DELETE INSTANCEOF TYPEOF
|
33
|
-
right INDENT
|
34
|
-
left OUTDENT
|
35
|
-
right WHEN LEADING_WHEN IN OF BY
|
36
|
-
right THROW FOR NEW SUPER
|
37
|
-
left EXTENDS
|
38
|
-
right ASSIGN RETURN
|
39
|
-
right '->' '=>' UNLESS IF ELSE WHILE
|
40
|
-
preclow
|
41
|
-
|
42
|
-
rule
|
43
|
-
|
44
|
-
# All parsing will end in this rule, being the trunk of the AST.
|
45
|
-
Root:
|
46
|
-
/* nothing */ { result = Expressions.new }
|
47
|
-
| Terminator { result = Expressions.new }
|
48
|
-
| Expressions { result = val[0] }
|
49
|
-
| Block Terminator { result = val[0] }
|
50
|
-
;
|
51
|
-
|
52
|
-
# Any list of expressions or method body, seperated by line breaks or semis.
|
53
|
-
Expressions:
|
54
|
-
Expression { result = Expressions.wrap(val) }
|
55
|
-
| Expressions Terminator Expression { result = val[0] << val[2] }
|
56
|
-
| Expressions Terminator { result = val[0] }
|
57
|
-
;
|
58
|
-
|
59
|
-
# All types of expressions in our language. The basic unit of CoffeeScript
|
60
|
-
# is the expression.
|
61
|
-
Expression:
|
62
|
-
Value
|
63
|
-
| Call
|
64
|
-
| Code
|
65
|
-
| Operation
|
66
|
-
| Assign
|
67
|
-
| If
|
68
|
-
| Try
|
69
|
-
| Throw
|
70
|
-
| Return
|
71
|
-
| While
|
72
|
-
| For
|
73
|
-
| Switch
|
74
|
-
| Extends
|
75
|
-
| Splat
|
76
|
-
| Existence
|
77
|
-
| Comment
|
78
|
-
;
|
79
|
-
|
80
|
-
# A block of expressions. Note that the Rewriter will convert some postfix
|
81
|
-
# forms into blocks for us, by altering the token stream.
|
82
|
-
Block:
|
83
|
-
INDENT Expressions OUTDENT { result = val[1] }
|
84
|
-
| INDENT OUTDENT { result = Expressions.new }
|
85
|
-
;
|
86
|
-
|
87
|
-
# Tokens that can terminate an expression.
|
88
|
-
Terminator:
|
89
|
-
"\n"
|
90
|
-
| ";"
|
91
|
-
;
|
92
|
-
|
93
|
-
# All hard-coded values. These can be printed straight to JavaScript.
|
94
|
-
Literal:
|
95
|
-
NUMBER { result = LiteralNode.new(val[0]) }
|
96
|
-
| STRING { result = LiteralNode.new(val[0]) }
|
97
|
-
| JS { result = LiteralNode.new(val[0]) }
|
98
|
-
| REGEX { result = LiteralNode.new(val[0]) }
|
99
|
-
| BREAK { result = LiteralNode.new(val[0]) }
|
100
|
-
| CONTINUE { result = LiteralNode.new(val[0]) }
|
101
|
-
| TRUE { result = LiteralNode.new(Value.new(true)) }
|
102
|
-
| FALSE { result = LiteralNode.new(Value.new(false)) }
|
103
|
-
| YES { result = LiteralNode.new(Value.new(true)) }
|
104
|
-
| NO { result = LiteralNode.new(Value.new(false)) }
|
105
|
-
| ON { result = LiteralNode.new(Value.new(true)) }
|
106
|
-
| OFF { result = LiteralNode.new(Value.new(false)) }
|
107
|
-
;
|
108
|
-
|
109
|
-
# Assignment to a variable (or index).
|
110
|
-
Assign:
|
111
|
-
Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) }
|
112
|
-
;
|
113
|
-
|
114
|
-
# Assignment within an object literal (can be quoted).
|
115
|
-
AssignObj:
|
116
|
-
IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
|
117
|
-
| STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
|
118
|
-
| Comment { result = val[0] }
|
119
|
-
;
|
120
|
-
|
121
|
-
# A return statement.
|
122
|
-
Return:
|
123
|
-
RETURN Expression { result = ReturnNode.new(val[1]) }
|
124
|
-
| RETURN { result = ReturnNode.new(ValueNode.new(Value.new('null'))) }
|
125
|
-
;
|
126
|
-
|
127
|
-
# A comment.
|
128
|
-
Comment:
|
129
|
-
COMMENT { result = CommentNode.new(val[0]) }
|
130
|
-
;
|
131
|
-
|
132
|
-
# Arithmetic and logical operators
|
133
|
-
# For Ruby's Operator precedence, see:
|
134
|
-
# https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
|
135
|
-
Operation:
|
136
|
-
'!' Expression { result = OpNode.new(val[0], val[1]) }
|
137
|
-
| '!!' Expression { result = OpNode.new(val[0], val[1]) }
|
138
|
-
| '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
|
139
|
-
| '+' Expression = UPLUS { result = OpNode.new(val[0], val[1]) }
|
140
|
-
| NOT Expression { result = OpNode.new(val[0], val[1]) }
|
141
|
-
| '~' Expression { result = OpNode.new(val[0], val[1]) }
|
142
|
-
| '--' Expression { result = OpNode.new(val[0], val[1]) }
|
143
|
-
| '++' Expression { result = OpNode.new(val[0], val[1]) }
|
144
|
-
| DELETE Expression { result = OpNode.new(val[0], val[1]) }
|
145
|
-
| TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
|
146
|
-
| Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
|
147
|
-
| Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
|
148
|
-
|
149
|
-
| Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
150
|
-
| Expression '/' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
151
|
-
| Expression '%' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
152
|
-
|
153
|
-
| Expression '+' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
154
|
-
| Expression '-' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
155
|
-
|
156
|
-
| Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
157
|
-
| Expression '>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
158
|
-
| Expression '>>>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
159
|
-
|
160
|
-
| Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
161
|
-
| Expression '|' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
162
|
-
| Expression '^' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
163
|
-
|
164
|
-
| Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
165
|
-
| Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
166
|
-
| Expression '>' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
167
|
-
| Expression '>=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
168
|
-
|
169
|
-
| Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
170
|
-
| Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
171
|
-
| Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
172
|
-
| Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
173
|
-
|
174
|
-
| Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
175
|
-
| Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
176
|
-
| Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
177
|
-
| Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
178
|
-
| Expression '?' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
179
|
-
|
180
|
-
| Expression '-=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
181
|
-
| Expression '+=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
182
|
-
| Expression '/=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
183
|
-
| Expression '*=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
184
|
-
| Expression '%=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
185
|
-
| Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
186
|
-
| Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
187
|
-
| Expression '?=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
188
|
-
|
189
|
-
| Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
190
|
-
| Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) }
|
191
|
-
;
|
192
|
-
|
193
|
-
# The existence operator.
|
194
|
-
Existence:
|
195
|
-
Expression '?' { result = ExistenceNode.new(val[0]) }
|
196
|
-
;
|
197
|
-
|
198
|
-
# Function definition.
|
199
|
-
Code:
|
200
|
-
PARAM_START ParamList PARAM_END
|
201
|
-
FuncGlyph Block { result = CodeNode.new(val[1], val[4], val[3]) }
|
202
|
-
| FuncGlyph Block { result = CodeNode.new([], val[1], val[0]) }
|
203
|
-
;
|
204
|
-
|
205
|
-
# The symbols to signify functions, and bound functions.
|
206
|
-
FuncGlyph:
|
207
|
-
'->' { result = :func }
|
208
|
-
| '=>' { result = :boundfunc }
|
209
|
-
;
|
210
|
-
|
211
|
-
# The parameters to a function definition.
|
212
|
-
ParamList:
|
213
|
-
Param { result = val }
|
214
|
-
| ParamList "," Param { result = val[0] << val[2] }
|
215
|
-
;
|
216
|
-
|
217
|
-
# A Parameter (or ParamSplat) in a function definition.
|
218
|
-
Param:
|
219
|
-
PARAM
|
220
|
-
| PARAM "." "." "." { result = SplatNode.new(val[0]) }
|
221
|
-
;
|
222
|
-
|
223
|
-
# A regular splat.
|
224
|
-
Splat:
|
225
|
-
Expression "." "." "." { result = SplatNode.new(val[0]) }
|
226
|
-
;
|
227
|
-
|
228
|
-
# Expressions that can be treated as values.
|
229
|
-
Value:
|
230
|
-
IDENTIFIER { result = ValueNode.new(val[0]) }
|
231
|
-
| Literal { result = ValueNode.new(val[0]) }
|
232
|
-
| Array { result = ValueNode.new(val[0]) }
|
233
|
-
| Object { result = ValueNode.new(val[0]) }
|
234
|
-
| Parenthetical { result = ValueNode.new(val[0]) }
|
235
|
-
| Range { result = ValueNode.new(val[0]) }
|
236
|
-
| This { result = ValueNode.new(val[0]) }
|
237
|
-
| Value Accessor { result = val[0] << val[1] }
|
238
|
-
| Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
|
239
|
-
;
|
240
|
-
|
241
|
-
# Accessing into an object or array, through dot or index notation.
|
242
|
-
Accessor:
|
243
|
-
PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
|
244
|
-
| PROTOTYPE_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :prototype) }
|
245
|
-
| SOAK_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :soak) }
|
246
|
-
| Index { result = val[0] }
|
247
|
-
| Slice { result = SliceNode.new(val[0]) }
|
248
|
-
;
|
249
|
-
|
250
|
-
# Indexing into an object or array.
|
251
|
-
Index:
|
252
|
-
INDEX_START Expression INDEX_END { result = IndexNode.new(val[1]) }
|
253
|
-
;
|
254
|
-
|
255
|
-
# An object literal.
|
256
|
-
Object:
|
257
|
-
"{" AssignList "}" { result = ObjectNode.new(val[1]) }
|
258
|
-
;
|
259
|
-
|
260
|
-
# Assignment within an object literal (comma or newline separated).
|
261
|
-
AssignList:
|
262
|
-
/* nothing */ { result = [] }
|
263
|
-
| AssignObj { result = val }
|
264
|
-
| AssignList "," AssignObj { result = val[0] << val[2] }
|
265
|
-
| AssignList Terminator AssignObj { result = val[0] << val[2] }
|
266
|
-
| AssignList ","
|
267
|
-
Terminator AssignObj { result = val[0] << val[3] }
|
268
|
-
| INDENT AssignList OUTDENT { result = val[1] }
|
269
|
-
;
|
270
|
-
|
271
|
-
# All flavors of function call (instantiation, super, and regular).
|
272
|
-
Call:
|
273
|
-
Invocation { result = val[0] }
|
274
|
-
| NEW Invocation { result = val[1].new_instance }
|
275
|
-
| Super { result = val[0] }
|
276
|
-
;
|
277
|
-
|
278
|
-
# Extending an object's prototype.
|
279
|
-
Extends:
|
280
|
-
Value EXTENDS Value { result = ExtendsNode.new(val[0], val[2]) }
|
281
|
-
;
|
282
|
-
|
283
|
-
# A generic function invocation.
|
284
|
-
Invocation:
|
285
|
-
Value Arguments { result = CallNode.new(val[0], val[1]) }
|
286
|
-
| Invocation Arguments { result = CallNode.new(val[0], val[1]) }
|
287
|
-
;
|
288
|
-
|
289
|
-
# The list of arguments to a function invocation.
|
290
|
-
Arguments:
|
291
|
-
CALL_START ArgList CALL_END { result = val[1] }
|
292
|
-
;
|
293
|
-
|
294
|
-
# Calling super.
|
295
|
-
Super:
|
296
|
-
SUPER CALL_START ArgList CALL_END { result = CallNode.new(Value.new('super'), val[2]) }
|
297
|
-
;
|
298
|
-
|
299
|
-
# This references, either naked or to a property.
|
300
|
-
This:
|
301
|
-
'@' { result = ThisNode.new }
|
302
|
-
| '@' IDENTIFIER { result = ThisNode.new(val[1]) }
|
303
|
-
;
|
304
|
-
|
305
|
-
# The range literal.
|
306
|
-
Range:
|
307
|
-
"[" Expression
|
308
|
-
"." "." Expression "]" { result = RangeNode.new(val[1], val[4]) }
|
309
|
-
| "[" Expression
|
310
|
-
"." "." "." Expression "]" { result = RangeNode.new(val[1], val[5], true) }
|
311
|
-
;
|
312
|
-
|
313
|
-
# The slice literal.
|
314
|
-
Slice:
|
315
|
-
INDEX_START Expression "." "."
|
316
|
-
Expression INDEX_END { result = RangeNode.new(val[1], val[4]) }
|
317
|
-
| INDEX_START Expression "." "." "."
|
318
|
-
Expression INDEX_END { result = RangeNode.new(val[1], val[5], true) }
|
319
|
-
;
|
320
|
-
|
321
|
-
# The array literal.
|
322
|
-
Array:
|
323
|
-
"[" ArgList "]" { result = ArrayNode.new(val[1]) }
|
324
|
-
;
|
325
|
-
|
326
|
-
# A list of arguments to a method call, or as the contents of an array.
|
327
|
-
ArgList:
|
328
|
-
/* nothing */ { result = [] }
|
329
|
-
| Expression { result = val }
|
330
|
-
| INDENT Expression { result = [val[1]] }
|
331
|
-
| ArgList "," Expression { result = val[0] << val[2] }
|
332
|
-
| ArgList Terminator Expression { result = val[0] << val[2] }
|
333
|
-
| ArgList "," Terminator Expression { result = val[0] << val[3] }
|
334
|
-
| ArgList "," INDENT Expression { result = val[0] << val[3] }
|
335
|
-
| ArgList OUTDENT { result = val[0] }
|
336
|
-
;
|
337
|
-
|
338
|
-
# Just simple, comma-separated, required arguments (no fancy syntax).
|
339
|
-
SimpleArgs:
|
340
|
-
Expression { result = val[0] }
|
341
|
-
| SimpleArgs "," Expression { result = ([val[0]] << val[2]).flatten }
|
342
|
-
;
|
343
|
-
|
344
|
-
# Try/catch/finally exception handling blocks.
|
345
|
-
Try:
|
346
|
-
TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
|
347
|
-
| TRY Block FINALLY Block { result = TryNode.new(val[1], nil, nil, val[3]) }
|
348
|
-
| TRY Block Catch
|
349
|
-
FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) }
|
350
|
-
;
|
351
|
-
|
352
|
-
# A catch clause.
|
353
|
-
Catch:
|
354
|
-
CATCH IDENTIFIER Block { result = [val[1], val[2]] }
|
355
|
-
;
|
356
|
-
|
357
|
-
# Throw an exception.
|
358
|
-
Throw:
|
359
|
-
THROW Expression { result = ThrowNode.new(val[1]) }
|
360
|
-
;
|
361
|
-
|
362
|
-
# Parenthetical expressions.
|
363
|
-
Parenthetical:
|
364
|
-
"(" Expression ")" { result = ParentheticalNode.new(val[1], val[0].line) }
|
365
|
-
;
|
366
|
-
|
367
|
-
# The while loop. (there is no do..while).
|
368
|
-
While:
|
369
|
-
WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
|
370
|
-
| WHILE Expression { result = WhileNode.new(val[1], nil) }
|
371
|
-
| Expression WHILE Expression { result = WhileNode.new(val[2], Expressions.wrap(val[0])) }
|
372
|
-
;
|
373
|
-
|
374
|
-
# Array comprehensions, including guard and current index.
|
375
|
-
# Looks a little confusing, check nodes.rb for the arguments to ForNode.
|
376
|
-
For:
|
377
|
-
Expression FOR
|
378
|
-
ForVariables ForSource { result = ForNode.new(val[0], val[3], val[2][0], val[2][1]) }
|
379
|
-
| FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2], val[1][0], val[1][1]) }
|
380
|
-
;
|
381
|
-
|
382
|
-
# An array comprehension has variables for the current element and index.
|
383
|
-
ForVariables:
|
384
|
-
IDENTIFIER { result = val }
|
385
|
-
| IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] }
|
386
|
-
;
|
387
|
-
|
388
|
-
# The source of the array comprehension can optionally be filtered.
|
389
|
-
ForSource:
|
390
|
-
IN Expression { result = {:source => val[1]} }
|
391
|
-
| OF Expression { result = {:source => val[1], :object => true} }
|
392
|
-
| ForSource
|
393
|
-
WHEN Expression { result = val[0].merge(:filter => val[2]) }
|
394
|
-
| ForSource
|
395
|
-
BY Expression { result = val[0].merge(:step => val[2]) }
|
396
|
-
;
|
397
|
-
|
398
|
-
# Switch/When blocks.
|
399
|
-
Switch:
|
400
|
-
SWITCH Expression INDENT
|
401
|
-
Whens OUTDENT { result = val[3].rewrite_condition(val[1]) }
|
402
|
-
| SWITCH Expression INDENT
|
403
|
-
Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
|
404
|
-
;
|
405
|
-
|
406
|
-
# The inner list of whens.
|
407
|
-
Whens:
|
408
|
-
When { result = val[0] }
|
409
|
-
| Whens When { result = val[0] << val[1] }
|
410
|
-
;
|
411
|
-
|
412
|
-
# An individual when.
|
413
|
-
When:
|
414
|
-
LEADING_WHEN SimpleArgs Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
415
|
-
| LEADING_WHEN SimpleArgs Block
|
416
|
-
Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
|
417
|
-
| Comment Terminator When { result = val[2].add_comment(val[0]) }
|
418
|
-
;
|
419
|
-
|
420
|
-
# The most basic form of "if".
|
421
|
-
IfBlock:
|
422
|
-
IF Expression Block { result = IfNode.new(val[1], val[2]) }
|
423
|
-
;
|
424
|
-
|
425
|
-
# An elsif portion of an if-else block.
|
426
|
-
ElsIf:
|
427
|
-
ELSE IfBlock { result = val[1].force_statement }
|
428
|
-
;
|
429
|
-
|
430
|
-
# Multiple elsifs can be chained together.
|
431
|
-
ElsIfs:
|
432
|
-
ElsIf { result = val[0] }
|
433
|
-
| ElsIfs ElsIf { result = val[0].add_else(val[1]) }
|
434
|
-
;
|
435
|
-
|
436
|
-
# Terminating else bodies are strictly optional.
|
437
|
-
ElseBody
|
438
|
-
/* nothing */ { result = nil }
|
439
|
-
| ELSE Block { result = val[1] }
|
440
|
-
;
|
441
|
-
|
442
|
-
# All the alternatives for ending an if-else block.
|
443
|
-
IfEnd:
|
444
|
-
ElseBody { result = val[0] }
|
445
|
-
| ElsIfs ElseBody { result = val[0].add_else(val[1]) }
|
446
|
-
;
|
447
|
-
|
448
|
-
# The full complement of if blocks, including postfix one-liner ifs and unlesses.
|
449
|
-
If:
|
450
|
-
IfBlock IfEnd { result = val[0].add_else(val[1]) }
|
451
|
-
| Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) }
|
452
|
-
| Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) }
|
453
|
-
;
|
454
|
-
|
455
|
-
end
|
456
|
-
|
457
|
-
---- header
|
458
|
-
module CoffeeScript
|
459
|
-
|
460
|
-
---- inner
|
461
|
-
# Lex and parse a CoffeeScript.
|
462
|
-
def parse(code)
|
463
|
-
# Uncomment the following line to enable grammar debugging, in combination
|
464
|
-
# with the -g flag in the Rake build task.
|
465
|
-
# @yydebug = true
|
466
|
-
@tokens = Lexer.new.tokenize(code)
|
467
|
-
do_parse
|
468
|
-
end
|
469
|
-
|
470
|
-
# Retrieve the next token from the list.
|
471
|
-
def next_token
|
472
|
-
@tokens.shift
|
473
|
-
end
|
474
|
-
|
475
|
-
# Raise a custom error class that knows about line numbers.
|
476
|
-
def on_error(error_token_id, error_value, value_stack)
|
477
|
-
raise ParseError.new(token_to_str(error_token_id), error_value, value_stack)
|
478
|
-
end
|
479
|
-
|
480
|
-
---- footer
|
481
|
-
end
|