style-script 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ require "style_script/lexer"
3
+ require "style_script/parser"
4
+ require "style_script/nodes"
5
+ require "style_script/value"
6
+ require "style_script/scope"
7
+ require "style_script/rewriter"
8
+ require "style_script/parse_error"
9
+
10
+ # Namespace for all StyleScript internal classes.
11
+ module StyleScript
12
+
13
+ VERSION = '1.0.0' # Keep in sync with the gemspec.
14
+
15
+ # Compile a script (String or IO) to JavaScript.
16
+ def self.compile(script, options={})
17
+ script = script.read if script.respond_to?(:read)
18
+ Parser.new.parse(script).compile(options)
19
+ end
20
+
21
+ end
@@ -0,0 +1,235 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+ require 'open3'
4
+ begin
5
+ require File.expand_path(File.dirname(__FILE__) + '/../style-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 StyleScript
13
+
14
+ # The CommandLine handles all of the functionality of the `style`
15
+ # utility.
16
+ class CommandLine
17
+
18
+ BANNER = <<-EOS
19
+ style compiles StyleScript source files into JavaScript.
20
+
21
+ Usage:
22
+ style path/to/script.style
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 StyleScript install.
29
+ ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
30
+
31
+ # Commands to execute StyleScripts.
32
+ RUNNERS = {
33
+ :node => "node #{ROOT}/lib/style_script/runner.js",
34
+ :std => "std -p #{ROOT} -e 'require(\"style-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_style_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 StyleScript 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_style_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 StyleScript 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 StyleScript 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 StyleScripts.
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
+ StyleScript.compile(script, options)
149
+ rescue StyleScript::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 StyleScript 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 StyleScript 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/StyleScript.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 StyleScript REPL') do |i|
175
+ @options[:interactive] = true
176
+ end
177
+ opts.on('-r', '--run', 'compile and run a StyleScript') 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('--std', 'use STD instead of Node.js') do |n|
209
+ @options[:runner] = :std
210
+ end
211
+ opts.on_tail('--install-bundle', 'install the StyleScript TextMate bundle') do |i|
212
+ install_bundle
213
+ exit
214
+ end
215
+ opts.on_tail('--version', 'display StyleScript version') do
216
+ puts "StyleScript version #{StyleScript::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
@@ -0,0 +1,491 @@
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 UNTIL
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
+ left '?'
25
+ nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
26
+ left '*' '/' '%' '.'
27
+ left '+' '-'
28
+ left '<<' '>>' '>>>' '&' '|' '^'
29
+ left '<=' '<' '>' '>='
30
+ right '==' '!=' IS ISNT
31
+ left '&&' '||' AND OR
32
+ right '-=' '+=' '/=' '*=' '%=' '||=' '&&=' '?='
33
+ right DELETE INSTANCEOF TYPEOF
34
+ right INDENT
35
+ left OUTDENT
36
+ right WHEN LEADING_WHEN IN OF BY
37
+ right THROW FOR NEW SUPER
38
+ left EXTENDS
39
+ right ASSIGN RETURN
40
+ right '->' '=>' UNLESS IF ELSE WHILE UNTIL
41
+ preclow
42
+
43
+ rule
44
+
45
+ # All parsing will end in this rule, being the trunk of the AST.
46
+ Root:
47
+ /* nothing */ { result = Expressions.new }
48
+ | Terminator { result = Expressions.new }
49
+ | Expressions { result = val[0] }
50
+ | Block Terminator { result = val[0] }
51
+ ;
52
+
53
+ # Any list of expressions or method body, seperated by line breaks or semis.
54
+ Expressions:
55
+ Expression { result = Expressions.wrap(val) }
56
+ | Expressions Terminator Expression { result = val[0] << val[2] }
57
+ | Expressions Terminator { result = val[0] }
58
+ ;
59
+
60
+ # All types of expressions in our language. The basic unit of StyleScript
61
+ # is the expression.
62
+ Expression:
63
+ Value
64
+ | Call
65
+ | Code
66
+ | Operation
67
+ | Assign
68
+ | If
69
+ | Try
70
+ | Throw
71
+ | Return
72
+ | While
73
+ | Until
74
+ | For
75
+ | Switch
76
+ | Extends
77
+ | Splat
78
+ | Existence
79
+ | Comment
80
+ ;
81
+
82
+ # A block of expressions. Note that the Rewriter will convert some postfix
83
+ # forms into blocks for us, by altering the token stream.
84
+ Block:
85
+ INDENT Expressions OUTDENT { result = val[1] }
86
+ | INDENT OUTDENT { result = Expressions.new }
87
+ ;
88
+
89
+ # Tokens that can terminate an expression.
90
+ Terminator:
91
+ "\n"
92
+ | ";"
93
+ ;
94
+
95
+ # All hard-coded values. These can be printed straight to JavaScript.
96
+ Literal:
97
+ NUMBER { result = LiteralNode.new(val[0]) }
98
+ | STRING { result = LiteralNode.new(val[0]) }
99
+ | JS { result = LiteralNode.new(val[0]) }
100
+ | REGEX { result = LiteralNode.new(val[0]) }
101
+ | BREAK { result = LiteralNode.new(val[0]) }
102
+ | CONTINUE { result = LiteralNode.new(val[0]) }
103
+ | TRUE { result = LiteralNode.new(Value.new(true)) }
104
+ | FALSE { result = LiteralNode.new(Value.new(false)) }
105
+ | YES { result = LiteralNode.new(Value.new(true)) }
106
+ | NO { result = LiteralNode.new(Value.new(false)) }
107
+ | ON { result = LiteralNode.new(Value.new(true)) }
108
+ | OFF { result = LiteralNode.new(Value.new(false)) }
109
+ ;
110
+
111
+ # Assignment to a variable (or index).
112
+ Assign:
113
+ Value ASSIGN Expression { result = AssignNode.new(val[0], val[2]) }
114
+ ;
115
+
116
+ # Assignment within an object literal (can be quoted).
117
+ AssignObj:
118
+ IDENTIFIER ASSIGN Expression { result = AssignNode.new(ValueNode.new(val[0]), val[2], :object) }
119
+ | STRING ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
120
+ | NUMBER ASSIGN Expression { result = AssignNode.new(ValueNode.new(LiteralNode.new(val[0])), val[2], :object) }
121
+ | Comment { result = val[0] }
122
+ ;
123
+
124
+ # A return statement.
125
+ Return:
126
+ RETURN Expression { result = ReturnNode.new(val[1]) }
127
+ | RETURN { result = ReturnNode.new(ValueNode.new(Value.new('null'))) }
128
+ ;
129
+
130
+ # A comment.
131
+ Comment:
132
+ COMMENT { result = CommentNode.new(val[0]) }
133
+ ;
134
+
135
+ # Arithmetic and logical operators
136
+ # For Ruby's Operator precedence, see:
137
+ # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html
138
+ Operation:
139
+ '!' Expression { result = OpNode.new(val[0], val[1]) }
140
+ | '!!' Expression { result = OpNode.new(val[0], val[1]) }
141
+ | '-' Expression = UMINUS { result = OpNode.new(val[0], val[1]) }
142
+ | '+' Expression = UPLUS { result = OpNode.new(val[0], val[1]) }
143
+ | NOT Expression { result = OpNode.new(val[0], val[1]) }
144
+ | '~' Expression { result = OpNode.new(val[0], val[1]) }
145
+ | '--' Expression { result = OpNode.new(val[0], val[1]) }
146
+ | '++' Expression { result = OpNode.new(val[0], val[1]) }
147
+ | DELETE Expression { result = OpNode.new(val[0], val[1]) }
148
+ | TYPEOF Expression { result = OpNode.new(val[0], val[1]) }
149
+ | Expression '--' { result = OpNode.new(val[1], val[0], nil, true) }
150
+ | Expression '++' { result = OpNode.new(val[1], val[0], nil, true) }
151
+
152
+ | Expression '*' Expression { result = OpNode.new(val[1], val[0], val[2]) }
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
+
159
+ | Expression '<<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
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
+
163
+ | Expression '&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
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
+
167
+ | Expression '<=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
168
+ | Expression '<' Expression { result = OpNode.new(val[1], val[0], val[2]) }
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
+
172
+ | Expression '==' Expression { result = OpNode.new(val[1], val[0], val[2]) }
173
+ | Expression '!=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
174
+ | Expression IS Expression { result = OpNode.new(val[1], val[0], val[2]) }
175
+ | Expression ISNT Expression { result = OpNode.new(val[1], val[0], val[2]) }
176
+
177
+ | Expression '&&' Expression { result = OpNode.new(val[1], val[0], val[2]) }
178
+ | Expression '||' Expression { result = OpNode.new(val[1], val[0], val[2]) }
179
+ | Expression AND Expression { result = OpNode.new(val[1], val[0], val[2]) }
180
+ | Expression OR Expression { result = OpNode.new(val[1], val[0], val[2]) }
181
+ | Expression '?' Expression { result = OpNode.new(val[1], val[0], val[2]) }
182
+
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
+ | Expression '||=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
189
+ | Expression '&&=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
190
+ | Expression '?=' Expression { result = OpNode.new(val[1], val[0], val[2]) }
191
+
192
+ | Expression INSTANCEOF Expression { result = OpNode.new(val[1], val[0], val[2]) }
193
+ | Expression IN Expression { result = OpNode.new(val[1], val[0], val[2]) }
194
+ ;
195
+
196
+ # The existence operator.
197
+ Existence:
198
+ Expression '?' { result = ExistenceNode.new(val[0]) }
199
+ ;
200
+
201
+ # Function definition.
202
+ Code:
203
+ PARAM_START ParamList PARAM_END
204
+ FuncGlyph Block { result = CodeNode.new(val[1], val[4], val[3]) }
205
+ | FuncGlyph Block { result = CodeNode.new([], val[1], val[0]) }
206
+ ;
207
+
208
+ # The symbols to signify functions, and bound functions.
209
+ FuncGlyph:
210
+ '->' { result = :func }
211
+ | '=>' { result = :boundfunc }
212
+ ;
213
+
214
+ # The parameters to a function definition.
215
+ ParamList:
216
+ Param { result = val }
217
+ | ParamList "," Param { result = val[0] << val[2] }
218
+ ;
219
+
220
+ # A Parameter (or ParamSplat) in a function definition.
221
+ Param:
222
+ PARAM
223
+ | PARAM "." "." "." { result = SplatNode.new(val[0]) }
224
+ ;
225
+
226
+ # A regular splat.
227
+ Splat:
228
+ Expression "." "." "." { result = SplatNode.new(val[0]) }
229
+ ;
230
+
231
+ # Expressions that can be treated as values.
232
+ Value:
233
+ IDENTIFIER { result = ValueNode.new(val[0]) }
234
+ | Literal { result = ValueNode.new(val[0]) }
235
+ | Array { result = ValueNode.new(val[0]) }
236
+ | Object { result = ValueNode.new(val[0]) }
237
+ | Parenthetical { result = ValueNode.new(val[0]) }
238
+ | Range { result = ValueNode.new(val[0]) }
239
+ | This { result = ValueNode.new(val[0]) }
240
+ | Value Accessor { result = val[0] << val[1] }
241
+ | Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
242
+ ;
243
+
244
+ # Accessing into an object or array, through dot or index notation.
245
+ Accessor:
246
+ PROPERTY_ACCESS IDENTIFIER { result = AccessorNode.new(val[1]) }
247
+ | PROTOTYPE_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :prototype) }
248
+ | SOAK_ACCESS IDENTIFIER { result = AccessorNode.new(val[1], :soak) }
249
+ | Index { result = val[0] }
250
+ | Slice { result = SliceNode.new(val[0]) }
251
+ ;
252
+
253
+ # Indexing into an object or array.
254
+ Index:
255
+ INDEX_START Expression INDEX_END { result = IndexNode.new(val[1]) }
256
+ ;
257
+
258
+ # An object literal.
259
+ Object:
260
+ "{" AssignList "}" { result = ObjectNode.new(val[1]) }
261
+ ;
262
+
263
+ # Assignment within an object literal (comma or newline separated).
264
+ AssignList:
265
+ /* nothing */ { result = [] }
266
+ | AssignObj { result = val }
267
+ | AssignList "," AssignObj { result = val[0] << val[2] }
268
+ | AssignList Terminator AssignObj { result = val[0] << val[2] }
269
+ | AssignList ","
270
+ Terminator AssignObj { result = val[0] << val[3] }
271
+ | INDENT AssignList OUTDENT { result = val[1] }
272
+ ;
273
+
274
+ # All flavors of function call (instantiation, super, and regular).
275
+ Call:
276
+ Invocation { result = val[0] }
277
+ | NEW Invocation { result = val[1].new_instance }
278
+ | Super { result = val[0] }
279
+ ;
280
+
281
+ # Extending an object's prototype.
282
+ Extends:
283
+ Value EXTENDS Value { result = ExtendsNode.new(val[0], val[2]) }
284
+ ;
285
+
286
+ # A generic function invocation.
287
+ Invocation:
288
+ Value Arguments { result = CallNode.new(val[0], val[1]) }
289
+ | Invocation Arguments { result = CallNode.new(val[0], val[1]) }
290
+ ;
291
+
292
+ # The list of arguments to a function invocation.
293
+ Arguments:
294
+ CALL_START ArgList CALL_END { result = val[1] }
295
+ ;
296
+
297
+ # Calling super.
298
+ Super:
299
+ SUPER CALL_START ArgList CALL_END { result = CallNode.new(Value.new('super'), val[2]) }
300
+ ;
301
+
302
+ # This references, either naked or to a property.
303
+ This:
304
+ '@' { result = ThisNode.new }
305
+ | '@' IDENTIFIER { result = ThisNode.new(val[1]) }
306
+ ;
307
+
308
+ # The range literal.
309
+ Range:
310
+ "[" Expression
311
+ "." "." Expression "]" { result = RangeNode.new(val[1], val[4]) }
312
+ | "[" Expression
313
+ "." "." "." Expression "]" { result = RangeNode.new(val[1], val[5], true) }
314
+ ;
315
+
316
+ # The slice literal.
317
+ Slice:
318
+ INDEX_START Expression "." "."
319
+ Expression INDEX_END { result = RangeNode.new(val[1], val[4]) }
320
+ | INDEX_START Expression "." "." "."
321
+ Expression INDEX_END { result = RangeNode.new(val[1], val[5], true) }
322
+ ;
323
+
324
+ # The array literal.
325
+ Array:
326
+ "[" ArgList "]" { result = ArrayNode.new(val[1]) }
327
+ ;
328
+
329
+ # A list of arguments to a method call, or as the contents of an array.
330
+ ArgList:
331
+ /* nothing */ { result = [] }
332
+ | Expression { result = val }
333
+ | INDENT Expression { result = [val[1]] }
334
+ | ArgList "," Expression { result = val[0] << val[2] }
335
+ | ArgList Terminator Expression { result = val[0] << val[2] }
336
+ | ArgList "," Terminator Expression { result = val[0] << val[3] }
337
+ | ArgList "," INDENT Expression { result = val[0] << val[3] }
338
+ | ArgList OUTDENT { result = val[0] }
339
+ ;
340
+
341
+ # Just simple, comma-separated, required arguments (no fancy syntax).
342
+ SimpleArgs:
343
+ Expression { result = val[0] }
344
+ | SimpleArgs "," Expression { result = ([val[0]] << val[2]).flatten }
345
+ ;
346
+
347
+ # Try/catch/finally exception handling blocks.
348
+ Try:
349
+ TRY Block Catch { result = TryNode.new(val[1], val[2][0], val[2][1]) }
350
+ | TRY Block FINALLY Block { result = TryNode.new(val[1], nil, nil, val[3]) }
351
+ | TRY Block Catch
352
+ FINALLY Block { result = TryNode.new(val[1], val[2][0], val[2][1], val[4]) }
353
+ ;
354
+
355
+ # A catch clause.
356
+ Catch:
357
+ CATCH IDENTIFIER Block { result = [val[1], val[2]] }
358
+ ;
359
+
360
+ # Throw an exception.
361
+ Throw:
362
+ THROW Expression { result = ThrowNode.new(val[1]) }
363
+ ;
364
+
365
+ # Parenthetical expressions.
366
+ Parenthetical:
367
+ "(" Expression ")" { result = ParentheticalNode.new(val[1], val[0].line) }
368
+ ;
369
+
370
+ # The while loop. (there is no do..while).
371
+ While:
372
+ WHILE Expression Block { result = WhileNode.new(val[1], val[2]) }
373
+ | WHILE Expression { result = WhileNode.new(val[1], nil) }
374
+ | Expression WHILE Expression { result = WhileNode.new(val[2], Expressions.wrap(val[0])) }
375
+ ;
376
+
377
+ # The until loop.
378
+ Until:
379
+ UNTIL Expression Block { result = UntilNode.new(val[1], val[2]) }
380
+ | UNTIL Expression { result = UntilNode.new(val[1], nil) }
381
+ | Expression UNTIL Expression { result = UntilNode.new(val[2], Expressions.wrap(val[0])) }
382
+ ;
383
+
384
+ # Array comprehensions, including guard and current index.
385
+ # Looks a little confusing, check nodes.rb for the arguments to ForNode.
386
+ For:
387
+ Expression FOR
388
+ ForVariables ForSource { result = ForNode.new(val[0], val[3], val[2][0], val[2][1]) }
389
+ | FOR ForVariables ForSource Block { result = ForNode.new(val[3], val[2], val[1][0], val[1][1]) }
390
+ ;
391
+
392
+ # An array comprehension has variables for the current element and index.
393
+ ForVariables:
394
+ IDENTIFIER { result = val }
395
+ | IDENTIFIER "," IDENTIFIER { result = [val[0], val[2]] }
396
+ ;
397
+
398
+ # The source of the array comprehension can optionally be filtered.
399
+ ForSource:
400
+ IN Expression { result = {:source => val[1]} }
401
+ | OF Expression { result = {:source => val[1], :object => true} }
402
+ | ForSource
403
+ WHEN Expression { result = val[0].merge(:filter => val[2]) }
404
+ | ForSource
405
+ BY Expression { result = val[0].merge(:step => val[2]) }
406
+ ;
407
+
408
+ # Switch/When blocks.
409
+ Switch:
410
+ SWITCH Expression INDENT
411
+ Whens OUTDENT { result = val[3].rewrite_condition(val[1]) }
412
+ | SWITCH Expression INDENT
413
+ Whens ELSE Block OUTDENT { result = val[3].rewrite_condition(val[1]).add_else(val[5]) }
414
+ ;
415
+
416
+ # The inner list of whens.
417
+ Whens:
418
+ When { result = val[0] }
419
+ | Whens When { result = val[0] << val[1] }
420
+ ;
421
+
422
+ # An individual when.
423
+ When:
424
+ LEADING_WHEN SimpleArgs Block { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
425
+ | LEADING_WHEN SimpleArgs Block
426
+ Terminator { result = IfNode.new(val[1], val[2], nil, {:statement => true}) }
427
+ | Comment Terminator When { result = val[2].add_comment(val[0]) }
428
+ ;
429
+
430
+ # The most basic form of "if".
431
+ IfBlock:
432
+ IF Expression Block { result = IfNode.new(val[1], val[2]) }
433
+ ;
434
+
435
+ # An elsif portion of an if-else block.
436
+ ElsIf:
437
+ ELSE IfBlock { result = val[1].force_statement }
438
+ ;
439
+
440
+ # Multiple elsifs can be chained together.
441
+ ElsIfs:
442
+ ElsIf { result = val[0] }
443
+ | ElsIfs ElsIf { result = val[0].add_else(val[1]) }
444
+ ;
445
+
446
+ # Terminating else bodies are strictly optional.
447
+ ElseBody
448
+ /* nothing */ { result = nil }
449
+ | ELSE Block { result = val[1] }
450
+ ;
451
+
452
+ # All the alternatives for ending an if-else block.
453
+ IfEnd:
454
+ ElseBody { result = val[0] }
455
+ | ElsIfs ElseBody { result = val[0].add_else(val[1]) }
456
+ ;
457
+
458
+ # The full complement of if blocks, including postfix one-liner ifs and unlesses.
459
+ If:
460
+ IfBlock IfEnd { result = val[0].add_else(val[1]) }
461
+ | Expression IF Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true}) }
462
+ | Expression UNLESS Expression { result = IfNode.new(val[2], Expressions.wrap(val[0]), nil, {:statement => true, :invert => true}) }
463
+ ;
464
+
465
+ end
466
+
467
+ ---- header
468
+ module StyleScript
469
+
470
+ ---- inner
471
+ # Lex and parse a StyleScript.
472
+ def parse(code)
473
+ # Uncomment the following line to enable grammar debugging, in combination
474
+ # with the -g flag in the Rake build task.
475
+ # @yydebug = true
476
+ @tokens = Lexer.new.tokenize(code)
477
+ do_parse
478
+ end
479
+
480
+ # Retrieve the next token from the list.
481
+ def next_token
482
+ @tokens.shift
483
+ end
484
+
485
+ # Raise a custom error class that knows about line numbers.
486
+ def on_error(error_token_id, error_value, value_stack)
487
+ raise ParseError.new(token_to_str(error_token_id), error_value, value_stack)
488
+ end
489
+
490
+ ---- footer
491
+ end