class Rley::Syntax::BaseGrammarBuilder
Builder GoF pattern. Builder builds a complex object
(say, a grammar) from simpler objects (terminals and productions) and using a step by step approach.
Attributes
@return [Array<Production>] The list of production rules for
the grammar to build.
@return [Hash{String, GrmSymbol}] The mapping of grammar symbol names
to the matching grammar symbol object.
Public Class Methods
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 33 def initialize(&aBlock) @symbols = {} @productions = [] instance_exec(&aBlock) if block_given? end
Creates a new grammar builder. @param aBlock [Proc] code block used to build the grammar. @example Building a tiny English grammar
builder = Rley::Syntax::GrammarBuilder.new do add_terminals('n', 'v', 'adj', 'det') rule 'S' => %w[NP VP] rule 'VP' => %w[v NP] rule 'NP' => %w[det n] rule 'NP' => %w[adj NP] end tiny_eng = builder.grammar
Public Instance Methods
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 44 def [](aSymbolName) symbols[aSymbolName] end
Retrieve a grammar symbol from its name. Raise an exception if not found. @param aSymbolName [String] the name of a grammar symbol. @return [GrmSymbol] the retrieved symbol object.
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 70 def add_production(aProductionRepr) aProductionRepr.each_pair do |(lhs_name, rhs_repr)| lhs = get_grm_symbol(lhs_name) case rhs_repr when Array rhs_members = rhs_repr.map { |name| get_grm_symbol(name) } when String rhs_lexemes = rhs_repr.scan(/\S+/) rhs_members = rhs_lexemes.map { |name| get_grm_symbol(name) } when Terminal rhs_members = [rhs_repr] end new_prod = Production.new(lhs, rhs_members) productions << new_prod end return productions.last end
Add a production rule in the grammar given one key-value pair of the form: String => Array.
Where the key is the name of the non-terminal appearing in the left side of the rule. The value, an Array, is a sequence of grammar symbol names.
The rule is created and inserted in the grammar. @example Equivalent call syntaxes
builder.add_production('A' => ['a', 'A', 'c']) builder.rule('A' => ['a', 'A', 'c']) # 'rule' is a synonym builder.rule('A' => %w[a A c]) # Use %w syntax for Array of String builder.rule 'A' => %w[a A c] # Call parentheses are optional
@param aProductionRepr [Hash{String, Array<String>}]
A Hash-based representation of a production.
@return [Production] The created Production
instance
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 51 def add_terminals(*terminalSymbols) new_symbs = build_symbols(Terminal, terminalSymbols) symbols.merge!(new_symbs) end
Add the given terminal symbols to the grammar of the language @param terminalSymbols [String or Terminal] 1..* terminal symbols. @return [void]
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 92 def grammar unless @grammar raise StandardError, 'No symbol found for grammar' if symbols.empty? if productions.empty? raise StandardError, 'No production found for grammar' end # Check that each terminal appears at least in a rhs of a production all_terminals = symbols.values.select do |a_symb| a_symb.is_a?(Terminal) end in_use = Set.new productions.each do |prod| prod.rhs.members.each do |symb| in_use << symb if symb.is_a?(Syntax::Terminal) end end unused = all_terminals.reject { |a_term| in_use.include?(a_term) } unless unused.empty? suffix = "#{unused.map(&:name).join(', ')}." raise StandardError, "Useless terminal symbol(s): #{suffix}" end @grammar = Grammar.new(productions.dup) end return @grammar end
Given the grammar symbols and productions added to the builder, build the resulting grammar (if not yet done). @return [Grammar] the created grammar object.
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 124 def suffix_plus '_plus' end
When a symbol, say symb, in a rhs is followed by a ‘+’ modifier, then a rule will be generated with a lhs named symb + suffix_plus
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 132 def suffix_plus_last 'base_plus_last' end
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 128 def suffix_plus_more 'base_plus_more' end
Private Instance Methods
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 162 def build_symbol(aClass, aSymbolArg) if aSymbolArg.is_a?(GrmSymbol) aSymbolArg else aClass.new(aSymbolArg) end end
If the argument is already a grammar symbol object then it is returned as is. Otherwise, the argument is treated as a name for a new instance of the given class. @param aClass [Class] The class of grammar symbols to instantiate @param aSymbolArg [GrmSymbol-like or String] @return [Array] list of grammar symbols
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 146 def build_symbols(aClass, theSymbols) symbs = {} theSymbols.each do |s| new_symbol = build_symbol(aClass, s) symbs[new_symbol.name] = new_symbol end return symbs end
Add the given grammar symbols. @param aClass [Class] The class of grammar symbols to instantiate. @param theSymbols [Array] array of elements are treated as follows:
if the element is already a grammar symbol, then it added as is, otherwise it is considered as the name of a grammar symbol
of the specified class to build.
Source
# File lib/rley/syntax/base_grammar_builder.rb, line 174 def get_grm_symbol(aSymbolName) unless aSymbolName.end_with?('+') && aSymbolName.length > 1 name = aSymbolName else name = aSymbolName.chop case aSymbolName[-1] when '+' name_modified = "#{name}#{suffix_plus}" unless symbols.include? name_modified symbols[name_modified] = NonTerminal.new(name_modified) rule(name_modified => [name_modified, name]).as suffix_plus_more rule(name_modified => name).as suffix_plus_last end name = name_modified else err_msg = "Unknown symbol modifier #{aSymbolName[-1]}" raise NotImplementedError, err_msg end end symbols[name] = NonTerminal.new(name) unless symbols.include? name symbols[name] end
Retrieve the non-terminal symbol with given name. If it doesn’t exist yet, then it is created on the fly. @param aSymbolName [String] the name of the grammar symbol to retrieve @return [NonTerminal]