Class: Walrus::Grammar::ParsletSequence

Inherits:
ParsletCombination show all
Defined in:
lib/walrus/grammar/parslet_sequence.rb

Direct Known Subclasses

ParsletMerge

Constant Summary collapse

SKIP_FIRST =
true
NO_SKIP =
false

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from ParsletCombination

#to_parseable

Methods included from Memoizing

#check_left_recursion, #memoizing_parse

Methods included from ParsletCombining

#>>, #and?, #and_predicate, #choice, #memoizing_parse, #merge, #not!, #not_predicate, #omission, #one_or_more, #optional, #repeat, #repeat_with_default, #repetition, #repetition_with_default, #sequence, #skip, #zero_or_more, #zero_or_one, #|

Constructor Details

#initialize(first, second, *others) ⇒ ParsletSequence

first and second may not be nil.

Raises:

  • (ArgumentError)


25
26
27
28
29
30
# File 'lib/walrus/grammar/parslet_sequence.rb', line 25

def initialize(first, second, *others)
  raise ArgumentError if first.nil?
  raise ArgumentError if second.nil?
  @components = [first, second] + others
  update_hash
end

Instance Attribute Details

#hashObject (readonly)

Returns the value of attribute hash.



22
23
24
# File 'lib/walrus/grammar/parslet_sequence.rb', line 22

def hash
  @hash
end

Instance Method Details

#&(next_parslet) ⇒ Object

Override so that sequences are appended to an existing sequence: Consider the following example:

A & B

This constitutes a single sequence:

(A & B)

If we then make this a three-element sequence:

A & B & C

We are effectively creating an nested sequence containing the original sequence and an additional element:

((A & B) & C)

Although such a nested sequence is correctly parsed it produces unwanted nesting in the results because instead of returning a one-dimensional an array of results:

[a, b, c]

It returns a nested array:

[[a, b], c]

The solution to this unwanted nesting is to allowing appending to an existing sequence by using the private “append” method. This ensures that:

A & B & C

Translates to a single sequence:

(A & B & C)

And a single, uni-dimensional results array:

[a, b, c]


53
54
55
# File 'lib/walrus/grammar/parslet_sequence.rb', line 53

def &(next_parslet)
  append(next_parslet)
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


175
176
177
178
179
180
181
182
183
# File 'lib/walrus/grammar/parslet_sequence.rb', line 175

def eql?(other)
  return false if not other.instance_of? ParsletSequence
  other_components = other.components
  return false if @components.length != other_components.length
  for i in 0..(@components.length - 1)
    return false unless @components[i].eql? other_components[i]
  end
  true
end

#parse(string, options = {}) ⇒ Object



60
61
62
# File 'lib/walrus/grammar/parslet_sequence.rb', line 60

def parse(string, options = {})
  parse_common(NO_SKIP, string, options)
end

#parse_common(skip_first, string, options = {}) ⇒ Object

Raises:

  • (ArgumentError)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/walrus/grammar/parslet_sequence.rb', line 68

def parse_common(skip_first, string, options = {})
  raise ArgumentError if string.nil?
  state                         = ParserState.new(string, options)
  last_caught                   = nil   # keep track of the last kind of throw to be caught
  left_recursion                = false # keep track of whether left recursion was detected
  
  @components.each_with_index do |parseable, index|
    
    if index == 0 # for first component only
      if skip_first
        next
      end
      begin
        check_left_recursion(parseable, options)
      rescue LeftRecursionException => e
        left_recursion  = true
        continuation    = nil
        
        # Ruby 1.9/2.0 will almost certainly not support continuations so need to come up with an alternative
        # for handling left recursion here
        value           = callcc { |c| continuation = c }         
        if value == continuation                    # first time that we're here
          e.continuation = continuation             # pack continuation into exception
          raise e                                   # and propagate
        else
          grammar   = state.options[:grammar]
          rule_name = state.options[:rule_name]
          state.parsed(grammar.wrap(value, rule_name))
          next
        end
      end
    end
    
    catch :ProcessNextComponent do
      catch :NotPredicateSuccess do
        catch :AndPredicateSuccess do
          catch :ZeroWidthParseSuccess do
            begin
              parsed = parseable.memoizing_parse(state.remainder, state.options)
              state.parsed(parsed)
            rescue SkippedSubstringException => e
              state.skipped(e)
            rescue ParseError => e # failed, will try to skip; save original error in case skipping fails                                        
              if options.has_key?(:skipping_override)
                skipping_parslet = options[:skipping_override]
              elsif options.has_key?(:skipping)
                skipping_parslet = options[:skipping]
              else
                skipping_parslet = nil
              end
              raise e if skipping_parslet.nil?        # no skipper defined, raise original error
              begin
                parsed = skipping_parslet.memoizing_parse(state.remainder, state.options) # guard against self references (possible infinite recursion) here?
                state.skipped(parsed)
                redo              # skipping succeeded, try to redo
              rescue ParseError
                raise e           # skipping didn't help either, raise original error
              end
            end
            last_caught = nil
            throw :ProcessNextComponent   # can't use "next" here because it would only break out of innermost "do" rather than continuing the iteration
          end
          last_caught = :ZeroWidthParseSuccess
          throw :ProcessNextComponent
        end
        last_caught = :AndPredicateSuccess
        throw :ProcessNextComponent
      end
      last_caught = :NotPredicateSuccess
    end
  end
  
  if left_recursion
    results = recurse(state)
  else
    results = state.results
  end
  
  if skip_first
    return results
  end
  
  if results.respond_to? :empty? and results.empty? and last_caught
    throw last_caught
  else
    results
  end
  
end

#parse_remainder(string, options = {}) ⇒ Object



64
65
66
# File 'lib/walrus/grammar/parslet_sequence.rb', line 64

def parse_remainder(string, options = {})
  parse_common(SKIP_FIRST, string, options)
end

#recurse(state) ⇒ Object

Left-recursion helper



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/walrus/grammar/parslet_sequence.rb', line 159

def recurse(state)
  return state.results if state.remainder == '' # further recursion is not possible
  new_state = ParserState.new(state.remainder, state.options)
  last_successful_result = nil
  while state.remainder != ''
    begin
      new_results = parse_remainder(new_state.remainder, new_state.options)
      new_state.parsed(new_results)
      last_successful_result = ArrayResult[last_successful_result || state.results, new_results]
    rescue ParseError
      break
    end
  end
  last_successful_result || state.results
end