File: base.rb

package info (click to toggle)
ruby-parslet 1.6.1-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 908 kB
  • ctags: 473
  • sloc: ruby: 5,220; makefile: 2
file content (151 lines) | stat: -rw-r--r-- 4,944 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
# Base class for all parslets, handles orchestration of calls and implements
# a lot of the operator and chaining methods.
#
# Also see Parslet::Atoms::DSL chaining parslet atoms together.
#
class Parslet::Atoms::Base
  include Parslet::Atoms::Precedence
  include Parslet::Atoms::DSL
  include Parslet::Atoms::CanFlatten
  
  # Given a string or an IO object, this will attempt a parse of its contents
  # and return a result. If the parse fails, a Parslet::ParseFailed exception
  # will be thrown. 
  #
  # @param io [String, Source] input for the parse process
  # @option options [Parslet::ErrorReporter] :reporter error reporter to use, 
  #   defaults to Parslet::ErrorReporter::Tree 
  # @option options [Boolean] :prefix Should a prefix match be accepted? 
  #   (default: false)
  # @return [Hash, Array, Parslet::Slice] PORO (Plain old Ruby object) result
  #   tree
  #
  def parse(io, options={})
    source = io.respond_to?(:line_and_column) ? 
      io : 
      Parslet::Source.new(io)

    # Try to cheat. Assuming that we'll be able to parse the input, don't 
    # run error reporting code. 
    success, value = setup_and_apply(source, nil, !options[:prefix])
    
    # If we didn't succeed the parse, raise an exception for the user. 
    # Stack trace will be off, but the error tree should explain the reason
    # it failed.
    unless success
      # Cheating has not paid off. Now pay the cost: Rerun the parse,
      # gathering error information in the process.
      reporter = options[:reporter] || Parslet::ErrorReporter::Tree.new
      source.bytepos = 0
      success, value = setup_and_apply(source, reporter, !options[:prefix])
      
      fail "Assertion failed: success was true when parsing with reporter" \
        if success
      
      # Value is a Parslet::Cause, which can be turned into an exception:
      value.raise
      
      fail "NEVER REACHED"
    end
    
    # assert: success is true

    # Extra input is now handled inline with the rest of the parsing. If 
    # really we have success == true, prefix: false and still some input 
    # is left dangling, that is a BUG.
    if !options[:prefix] && source.chars_left > 0
      fail "BUG: New error strategy should not reach this point."
    end
    
    return flatten(value)
  end
  
  # Creates a context for parsing and applies the current atom to the input. 
  # Returns the parse result. 
  #
  # @return [<Boolean, Object>] Result of the parse. If the first member is 
  #   true, the parse has succeeded. 
  def setup_and_apply(source, error_reporter, consume_all)
    context = Parslet::Atoms::Context.new(error_reporter)
    apply(source, context, consume_all)
  end

  # Calls the #try method of this parslet. Success consumes input, error will 
  # rewind the input. 
  #
  # @param source [Parslet::Source] source to read input from
  # @param context [Parslet::Atoms::Context] context to use for the parsing
  # @param consume_all [Boolean] true if the current parse must consume
  #   all input by itself.
  def apply(source, context, consume_all=false)
    old_pos = source.bytepos
    
    success, value = result = context.try_with_cache(self, source, consume_all)

    if success
      # If a consume_all parse was made and doesn't result in the consumption
      # of all the input, that is considered an error. 
      if consume_all && source.chars_left>0
        # Read 10 characters ahead. Why ten? I don't know. 
        offending_pos   = source.pos
        offending_input = source.consume(10)
        
        # Rewind input (as happens always in error case)
        source.bytepos  = old_pos
        
        return context.err_at(
          self, 
          source, 
          "Don't know what to do with #{offending_input.to_s.inspect}", 
          offending_pos
        ) 
      end
      
      # Looks like the parse was successful after all. Don't rewind the input.
      return result
    end
    
    # We only reach this point if the parse has failed. Rewind the input.
    source.bytepos = old_pos
    return result
  end
  
  # Override this in your Atoms::Base subclasses to implement parsing
  # behaviour. 
  #
  def try(source, context, consume_all)
    raise NotImplementedError, \
      "Atoms::Base doesn't have behaviour, please implement #try(source, context)."
  end

  # Returns true if this atom can be cached in the packrat cache. Most parslet
  # atoms are cached, so this always returns true, unless overridden.
  #
  def cached?
    true
  end

  # Debug printing - in Treetop syntax. 
  #
  def self.precedence(prec)
    define_method(:precedence) { prec }
  end
  precedence BASE
  def to_s(outer_prec=OUTER)
    if outer_prec < precedence
      "("+to_s_inner(precedence)+")"
    else
      to_s_inner(precedence)
    end
  end
  def inspect
    to_s(OUTER)
  end
private

  # Produces an instance of Success and returns it. 
  #
  def succ(result)
    [true, result]
  end
end