class EBNF::Base
Attributes
Abstract syntax tree from parse
@return [Array<Rule>]
Grammar errors, or errors found genering parse tables
@return [Array<String>]
Public Class Methods
Parse the string or file input generating an abstract syntax tree in S-Expressions (similar to SPARQL SSE)
@param [#read, to_s
] input @param [Symbol] format (:ebnf)
Format of input, one of `:abnf`, `:ebnf`, `:isoebnf`, `:isoebnf`, `:native`, or `:sxp`. Use `:native` for the native EBNF parser, rather than the PEG parser.
@param [Hash{Symbol => Object}] options @option options [Boolean, Array] :debug
Output debug information to an array or $stdout.
@option options [Boolean, Array] :validate
Validate resulting grammar.
# File lib/ebnf/base.rb, line 112 def initialize(input, format: :ebnf, **options) @options = options.dup @lineno, @depth, @errors = 1, 0, [] @ast = [] input = input.respond_to?(:read) ? input.read : input.to_s case format when :abnf abnf = ABNF.new(input, **options) @ast = abnf.ast when :ebnf ebnf = Parser.new(input, **options) @ast = ebnf.ast when :isoebnf iso = ISOEBNF.new(input, **options) @ast = iso.ast when :native terminals = false scanner = StringScanner.new(input) eachRule(scanner) do |r| debug("rule string") {r.inspect} case r when /^@terminals/ # Switch mode to parsing terminals terminals = true rule = Rule.new(nil, nil, nil, kind: :terminals, ebnf: self) @ast << rule when /^@pass\s*(.*)$/m expr = expression($1).first rule = Rule.new(nil, nil, expr, kind: :pass, ebnf: self) rule.orig = expr @ast << rule else rule = depth {ruleParts(r)} rule.kind = :terminal if terminals # Override after we've parsed @terminals rule.orig = r @ast << rule end end when :sxp require 'sxp' unless defined?(SXP) @ast = SXP::Reader::Basic.read(input).map {|e| Rule.from_sxp(e)} else raise "unknown input format #{format.inspect}" end validate! if @options[:validate] end
Public Instance Methods
Progress output when debugging
@overload debug(node, message)
@param [String] node relative location in input @param [String] message ("")
@overload debug(message)
@param [String] message ("")
@yieldreturn [String] added to message
# File lib/ebnf/base.rb, line 345 def debug(*args, **options) return unless @options[:debug] depth = options[:depth] || @depth args << yield if block_given? message = "#{args.join(': ')}" str = "[#{@lineno}]#{' ' * depth}#{message}" @options[:debug] << str if @options[:debug].is_a?(Array) $stderr.puts(str) if @options[:debug] == true end
# File lib/ebnf/base.rb, line 305 def depth @depth += 1 ret = yield @depth -= 1 ret end
# File lib/ebnf/base.rb, line 291 def dup new_obj = super new_obj.instance_variable_set(:@ast, @ast.dup) new_obj end
Iterate over each rule or terminal, except empty @param [:termina, :rule] kind @yield rule @yieldparam [Rule] rule
# File lib/ebnf/base.rb, line 198 def each(kind, &block) ast.each {|r| block.call(r) if r.kind == kind && r.sym != :_empty} end
Error output
# File lib/ebnf/base.rb, line 324 def error(*args, **options) depth = options[:depth] || @depth args << yield if block_given? message = "#{args.join(': ')}" @errors << message str = "[#{@lineno}]#{' ' * depth}#{message}" @options[:debug] << str if @options[:debug].is_a?(Array) $stderr.puts(str) end
Find a rule given a symbol @param [Symbol] sym @return [Rule]
# File lib/ebnf/base.rb, line 301 def find_rule(sym) (@find ||= {})[sym] ||= ast.detect {|r| r.sym == sym} end
Progress output, less than debugging
# File lib/ebnf/base.rb, line 313 def progress(*args, **options) return unless @options[:progress] || @options[:debug] depth = options[:depth] || @depth args << yield if block_given? message = "#{args.join(': ')}" str = "[#{@lineno}]#{' ' * depth}#{message}" @options[:debug] << str if @options[:debug].is_a?(Array) $stderr.puts(str) if @options[:progress] || @options[:debug] == true end
Renumber, rule identifiers
# File lib/ebnf/base.rb, line 261 def renumber! ast.each_with_index do |rule, index| rule.id = (index + 1).to_s end end
Output formatted EBNF
as HTML
@param [:abnf, :ebnf, :isoebnf] format (:ebnf) @param [Boolean] validate (false) validate generated HTML. @return [String]
# File lib/ebnf/base.rb, line 226 def to_html(format: :ebnf, validate: false) Writer.html(*ast, format: format, validate: validate) end
Output Ruby parser files
@param [IO, StringIO] output @param [String] grammarFile @param [String] mod_name (‘Meta’)
# File lib/ebnf/base.rb, line 236 def to_ruby(output = $stdout, grammarFile: nil, mod_name: 'Meta', **options) unless output == $stdout output.puts "# This file is automatically generated by ebnf version #{EBNF::VERSION}" output.puts "# Derived from #{grammarFile}" if grammarFile unless self.errors.empty? output.puts "# Note, grammar has errors, may need to be resolved manually:" #output.puts "# #{pp.conflicts.map{|c| c.join("\n# ")}.join("\n# ")}" end output.puts "module #{mod_name}" output.puts " START = #{self.start.inspect}\n" if self.start end # Either output LL(1) BRANCH tables or rules for PEG parsing if ast.first.first to_ruby_ll1(output) else to_ruby_peg(output) end unless output == $stdout output.puts "end" end end
Output formatted EBNF
@param [:abnf, :ebnf, :isoebnf] format (:ebnf) @return [String]
# File lib/ebnf/base.rb, line 216 def to_s(format: :ebnf) Writer.string(*ast, format: format) end
Write out parsed syntax string as an S-Expression
@return [String]
# File lib/ebnf/base.rb, line 206 def to_sxp(**options) require 'sxp' unless defined?(SXP) SXP::Generator.string(ast.map(&:for_sxp)) end
Write out syntax tree as Turtle @param [String] prefix for language @param [String] ns URI for language @return [String]
# File lib/ebnf/base.rb, line 272 def to_ttl(prefix = nil, ns = "http://example.org/") unless ast.empty? [ "@prefix dc: <http://purl.org/dc/terms/>.", "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.", "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.", ("@prefix #{prefix}: <#{ns}>." if prefix), "@prefix : <#{ns}>.", "@prefix re: <http://www.w3.org/2000/10/swap/grammar/regex#>.", "@prefix g: <http://www.w3.org/2000/10/swap/grammar/ebnf#>.", "", ":language rdfs:isDefinedBy <>; g:start :#{ast.first.id}.", "", ].compact end.join("\n") + ast.map(&:to_ttl).join("\n") end
Is the grammar valid?
Uses ‘#validate!` and catches `RangeError`
@return [Boolean]
# File lib/ebnf/base.rb, line 187 def valid? validate! true rescue SyntaxError false end
Validate the grammar.
Makes sure that rules reference either strings or other defined rules.
@raise [RangeError]
# File lib/ebnf/base.rb, line 170 def validate! ast.each do |rule| begin rule.validate!(@ast) rescue SyntaxError => e error("In rule #{rule.sym}: #{e.message}") end end raise SyntaxError, errors.join("\n") unless errors.empty? end