Class: Rley::RGN::GrammarBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/rley/rgn/grammar_builder.rb

Overview

Builder GoF pattern. Builder builds a complex object (say, a grammar) from simpler objects (terminals and productions) and using a step by step approach.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(&aBlock) ⇒ GrammarBuilder

Creates a new RGN grammar builder.

Parameters:

  • aBlock (Proc)

    code block used to build the grammar.



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rley/rgn/grammar_builder.rb', line 38

def initialize(&aBlock)
  @symbols = {}
  @productions = []
  @parser = RGN::Parser.new
  @visitor2rhs = {}
  @synthetized = {}

  if block_given?
    instance_exec(&aBlock)
    grammar_complete!
  end
end

Instance Attribute Details

#parserRGN::Parser (readonly)

Returns Parser for the right-side of productions.

Returns:

  • (RGN::Parser)

    Parser for the right-side of productions



24
25
26
# File 'lib/rley/rgn/grammar_builder.rb', line 24

def parser
  @parser
end

#productionsArray<Production> (readonly)

Returns The list of production rules for the grammar to build.

Returns:

  • (Array<Production>)

    The list of production rules for the grammar to build.



31
32
33
# File 'lib/rley/rgn/grammar_builder.rb', line 31

def productions
  @productions
end

#symbolsHash{String, GrmSymbol} (readonly)

Returns The mapping of grammar symbol names to the matching grammar symbol object.

Returns:

  • (Hash{String, GrmSymbol})

    The mapping of grammar symbol names to the matching grammar symbol object.



21
22
23
# File 'lib/rley/rgn/grammar_builder.rb', line 21

def symbols
  @symbols
end

#synthetizedHash{String, String} (readonly)

Returns The synthesized raw productions.

Returns:

  • (Hash{String, String})

    The synthesized raw productions



34
35
36
# File 'lib/rley/rgn/grammar_builder.rb', line 34

def synthetized
  @synthetized
end

#visitor2rhsHash{ASTVisitor, Array} (readonly)

Returns:



27
28
29
# File 'lib/rley/rgn/grammar_builder.rb', line 27

def visitor2rhs
  @visitor2rhs
end

Instance Method Details

#[](aSymbolName) ⇒ GrmSymbol

Retrieve a grammar symbol from its name. Raise an exception if not found.

Parameters:

  • aSymbolName (String)

    the name of a grammar symbol.

Returns:

  • (GrmSymbol)

    the retrieved symbol object.



55
56
57
# File 'lib/rley/rgn/grammar_builder.rb', line 55

def [](aSymbolName)
  symbols[aSymbolName]
end

#add_marker(aMarkerSymbol) ⇒ void

This method returns an undefined value.

Add the given marker symbol to the grammar of the language

Parameters:

  • aMarkerSymbol (String)

    A marker symbol



70
71
72
73
# File 'lib/rley/rgn/grammar_builder.rb', line 70

def add_marker(aMarkerSymbol)
  new_symb = build_symbol(Syntax::Marker, aMarkerSymbol)
  symbols[new_symb.name] = new_symb
end

#add_production(aProductionRepr) ⇒ Production Also known as: rule

Add a production rule in the grammar given one key-value pair of the form: String => String. Where the key is the name of the non-terminal appearing in the left side of the rule. The value is a sequence of grammar symbol names (optionally quantified). The rule is created and inserted in the grammar.

Examples:

Equivalent call syntax

builder.add_production('A' => 'a  A c)
builder.rule('A' => 'a A  c]) # 'rule' is a synonym

Parameters:

  • aProductionRepr (Hash{String, String})

    A Hash-based representation of a production.

Returns:

  • (Production)

    The created Production instance



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rley/rgn/grammar_builder.rb', line 87

def add_production(aProductionRepr)
  aProductionRepr.each_pair do |(lhs_name, rhs_repr)|
    lhs = get_grm_symbol(lhs_name)
    rhs = rhs_repr.kind_of?(Array) && rhs_repr.empty? ? '' : rhs_repr.strip
    constraints = []
    if rhs.empty?
      rhs_members = []
    else
      ast = parser.parse(rhs)
      visitor = ASTVisitor.new(ast)
      visitor2rhs[visitor] = []
      visitor.subscribe(self)
      visitor.start
      root_node = ast.root
      constraints = root_node.constraints unless root_node.kind_of?(SymbolNode)

      rhs_members = visitor2rhs.delete(visitor)
    end
    new_prod = Syntax::Production.new(lhs, rhs_members)
    new_prod.constraints = constraints
    productions << new_prod
  end

  productions.last
end

#add_terminals(*terminalSymbols) ⇒ void

This method returns an undefined value.

Add the given terminal symbols to the grammar of the language

Parameters:

  • terminalSymbols (String or Terminal)

    1..* terminal symbols.



62
63
64
65
# File 'lib/rley/rgn/grammar_builder.rb', line 62

def add_terminals(*terminalSymbols)
  new_symbs = build_symbols(Syntax::Terminal, terminalSymbols)
  symbols.merge!(new_symbs)
end

#after_repetition_node(aRepNode, aVisitor) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/rley/rgn/grammar_builder.rb', line 230

def after_repetition_node(aRepNode, aVisitor)
  add_constraints(aRepNode)
  return if aRepNode.repetition == :exactly_one

  node_name = aRepNode.name
  child_name = aRepNode.subnodes[0].name

  if aRepNode.child.is_a?(SequenceNode) &&
     !symbols.include?(child_name) && aRepNode.repetition != :zero_or_one
    add_nonterminal(child_name)
    rhs = aRepNode.child.to_text
    add_raw_rule(child_name, rhs, 'return_children', true)
  end

  case aRepNode.repetition
  when :zero_or_one
    # implicitly called: rule('node_name_qmark' => 'node_name_qmark').tag suffix_qmark_one
    # implicitly called: rule('node_name_qmark' => '').tag suffix_qmark_none
    unless symbols.include? node_name
      add_nonterminal(node_name)
      if aRepNode.child.is_a?(SequenceNode) && !aRepNode.child.constraints.empty?
        aRepNode.constraints.merge(aRepNode.child.constraints)
      end
      rhs = aRepNode.child.to_text
      add_raw_rule(node_name, rhs, 'return_children', false, aRepNode.constraints)
      add_raw_rule(node_name, [], suffix_qmark_none, true)
    end

  when :zero_or_more
    # implicitly called: rule('node_name_star' => 'node_name_star node_name').tag suffix_star_more
    # implicitly called: rule('node_name_star' => '').tag suffix_star_none
    unless symbols.include? node_name
      add_nonterminal(node_name)
      rhs = "#{node_name} #{child_name}"
      add_raw_rule(node_name, rhs, suffix_star_more)
      add_raw_rule(node_name, '', suffix_star_none)
    end

  when :one_or_more
    unless symbols.include? node_name
      add_nonterminal(node_name)
      add_raw_rule(node_name, "#{node_name} #{child_name}", suffix_plus_more)
      add_raw_rule(node_name, child_name, suffix_plus_one)
    end
  else
    raise StandardError, 'Unhandled multiplicity'
  end

  symb = get_grm_symbol(node_name)
  visitor2rhs[aVisitor] << symb
end

#after_sequence_node(aSequenceNode, _visitor) ⇒ Object



226
227
228
# File 'lib/rley/rgn/grammar_builder.rb', line 226

def after_sequence_node(aSequenceNode, _visitor)
  add_constraints(aSequenceNode)
end

#after_symbol_node(aSymbolNode, aVisitor) ⇒ Object

RGN's AST visit notification events



220
221
222
223
224
# File 'lib/rley/rgn/grammar_builder.rb', line 220

def after_symbol_node(aSymbolNode, aVisitor)
  symb_name = aSymbolNode.name
  symb = get_grm_symbol(symb_name)
  visitor2rhs[aVisitor] << symb
end

#grammarGrammar

Given the grammar symbols and productions added to the builder, build the resulting grammar (if not yet done).

Returns:

  • (Grammar)

    the created grammar object.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/rley/rgn/grammar_builder.rb', line 116

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.kind_of?(Syntax::Terminal)
    end
    in_use = Set.new
    productions.each do |prod|
      prod.rhs.members.each do |symb|
        in_use << symb if symb.kind_of?(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 = Syntax::Grammar.new(productions.dup)
  end

  @grammar
end

#grammar_complete!Object

A notification to the builderobject that the programmer has completed the entry of terminals and production rules



284
285
286
# File 'lib/rley/rgn/grammar_builder.rb', line 284

def grammar_complete!
  process_raw_rules
end

#modifier2suffix(aModifier) ⇒ Object



207
208
209
210
211
212
213
214
215
# File 'lib/rley/rgn/grammar_builder.rb', line 207

def modifier2suffix(aModifier)
  mapping = {
    '?' => suffix_qmark,
    '*' => suffix_star,
    '+' => suffix_plus
  }

  mapping[aModifier]
end

#repetition2suffix(aRepetition) ⇒ Object



196
197
198
199
200
201
202
203
204
205
# File 'lib/rley/rgn/grammar_builder.rb', line 196

def repetition2suffix(aRepetition)
  mapping = {
    zero_or_one: suffix_qmark,
    zero_or_more: suffix_star,
    exactly_one: '',
    one_or_more: suffix_plus
  }

  mapping[aRepetition]
end

#suffix_plusObject

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 implicitly called: rule('digit_plus' => 'digit_plus digit').tag suffix_plus_more implicitly called: rule('digit_plus' => 'digit').tag suffix_plus_last



184
185
186
# File 'lib/rley/rgn/grammar_builder.rb', line 184

def suffix_plus
  '_plus'
end

#suffix_plus_moreObject



188
189
190
# File 'lib/rley/rgn/grammar_builder.rb', line 188

def suffix_plus_more
  '_plus_more'
end

#suffix_plus_oneObject



192
193
194
# File 'lib/rley/rgn/grammar_builder.rb', line 192

def suffix_plus_one
  '_plus_one'
end

#suffix_qmarkObject

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 implicitly called: rule('declaration_star' => 'declaration_star declaration').tag suffix_star_more implicitly called: rule('declaration_star' => '').tag suffix_star_last



152
153
154
# File 'lib/rley/rgn/grammar_builder.rb', line 152

def suffix_qmark
  '_qmark'
end

#suffix_qmark_noneObject



160
161
162
# File 'lib/rley/rgn/grammar_builder.rb', line 160

def suffix_qmark_none
  '_qmark_none'
end

#suffix_qmark_oneObject



156
157
158
# File 'lib/rley/rgn/grammar_builder.rb', line 156

def suffix_qmark_one
  '_qmark_one'
end

#suffix_starObject

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 implicitly called: rule('declaration_star' => 'declaration_star declaration').tag suffix_star_more implicitly called: rule('declaration_star' => '').tag suffix_star_last



168
169
170
# File 'lib/rley/rgn/grammar_builder.rb', line 168

def suffix_star
  '_star'
end

#suffix_star_moreObject



172
173
174
# File 'lib/rley/rgn/grammar_builder.rb', line 172

def suffix_star_more
  '_star_more'
end

#suffix_star_noneObject



176
177
178
# File 'lib/rley/rgn/grammar_builder.rb', line 176

def suffix_star_none
  '_star_none'
end