Class: Parslet::Atoms::Base

Inherits:
Object
  • Object
show all
Includes:
CanFlatten, DSL, Precedence
Defined in:
lib/parslet/atoms/base.rb,
lib/parslet/convenience.rb,
lib/parslet/atoms/visitor.rb

Overview

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.

Constant Summary

Constants included from Precedence

Precedence::ALTERNATE, Precedence::BASE, Precedence::LOOKAHEAD, Precedence::OUTER, Precedence::REPETITION, Precedence::SEQUENCE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CanFlatten

#flatten, #flatten_repetition, #flatten_sequence, #foldl, #merge_fold, #warn_about_duplicate_keys

Methods included from DSL

#>>, #absent?, #as, #capture, #ignore, #maybe, #present?, #repeat, #|

Instance Attribute Details

#labelObject

Parslet label as provided in grammar



12
13
14
# File 'lib/parslet/atoms/base.rb', line 12

def label
  @label
end

Class Method Details

.precedence(prec) ⇒ Object

Debug printing - in Treetop syntax.



135
136
137
# File 'lib/parslet/atoms/base.rb', line 135

def self.precedence(prec)
  define_method(:precedence) { prec }
end

Instance Method Details

#accept(visitor) ⇒ Object

Raises:

  • (NotImplementedError)


7
8
9
# File 'lib/parslet/atoms/visitor.rb', line 7

def accept(visitor)
  raise NotImplementedError, "No #accept method on #{self.class.name}."
end

#apply(source, context, consume_all = false) ⇒ Object

Calls the #try method of this parslet. Success consumes input, error will rewind the input.

 @param context [Parslet::Atoms::Context] context to use for the parsing

Parameters:

  • source (Parslet::Source)

    source to read input from

  • consume_all (Boolean) (defaults to: false)

    true if the current parse must consume all input by itself.



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
# File 'lib/parslet/atoms/base.rb', line 83

def apply(source, context, consume_all=false)
  old_pos = source.bytepos
  
  success, _ = result = context.try_with_cache(self, source, consume_all)

  if success
    # Notify context
    context.succ(source)
    # 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

#cached?Boolean

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

Returns:

  • (Boolean)


129
130
131
# File 'lib/parslet/atoms/base.rb', line 129

def cached?
  true
end

#inspectObject



147
148
149
# File 'lib/parslet/atoms/base.rb', line 147

def inspect
  to_s(OUTER)
end

#parse(io, options = {}) ⇒ Hash, ...

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.

Parameters:

  • io (String, Source)

    input for the parse process

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :reporter (Parslet::ErrorReporter)

    error reporter to use, defaults to Parslet::ErrorReporter::Tree

  • :prefix (Boolean)

    Should a prefix match be accepted? (default: false)

Returns:

  • (Hash, Array, Parslet::Slice)

    PORO (Plain old Ruby object) result tree



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
# File 'lib/parslet/atoms/base.rb', line 26

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

#parse_with_debug(str, opts = {}) ⇒ Object

Packages the common idiom

begin
  tree = parser.parse('something')
rescue Parslet::ParseFailed => error
  puts parser.parse_failure_cause.ascii_tree
end

into a convenient method.

Usage:

require 'parslet'
require 'parslet/convenience'

class FooParser < Parslet::Parser
  rule(:foo) { str('foo') }
  root(:foo)
end

FooParser.new.parse_with_debug('bar')

See Also:



27
28
29
30
31
# File 'lib/parslet/convenience.rb', line 27

def parse_with_debug str, opts={}
  parse str, opts
rescue Parslet::ParseFailed => error
  puts error.parse_failure_cause.ascii_tree
end

#setup_and_apply(source, error_reporter, consume_all) ⇒ <Boolean, Object>

Creates a context for parsing and applies the current atom to the input. Returns the parse result.

Returns:

  • (<Boolean, Object>)

    Result of the parse. If the first member is true, the parse has succeeded.



71
72
73
74
# File 'lib/parslet/atoms/base.rb', line 71

def setup_and_apply(source, error_reporter, consume_all)
  context = Parslet::Atoms::Context.new(error_reporter)
  apply(source, context, consume_all)
end

#to_s(outer_prec = OUTER) ⇒ Object



139
140
141
142
143
144
145
146
# File 'lib/parslet/atoms/base.rb', line 139

def to_s(outer_prec=OUTER)
  str = label || to_s_inner(precedence)
  if outer_prec < precedence
    "(#{str})"
  else
    str
  end
end

#try(source, context, consume_all) ⇒ Object

Override this in your Atoms::Base subclasses to implement parsing behaviour.

Raises:

  • (NotImplementedError)


121
122
123
124
# File 'lib/parslet/atoms/base.rb', line 121

def try(source, context, consume_all)
  raise NotImplementedError, \
    "Atoms::Base doesn't have behaviour, please implement #try(source, context)."
end