parser.rb |
|
---|---|
EBNF Parser for EBNF.Produces an Abstract Synatx Tree in S-Expression form for the input grammar file |
require 'ebnf'
require 'ebnf/terminals'
require 'ebnf/peg/parser'
require 'meta'
require 'sxp'
require 'logger'
class EBNFPegParser
include EBNF::PEG::Parser
include EBNF::Terminals |
Abstract syntax tree from parse @return [ArrayEBNF::Rule] |
attr_reader :ast |
TerminalsDefine rules for Terminals, placing results on the input stack, making them available to upstream non-Terminal rules. Terminals are defined with a symbol matching the associated rule name, and an optional (although strongly encouraged) regular expression used to match the head of the input stream. The result of the terminal block is the semantic value of that terminal, which if often a string, but may be any instance which reflects the semantic interpretation of that terminal. The The If no block is provided, then the value which would have been passed to the block is used as the result directly. |
|
Match the Left hand side of a rule or terminal
|
terminal(:LHS, LHS) do |value, prod|
value.to_s.scan(/(?:\[([^\]]+)\])?\s*(\w+)\s*::=/).first
end |
Match
|
terminal(:SYMBOL, SYMBOL) do |value|
value.to_sym
end |
Match
|
terminal(:HEX, HEX) do |value|
[:hex, value]
end |
Terminal for
|
terminal(:RANGE, RANGE) do |value|
[:range, value[1..-2]]
end |
Terminal for
|
terminal(:O_RANGE, O_RANGE) do |value|
[:range, value[1..-2]]
end |
Match double quote string
|
terminal(:STRING1, STRING1) do |value|
value[1..-2]
end |
Match single quote string
|
terminal(:STRING2, STRING2) do |value|
value[1..-2]
end |
The |
|
Match
|
terminal(:POSTFIX, POSTFIX) |
The |
|
Non-terminal productionsDefine productions for non-Termainals. This can include Productions are defined with a symbol matching the associated rule name. The result of the productions is typically the abstract syntax tree matched by the rule, so far, but could be a specific semantic value, or could be ignored with the result being returned via the The The The |
|
Production for end of Look for Clears the packrat parser when called.
|
production(:declaration, clear_packrat: true) do |value, data, callback| |
value contains a declaration. Invoke callback |
callback.call(:terminals) if value == '@terminals'
nil
end |
Production for end of By setting Clears the packrat parser when called. Create rule from expression value and pass to callback
|
start_production(:rule, as_hash: true)
production(:rule, clear_packrat: true) do |value, data, callback| |
value contains an expression. Invoke callback |
id, sym = value[:LHS]
expression = value[:expression]
callback.call(:rule, EBNF::Rule.new(sym.to_sym, id, expression))
nil
end |
Production for end of The
|
production(:expression) do |value|
value.first[:alt]
end |
Production for end of The
Note that this also may just pass through from
|
start_production(:alt, as_hash: true)
production(:alt) do |value|
if value[:_alt_1].length > 0
[:alt, value[:seq]] + value[:_alt_1]
else
value[:seq]
end
end |
Production for end of The
|
production(:_alt_1) do |value|
value.map {|a1| a1.last[:seq]}.compact # Get rid of '|'
end |
Production for end of The
Note that this also may just pass through from
|
production(:seq) do |value|
value.length == 1 ? value.first : ([:seq] + value)
end |
The
|
start_production(:diff, as_hash: true)
production(:diff) do |value|
if value[:_diff_1]
[:diff, value[:postfix], value[:_diff_1]]
else
value[:postfix]
end
end
production(:_diff_1) do |value|
value.last[:postfix] if value
end |
Production for end of The
|
start_production(:postfix, as_hash: true)
production(:postfix) do |value| |
Push result onto input stack, as the |
case value[:_postfix_1]
when "*" then [:star, value[:primary]]
when "+" then [:plus, value[:primary]]
when "?" then [:opt, value[:primary]]
else value[:primary]
end
end |
Production for end of The This may either be a terminal, or the result of an
|
production(:primary) do |value|
Array(value).length > 2 ? value[1][:expression] : value
end |
Production for end of pass non-terminal.
|
production(:pass) do |value, data, callback| |
Invoke callback |
callback.call(:pass, value.last[:expression])
end |
Parser invocation.On start, yield ourselves if a block is given, otherwise, return this parser instance @param [#read, #to_s] input @param [Hash{Symbol => Object}] options @option options [Boolean] :level Trace level. 0(debug), 1(info), 2(warn), 3(error). @return [EBNFParser] |
def initialize(input, **options, &block) |
If the |
if options.has_key?(:level)
options[:logger] = Logger.new(STDERR)
options[:logger].level = options[:level]
options[:logger].formatter = lambda {|severity, datetime, progname, msg| "#{severity} #{msg}\n"}
end |
Read input, if necessary, which will be used in a Scanner. |
@input = input.respond_to?(:read) ? input.read : input.to_s
parsing_terminals = false
@ast = []
parse(@input, :ebnf, EBNFPegMeta::RULES, |
Use an optimized Regexp for whitespace |
whitespace: EBNF::Terminals::PASS,
**options
) do |context, *data|
rule = case context
when :terminals |
After parsing |
parsing_terminals = true
rule = EBNF::Rule.new(nil, nil, data.first, kind: :terminals)
when :pass |
After parsing |
rule = EBNF::Rule.new(nil, nil, data.first, kind: :pass)
when :rule |
A rule which has already been turned into a |
rule = data.first
rule.kind = :terminal if parsing_terminals
rule
end
@ast << rule if rule
end
@ast
end |
Output formatted S-Expression of grammar |
def to_sxp
require 'sxp' unless defined?(SXP) |
Output rules as a formatted S-Expression |
SXP::Generator.string(@ast.map(&:for_sxp))
end
end |