class EBNF::Base

Attributes

ast[R]

Abstract syntax tree from parse

@return [Array<Rule>]

errors[RW]

Grammar errors, or errors found genering parse tables

@return [Array<String>]

Public Class Methods

new(input, format: :ebnf, **options) click to toggle source

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

debug(*args, **options) { || ... } click to toggle source

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
depth() { || ... } click to toggle source
# File lib/ebnf/base.rb, line 305
def depth
  @depth += 1
  ret = yield
  @depth -= 1
  ret
end
dup() click to toggle source
Calls superclass method
# File lib/ebnf/base.rb, line 291
def dup
  new_obj = super
  new_obj.instance_variable_set(:@ast, @ast.dup)
  new_obj
end
each(kind, &block) click to toggle source

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(*args, **options) { || ... } click to toggle source

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_rule(sym) click to toggle source

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(*args, **options) { || ... } click to toggle source

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!() click to toggle source

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
to_html(format: :ebnf, validate: false) click to toggle source

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
to_ruby(output = $stdout, grammarFile: nil, mod_name: 'Meta', **options) click to toggle source

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
to_s(format: :ebnf) click to toggle source

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
to_sxp(**options) click to toggle source

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
to_ttl(prefix = nil, ns = "http://example.org/") click to toggle source

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
valid?() click to toggle source

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!() click to toggle source

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