rubyx/lib/parslet/accelerator.rb

161 lines
4.4 KiB
Ruby

# Optimizes the parsers by pattern matching on the parser atoms and replacing
# matches with better versions. See the file qed/accelerators.md for a more
# in-depth description.
#
# Example:
# quote = str('"')
# parser = quote >> (quote.absent? >> any).repeat >> quote
#
# A = Accelerator # for making what follows a bit shorter
# optimized_parser = A.apply(parser,
# A.rule( (A.str(:x).absent? >> A.any).repeat ) { GobbleUp.new(x) })
#
# optimized_parser.parse('"Parsing is now fully optimized! (tm)"')
#
module Parslet::Accelerator
# An expression to match against a tree of parser atoms. Normally, an
# expression is produced by Parslet::Accelerator.any,
# Parslet::Accelerator.str or Parslet::Accelerator.re.
#
# Expressions can be chained much like parslet atoms can be:
#
# expr.repeat(1) # matching repetition
# expr.absent? # matching absent?
# expr.present? # matching present?
# expr1 >> expr2 # matching a sequence
# expr1 | expr2 # matching an alternation
#
# @see Parslet::Accelerator.str
# @see Parslet::Accelerator.re
# @see Parslet::Accelerator.any
#
# @see Parslet::Accelerator
#
class Expression
attr_reader :type
attr_reader :args
def initialize(type, *args)
@type = type
@args = args
end
# @return [Expression]
def >> other_expr
join_or_new :seq, other_expr
end
# @return [Expression]
def | other_expr
join_or_new :alt, other_expr
end
# @return [Expression]
def absent?
Expression.new(:absent, self)
end
# @return [Expression]
def present?
Expression.new(:present, self)
end
# @return [Expression]
def repeat min=0, max=nil
Expression.new(:rep, min, max, self)
end
# @return [Expression]
def as name
Expression.new(:as, name)
end
# @api private
# @return [Expression]
def join_or_new tag, other_expr
if type == tag
@args << other_expr
else
Expression.new(tag, self, other_expr)
end
end
end
module_function
# Returns a match expression that will match `str` parslet atoms.
#
# @return [Parslet::Accelerator::Expression]
#
def str variable, *constraints
Expression.new(:str, variable, *constraints)
end
# Returns a match expression that will match `match` parslet atoms.
#
# @return [Parslet::Accelerator::Expression]
#
def re variable, *constraints
Expression.new(:re, variable, *constraints)
end
# Returns a match expression that will match `any` parslet atoms.
#
# @return [Parslet::Accelerator::Expression]
#
def any
Expression.new(:re, ".")
end
# Given a parslet atom and an expression, will determine if the expression
# matches the atom. If successful, returns the bindings into the pattern
# that were made. If no bindings had to be made to make the match successful,
# the empty hash is returned.
#
# @param atom [Parslet::Atoms::Base] parslet atom to match against
# @param expr [Parslet::Accelerator::Expression] expression to match
# @return [nil, Hash] bindings for the match, nil on failure
#
def match atom, expr
engine = Engine.new
return engine.bindings if engine.match(atom, expr)
end
# Constructs an accelerator rule. A rule is a matching expression and the
# code that should be executed once the expression could be bound to a
# parser.
#
# Example:
# Accelerator.rule(Accelerator.any) { Parslet.match('.') }
#
def rule expression, &action
[expression, action]
end
# Given a parslet atom and a set of rules, tries to match the rules
# recursively through the parslet atom. Once a rule could be matched,
# its action block will be called.
#
# Example:
# quote = str('"')
# parser = quote >> (quote.absent? >> any).repeat >> quote
#
# A = Accelerator # for making what follows a bit shorter
# optimized_parser = A.apply(parser,
# A.rule( (A.str(:x).absent? >> A.any).repeat ) { GobbleUp.new(x) })
#
# optimized_parser.parse('"Parsing is now fully optimized! (tm)"')
#
# @param atom [Parslet::Atoms::Base] a parser to optimize
# @param *rules [Parslet::Accelerator::Rule] rules produced by .rule
# @return [Parslet::Atoms::Base] optimized parser
#
def apply atom, *rules
Application.new(atom, rules).call
end
end
require 'parslet/accelerator/engine'
require 'parslet/accelerator/application'