voodoo 1.0.2 → 1.1.1

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.
@@ -20,6 +20,10 @@ module Voodoo
20
20
  # end
21
21
  # end
22
22
  class Parser
23
+ NUMBER_STARTER = /\d|-/
24
+ STRING_STARTER = '"'
25
+ SYMBOL_STARTER = /[[:alpha:]]|\\/
26
+
23
27
  # Creates a parser using the specified object as input.
24
28
  # The input object must support a method +getc+, which must
25
29
  # return the next character of the input, or +nil+ to indicate
@@ -157,7 +161,9 @@ module Voodoo
157
161
  end
158
162
  end
159
163
 
160
- # Parses a body for a function or a conditional
164
+ # Parses statements up to "end X". _kind_ should indicate the type
165
+ # of body being parsed: :block, :conditional, :function, or :group.
166
+ # Returns an array of statements.
161
167
  def parse_body kind
162
168
  wrap_exceptions do
163
169
  body = []
@@ -168,6 +174,9 @@ module Voodoo
168
174
  else
169
175
  kind_text = kind.to_s
170
176
  end
177
+ # Groups are allowed to contain top-level statements.
178
+ # All other kinds aren't.
179
+ top_level = kind == :group
171
180
  done = false
172
181
  until done
173
182
  begin
@@ -186,14 +195,22 @@ module Voodoo
186
195
  done = true
187
196
  else
188
197
  # Should be a normal statement. Validate it, then add it to body
189
- if statement[0] == :function
190
- parse_error "Function definitions are only allowed at top-level"
191
- end
192
198
  begin
193
- Validator.validate_statement statement
199
+ if top_level
200
+ Validator.validate_top_level statement
201
+ else
202
+ Validator.validate_statement statement
203
+ end
194
204
  body << statement
195
205
  rescue Validator::ValidationError => e
196
- parse_error e.message
206
+ magic_word = statement[0]
207
+ if !top_level &&
208
+ Validator::TOP_LEVELS.member?(magic_word) &&
209
+ !Validator::STATEMENTS.member?(magic_word)
210
+ parse_error "#{magic_word} is only allowed at top-level"
211
+ else
212
+ parse_error e.message
213
+ end
197
214
  end
198
215
  end
199
216
  end
@@ -348,7 +365,7 @@ module Voodoo
348
365
  symbol.to_s[-1] == ?:
349
366
  end
350
367
 
351
- # Tests if a symbol is a conditional starter
368
+ # Tests if a symbol is a conditional starter.
352
369
  def is_conditional? symbol
353
370
  [:ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne].member? symbol
354
371
  end
@@ -368,7 +385,7 @@ module Voodoo
368
385
  @lookahead
369
386
  end
370
387
 
371
- # Parses a conditional statement
388
+ # Parses a conditional statement.
372
389
  def parse_conditional1 condition, operands
373
390
  # Parse first clause and condition for next clause
374
391
  consequent, next_condition = split_if_clause parse_body(:conditional)
@@ -386,7 +403,7 @@ module Voodoo
386
403
  [condition, operands, consequent, alternative]
387
404
  end
388
405
 
389
- # Raises a ParseError at the current input position
406
+ # Raises a ParseError at the current input position.
390
407
  def parse_error message, text = @text
391
408
  # Create the error object
392
409
  error = ParseError.new(message, @input_name, @start_line,
@@ -404,7 +421,7 @@ module Voodoo
404
421
  raise error
405
422
  end
406
423
 
407
- # Parses a top-level directive without validating it
424
+ # Parses a top-level incantation without validating it.
408
425
  def parse_top_level_nonvalidating
409
426
  # Skip whitespace, comments, and empty lines
410
427
  skip_to_next_top_level
@@ -458,16 +475,16 @@ module Voodoo
458
475
  [:function, words[1..-1]] + body
459
476
  elsif is_conditional?(words[0])
460
477
  parse_conditional1 words[0], words[1..-1]
461
- elsif words[0] == :block
462
- body = parse_body :block
463
- [:block] + body
478
+ elsif words[0] == :block || words[0] == :group
479
+ body = parse_body words[0]
480
+ [words[0]] + body
464
481
  else
465
482
  # Statement or data declaration; simply return it
466
483
  words
467
484
  end
468
485
  end
469
486
 
470
- # Skips whitespace, newlines, and comments before a top-level directive
487
+ # Skips whitespace, newlines, and comments before a top-level incantation.
471
488
  def skip_to_next_top_level
472
489
  while true
473
490
  case lookahead
@@ -527,14 +544,14 @@ module Voodoo
527
544
  case lookahead
528
545
  when :eof
529
546
  nil
530
- when /\d|-/
547
+ when NUMBER_STARTER
531
548
  # Digit; parse number
532
549
  parse_number
533
- when /\w|\\/
550
+ when SYMBOL_STARTER
534
551
  # Letter, underscore, or backslash; parse symbol
535
552
  # Note: \w matches digits, too, so keep this case after \d
536
553
  parse_symbol
537
- when "\""
554
+ when STRING_STARTER
538
555
  # Double quote; parse string
539
556
  parse_string
540
557
  when '@'
@@ -542,23 +559,30 @@ module Voodoo
542
559
  # '@' must be followed by a number or symbol.
543
560
  consume
544
561
  case lookahead
545
- when /\d|-/
562
+ when NUMBER_STARTER
546
563
  expr = parse_number
547
- when /\w|\\/
564
+ when SYMBOL_STARTER
548
565
  expr = parse_symbol
549
566
  else
550
567
  parse_error "Invalid character (#{lookahead}) " +
551
568
  "in at-expression; expecting number or symbol"
552
569
  end
553
570
  [:'@', expr]
571
+ when '%'
572
+ consume
573
+ # Must be followed by a symbol.
574
+ if lookahead !~ SYMBOL_STARTER
575
+ parse_error "'%' must be followed by a symbol"
576
+ end
577
+ [:'%', parse_symbol]
554
578
  else
555
579
  # No valid starter for a token, return nil
556
580
  nil
557
581
  end
558
582
  end
559
583
 
560
- # Evaluate block and check that the result is a valid top-level
561
- # directive.
584
+ # Evaluates _block_ and checks that the result is a valid top-level
585
+ # incantation.
562
586
  def validate_top_level &block
563
587
  with_position do
564
588
  result = yield
@@ -573,7 +597,7 @@ module Voodoo
573
597
  end
574
598
  end
575
599
 
576
- # Evaluate block, keeping track of @start_line, @start_column
600
+ # Evaluates block, keeping track of @start_line, @start_column
577
601
  # at the beginning of the block, and @text during the evaluation
578
602
  # of block.
579
603
  def with_position &block
@@ -1,25 +1,31 @@
1
1
  module Voodoo
2
2
  # Functionality for validating Voodoo code.
3
+ #
4
+ # See validate_top_level, validate_statement, and validate_expression.
3
5
  module Validator
4
6
 
5
- # Expressions that take two arguments
7
+ # Expressions that take two parameters.
6
8
  BINOPS = [:add, :and, :asr, :bsr, :div, :'get-byte', :'get-word',
7
9
  :mod, :mul, :or, :rol, :ror, :shl, :shr, :sub, :xor]
8
- # Expressions that take zero or more parameters
10
+ # Expressions that take a single parameter.
11
+ UNOPS = [:'auto-bytes', :'auto-words', :not]
12
+ # Expressions that take zero or more parameters.
9
13
  VARARG_EXPRS = [:call, :'tail-call']
10
- # Symbols that may occur as the first word of an expression
11
- EXPRS = BINOPS + VARARG_EXPRS + [:not]
14
+ # Symbols that may occur as the first word of an expression.
15
+ EXPRS = BINOPS + UNOPS + VARARG_EXPRS
12
16
 
13
- # Symbols that are a valid start of a statement
14
- STATEMENTS = [:block, :call, :goto,
15
- :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne,
16
- :label, :let, :return, :set,
17
- :'set-byte', :'set-word', :'tail-call']
17
+ # Symbols that are a valid start of a statement.
18
+ STATEMENTS = [:byte, :block, :call, :goto, :ifeq, :ifge,
19
+ :ifgt, :ifle, :iflt, :ifne, :label, :let, :return,
20
+ :'restore-frame', :'restore-locals', :'save-frame',
21
+ :'save-frame-and-locals', :'save-locals', :set,
22
+ :'set-byte', :'set-word', :string, :'tail-call', :word]
18
23
 
19
- # Symbols that are valid at top-level
20
- TOP_LEVELS = [:align, :byte, :export, :function, :import,
21
- :section, :string, :word] + STATEMENTS
24
+ # Symbols that are valid at top-level.
25
+ TOP_LEVELS = [:align, :export, :function, :group, :import, :section] +
26
+ STATEMENTS
22
27
 
28
+ # Maps indices 0, 1, 2 to English words.
23
29
  NTH = ['First', 'Second', 'Third']
24
30
 
25
31
  module_function
@@ -31,13 +37,18 @@ module Voodoo
31
37
  if int_or_symbol_or_at? code
32
38
  true
33
39
  elsif code.respond_to? :[]
34
- if BINOPS.member? code[0]
40
+ op = code[0]
41
+ if BINOPS.member? op
35
42
  # binop should have 2 parameters, both of them atomic values
36
43
  assert_n_params code, 2
37
44
  assert_params_are_values code
45
+ elsif UNOPS.member? op
46
+ # should have a single, atomic parameter
47
+ assert_n_params code, 1
48
+ assert_params_are_values code
38
49
  else
39
- # code[0] is not a binop
40
- case code[0]
50
+ # op is not a unary or binary operator
51
+ case op
41
52
  when :call, :'tail-call'
42
53
  # call should have at least 1 parameter
43
54
  # and all parameters should be atomic values
@@ -47,10 +58,6 @@ module Voodoo
47
58
  # Should have exactly 2 parameters, both of which should be values.
48
59
  assert_n_params code, 2
49
60
  assert_params_are_values code
50
- when :not
51
- # should have a single, atomic parameter
52
- assert_n_params code, 1
53
- assert_params_are_values code
54
61
  else
55
62
  raise ValidationError.new("#{code[0].inspect}" +
56
63
  " cannot start an expression",
@@ -74,6 +81,16 @@ module Voodoo
74
81
  code[1..-1].each {|stmt| validate_statement stmt}
75
82
  true
76
83
 
84
+ when :byte, :word
85
+ # Should have a single integer or symbol parameter
86
+ if code.length != 2 || !int_or_symbol?(code[1])
87
+ raise ValidationError.new("#{code[0]} requires a single" +
88
+ " parameter that is either an " +
89
+ " integer or a symbol", code)
90
+ else
91
+ true
92
+ end
93
+
77
94
  when :call, :'tail-call'
78
95
  validate_expression code
79
96
 
@@ -133,12 +150,12 @@ module Voodoo
133
150
  true
134
151
  end
135
152
 
136
- when :let, :set
153
+ when :let
137
154
  # should have at least 2 parameters
138
155
  if code.length < 3
139
156
  raise ValidationError.new("#{code[0]} requires a symbol" +
140
157
  " and an expression", code)
141
- elsif code[1].kind_of? ::Symbol
158
+ elsif symbol? code[1]
142
159
  # After the symbol, there should be an expression
143
160
  expr = code[2..-1]
144
161
  if expr.length == 1
@@ -151,6 +168,25 @@ module Voodoo
151
168
  " a symbol", code)
152
169
  end
153
170
 
171
+ when :set
172
+ # should have at least 2 parameters
173
+ if code.length < 3
174
+ raise ValidationError.new(
175
+ "#{code[0]} requires a symbol or at-expression" +
176
+ " followed by an expression", code)
177
+ elsif symbol_or_at? code[1]
178
+ # After the symbol/at-expr, there should be an expression
179
+ expr = code[2..-1]
180
+ if expr.length == 1
181
+ validate_expression expr[0]
182
+ else
183
+ validate_expression expr
184
+ end
185
+ else
186
+ raise ValidationError.new("First parameter to #{code[0]} should be" +
187
+ " a symbol or an at-expression", code)
188
+ end
189
+
154
190
  when :return
155
191
  # Should either have no parameters, or a single expression as
156
192
  # a parameter.
@@ -169,9 +205,33 @@ module Voodoo
169
205
  assert_n_params code, 3
170
206
  assert_params_are_values code
171
207
 
208
+ when :'restore-frame', :'save-frame'
209
+ # Should have exactly 1 parameter.
210
+ assert_n_params code, 1
211
+ assert_params_are_values code
212
+
213
+ when :'restore-locals', :'save-frame-and-locals', :'save-locals'
214
+ # Should have 1 or more parameters.
215
+ assert_at_least_n_params code, 1
216
+ assert_params_are_values code
217
+
218
+ when :string
219
+ # Should have a single string parameter
220
+ if code.length != 2 || !code[1].kind_of?(::String)
221
+ raise ValidationError.new("string requires a single string" +
222
+ " as a parameter", code)
223
+ else
224
+ true
225
+ end
226
+
172
227
  else
173
- raise ValidationError.new("Not a valid statement: #{code.inspect}",
174
- code)
228
+ if TOP_LEVELS.member?(code[0]) && !STATEMENTS.member?(code[0])
229
+ raise ValidationError.new("#{code[0]} is only valid at top-level", code)
230
+ else
231
+
232
+ raise ValidationError.new("Not a valid statement: #{code.inspect}",
233
+ code)
234
+ end
175
235
  end
176
236
 
177
237
  rescue ValidationError
@@ -190,9 +250,9 @@ module Voodoo
190
250
  end
191
251
  end
192
252
 
193
- # Validates a top-level directive.
194
- # Returns true if the directive is valid.
195
- # Raises ValidationError if the directive is not valid.
253
+ # Validates a top-level incantation.
254
+ # Returns true if the incantation is valid.
255
+ # Raises ValidationError if the incantation is not valid.
196
256
  def validate_top_level code
197
257
  begin
198
258
  case code[0]
@@ -207,16 +267,6 @@ module Voodoo
207
267
  code)
208
268
  end
209
269
 
210
- when :byte, :word
211
- # Should have a single integer or symbol parameter
212
- if code.length != 2 || !int_or_symbol?(code[1])
213
- raise ValidationError.new("#{code[0]} requires a single" +
214
- " parameter that is either an " +
215
- " integer or a symbol", code)
216
- else
217
- true
218
- end
219
-
220
270
  when :export, :import
221
271
  # Should have at least 1 parameter, and all parameters should
222
272
  # be symbols.
@@ -248,15 +298,12 @@ module Voodoo
248
298
  end
249
299
  end
250
300
 
251
- # Verify statements
252
- code[2..-1].each do |stmt|
253
- if stmt.respond_to?(:[]) && stmt[0] == :function
254
- raise ValidationError.new("Function definitions are only " +
255
- "valid at top-level", stmt)
256
- else
257
- validate_statement stmt
258
- end
259
- end
301
+ # Verify body.
302
+ code[2..-1].each { |stmt| validate_statement stmt }
303
+
304
+ when :group
305
+ # Verify body.
306
+ code[1..-1].each { |stmt| validate_top_level stmt }
260
307
 
261
308
  when :section
262
309
 
@@ -273,25 +320,16 @@ module Voodoo
273
320
  end
274
321
 
275
322
  else
276
- raise ValidationError.new("section directive should have only" +
323
+ raise ValidationError.new("section incantation should have only" +
277
324
  " a single parameter",
278
325
  code);
279
326
  end
280
327
 
281
- when :string
282
- # Should have a single string parameter
283
- if code.length != 2 || !code[1].kind_of?(::String)
284
- raise ValidationError.new("string requires a single string" +
285
- " as a parameter", code)
286
- else
287
- true
288
- end
289
-
290
328
  else
291
329
  if STATEMENTS.member? code[0]
292
330
  validate_statement code
293
331
  else
294
- raise ValidationError.new("Directive #{code[0]}" +
332
+ raise ValidationError.new("Incantation #{code[0]}" +
295
333
  " not valid at top-level",
296
334
  code)
297
335
  end
@@ -377,14 +415,34 @@ module Voodoo
377
415
  true
378
416
  end
379
417
 
418
+ def at_expr? x
419
+ x.respond_to?(:length) && x.length == 2 && x[0] == :'@' &&
420
+ int_or_symbol?(x[1])
421
+ end
422
+
423
+ def int? x
424
+ x.kind_of?(::Integer) || substitution?(x)
425
+ end
426
+
380
427
  def int_or_symbol? x
381
- x.kind_of?(::Symbol) || x.kind_of?(::Integer)
428
+ x.kind_of?(::Symbol) || int?(x)
382
429
  end
383
430
 
384
431
  def int_or_symbol_or_at? x
385
- int_or_symbol?(x) ||
386
- (x.respond_to?(:length) && x.length == 2 && x[0] == :'@' &&
387
- int_or_symbol?(x[1]))
432
+ int_or_symbol?(x) || at_expr?(x)
433
+ end
434
+
435
+ def substitution? x
436
+ x.respond_to?(:length) && x.length == 2 && x[0] == :'%' &&
437
+ symbol?(x[1])
438
+ end
439
+
440
+ def symbol? x
441
+ x.kind_of? ::Symbol
442
+ end
443
+
444
+ def symbol_or_at? x
445
+ symbol?(x) || at_expr?(x)
388
446
  end
389
447
 
390
448
  end