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 152 153 154 155 156 157 158 159 160 161
|
# 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'
|