Class: Violet::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/violet/parser.rb

Overview

Public: Parses a JavaScript source string.

Constant Summary collapse

FUTURE_RESERVED_WORDS =

Public: The future reserved words specified in section 7.6.1.2.

%w( class const enum export extends import super )
KEYWORDS =

Public: The reserved keywords specified in section 7.6.1.1, excluding ‘this` and the four unary operators.

%w( break case catch continue debugger default do else finally for function if in instanceof return switch throw try var while with )
LITERALS =

Public: The ‘true`, `false`, and `null` literals specified in section 7.8. The `this` keyword is grouped with the literals for convenience.

%w( this null true false )
UNARY_KEYWORDS =

Public: The four unary keyword operators.

%w( delete void typeof new )
KEYWORD_OR_RESERVED =

Public: A list of reserved words that may not be used as labels or function, argument, or variable names. Comprises the future reserved words, keywords, and literals.

FUTURE_RESERVED_WORDS.dup.push(*KEYWORDS, *LITERALS, *UNARY_KEYWORDS)
LHS_START =

Public: Matches valid left-hand side start operators.

/[-+~!({\[]/
UNARY_OPERATORS =

Public: Matches unary operators.

/[-+~!]/
ASSIGNMENTS =

Public: Matches assignment operators.

/^[\+\-\*\%\&\|\^\/]?=$|^\<\<\=$|^\>{2,3}\=$/
BINARY_OPERATORS =

Public: Matches binary operators.

/^[\+\-\*\%\|\^\&\?\/]$|^[\<\>]\=?$|^[\=\!]\=\=?$|^\<\<|\>\>\>?$|^\&\&$|^\|\|$/
STATEMENT_ERROR =

Internal: The error message produced when a statement cannot be parsed.

"Expected a statement."
PARSE_EXPRESSIONS_DEFAULTS =

Internal: A ‘Hash` containing the default options for the `expressions` method.

{
  # If the two initial tokens comprise an identifier followed by a colon
  # and this option is set to `true`, the parser will treat the lexed
  # token as a labeled statement. The identifier must not be a reserved
  # word.
  :label => false,
  # Parses a single assignment expression if set to `true`.
  :single => false,
  # Parses the expression as part of a `for` loop header. If set to `true`,
  # the `in` operator is disallowed.
  :header => false,
  # Parses the expression as an identifier following a `break` or
  # `continue` statement.
  :identifier => false
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source) ⇒ Parser

Public: Creates a new ‘Parser` with a source string.

source - The source ‘String`.



70
71
72
73
74
75
# File 'lib/violet/parser.rb', line 70

def initialize(source)
  @lexer = Lexer.new(source)
  @tokens = []
  @exception = nil
  @try = false
end

Instance Attribute Details

#lexerObject (readonly)

Public: Gets the ‘Lexer` instance associated with this parser.



65
66
67
# File 'lib/violet/parser.rb', line 65

def lexer
  @lexer
end

Class Method Details

.parse(source) ⇒ Object

Public: Parses a string of JavaScript source code.

source - The source ‘String`.

Returns the resulting ‘Token` stream as an `Array`.



26
27
28
# File 'lib/violet/parser.rb', line 26

def self.parse(source)
  new(source).parse
end

Instance Method Details

#continue(token) ⇒ Object

Internal: Recursively parses tokens until the source string is consumed.

token - The current token.

Returns the token immediately before the end-of-file mark.



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/violet/parser.rb', line 142

def continue(token)
  save token if token
  # Parse as many tokens as possible. `token` should be the end-of-file mark
  # following the call to `parse_source_elements`. If it is not, the source
  # is malformed and requires additional parsing.
  token = parse_source_elements get(:pattern)
  parsed = false
  loop do
    unless token[:name] == 12
      # Add a generic error to the token stream unless the current token
      # already contains an error.
      unless token[:name] == 14
        fail "Unexpected token.", token
      end
      # Save the malformed token as-is and continue parsing.
      save token
      token = get :pattern
      parsed = true
    end
    break unless token[:name] == 14
  end
  # Recursively parse the source until the end-of-file mark is reached.
  token = continue(token) if token[:name] != 12 && parsed
  # If an error occured at the end of the file, add it to the token stream.
  if @exception
    save @exception
    @exception = nil
  end
  token
end

#fail(message, token) ⇒ Object

Internal: Emits an error.

message - The warning message. token - The malformed token.

Returns nothing.



1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
# File 'lib/violet/parser.rb', line 1429

def fail(message, token)
  message = ParserError.new(message, token)
  # Ignore the parse error. This will affect how the remainder of the
  # source is parsed.
  error = Token.new(lexer, :error, token[:start]...token[:start])
  error[:error] = message
  save error
  lexer.insert_before(error, token)
  nil
end

#get(pattern = false) ⇒ Object

Internal: Lexes the next non-whitespace token.

pattern - If the token is ‘/` or `/=`, specifies whether it may be lexed

as part of a regular expression. If `false`, the token will be lexed as
a division operator instead (default: false).

Returns the lexed ‘Token`.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/violet/parser.rb', line 103

def get(pattern = false)
  # If the parser encountered an exception, the `@exception` instance
  # variable will be set to the token that triggered the error.
  if @exception
    token, @exception = @exception, nil
    return token
  end

  # Mark lines for automatic semicolon insertion.
  multiline = false

  # Consume tokens until a non-whitespace token is encountered.
  loop do
    token = lexer.lex pattern
    # The end-of-file token is not stored in the syntax tree.
    return token if token[:name] == 12
    multiline ||= true if token[:lines] && token[:lines] > 0

    # If the lexed token is a whitespace token, save it and continue
    # consuming tokens. Otherwise, break and return the token. Note
    # that the non-whitespace token is **not** automatically saved.
    if token[:isWhite]
      save token
    else
      break
    end
  end

  # If multiple lines were lexed, mark the token for automatic semicolon
  # insertion.
  token[:asi] = true if multiline
  token
end

#parseObject

Public: Parses the JavaScript source string associated with this parser instance.

Returns the token stream as an ‘Array`.



81
82
83
84
# File 'lib/violet/parser.rb', line 81

def parse
  continue false
  @tokens
end

#parse_assignment_expressions(token, options = {}) ⇒ Object

Internal: Parses one or more assignment expressions or a labeled statement.

token - The current token. options - The expression parsing options.

Returns the token immediately following the last expression.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
# File 'lib/violet/parser.rb', line 283

def parse_assignment_expressions(token, options = {})
  # @kitcambridge: This method should be broken up into several smaller
  # helper methods.
  options = PARSE_EXPRESSIONS_DEFAULTS.merge(options)
  initial = true

  loop do
    # Specifies whether the current expression is a reference. Once a non-
    # reference expression is encountered, subsequent expressions may not
    # contain assignments.
    reference = true
    unless initial
      save token
      token = get :pattern
      # A left-hand side start expression must follow the initial
      # expression.
      unless token[:name] <= 6 || LHS_START.match(token[:value])
        token = warn "Expected a left-hand side expression following `,`.", token
      end
    end

    # The following loop begins by parsing the left-hand side expression.
    # If an identifier is encountered, an assignment operator is parsed.
    # Otherwise, unary expressions are consumed until a binary operator
    # is encountered. If a `new` or `()` expression was parsed, assignments
    # are permitted. The right-hand side is then parsed.
    continue = true
    while continue
      unary = false
      # A unary operator cannot be used as an identifier following a
      # `break` or `continue` statement, as it is reserved.
      unless options[:identifier]
        loop do
          unary = token[:value] && UNARY_KEYWORDS.include?(token[:value])
          # Break if the token is not a unary keyword or operator.
          break unless unary || token[:name] == 11 && UNARY_OPERATORS.match(token[:value])
          token[:isUnaryOp] = true if unary
          save token
          token = get :pattern
          # A left-hand side expression must follow a unary operator.
          unless token[:name] <= 6 || LHS_START.match(token[:value])
            token = warn "Expected a left-hand side expression.", token
          end
        end
      end
      # If a unary operator was encountered, subsequent expressions may not
      # be parsed as labeled statements.
      options[:label] = false if unary
      # Specifies whether an assignment may follow a parsed expression.
      assignment_accepted = false

      case token[:value]
      # Parse a grouped expression.
      # ---------------------------
      when ?(
        save token
        token = get :pattern
        unless token[:name] <= 6 || LHS_START.match(token[:value])
          token = warn "A grouped expression should start with a left-hand-side expression.", token
        end
        # Recursively consume comma-separated expressions. The final token
        # should close the group.
        token = parse_assignment_expressions token
        unless token[:value] == ?)
          token = warn "Unterminated grouped expression.", token
        end
        save token
        # In this context, `/` and `/=` are interpreted as division
        # operators, rather than regular expressions.
        token = get
        # If the grouped expression contains a valid reference and excludes
        # the comma operator, it may precede an assignment. `(b) = 1` is
        # valid; `(a, b) = 1` is not.
        assignment_accepted = true
      # Parse an array literal.
      # -----------------------
      when ?[
        save token
        token = get :pattern
        # Leading commas are treated as elisions.
        while token[:value] == ?,
          save token
          token = get :pattern
        end
        # Parse the contents of the array literal. `token` should contain
        # the closing array punctuator at the end of the following loop.
        until token[:value] == ?]
          unless token[:name] <= 6 || LHS_START.match(token[:value]) || token[:name] == 14
            token = warn "An array literal should start with a left-hand side expression.", token
          end
          # Recursively parse each expression stored in the array.
          # Consecutive commas are treated as elisions.
          token = parse_assignment_expressions token, :single => true
          while token[:value] == ?,
            save token
            token = get :pattern
          end
        end
        unless token[:value] == ?]
          token = warn "Unterminated array literal.", token
        end
        save token
        # Treat `/` and `/=` as the division and division assignment
        # operators, respectively.
        token = get
        # The postfix increment and decrement operators cannot follow
        # values that are not references.
        while %w(++ --).include? token[:value]
          fail "The unary increment (`++`) and decrement (`--`) operators cannot be applied to an array literal.", token
          save token
          token = get :pattern
        end
      # Parse an object literal.
      # ------------------------
      when ?{
        save token
        token = get :pattern
        # Unexpected end-of-file mark encountered.
        if token[:name] == 12
          token = warn "A colon should precede every object property value.", token
        end
        # Parse object members until either the closing brace or a parse
        # error is encountered.
        until token[:value] == ?} || token[:name] == 14
          unless token[:isNumber] || token[:isString] || token[:name] == 2
            token = warn "Object literal property names can only be strings, numbers, or identifiers.", token
          end
          # The `accessor` token references the property name.
          accessor = token[:value]
          save token
          token = get :pattern
          # Parse attribute accessors-getters and setters.
          case accessor
          when "get"
            # If the current token is the `:` delimiter, the object member
            # is a standard property, not a getter.
            if token[:value] == ?:
              token = parse_object_literal_member token
            else
              # Parse the getter as a function.
              unless token[:isNumber] || token[:isString] || token[:name] == 2
                # @kitcambridge: This error token is not added to the token
                # stream in Peter's original implementation.
                token = warn "Getter names can only be strings, numbers, or identifiers.", token
              end
              save token
              token = get :pattern
              # Getters cannot accept arguments.
              unless token[:value] == ?(
                token = warn "The name of the getter should be followed by the opening parenthesis.", token
              end
              save token
              token = get :pattern
              unless token[:value] == ?)
                token = warn "A getter cannot accept arguments.", token
              end
              save token
              token = get :pattern
              token = parse_function_body(token, :pattern)
            end
          when "set"
            if token[:value] == ?:
              # The member is a standard property, not a setter.
              token = parse_object_literal_member token
            else
              unless token[:isNumber] || token[:isString] || token[:name] == 2
                # @kitcambridge: This error token *is* added in Peter's
                # implementation.
                token = warn "Setter names can only be strings, numbers, or identifiers.", token
              end
              save token
              token = get :pattern
              unless token[:value] == ?(
                token = warn "The name of the setter should be followed by the opening parenthesis.", token
              end
              save token
              token = get :pattern
              # Setters can accept only one argument.
              unless token[:name] == 2
                if token[:value] == ?)
                  token = warn "Setter functions must accept only one argument.", token
                else
                  token = warn "The setter argument name must be an identifier.", token
                end
              end
              save token
              token = get :pattern
              unless token[:value] == ?)
                if token[:value] == ?,
                  token = warn "Setters may not accept multiple arguments.", token
                else
                  token = warn "A closing parenthesis should follow the setter argument.", token
                end
              end
              save token
              token = get :pattern
              token = parse_function_body(token, :pattern)
            end
          else
            # Standard object member.
            token = parse_object_literal_member token
          end
          # A single trailing comma is permitted.
          if token[:value] == ?,
            save token
            token = get :pattern
            if token[:value] == ?,
              token = warn "Multiple trailing commas in object literals are not permitted.", token
            end
          elsif token[:value] != ?}
            token = warn "Expected `,` or `}`. Unexpected member delimiter.", token
          end
        end
        save token
        # `/` and `/=` are treated as division operators.
        token = get
        # Object literals are not references.
        while %w(++ --).include? token[:value]
          fail "The unary increment (`++`) and decrement (`--`) operators cannot be applied to an object literal.", token
          save token
          token = get :pattern
        end
      # Parse a function expression.
      # ----------------------------
      when "function"
        save token
        token = get :pattern
        # A label may precede a function expression or declaration.
        if options[:label] && token[:value] == ?:
          token = warn "Labels may not be keywords or future reserved words.", token
        end
        # Validate the function name, if one is found. Anonymous function
        # expressions will omit the name.
        if token[:name] == 2
          if KEYWORD_OR_RESERVED.include?(token[:value])
            token = warn "Function names may not be keywords or future reserved words.", token
          end
          save token
          token = get :pattern
        end
        # Parse the function arguments and body. The returned token should
        # be the token immediately following the closing brace of the body.
        token = parse_function_arguments token
        while %w(++ --).include? token[:value]
          fail "The unary increment (`++`) and decrement (`--`) operators cannot be applied to a function.", token
          save token
          token = get :pattern
        end
      # Parse labels and error cases.
      # -----------------------------
      else
        # Parse labels for right-hand side expressions.
        if token[:name] <= 6
          # Save the current token, which may be a label.
          possible_label = token

          # Validate the label identifier. This is not optional if the
          # expression should be parsed as an identifier following a
          # `break` or `continue` statement.
          if token[:name] == 2
            # @kitcambridge: Peter's original parser allows `LITERALS` to
            # be used as label names; all popular implementations disagree.
            # Removing this causes an unexpected token error to be emitted
            # for standalone literals. @qfox/ZeParser#9.
            if !LITERALS.include?(token[:value]) && KEYWORD_OR_RESERVED.include?(token[:value])
              # A statement label is an identifier, which may not be a
              # reserved word.
              if options[:identifier]
                fail "Statement labels passed to `break` and `continue` must be identifiers.", token
              elsif token[:value] == "else"
                fail "Dangling `else`.", token
              else
                # @kitcambridge: Peter is considering adding a lookahead to
                # check for a trailing colon and emit a malformed label
                # error. This may also solve issue #9 above.
                fail "Unexpected token.", token
              end
            end
            # Assignments are only accepted after member expressions:
            # either an identifier or square brackets.
            assignment_accepted = true
          elsif options[:identifier]
            # Malformed label.
            token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
          end
          save token
          # `/` and `/=` are treated as division operators.
          token = get
          # Check for a labeled statement. If the `label` option was
          # specified and the current token is the `:` delimiter,
          # the previous `possible_label` token must be an
          # identifier (validation occured above; this routine merely
          # catches malformed labels).
          if options[:label] && token[:value] == ?:
            unless possible_label[:name] == 2
              fail "Label names must be identifiers.", token
            end
            save token
            token = get :pattern
            possible_label[:isLabel] = true
            # If the label is valid, the subsequent token marks the
            # beginning of the labeled statement. `token` should
            # reference the first token following the statement.
            token = parse_statement token
            # If the statement could not be parsed, correct the error to
            # account for the label.
            if token[:error] && token[:error].message == STATEMENT_ERROR
              token[:error] = ParserError.new("Expected a statement after the label.", token)
            end
            token[:wasLabel] = true
            return token
          end
          options[:label] = false
        # Lexer Errors.
        # -------------
        elsif token[:name] == 14
          loop do
            if token[:tokenError]
              error = Token.new(lexer, :error, token[:start]...token[:start])
              error[:error] = ParserError.new("Lexer Error: #{token[:error].message}", token)
              save error
              lexer.insert_before(error, token)
            end
            save token
            token = get :pattern
            break unless token[:name] == 14
          end
        # Unexpected End-of-File.
        # -----------------------
        elsif token[:name] == 12
          return token
        # Fail.
        # -----
        # If the token value is the closing curly brace (`}`), it is
        # ignored. According to Peter, automatic semicolon insertion may be
        # applied; alternatively, this may be part of an object literal.
        # The other parsing routines will attempt to handle it.
        elsif token[:value] != ?}
          fail "Unexpected token.", token
          save token
          token = get :pattern
        end
      end
      # Property Access and Call Expressions.
      # -------------------------------------
      while token[:value] == ?. || token[:value] == ?[ || token[:value] == ?(
        # None of the characters may occur in an identifier.
        if options[:identifier]
          token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
        end
        case token[:value]
        # Dot Member Operator.
        # --------------------
        when ?.
          save token
          token = get :pattern
          # The referenced property name must be an identifier. ES 5 allows
          # the use of reserved words as property names, so no additional
          # checks are performed.
          unless token[:name] == 2
            fail "Property names following the dot member operator must be identifiers.", token
          end
          save token
          # `/` and `/=` are treated as division operators.
          token = get
          # The result of a member access operation may be assigned to.
          assignment_accepted = true
        # Square Bracket Member Operator.
        # -------------------------------
        when ?[
          save token
          token = get :pattern
          # The brackets must contain at least one left-hand side start
          # expression.
          unless token[:name] <= 6 || LHS_START.match(token[:value])
            token = warn "Square brackets must contain an expression.", token
          end
          # Recursively parse assignment expressions. The final token should
          # be the terminating bracket.
          token = parse_assignment_expressions token
          unless token[:value] == ?]
            token = warn "Unterminated square bracket member accessor.", token
          end
          save token
          # `/` and `/=` are treated as division operators.
          token = get
          assignment_accepted = true
        # Call Expression.
        # ----------------
        when ?(
          save token
          token = get :pattern
          # The current token marks either the closing parenthesis of an
          # empty argument list, or the first argument, which must be a
          # left-hand side start expression.
          if token[:name] <= 6 || LHS_START.match(token[:value])
            # Recursively parse the arguments, which may be assignment
            # expressions. The comma is used as a separator, not as an
            # operator.
            token = parse_assignment_expressions token
          end
          unless token[:value] == ?)
            token = warn "Unterminated parentheses following the call expression.", token
          end
          save token
          # `/` and `/=` are treated as division operators.
          token = get
          # The result of a function call may not be assigned to.
          assignment_accepted = false
        end
      end
      # Postfix unary increment and decrement operators.
      if (token[:value] == "++" || token[:value] == "--") && !token[:asi]
        if options[:identifier]
          token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
        end
        save token
        # `/` and `/=` are treated as division operators.
        token = get
      end

      # Parse any subsequent operators.
      # -------------------------------
      loop do
        conditional = false
        # Parse the `instanceof` operator, assignment operators (+=, /=,
        # >>=, etc.), and binary expression operators. The `in` operator
        # is parsed only if the `header` option is set-this specifies
        # that the expression should be treated as part of a `for` loop
        # header.
        if !options[:header] && token[:value] == "in" || token[:value] == "instanceof" || (token[:name] == 11 && (token[:isAssignment] = !!ASSIGNMENTS.match(token[:value])) || BINARY_OPERATORS.match(token[:value]))
          if token[:isAssignment]
            # Emit an error if an expression may not be assigned to or is
            # not a valid reference.
            case false
            when assignment_accepted
              fail "The left-hand side of the assignment expression is not a reference.", token
            when reference
              fail "An assignment expression may not follow a non-assignment operator.", token
            end
          end
          # Labels may not share operator names.
          if options[:identifier]
            token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
          end
          # If a non-assignment operator was parsed, the remainder of the
          # expression may not contain assignments.
          reference = false unless token[:isAssignment]

          # Conditional (Ternary) Operator.
          # -------------------------------
          conditional = token[:value] == ??
          save token
          token = get :pattern
          if conditional
            # The `true` condition should begin with a left-hand side
            # expression.
            unless token[:name] <= 6 || LHS_START.match(token[:value])
              fail "Invalid conditional expression.", token
            end
            # Recursively parse the `true` assignment expression.
            token = parse_assignment_expressions token, :single => true, :header => options[:header]
            unless token[:value] == ?:
              if token[:value] == ?,
                token = warn "The comma operator may only be used as part of a nested expression.", token
              else
                token = warn "Missing second conditional expression.", token
              end
            end
            save token
            token = get :pattern
            # Parse the `false` assignment expression.
            token = parse_assignment_expressions token, :single => true, :header => options[:header]
          end
        else
          # The operator is invalid.
          continue = false
        end
        # If a ternary expression was parsed, verify if the next token is a
        # binary operator.
        break unless conditional
      end
      # Parse the next component. `token` marks the end of the right-hand
      # side for the current left-hand side expression, and the left-hand
      # side for the next.
      if continue && !(token[:name] <= 6 || LHS_START.match(token[:value]))
        fail "Expected a right-hand side expression following the assignment operator.", token
      end
    end

    # Cleanup
    # -------
    # Subsequent tokens may not be parsed as labeled statements, and do not
    # require an initial left-hand side expression.
    options[:label] = initial = false
    # Continue parsing expressions unless the `single` option was explicitly
    # specified or the current token is the comma operator.
    break unless !options[:single] && token[:value] == ?,
  end
  token
end

#parse_block(token) ⇒ Object

Internal: Parses a block.

token - The current token.

Returns the token immediately adjacent to the block.



886
887
888
889
890
891
892
893
894
895
896
897
# File 'lib/violet/parser.rb', line 886

def parse_block(token)
  save token
  token = get :pattern
  unless token[:value] == ?}
    token = parse_statements token
  end
  unless token[:value] == ?}
    token = warn "Unterminated block. Expected `}`.", token
  end
  save token
  get :pattern
end

#parse_break_statement(token) ⇒ Object

Internal.



1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
# File 'lib/violet/parser.rb', line 1140

def parse_break_statement(token)
  save token
  token = get :pattern
  unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
    token = parse_assignment_expressions token, :single => true, :identifier => true
    unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
      token = warn "The argument to a `break` statement must be an identifier that references an existing label.", token
    end
  end
  parse_semicolon token
end

#parse_catch_block(token) ⇒ Object

Internal.



1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
# File 'lib/violet/parser.rb', line 1307

def parse_catch_block(token)
  @try = true
  save token
  token = get :pattern
  unless token[:value] == ?(
    token = warn "The `catch` block header must accept a single argument.", token
  end
  save token
  token = get :pattern
  if token[:name] != 2
    token = warn "The `catch` block argument must be an identifier.", token
  elsif KEYWORD_OR_RESERVED.include?(token[:value])
    fail "The `catch` block argument may not be a keyword or future reserved word.", token
  end
  save token
  token = get :pattern
  unless token[:value] == ?)
    token = warn "Expected `)`.", token
  end
  save token
  token = get :pattern
  unless token[:value] == ?{
    token = warn "A `catch` block must begin with `{`.", token
  end
  save token
  token = get :pattern
  # Statements inside the `catch` block are optional.
  token = parse_statements(token) unless token[:value] == ?}
  unless token[:value] == ?}
    token = warn "A `catch` block must end with `}`.", token
  end
  save token
  get :pattern
end

#parse_continue_statement(token) ⇒ Object

Internal.



1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
# File 'lib/violet/parser.rb', line 1126

def parse_continue_statement(token)
  save token
  token = get :pattern
  unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
    # Only identifiers may follow a `continue` statement.
    token = parse_assignment_expressions token, :single => true, :identifier => true
    unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
      token = warn "The argument to a `continue` statement must be an identifier that references an existing label.", token
    end
  end
  parse_semicolon token
end

#parse_debugger_statement(token) ⇒ Object

Internal: Parses a ‘debugger` statement.

token - The current token.

Returns the token immediately following the statement.



1370
1371
1372
1373
1374
# File 'lib/violet/parser.rb', line 1370

def parse_debugger_statement(token)
  save token
  token = get :pattern
  parse_semicolon token
end

#parse_do_statement(token) ⇒ Object

Internal.



1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
# File 'lib/violet/parser.rb', line 1023

def parse_do_statement(token)
  save token
  token = get :pattern
  token = parse_statement token
  unless token[:value] == "while"
    token = warn "The `do...while` loop requires the `while` statement after the loop body.", token
  end
  save token
  token = get :pattern
  unless token[:value] == ?(
    token = warn "Expected `(`.", token
  end
  save token
  token = get :pattern
  unless token[:name] <= 6 || LHS_START.match(token[:value])
    token = warn "A statement header must contain at least one expression.", token
  end
  token = parse_assignment_expressions token
  unless token[:value] == ?)
    token = warn "Expected `)`.", token
  end
  save token
  token = get :pattern
  # @kitcambridge: According to Peter, the trailing semicolon is not
  # optional, though implementations apply ASI nonetheless.
  parse_semicolon token
end

#parse_else_statement(token) ⇒ Object

Internal.



1016
1017
1018
1019
1020
# File 'lib/violet/parser.rb', line 1016

def parse_else_statement(token)
  save token
  token = get :pattern
  parse_statement token
end

#parse_expression_or_label(token) ⇒ Object

Internal: Parses an expression or labeled statement.

token - The current token.

Returns the token immediately following the result.



904
905
906
907
908
909
910
911
# File 'lib/violet/parser.rb', line 904

def parse_expression_or_label(token)
  token = parse_assignment_expressions token, :label => true
  # Only parse a semicolon if a label was not parsed.
  unless token[:wasLabel]
    token = parse_semicolon token
  end
  token
end

#parse_finally_block(token) ⇒ Object

Internal: Parses a ‘finally` clause of a `try` block.

token - The current token.

Returns the token immediately following the block.



1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
# File 'lib/violet/parser.rb', line 1347

def parse_finally_block(token)
  @try = true
  save token
  token = get :pattern
  unless token[:value] == ?{
    token = warn "A `finally` block must begin with `{`.", token
  end
  save token
  token = get :pattern
  # A `finally` block can be empty.
  token = parse_statements(token) unless token[:value] == ?}
  unless token[:value] == ?}
    token = warn "A `finally` block must end with `}`.", token
  end
  save token
  get :pattern
end

#parse_for_statement(token) ⇒ Object

Public: Parses a ‘for` or `for…in` loop.



1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
# File 'lib/violet/parser.rb', line 1075

def parse_for_statement(token)
  save token
  token = get :pattern
  unless token[:value] == ?(
    token = warn "Expected `(`.", token
  end
  save token
  token = get :pattern
  # Parse either the initialization section of a `for` loop or the left-hand
  # side `in` operand of a `for...in` loop. The parser subsequently
  # determines which loop type is used.
  if token[:value] == "var"
    token = parse_variable_declarations(token, :header)
  elsif token[:value] != ?;
    unless token[:name] <= 6 || LHS_START.match(token[:value])
      fail "A `for` statement header must contain at least one expression.", token
    end
    # Multiple expressions are permitted, but the `in` operator is not.
    token = parse_assignment_expressions token, :header => true
  end
  if token[:value] == "in"
    # @kitcambridge: Violet does not verify if the `for...in` loop header
    # introduces only one variable. This requires the full AST.
    save token
    token = get :pattern
    token = parse_assignment_expressions token
  else
    unless token[:value] == ?;
      token = warn "A `for` loop header must contain either the `in` operator or two consecutive semicolons (`;;`).", token
    end
    # Parse two optional expressions separated by a semicolon. The `in`
    # operator is permitted.
    save token
    token = get :pattern
    token = parse_assignment_expressions(token) if token[:name] <= 6 || LHS_START.match(token[:value])
    unless token[:value] == ?;
      token = warn "Expected `;`.", token
    end
    save token
    token = get :pattern
    token = parse_assignment_expressions(token) if token[:name] <= 6 || LHS_START.match(token[:value])
  end
  unless token[:value] == ?)
    token = warn "Expected `)`.", token
  end
  save token
  token = get :pattern
  parse_statement(token)
end

#parse_function_arguments(token, pattern = false) ⇒ Object

Internal: Parses an arguments list.

token - The current token. pattern - If the ‘/` or `/=` token immediately follows the function body,

this boolean argument specifies whether it may be lexed as part of a
regular expression (default: false).

Returns the token immediately following the arguments list.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/violet/parser.rb', line 201

def parse_function_arguments(token, pattern = false)
  unless token[:value] == ?(
    token = warn "Expected to see an opening parenthesis after the function name.", token
  end
  save token
  token = get :pattern

  if token[:name] == 2
    if KEYWORD_OR_RESERVED.include? token[:value]
      fail "Function argument names may not be keywords or future reserved words.", token
    end
    save token
    token = get :pattern

    # Parse the argument list.
    while token[:value] == ?,
      save token
      token = get :pattern
      if token[:name] != 2
        fail "Function argument names must be identifiers.", token
      elsif KEYWORD_OR_RESERVED.include? token[:value]
        fail "Function argument names may not be keywords or future reserved words.", token
      end
      save token
      token = get :pattern
    end
  end
  unless token[:value] == ?)
    token = warn "Expected to see a closing parenthesis after the list of arguments.", token
  end
  save token
  token = get :pattern
  parse_function_body(token, pattern)
end

#parse_function_body(token, pattern = false) ⇒ Object

Internal: Parses a function body.

token - The current token. pattern - If the ‘/` or `/=` token immediately follows the function body,

this boolean argument specifies whether it may be lexed as part of a
regular expression (default: false).

Returns the token immediately following the function body.



244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/violet/parser.rb', line 244

def parse_function_body(token, pattern = false)
  unless token[:value] == ?{
    token = warn "Expected `{` after the function header.", token
  end
  save token
  token = get :pattern
  token = parse_source_elements token
  unless token[:value] == ?}
    token = warn "Expected `}` after the function body.", token
  end
  save token
  get pattern
end

#parse_function_declaration(token) ⇒ Object

Internal: Parses a function declaration.

token - The current token.

Returns the token immediately following the function body.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/violet/parser.rb', line 178

def parse_function_declaration(token)
  save token
  token = get :pattern
  if token[:name] != 2
    token = warn "Function declarations cannot be anonymous.", token
  elsif KEYWORD_OR_RESERVED.include? token[:value]
    token = warn "Function names may not be keywords or future reserved words.", token
  end
  # The subsequent token marks the beginning of the argument list.
  save token
  token = get :pattern
  # Parse the function arguments and body.
  parse_function_arguments(token, :pattern)
end

#parse_if_statement(token) ⇒ Object

Internal.



993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/violet/parser.rb', line 993

def parse_if_statement(token)
  save token
  token = get :pattern
  unless token[:value] == ?(
    token = warn "Expected `(`.", token
  end
  save token
  token = get :pattern
  unless token[:name] <= 6 || LHS_START.match(token[:value])
    token = warn "A statement header must contain at least one expression.", token
  end
  token = parse_assignment_expressions token
  unless token[:value] == ?)
    token = warn "Expected `)`.", token
  end
  save token
  token = get :pattern
  token = parse_statement token
  token = parse_else_statement(token) if token[:value] == "else"
  token
end

#parse_object_literal_member(token) ⇒ Object

Internal: Parses an object literal member.

token - The current token.

Returns the token immediately following the member.



790
791
792
793
794
795
796
797
798
799
800
801
802
# File 'lib/violet/parser.rb', line 790

def parse_object_literal_member(token)
  unless token[:value] == ?:
    token = warn "A colon should follow every property name.", token
  end
  save token
  token = get :pattern
  previous_token = token
  token = parse_assignment_expressions token, :single => true
  if previous_token == token
    token = warn "Missing object literal property value.", token
  end
  token
end

#parse_return_statement(token) ⇒ Object

Internal.



1153
1154
1155
1156
1157
1158
1159
1160
# File 'lib/violet/parser.rb', line 1153

def parse_return_statement(token)
  save token
  token = get :pattern
  unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
    token = parse_assignment_expressions token
  end
  parse_semicolon token
end

#parse_semicolon(token) ⇒ Object

Internal: Parses a semicolon.

token - The current token.

Returns the token immediately adjacent to the semicolon.



809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
# File 'lib/violet/parser.rb', line 809

def parse_semicolon(token)
  if token[:value] == ?;
    save token
    token = get :pattern
  else
    # Automatic semicolon insertion cannot be applied if the end-of-file
    # mark has not been reached and the current token either is a semicolon,
    # or does not follow at least one line terminator and is not a closing
    # curly brace, and is not preceded by a line terminator or is not a
    # unary increment or decrement operator.
    if token[:name] != 12 && (token[:semicolon] || !(token[:asi] || token[:value] == ?})) && !(token[:asi] && %w(++ --).include?(token[:value]))
      fail "Expected `;`. Automatic semicolon insertion cannot be applied.", token
    else
      # @kitcambridge: This is an inversion of Peter's code, which checks
      # for the affirmative condition. Peter also notes that this method
      # does not check for restricted productions (`break`, `continue`, and
      # `return`), if the subsequent line is a regular expression, or the
      # semicolon is part of a `for` loop header. He notes that the parser
      # is designed to automatically catch the latter two exceptions.
      #
      # The current token is the token that occurs *after* the insertion,
      # not before; hence, its position is the *beginning* of the current
      # token.
      semicolon = Token.new(lexer, :asi, token[:start]...token[:start])
      save semicolon
      lexer.insert_before(semicolon, token)
    end
  end
  token[:semicolon] = true
  token
end

#parse_source_elements(token) ⇒ Object

Internal: Continuously parses source elements.



943
944
945
# File 'lib/violet/parser.rb', line 943

def parse_source_elements(token)
  parse_tokens(token, :functions)
end

#parse_statement(token, optional = false) ⇒ Object

Internal: Parses a statement.

token - The current token.

Returns the token immediately adjacent to the statement.



846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
# File 'lib/violet/parser.rb', line 846

def parse_statement(token, optional = false)
  # @kitcambridge: Peter's parser returns the value of the `token` parameter
  # if it is falsy and the `optional` option is set. Because Ruby enforces
  # method arity and none of the operations may produce a `nil` token, this
  # seems redundant.
  if token[:name] == 2
    # If the token is an identifier, determine if it can be parsed as a
    # statement.
    if %w( var if do while for continue break return throw switch try debugger with ).include? token[:value]
      token = send "parse_#{token[:value]}_statement", token
    elsif token[:value] == "function"
      fail "Function statements are an extension of ECMAScript semantics. Their use is discouraged.", token
      token = parse_function_declaration token
    else
      token = parse_expression_or_label token
    end
  elsif token[:value] == ?{
    # Blocks are parsed before expressions.
    token = parse_block token
  elsif token[:isString] || token[:isNumber] || token[:name] == 1 || LHS_START.match(token[:value])
    # Parse expressions (strings, numbers, RegExps, and left-hand side start
    # values). Each expression should be followed by a semicolon, whether
    # explicit or implicit through ASI.
    token = parse_assignment_expressions token
    token = parse_semicolon token
  elsif token[:value] == ?;
    # The empty statement. Parse the current token as a semicolon.
    token[:emptyStatement] = true
    token = parse_semicolon token
  elsif !optional
    token = warn STATEMENT_ERROR, token
  end
  token
end

#parse_statements(token) ⇒ Object

Internal: Continuously parses statements.



938
939
940
# File 'lib/violet/parser.rb', line 938

def parse_statements(token)
  parse_tokens(token)
end

#parse_switch_block(token) ⇒ Object

Internal.



1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
# File 'lib/violet/parser.rb', line 1242

def parse_switch_block(token)
  previous_token = nil
  # `switch` clauses may be empty.
  until %w( } case default).include?(token[:value]) || [14, 12].include?(token[:name]) || previous_token == token
    previous_token = token
    token = parse_statement token, :optional
  end
  if previous_token == token
    warn "Unexpected token in `switch` block.", token
  end
  token
end

#parse_switch_case(token) ⇒ Object

Internal.



1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
# File 'lib/violet/parser.rb', line 1256

def parse_switch_case(token)
  save token
  token = get :pattern
  if token[:value] == ?:
    fail "A `case` clause expects an expression as its argument.", token
  else
    token = parse_assignment_expressions token
  end
  token
end

#parse_switch_clause(token) ⇒ Object

Internal.



1222
1223
1224
1225
# File 'lib/violet/parser.rb', line 1222

def parse_switch_clause(token)
  token = parse_switch_header token
  parse_switch_block token
end

#parse_switch_default(token) ⇒ Object

Internal.



1268
1269
1270
1271
# File 'lib/violet/parser.rb', line 1268

def parse_switch_default(token)
  save token
  get :pattern
end

#parse_switch_header(token) ⇒ Object

Internal.



1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
# File 'lib/violet/parser.rb', line 1228

def parse_switch_header(token)
  if token[:value] == "case"
    token = parse_switch_case token
  else
    token = parse_switch_default token
  end
  unless token[:value] == ?:
    token = warn "`switch` clauses must be followed by a colon.", token
  end
  save token
  get :pattern
end

#parse_switch_statement(token) ⇒ Object

Internal.



1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
# File 'lib/violet/parser.rb', line 1179

def parse_switch_statement(token)
  save token
  token = get :pattern
  unless token[:value] == ?(
    token = warn "Expected `(`.", token
  end
  save token
  token = get :pattern
  unless token[:name] <= 6 || LHS_START.match(token[:value])
    fail "A `switch` statement header must contain at least one expression.", token
  end
  token = parse_assignment_expressions token
  unless token[:value] == ?)
    token = warn "Expected `)`.", token
  end
  save token
  token = get :pattern
  unless token[:value] == ?{
    token = warn "A `switch` block must begin with `{`.", token
  end
  save token
  token = get :pattern
  # A `default` case may occur only once in a `switch` block, but may
  # occur anywhere.
  has_cases, default = false, false
  while token[:value] == "case" || !default && (default = token[:value] == "default")
    has_cases = true
    token = parse_switch_clause token
  end
  unless has_cases || token[:value] == ?}
    token = warn "A `switch` block must begin with a `case` or `default` clause.", token
  end
  if default && token[:value] == "default"
    fail "`switch` blocks may not contain multiple `default` clauses.", token
  end
  unless token[:value] == ?} || token[:name] == 14
    token = warn "A `switch` block must end with `}`.", token
  end
  save token
  get :pattern
end

#parse_throw_statement(token) ⇒ Object

Internal.



1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
# File 'lib/violet/parser.rb', line 1163

def parse_throw_statement(token)
  save token
  token = get :pattern
  # `throw` may not precede a line terminator, as it is a restricted
  # production for automatic semicolon insertion.
  if token[:asi]
    token = warn "A line terminator may not occur between a `throw` statement and its argument.", token
  end
  if token[:value] == ?;
    token = warn "The argument to `throw` is not optional.", token
  end
  token = parse_assignment_expressions token
  parse_semicolon token
end

#parse_tokens(token, allow_functions = false) ⇒ Object

Internal: Continuously parses statements.

token - The current token. allow_functions - If this option is set to ‘true`, function declarations

may be parsed. Statements should not begin with functions.

Returns the token immediately following the result.



920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
# File 'lib/violet/parser.rb', line 920

def parse_tokens(token, allow_functions = false)
  # @kitcambridge: Peter notes that detecting the beginning of a statement
  # is difficult. The parser consumes statements until the current token is
  # identical to the previous, which occurs if a statement is optional.
  previous_token = token
  loop do
    token = if allow_functions && previous_token[:value] == "function"
      parse_function_declaration previous_token
    else
      parse_statement(previous_token, :optional)
    end
    break if previous_token == token
    previous_token = token
  end
  token
end

#parse_try_block(token) ⇒ Object

Internal.



1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
# File 'lib/violet/parser.rb', line 1290

def parse_try_block(token)
  save token
  token = get :pattern
  unless token[:value] == ?{
    token = warn "A `try` block must begin with `{`."
  end
  save token
  token = get :pattern
  token = parse_statements(token) unless token[:value] == ?}
  unless token[:value] == ?}
    token = warn "A `try` block must end with `}`.", token
  end
  save token
  get :pattern
end

#parse_try_statement(token) ⇒ Object

Public: Parses a ‘try…catch…finally` statement. Returns the token immediately following the statement.

token - The current token.



1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
# File 'lib/violet/parser.rb', line 1277

def parse_try_statement(token)
  token = parse_try_block token
  token = parse_catch_block(token) if token[:value] == "catch"
  token = parse_finally_block(token) if token[:value] == "finally"
  unless @try
    fail "A `try` statement must be followed by either a `catch` block, a `finally` block, or both.", token
  end
  token
ensure
  @try = false
end

#parse_var_statement(token) ⇒ Object

Internal.



948
949
950
951
# File 'lib/violet/parser.rb', line 948

def parse_var_statement(token)
  token = parse_variable_declarations token
  parse_semicolon token
end

#parse_variable_declarations(token, header = false) ⇒ Object

Internal.



954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
# File 'lib/violet/parser.rb', line 954

def parse_variable_declarations(token, header = false)
  initial = true
  loop do
    save token
    # `token` references the `var` statement on the first iteration, and
    # the comma separator for all subsequent iterations.
    token = get :pattern
    # The subsequent token should be an identifier that specifies the
    # variable name.
    if token[:name] == 12
      # @kitcambridge: Peter proposes returning if an illegal trailing comma
      # is encountered.
      token = warn(initial ? "A `var` declaration should be followed by the variable name." : "Illegal trailing comma.", token)
    elsif token[:name] != 2
      token = warn "Variable names can only be identifiers.", token
    elsif KEYWORD_OR_RESERVED.include? token[:value]
      token = warn "Variable names may not be keywords or future reserved words.", token
    end
    save token
    # The next token should be either `=`, `;`, or `,`.
    token = get :pattern
    if token[:value] == ?=
      # Parse a single assignment expression and an optional trailing comma.
      token[:initializer] = true
      save token
      token = get :pattern
      unless token[:name] <= 6 || token[:name] == 14 || LHS_START.match(token[:value])
        token = warn "The variable value must be an expression that does not contain a comma.", token
      end
      token = parse_assignment_expressions token, :single => true, :header => header
    end
    initial = false
    # Loop until all trailing commas have been consumed.
    break unless token[:value] == ?,
  end
  token
end

#parse_while_statement(token) ⇒ Object

Internal.



1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
# File 'lib/violet/parser.rb', line 1052

def parse_while_statement(token)
  save token
  token = get :pattern
  unless token[:value] == ?(
    token = warn "Expected `(`.", token
  end
  save token
  token = get :pattern
  # The `while` loop header must contain a valid left-hand side start
  # value.
  unless token[:name] <= 6 || LHS_START.match(token[:value])
    token = warn "A statement header must contain at least one expression.", token
  end
  token = parse_assignment_expressions token
  unless token[:value] == ?)
    token = "Expected `)`.", token
  end
  save token
  token = get :pattern
  parse_statement(token)
end

#parse_with_statement(token) ⇒ Object

Internal: Parses a ‘with` statement.

token - The current token.

Returns the token immediately following the statement.



1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
# File 'lib/violet/parser.rb', line 1381

def parse_with_statement(token)
  save token
  token = get :pattern
  unless token[:value] == ?(
    token = warn "The `with` statement must begin with `{`.", token
  end
  save token
  token = get :pattern
  # The statement header must contain a left-hand side start value.
  unless token[:name] <= 6 || LHS_START.match(token[:value])
    token = warn "The `with` statement header cannot be empty.", token
  end
  token = parse_assignment_expressions token
  unless token[:value] == ?)
    token = warn "The `with` statement must end with `}`.", token
  end
  save token
  token = get :pattern
  parse_statement token
end

#save(token) ⇒ Object

Internal: Stores the token in the parser token stream.

token - The token to store.

Returns nothing.



91
92
93
94
# File 'lib/violet/parser.rb', line 91

def save(token)
  @tokens << token
  nil
end

#warn(message, token) ⇒ Object

Internal: Emits a warning.

message - The warning message. token - The malformed token.

Returns the warning ‘Token`.



1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
# File 'lib/violet/parser.rb', line 1408

def warn(message, token)
  message = ParserError.new(message, token)
  # If a malformed token was previously encountered, insert it into the
  # token stream at the current position.
  save(@exception) if @exception
  # Store the current malformed token.
  @exception = token
  # Produce an error token at the current position and add it to the
  # lexer's token stream.
  error = Token.new(lexer, :error, token[:start]...token[:start])
  error[:error] = message
  lexer.insert_before(error, token)
  error
end