Class: EBNF::Base

Inherits:
Object
  • Object
show all
Includes:
BNF, LL1, Parser
Defined in:
lib/ebnf/base.rb

Instance Attribute Summary collapse

Attributes included from LL1

#branch, #cleanup, #first, #follow, #pass, #start, #terminals

Instance Method Summary collapse

Methods included from Parser

#alt, #diff, #eachRule, #expression, #postfix, #primary, #ruleParts, #seq, #terminal

Methods included from LL1

#build_tables, #first_follow, #outputTable

Methods included from BNF

#make_bnf

Constructor Details

#initialize(input, options = {}) ⇒ Base

Parse the string or file input generating an abstract syntax tree in S-Expressions (similar to SPARQL SSE)

Parameters:

  • input (#read, #to_s)
  • options (Hash{Symbol => Object}) (defaults to: {})

Options Hash (options):

  • :debug (Boolean, Array)

    Output debug information to an array or STDOUT.

  • :format (Symbol) — default: :ebnf

    Format of input, one of :ebnf, or :sxp



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
# File 'lib/ebnf/base.rb', line 124

def initialize(input, options = {})
  @options = {format: :ebnf}.merge(options)
  @lineno, @depth, @errors = 1, 0, []
  terminal = false
  @ast = []

  input = input.respond_to?(:read) ? input.read : input.to_s

  case @options[:format]
  when :sxp
    require 'sxp' unless defined?(SXP)
    @ast = SXP::Reader::Basic.read(input).map {|e| Rule.from_sxp(e)}
  when :ebnf
    scanner = StringScanner.new(input)

    eachRule(scanner) do |r|
      debug("rule string") {r.inspect}
      case r
      when /^@terminals/
        # Switch mode to parsing terminals
        terminal = true
      when /^@pass\s*(.*)$/m
        expr = expression($1).first
        rule = Rule.new(nil, nil, expr, kind: :pass)
        rule.orig = expr
        @ast << rule
      else
        rule = depth {ruleParts(r)}

        rule.kind = :terminal if terminal # Override after we've parsed @terminals
        rule.orig = r
        @ast << rule
      end
    end
  else
    raise "unknown input format #{options[:format].inspect}"
  end
end

Instance Attribute Details

#astArray<Rule> (readonly)

Abstract syntax tree from parse

Returns:



108
109
110
# File 'lib/ebnf/base.rb', line 108

def ast
  @ast
end

#errorsArray<String>

Grammar errors, or errors found genering parse tables

Returns:

  • (Array<String>)


113
114
115
# File 'lib/ebnf/base.rb', line 113

def errors
  @errors
end

Instance Method Details

#debug(node, message) ⇒ Object #debug(message) ⇒ Object

Progress output when debugging

Overloads:

  • #debug(node, message) ⇒ Object

    Parameters:

    • node (String)

      relative location in input

    • message (String)

      (“”)

  • #debug(message) ⇒ Object

    Parameters:

    • message (String)

      (“”)

Yield Returns:

  • (String)

    added to message



301
302
303
304
305
306
307
308
309
310
# File 'lib/ebnf/base.rb', line 301

def debug(*args)
  return unless @options[:debug]
  options = args.last.is_a?(Hash) ? args.pop : {}
  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

#depthObject



259
260
261
262
263
264
# File 'lib/ebnf/base.rb', line 259

def depth
  @depth += 1
  ret = yield
  @depth -= 1
  ret
end

#dupObject



222
223
224
225
226
# File 'lib/ebnf/base.rb', line 222

def dup
  new_obj = super
  new_obj.instance_variable_set(:@ast, @ast.dup)
  new_obj
end

#each(kind) {|rule| ... } ⇒ Object

Iterate over each rule or terminal, except empty

Parameters:

  • kind (:termina, :rule)

Yields:

  • rule

Yield Parameters:



167
168
169
# File 'lib/ebnf/base.rb', line 167

def each(kind, &block)
  ast.each {|r| block.call(r) if r.kind == kind && r.sym != :_empty}
end

#error(*args) ⇒ Object

Error output



279
280
281
282
283
284
285
286
287
288
# File 'lib/ebnf/base.rb', line 279

def error(*args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  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) ⇒ Rule

Find a rule given a symbol

Parameters:

  • sym (Symbol)

Returns:



232
233
234
# File 'lib/ebnf/base.rb', line 232

def find_rule(sym)
  (@find ||= {})[sym] ||= ast.detect {|r| r.sym == sym}
end

#progress(*args) ⇒ Object

Progress output, less than debugging



267
268
269
270
271
272
273
274
275
276
# File 'lib/ebnf/base.rb', line 267

def progress(*args)
  return unless @options[:progress] || @options[:debug]
  options = args.last.is_a?(Hash) ? args.pop : {}
  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

#to_htmlString

Output formatted EBNF as HTML

Returns:

  • (String)


189
190
191
# File 'lib/ebnf/base.rb', line 189

def to_html
  Writer.html(*ast)
end

#to_ruby(output = STDOUT, options = {}) ⇒ Object

Output Ruby parser files

Parameters:

  • output (IO, StringIO) (defaults to: STDOUT)
  • options (Hash{Symbol => Object}) (defaults to: {})

Options Hash (options):

  • :grammarFile (String)


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/ebnf/base.rb', line 199

def to_ruby(output = STDOUT, options = {})
  unless output == STDOUT
    output.puts "# This file is automatically generated by #{__FILE__}"
    output.puts "# BRANCH derived from #{options[:grammarFile]}" if options[:grammarFile]
    unless self.errors.empty?
      output.puts "# Note, tables completed with errors, may need to be resolved manually:"
      #output.puts "#   #{pp.conflicts.map{|c| c.join("\n#      ")}.join("\n#   ")}"
    end
    output.puts "module #{options.fetch(:mod_name, 'Branch')}"
    output.puts "  START = #{self.start.inspect}"
    output.puts
  end
  self.outputTable(output, 'BRANCH', self.branch, 1)
  self.outputTable(output, 'TERMINALS', self.terminals, 1)
  self.outputTable(output, 'FIRST', self.first, 1)
  self.outputTable(output, 'FOLLOW', self.follow, 1)
  self.outputTable(output, 'CLEANUP', self.cleanup, 1)
  self.outputTable(output, 'PASS', [self.pass], 1) if self.pass
  unless output == STDOUT
    output.puts "end"
  end
end

#to_sString

Output formatted EBNF

Returns:

  • (String)


182
183
184
# File 'lib/ebnf/base.rb', line 182

def to_s
  Writer.string(*ast)
end

#to_sxpString

Write out parsed syntax string as an S-Expression

Returns:

  • (String)


174
175
176
177
# File 'lib/ebnf/base.rb', line 174

def to_sxp
  require 'sxp' unless defined?(SXP)
  SXP::Generator.string(ast.sort_by{|r| r.id.to_f}.map(&:for_sxp))
end

#to_ttl(prefix, ns) ⇒ String

Write out syntax tree as Turtle

Parameters:

  • prefix (String)

    for language

  • ns (String)

    URI for language

Returns:

  • (String)


241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/ebnf/base.rb', line 241

def to_ttl(prefix, ns)
  unless ast.empty?
    [
      "@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}>.",
      "@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}.",
      "",
    ]
  end.join("\n") +

  ast.sort.map(&:to_ttl).join("\n")
end