Class: Walrat::ParsletSequence

Inherits:
ParsletCombination show all
Defined in:
lib/walrat/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)


30
31
32
33
34
35
# File 'lib/walrat/parslet_sequence.rb', line 30

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.



27
28
29
# File 'lib/walrat/parslet_sequence.rb', line 27

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 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]


79
80
81
# File 'lib/walrat/parslet_sequence.rb', line 79

def &(next_parslet)
  append next_parslet
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


200
201
202
203
204
205
206
207
208
# File 'lib/walrat/parslet_sequence.rb', line 200

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



86
87
88
# File 'lib/walrat/parslet_sequence.rb', line 86

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

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

Raises:

  • (ArgumentError)


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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/walrat/parslet_sequence.rb', line 94

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
        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
                # guard against self references (possible infinite recursion) here?
                parsed = skipping_parslet.memoizing_parse state.remainder, state.options
                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

            # can't use "next" here because it would only break out of
            # innermost "do" rather than continuing the iteration
            throw :ProcessNextComponent
          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

  return results if skip_first

  if results.respond_to? :empty? and results.empty? and last_caught
    throw last_caught
  else
    results
  end
end

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



90
91
92
# File 'lib/walrat/parslet_sequence.rb', line 90

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

#recurse(state) ⇒ Object

Left-recursion helper



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/walrat/parslet_sequence.rb', line 184

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