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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
|
# Example that demonstrates how a simple erb-like parser could be constructed.
$:.unshift File.dirname(__FILE__) + "/../lib"
require 'parslet'
require 'parslet/atoms/visitor'
require 'parslet/convenience'
class ErbParser < Parslet::Parser
rule(:ruby) { (str('%>').absent? >> any).repeat.as(:ruby) }
rule(:expression) { (str('=') >> ruby).as(:expression) }
rule(:comment) { (str('#') >> ruby).as(:comment) }
rule(:code) { ruby.as(:code) }
rule(:erb) { expression | comment | code }
rule(:erb_with_tags) { str('<%') >> erb >> str('%>') }
rule(:text) { (str('<%').absent? >> any).repeat(1) }
rule(:text_with_ruby) { (text.as(:text) | erb_with_tags).repeat.as(:text) }
root(:text_with_ruby)
end
class Parslet::Source
def match_excluding str
slice_str = @str.check_until(Regexp.new(Regexp.escape(str)))
return @str.rest_size unless slice_str
return slice_str.size - str.size
end
end
class AbsentParser < Parslet::Atoms::Base
def initialize absent
@absent = absent
end
def try(source, context, consume_all)
excluding_length = source.match_excluding(@absent)
if excluding_length >= 1
return succ(source.consume(excluding_length))
else
return context.err(self, source, "Failed absence #{@absent.inspect}.")
end
end
end
class Parslet::Optimizer
module DSL
def >> other
Match::Sequence.new(self, other)
end
def absent?
Match::Lookahead.new(false, self)
end
def repeat(min=0, max=nil)
Match::Repetition.new(self, min, max)
end
end
module Match
class Base
include DSL
def visit_parser(root)
false
end
def visit_entity(name, block)
false
end
def visit_named(name, atom)
false
end
def visit_repetition(tag, min, max, atom)
false
end
def visit_alternative(alternatives)
false
end
def visit_sequence(sequence)
false
end
def visit_lookahead(positive, atom)
false
end
def visit_re(regexp)
false
end
def visit_str(str)
false
end
def match(other, bindings)
@bindings = bindings
other.accept(self)
end
end
class Str < Base
def initialize(variable)
@variable = variable
end
def visit_str(str)
if bound_value=@bindings[@variable]
return bound_value == str
else
@bindings[@variable] = str
return true
end
end
end
class Lookahead < Base
def initialize(positive, expression)
@positive, @expression = positive, expression
end
def visit_lookahead(positive, atom)
positive == @positive &&
@expression.match(atom, @bindings)
end
end
class Sequence < Base
def initialize(*parslets)
@parslets = parslets
end
def visit_sequence(sequence)
sequence.zip(@parslets).all? { |atom, expr| expr.match(atom, @bindings) }
end
end
class Repetition < Base
def initialize(expression, min, max)
@min, @max, @expression = min, max, expression
end
def visit_repetition(tag, min, max, atom)
@min == min && @max == max && @expression.match(atom, @bindings)
end
end
class Re < Base
def initialize(variable)
@variable = variable
end
def visit_re(regexp)
case @variable
when Symbol
p [@variable, regexp]
fail
else
@variable == regexp
end
end
end
end
def self.str(var)
Match::Str.new(var)
end
def self.any
Match::Re.new('.')
end
class Rule
def initialize(expression, replacement)
@expression, @replacement = expression, replacement
end
class Context
def initialize(bindings)
@bindings = bindings
end
def method_missing(sym, *args, &block)
if args.size == 0 && !block && @bindings.has_key?(sym)
return @bindings[sym]
end
super
end
def call(callable)
instance_eval(&callable)
end
end
def match other
bindings = {}
if @expression.match(other, bindings)
return bindings
end
end
def call(bindings)
context = Context.new(bindings)
context.call(@replacement)
end
end
def self.rule(expression, &replacement)
rules << Rule.new(expression, replacement)
end
def self.rules
@rules ||= []
end
def rules
self.class.rules
end
class Transform
def initialize(rules)
@rules = rules
@candidates = []
end
def default_parser(root)
root.accept(self)
end
def default_entity(name, block)
Parslet::Atoms::Entity.new(name) { block.call.accept(self) }
end
def default_named(name, atom)
Parslet::Atoms::Named.new(atom.accept(self), name)
end
def default_repetition(tag, min, max, atom)
Parslet::Atoms::Repetition.new(atom.accept(self), min, max, tag)
end
def default_alternative(alternatives)
Parslet::Atoms::Alternative.new(
*alternatives.map { |atom| atom.accept(self) })
end
def default_sequence(sequence)
Parslet::Atoms::Sequence.new(
*sequence.map { |atom| atom.accept(self) })
end
def default_lookahead(positive, atom)
Parslet::Atoms::Lookahead.new(atom, positive)
end
def default_re(regexp)
Parslet::Atoms::Re.new(regexp)
end
def default_str(str)
Parslet::Atoms::Str.new(str)
end
def method_missing(sym, *args, &block)
if (md=sym.to_s.match(/visit_([a-z]+)/)) && !block
# Obtain the default, which is a completely transformed new parser
default = self.send("default_#{md[1]}", *args)
# Try transforming this parser again at the current level
return transform(default)
end
super
end
def transform(atom)
# Try to match one of the rules against the newly constructed tree.
@rules.each do |rule|
if bindings=rule.match(atom)
return rule.call(bindings)
end
end
# No match, returning new atom.
return atom
end
end
def apply(parser)
parser.accept(Transform.new(rules))
end
end
class Optimizer < Parslet::Optimizer
rule((str(:x).absent? >> any).repeat(1)) {
AbsentParser.new(x) }
end
parser = ErbParser.new
optimized_parser = Optimizer.new.apply(parser)
# p optimized_parser.parse(File.read(ARGV.first))
p parser.parse_with_debug(File.read(ARGV.first))
|