vendored parslet, deemed stable enough and better without dependency
This commit is contained in:
162
lib/parslet/export.rb
Normal file
162
lib/parslet/export.rb
Normal file
@@ -0,0 +1,162 @@
|
||||
# Allows exporting parslet grammars to other lingos.
|
||||
|
||||
require 'set'
|
||||
require 'parslet/atoms/visitor'
|
||||
|
||||
class Parslet::Parser
|
||||
module Visitors
|
||||
class Citrus
|
||||
attr_reader :context, :output
|
||||
def initialize(context)
|
||||
@context = context
|
||||
end
|
||||
|
||||
def visit_str(str)
|
||||
"\"#{str.inspect[1..-2]}\""
|
||||
end
|
||||
def visit_re(match)
|
||||
match.to_s
|
||||
end
|
||||
|
||||
def visit_entity(name, block)
|
||||
context.deferred(name, block)
|
||||
|
||||
"(#{context.mangle_name(name)})"
|
||||
end
|
||||
def visit_named(name, parslet)
|
||||
parslet.accept(self)
|
||||
end
|
||||
|
||||
def visit_sequence(parslets)
|
||||
'(' <<
|
||||
parslets.
|
||||
map { |el| el.accept(self) }.
|
||||
join(' ') <<
|
||||
')'
|
||||
end
|
||||
def visit_repetition(tag, min, max, parslet)
|
||||
parslet.accept(self) << "#{min}*#{max}"
|
||||
end
|
||||
def visit_alternative(alternatives)
|
||||
'(' <<
|
||||
alternatives.
|
||||
map { |el| el.accept(self) }.
|
||||
join(' | ') <<
|
||||
')'
|
||||
end
|
||||
|
||||
def visit_lookahead(positive, bound_parslet)
|
||||
(positive ? '&' : '!') <<
|
||||
bound_parslet.accept(self)
|
||||
end
|
||||
end
|
||||
|
||||
class Treetop < Citrus
|
||||
def visit_repetition(tag, min, max, parslet)
|
||||
parslet.accept(self) << "#{min}..#{max}"
|
||||
end
|
||||
|
||||
def visit_alternative(alternatives)
|
||||
'(' <<
|
||||
alternatives.
|
||||
map { |el| el.accept(self) }.
|
||||
join(' / ') <<
|
||||
')'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A helper class that formats Citrus and Treetop grammars as a string.
|
||||
#
|
||||
class PrettyPrinter
|
||||
attr_reader :visitor
|
||||
def initialize(visitor_klass)
|
||||
@visitor = visitor_klass.new(self)
|
||||
end
|
||||
|
||||
# Pretty prints the given parslet using the visitor that has been
|
||||
# configured in initialize. Returns the string representation of the
|
||||
# Citrus or Treetop grammar.
|
||||
#
|
||||
def pretty_print(name, parslet)
|
||||
output = "grammar #{name}\n"
|
||||
|
||||
output << rule('root', parslet)
|
||||
|
||||
seen = Set.new
|
||||
loop do
|
||||
# @todo is constantly filled by the visitor (see #deferred). We
|
||||
# keep going until it is empty.
|
||||
break if @todo.empty?
|
||||
name, block = @todo.shift
|
||||
|
||||
# Track what rules we've already seen. This breaks loops.
|
||||
next if seen.include?(name)
|
||||
seen << name
|
||||
|
||||
output << rule(name, block.call)
|
||||
end
|
||||
|
||||
output << "end\n"
|
||||
end
|
||||
|
||||
# Formats a rule in either dialect.
|
||||
#
|
||||
def rule(name, parslet)
|
||||
" rule #{mangle_name name}\n" <<
|
||||
" " << parslet.accept(visitor) << "\n" <<
|
||||
" end\n"
|
||||
end
|
||||
|
||||
# Whenever the visitor encounters an rule in a parslet, it defers the
|
||||
# pretty printing of the rule by calling this method.
|
||||
#
|
||||
def deferred(name, content)
|
||||
@todo ||= []
|
||||
@todo << [name, content]
|
||||
end
|
||||
|
||||
# Mangles names so that Citrus and Treetop can live with it. This mostly
|
||||
# transforms some of the things that Ruby allows into other patterns. If
|
||||
# there is collision, we will not detect it for now.
|
||||
#
|
||||
def mangle_name(str)
|
||||
str.to_s.sub(/\?$/, '_p')
|
||||
end
|
||||
end
|
||||
|
||||
# Exports the current parser instance as a string in the Citrus dialect.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# require 'parslet/export'
|
||||
# class MyParser < Parslet::Parser
|
||||
# root(:expression)
|
||||
# rule(:expression) { str('foo') }
|
||||
# end
|
||||
#
|
||||
# MyParser.new.to_citrus # => a citrus grammar as a string
|
||||
#
|
||||
def to_citrus
|
||||
PrettyPrinter.new(Visitors::Citrus).
|
||||
pretty_print(self.class.name, root)
|
||||
end
|
||||
|
||||
# Exports the current parser instance as a string in the Treetop dialect.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# require 'parslet/export'
|
||||
# class MyParser < Parslet::Parser
|
||||
# root(:expression)
|
||||
# rule(:expression) { str('foo') }
|
||||
# end
|
||||
#
|
||||
# MyParser.new.to_treetop # => a treetop grammar as a string
|
||||
#
|
||||
def to_treetop
|
||||
PrettyPrinter.new(Visitors::Treetop).
|
||||
pretty_print(self.class.name, root)
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user