152 lines
4.8 KiB
Ruby
152 lines
4.8 KiB
Ruby
|
# 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.pos = 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.pos
|
|||
|
|
|||
|
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.pos = 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.pos = 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
|