dendroid 0.0.12 → 0.2.00

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/lib/dendroid/formatters/ascii_tree.rb +142 -0
  4. data/lib/dendroid/formatters/base_formatter.rb +25 -0
  5. data/lib/dendroid/formatters/bracket_notation.rb +50 -0
  6. data/lib/dendroid/grm_analysis/dotted_item.rb +46 -30
  7. data/lib/dendroid/grm_analysis/grm_analyzer.rb +24 -61
  8. data/lib/dendroid/grm_analysis/{choice_items.rb → rule_items.rb} +10 -10
  9. data/lib/dendroid/grm_dsl/base_grm_builder.rb +3 -4
  10. data/lib/dendroid/parsing/and_node.rb +56 -0
  11. data/lib/dendroid/parsing/chart_walker.rb +293 -0
  12. data/lib/dendroid/parsing/composite_parse_node.rb +21 -0
  13. data/lib/dendroid/parsing/empty_rule_node.rb +28 -0
  14. data/lib/dendroid/parsing/or_node.rb +51 -0
  15. data/lib/dendroid/parsing/parse_node.rb +26 -0
  16. data/lib/dendroid/parsing/parse_tree_visitor.rb +127 -0
  17. data/lib/dendroid/parsing/parser.rb +185 -0
  18. data/lib/dendroid/parsing/terminal_node.rb +32 -0
  19. data/lib/dendroid/parsing/walk_progress.rb +117 -0
  20. data/lib/dendroid/recognizer/chart.rb +18 -2
  21. data/lib/dendroid/recognizer/e_item.rb +21 -2
  22. data/lib/dendroid/recognizer/item_set.rb +7 -2
  23. data/lib/dendroid/recognizer/recognizer.rb +69 -69
  24. data/lib/dendroid/syntax/grammar.rb +72 -60
  25. data/lib/dendroid/syntax/rule.rb +71 -13
  26. data/spec/dendroid/grm_analysis/dotted_item_spec.rb +59 -47
  27. data/spec/dendroid/grm_analysis/{choice_items_spec.rb → rule_items_spec.rb} +5 -6
  28. data/spec/dendroid/parsing/chart_walker_spec.rb +223 -0
  29. data/spec/dendroid/parsing/terminal_node_spec.rb +36 -0
  30. data/spec/dendroid/recognizer/e_item_spec.rb +5 -5
  31. data/spec/dendroid/recognizer/item_set_spec.rb +16 -8
  32. data/spec/dendroid/recognizer/recognizer_spec.rb +57 -5
  33. data/spec/dendroid/support/sample_grammars.rb +2 -0
  34. data/spec/dendroid/syntax/grammar_spec.rb +44 -34
  35. data/spec/dendroid/syntax/rule_spec.rb +56 -7
  36. data/version.txt +1 -1
  37. metadata +20 -13
  38. data/lib/dendroid/grm_analysis/alternative_item.rb +0 -70
  39. data/lib/dendroid/grm_analysis/production_items.rb +0 -55
  40. data/lib/dendroid/syntax/choice.rb +0 -95
  41. data/lib/dendroid/syntax/production.rb +0 -82
  42. data/spec/dendroid/grm_analysis/alternative_item_spec.rb +0 -12
  43. data/spec/dendroid/grm_analysis/production_items_spec.rb +0 -68
  44. data/spec/dendroid/syntax/choice_spec.rb +0 -68
  45. data/spec/dendroid/syntax/production_spec.rb +0 -92
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56842965215f0cef73b768223b5acb907fc1642b57528a9e616852ae6adab2cc
4
- data.tar.gz: d53478ebcb86c89a407d648c67bfd34dd1f3333f41f7b6e0eac1dcb3e2a25cb6
3
+ metadata.gz: bb2bba5cca25fb6a95bfa820891edb4882595062be6a3a439a0fee8c2515efb1
4
+ data.tar.gz: 52643afdeded2e2944e93124267c009d6c496ab6d645d4878087072f24685e67
5
5
  SHA512:
6
- metadata.gz: 7ef9e4766ad0c786471d08ba6cffcdffaec2d9acf734e25dafa796e714e1103ee838421b3b817c0c732b91c1a238c5fd32c9c3a6f2954926880336b70caab8b9
7
- data.tar.gz: 7e389e83762cedfbdbdf23acbcf821f478a237d8e2b6da6c4299db17d0ade7e760f77328e8f9f59cb10c251f6b1711257793c2b94c12e7ddd8bde2bcad5add66
6
+ metadata.gz: 47db9f266723dc8d292bdc8cbb7006bd8e9da8471bd89e933312231bf26ba90e5c40e7bf3e6eead665918a968f476705936507b66b753debde18391a4fa6936a
7
+ data.tar.gz: 2fdc6b316b2f250644818af70ea40bf68c263776fcbb751ac2b05e03555194a3d86344f28574d3d6efd1a303f0d89c619f7ac199d57468c5e8eddff9f397f123
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.00] - 2023-12-16
6
+ Version bump: Very crude parser implementation (generate shared parse forests in case of ambiguity).
7
+
8
+ ## [0.1.00] - 2023-11-03
9
+ Version bump: the Earley recognizer is functional.
10
+
5
11
  ## [0.0.12] - 2023-11-02
6
12
  Added more tests.
7
13
 
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_formatter'
4
+
5
+ # A formatter class that draws parse trees by using characters
6
+ class Asciitree < BaseFormatter
7
+ # TODO
8
+ attr_reader(:curr_path)
9
+
10
+ # For each node in curr_path, there is a corresponding string value.
11
+ # Allowed string values are: 'first', 'last', 'first_and_last', 'other'
12
+ attr_reader(:ranks)
13
+
14
+ # @return [String] The character pattern used for rendering
15
+ # a parent - child nesting
16
+ attr_reader(:and_nesting_prefix)
17
+
18
+ # TODO: comment
19
+ attr_reader(:or_nesting_prefix)
20
+
21
+ # @return [String] The character pattern used for a blank indentation
22
+ attr_reader(:blank_indent)
23
+
24
+ # @return [String] The character pattern for indentation and nesting
25
+ # continuation.
26
+ attr_reader(:continuation_indent)
27
+
28
+ # Constructor.
29
+ # @param anIO [IO] The output stream to which the parse tree
30
+ # is written.
31
+ def initialize(anIO)
32
+ super(anIO)
33
+ @curr_path = []
34
+ @ranks = []
35
+
36
+ @and_nesting_prefix = '+-- '
37
+ @or_nesting_prefix = '/-- '
38
+ @blank_indent = ' '
39
+ @continuation_indent = '| '
40
+ end
41
+
42
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
43
+ # Notification of a visit event: the visitor is about to visit
44
+ # the children of a non-terminal node
45
+ # @param parent [NonTerminalNode]
46
+ # @param _children [Array<ParseTreeNode>] array of children nodes
47
+ def before_subnodes(parent, _children)
48
+ rank_of(parent)
49
+ curr_path << parent
50
+ end
51
+
52
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
53
+ # Notification of a visit event: the visitor is about to visit
54
+ # a non-terminal node
55
+ # @param aNonTerm [NonTerminalNode]
56
+ def before_and_node(aNonTerm)
57
+ emit_and(aNonTerm)
58
+ end
59
+
60
+ def before_or_node(aNonTerm)
61
+ emit_or(aNonTerm)
62
+ end
63
+
64
+ def before_empty_rule_node(anEmptyRuleNode)
65
+ emit_and(anEmptyRuleNode, ': .')
66
+ end
67
+
68
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
69
+ # Notification of a visit event: the visitor is about to visit
70
+ # a terminal node
71
+ # @param aTerm [TerminalNode]
72
+ def before_terminal(aTerm)
73
+ emit_terminal(aTerm, ": '#{aTerm.token.source}'")
74
+ end
75
+
76
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
77
+ # Notification of a visit event: the visitor completed the visit of
78
+ # the children of a non-terminal node.
79
+ # @param _parent [NonTerminalNode]
80
+ # @param _children [Array] array of children nodes
81
+ def after_subnodes(_parent, _children)
82
+ curr_path.pop
83
+ ranks.pop
84
+ end
85
+
86
+ private
87
+
88
+ # Parent node is last node in current path
89
+ # or current path is empty (then aChild is root node)
90
+ def rank_of(aChild)
91
+ if curr_path.empty?
92
+ rank = 'root'
93
+ elsif curr_path[-1].children.size == 1
94
+ rank = 'first_and_last'
95
+ else
96
+ parent = curr_path[-1]
97
+ siblings = parent.children
98
+ siblings_last_index = siblings.size - 1
99
+ rank = case siblings.find_index(aChild)
100
+ when 0 then 'first'
101
+ when siblings_last_index then 'last'
102
+ else
103
+ 'other'
104
+ end
105
+ end
106
+ ranks << rank
107
+ end
108
+
109
+ # 'root', 'first', 'first_and_last', 'last', 'other'
110
+ def path_prefix(connector)
111
+ return '' if ranks.empty?
112
+
113
+ prefix = +''
114
+ @ranks.each_with_index do |rank, i|
115
+ next if i.zero?
116
+
117
+ case rank
118
+ when 'first', 'other'
119
+ prefix << continuation_indent
120
+
121
+ when 'last', 'first_and_last', 'root'
122
+ prefix << blank_indent
123
+ end
124
+ end
125
+
126
+ nesting = (connector == :and) ? and_nesting_prefix : or_nesting_prefix
127
+ prefix << nesting
128
+ prefix
129
+ end
130
+
131
+ def emit_and(aNode, aSuffix = '')
132
+ output.puts("#{path_prefix(:and)}#{aNode.rule.lhs.name}#{aSuffix}")
133
+ end
134
+
135
+ def emit_or(aNode, aSuffix = '')
136
+ output.puts("#{path_prefix(:or)}OR #{aNode.symbol.name}#{aSuffix}")
137
+ end
138
+
139
+ def emit_terminal(aNode, aSuffix = '')
140
+ output.puts("#{path_prefix(:and)}#{aNode.symbol.name}#{aSuffix}")
141
+ end
142
+ end # class
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ # Superclass for parse tree formatters.
5
+ class BaseFormatter
6
+ # The IO output stream in which the formatter's result will be sent.
7
+ # @return [IO] The output stream for the formatter.
8
+ attr_reader(:output)
9
+
10
+ # Constructor.
11
+ # @param anIO [IO] an output IO where the formatter's result will
12
+ # be placed.
13
+ def initialize(anIO)
14
+ @output = anIO
15
+ end
16
+
17
+ # Given a parse tree visitor, perform the visit
18
+ # and render the visit events in the output stream.
19
+ # @param aVisitor [ParseTreeVisitor]
20
+ def render(aVisitor)
21
+ aVisitor.subscribe(self)
22
+ aVisitor.start
23
+ aVisitor.unsubscribe(self)
24
+ end
25
+ end # class
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_formatter'
4
+
5
+ class BracketNotation < BaseFormatter
6
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
7
+ # Notification of a visit event: the visitor is about to visit
8
+ # a non-terminal node
9
+ # @param and_node [ANDNode]
10
+ def before_and_node(and_node)
11
+ write("[#{and_node.rule.lhs.name} ")
12
+ end
13
+
14
+ def before_empty_rule_node(anEmptyRuleNode)
15
+ write("[#{anEmptyRuleNode.rule.lhs.name}]")
16
+ end
17
+
18
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
19
+ # Notification of a visit event: the visitor is about to visit
20
+ # a terminal node
21
+ # @param aTerm [TerminalNode]
22
+ def before_terminal(aTerm)
23
+ write("[#{aTerm.symbol.name} ")
24
+ end
25
+
26
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
27
+ # Notification of a visit event: the visitor completed the visit of
28
+ # a terminal node.
29
+ # @param aTerm [TerminalNode]
30
+ def after_terminal(aTerm)
31
+ # Escape all opening and closing square brackets
32
+ escape_lbrackets = aTerm.token.source.gsub(/\[/, '\[')
33
+ escaped = escape_lbrackets.gsub(/\]/, '\]')
34
+ write("#{escaped}]")
35
+ end
36
+
37
+ # Method called by a ParseTreeVisitor to which the formatter subscribed.
38
+ # Notification of a visit event: the visitor completed the visit of
39
+ # a non-terminal node
40
+ # @param _nonterm [NonTerminalNode]
41
+ def after_and_node(_nonterm)
42
+ write(']')
43
+ end
44
+
45
+ private
46
+
47
+ def write(aText)
48
+ output.write(aText)
49
+ end
50
+ end # class
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'weakref'
4
+
3
5
  module Dendroid
4
- # This module contains classes that from the analysis of grammar rules help to build objects
5
- # needed by a recognizer or a parser for the language.
6
6
  module GrmAnalysis
7
7
  # For a given production rule, a dotted item represents a recognition state.
8
8
  # The dot partitions the rhs of the rule in two parts:
@@ -20,42 +20,40 @@ module Dendroid
20
20
  # An item with a dot in front of a terminal is called a shift item.
21
21
  # An item with the dot not at the beginning is sometimes referred to as a kernel item
22
22
  class DottedItem
23
- # Reference to the production rule
23
+ # (Weak) reference to the production rule
24
24
  # @return [Dendroid::Syntax::Production]
25
25
  attr_reader :rule
26
26
 
27
27
  # @return [Integer] the dot position
28
28
  attr_reader :position
29
29
 
30
+ # @return [Integer] the alternative number
31
+ attr_reader :alt_index
32
+
30
33
  # Constructor.
31
- # @param aRule [Dendroid::Syntax::Rule]
34
+ # @param aChoice [Dendroid::Syntax::Rule]
32
35
  # @param aPosition [Integer] Position of the dot in rhs of production.
33
- def initialize(aRule, aPosition)
34
- @rule = aRule
36
+ # @param index [Integer] the rank of the alternative at hand
37
+ def initialize(aChoice, aPosition, index)
38
+ @alt_index = index
39
+ @rule = WeakRef.new(aChoice)
35
40
  @position = valid_position(aPosition)
36
41
  end
37
42
 
38
- # Return a String representation of the dotted item.
43
+ # Return a String representation of the alternative item.
39
44
  # @return [String]
40
45
  def to_s
41
- rhs_names = rule.body.map(&:to_s)
46
+ rhs_names = rule.alternatives[alt_index].members.map(&:to_s)
42
47
  dotted_rhs = rhs_names.insert(position, '.')
43
48
  "#{rule.head} => #{dotted_rhs.join(' ')}"
44
49
  end
45
50
 
46
- # Indicate whether the rhs of the rule is empty
51
+ alias inspect to_s
52
+
53
+ # Indicate whether the rhs of the alternative is empty
47
54
  # @return [Boolean]
48
55
  def empty?
49
- rule.empty?
50
- end
51
-
52
- # Terminology inspired from Luger's book
53
- # @return [Symbol] one of: :initial, :initial_and_completed, :partial, :completed
54
- def state
55
- return :initial_and_completed if empty?
56
- return :initial if position.zero?
57
-
58
- position == rule.body.size ? :completed : :partial
56
+ rule.alternatives[alt_index].empty?
59
57
  end
60
58
 
61
59
  # Indicate whether the dot is at the start of rhs
@@ -64,28 +62,46 @@ module Dendroid
64
62
  position.zero? || empty?
65
63
  end
66
64
 
67
- # Indicate whether the dot is at the end of rhs
65
+ # Indicate the dot isn't at start nor at end position
66
+ # @return [Boolean]
67
+ def intermediate_pos?
68
+ return false if empty? || position.zero?
69
+
70
+ position < rule.alternatives[alt_index].size
71
+ end
72
+
73
+ # Indicate whether the dot is at the start of rhs
68
74
  # @return [Boolean]
69
75
  def final_pos?
70
- empty? || position == rule.body.size
76
+ empty? || position == rule.alternatives[alt_index].size
71
77
  end
72
78
 
73
79
  alias completed? final_pos?
74
80
 
75
- # Indicate the dot isn't at start nor at end position
76
- # @return [Boolean]
77
- def intermediate_pos?
78
- return false if empty? || position.zero?
81
+ # Terminology inspired from Luger's book
82
+ # @return [Symbol] one of: :initial, :initial_and_completed, :partial, :completed
83
+ def state
84
+ return :initial_and_completed if empty?
85
+ return :initial if position.zero?
79
86
 
80
- position < rule.body.size
87
+ position == rule.alternatives[alt_index].size ? :completed : :partial
81
88
  end
82
89
 
90
+
83
91
  # Return the symbol right after the dot (if any)
84
92
  # @return [Dendroid::Syntax::GrmSymbol, NilClass]
85
93
  def next_symbol
86
94
  return nil if empty? || completed?
87
95
 
88
- rule.body[position]
96
+ rule.alternatives[alt_index].members[position]
97
+ end
98
+
99
+ # Return the symbol right before the dot (if any)
100
+ # @return [Dendroid::Syntax::GrmSymbol, NilClass]
101
+ def prev_symbol
102
+ return nil if empty? || position.zero?
103
+
104
+ rule.alternatives[alt_index].members[position - 1]
89
105
  end
90
106
 
91
107
  # Check whether the given symbol is the same as after the dot.
@@ -99,7 +115,7 @@ module Dendroid
99
115
  end
100
116
 
101
117
  # Check whether the dotted item is a shift item.
102
- # In other words, it expects a terminal to be next symbol
118
+ # In other words, it expects a terminal to be the next symbol
103
119
  # @return [Boolean]
104
120
  def pre_scan?
105
121
  next_symbol&.terminal?
@@ -112,13 +128,13 @@ module Dendroid
112
128
  def ==(other)
113
129
  return true if eql?(other)
114
130
 
115
- (position == other.position) && rule.eql?(other.rule)
131
+ (position == other.position) && rule.eql?(other.rule) && (alt_index == other.alt_index)
116
132
  end
117
133
 
118
134
  private
119
135
 
120
136
  def valid_position(aPosition)
121
- raise StandardError if aPosition.negative? || aPosition > rule.body.size
137
+ raise StandardError if aPosition.negative? || aPosition > rule.alternatives[alt_index].size
122
138
 
123
139
  aPosition
124
140
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../grm_analysis/production_items'
4
- require_relative '../grm_analysis/choice_items'
3
+ require_relative '../grm_analysis/rule_items.rb'
5
4
 
6
5
  module Dendroid
7
6
  module GrmAnalysis
@@ -13,7 +12,6 @@ module Dendroid
13
12
  attr_reader :grammar
14
13
  attr_reader :items
15
14
  attr_reader :production2items
16
- attr_reader :symbol2productions
17
15
 
18
16
  # @return [Dendroid::Syntax::Terminal] The pseudo-terminal `__epsilon` (for empty string)
19
17
  attr_reader :epsilon
@@ -37,7 +35,6 @@ module Dendroid
37
35
  @grammar = aGrammar
38
36
  @items = []
39
37
  @production2items = {}
40
- @symbol2productions = {}
41
38
  @epsilon = Syntax::Terminal.new(:__epsilon)
42
39
  @endmarker = Syntax::Terminal.new(:"$$")
43
40
  @first_sets = {}
@@ -56,16 +53,15 @@ module Dendroid
56
53
  prod.next_item(aDottedItem)
57
54
  end
58
55
 
56
+ def symbol2production(sym)
57
+ grammar.nonterm2production[sym]
58
+ end
59
+
59
60
  private
60
61
 
61
62
  def build_dotted_items
62
63
  grammar.rules.each do |prod|
63
- lhs = prod.head
64
- symbol2productions[lhs] = [] unless symbol2productions.include? lhs
65
- symbol2productions[lhs] << prod
66
- # production2items[prod] = []
67
- mixin = prod.choice? ? ChoiceItems : ProductionItems
68
- prod.extend(mixin)
64
+ prod.extend(RuleItems)
69
65
  prod.build_items
70
66
  rule_items = prod.items.flatten
71
67
  items.concat(rule_items)
@@ -76,33 +72,31 @@ module Dendroid
76
72
  def build_first_sets
77
73
  initialize_first_sets
78
74
 
79
- begin
75
+ loop do
80
76
  changed = false
81
77
  grammar.rules.each do |prod|
82
78
  head = prod.head
83
79
  first_head = first_sets[head]
84
80
  pre_first_size = first_head.size
85
- if prod.choice?
86
- prod.alternatives.each do |alt|
87
- first_head.merge(sequence_first(alt.members))
88
- end
89
- else
90
- first_head.merge(sequence_first(prod.body.members))
81
+ prod.rhs.each do |seq|
82
+ first_head.merge(sequence_first(seq.members))
91
83
  end
92
84
  changed = true if first_head.size > pre_first_size
93
85
  end
94
- end until !changed
86
+ break unless changed
87
+ end
95
88
  end
96
89
 
97
90
  def initialize_first_sets
98
91
  grammar.symbols.each do |symb|
99
- if symb.terminal?
100
- first_sets[symb] = Set.new([symb])
101
- elsif symb.nullable?
102
- first_sets[symb] = Set.new([epsilon])
103
- else
104
- first_sets[symb] = Set.new
105
- end
92
+ set_arg = if symb.terminal?
93
+ [symb]
94
+ elsif symb.nullable?
95
+ [epsilon]
96
+ else
97
+ nil
98
+ end
99
+ first_sets[symb] = Set.new(set_arg)
106
100
  end
107
101
  end
108
102
 
@@ -122,43 +116,11 @@ module Dendroid
122
116
  def build_follow_sets
123
117
  initialize_follow_sets
124
118
 
125
- begin
119
+ loop do
126
120
  changed = false
127
121
  grammar.rules.each do |prod|
128
- if prod.choice?
129
- prod.alternatives.each do |alt|
130
- body = alt.members
131
- next if body.empty?
132
-
133
- head = prod.head
134
- head_follow = follow_sets[head]
135
- # trailer = Set.new
136
- last = true
137
- last_index = body.size - 1
138
- last_index.downto(0) do |i|
139
- symbol = body[i]
140
- next if symbol.terminal?
141
-
142
- follow_symbol = follow_sets[symbol]
143
- size_before = follow_symbol.size
144
- if last
145
- # Rule: if last non-terminal member (symbol) is nullable
146
- # then add FOLLOW(head) to FOLLOW(symbol)
147
- follow_sets[symbol].merge(head_follow) if symbol.nullable?
148
- last = false
149
- else
150
- symbol_seq = body.slice(i + 1, last_index - i)
151
- trailer_first = sequence_first(symbol_seq)
152
- contains_epsilon = trailer_first.include? epsilon
153
- trailer_first.delete(epsilon) if contains_epsilon
154
- follow_sets[symbol].merge(trailer_first)
155
- follow_sets[symbol].merge(head_follow) if contains_epsilon
156
- end
157
- changed = true if follow_sets[symbol].size > size_before
158
- end
159
- end
160
- else
161
- body = prod.body.members
122
+ prod.rhs.each do |alt|
123
+ body = alt.members
162
124
  next if body.empty?
163
125
 
164
126
  head = prod.head
@@ -189,7 +151,8 @@ module Dendroid
189
151
  end
190
152
  end
191
153
  end
192
- end until !changed
154
+ break unless changed
155
+ end
193
156
  end
194
157
 
195
158
  def initialize_follow_sets
@@ -1,24 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'alternative_item'
3
+ require_relative 'dotted_item.rb'
4
4
 
5
5
  module Dendroid
6
6
  module GrmAnalysis
7
7
  # Mix-in module for extending the Syntax::Choice class
8
8
  # with dotted items manipulation methods
9
- module ChoiceItems
9
+ module RuleItems
10
10
  # Build the alternative items for this choice and assign them
11
11
  # to the `items` attributes
12
- # @return [Array<Array<GrmAnalysis::AlternativeItem>>]
12
+ # @return [Array<Array<GrmAnalysis::Dottedtem>>]
13
13
  def build_items
14
14
  # AlternativeItem
15
15
  @items = Array.new(alternatives.size) { |_| [] }
16
16
  alternatives.each_with_index do |alt_seq, index|
17
17
  if alt_seq.empty?
18
- @items[index] << AlternativeItem.new(self, 0, index)
18
+ @items[index] << DottedItem.new(self, 0, index)
19
19
  else
20
20
  (0..alt_seq.size).each do |pos|
21
- @items[index] << AlternativeItem.new(self, pos, index)
21
+ @items[index] << DottedItem.new(self, pos, index)
22
22
  end
23
23
  end
24
24
  end
@@ -26,29 +26,29 @@ module Dendroid
26
26
 
27
27
  # Read accessor for the `items` attribute.
28
28
  # Return the dotted items for this production
29
- # @return [Array<Array<GrmAnalysis::AlternativeItem>>]
29
+ # @return [Array<Array<GrmAnalysis::Dottedtem>>]
30
30
  def items
31
31
  @items
32
32
  end
33
33
 
34
34
  # Return the predicted items (i.e. the alternative items with the dot at start)
35
35
  # for this choice.
36
- # @return [Array<GrmAnalysis::AlternativeItem>]
36
+ # @return [Array<GrmAnalysis::Dottedtem>]
37
37
  def predicted_items
38
38
  @items.map(&:first)
39
39
  end
40
40
 
41
41
  # Return the reduce items (i.e. the alternative items with the dot at end)
42
42
  # for this choice.
43
- # @return [Array<GrmAnalysis::AlternativeItem>]
43
+ # @return [Array<GrmAnalysis::Dottedtem>]
44
44
  def reduce_items
45
45
  @items.map(&:last)
46
46
  end
47
47
 
48
48
  # Return the next item given the provided item.
49
49
  # In other words, advance the dot by one position.
50
- # @param anItem [GrmAnalysis::AlternativeItem]
51
- # @return [GrmAnalysis::AlternativeItem|NilClass]
50
+ # @param anItem [GrmAnalysis::Dottedtem]
51
+ # @return [GrmAnalysis::Dottedtem|NilClass]
52
52
  def next_item(anItem)
53
53
  items_arr = items[anItem.alt_index]
54
54
  return nil if anItem == items_arr.last
@@ -3,8 +3,7 @@
3
3
  require_relative '..\syntax\terminal'
4
4
  require_relative '..\syntax\non_terminal'
5
5
  require_relative '..\syntax\symbol_seq'
6
- require_relative '..\syntax\production'
7
- require_relative '..\syntax\choice'
6
+ require_relative '..\syntax\rule'
8
7
  require_relative '..\syntax\grammar'
9
8
 
10
9
  module Dendroid
@@ -90,10 +89,10 @@ module Dendroid
90
89
  raw_rhs = productionRuleRepr.values.first
91
90
 
92
91
  if raw_rhs.is_a? String
93
- new_prod = Dendroid::Syntax::Production.new(lhs, build_symbol_seq(raw_rhs))
92
+ new_prod = Dendroid::Syntax::Rule.new(lhs, [build_symbol_seq(raw_rhs)])
94
93
  else
95
94
  rhs = raw_rhs.map { |raw| build_symbol_seq(raw) }
96
- new_prod = Dendroid::Syntax::Choice.new(lhs, rhs)
95
+ new_prod = Dendroid::Syntax::Rule.new(lhs, rhs)
97
96
  end
98
97
  rules << new_prod
99
98
  new_prod
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'composite_parse_node'
4
+
5
+ module Dendroid
6
+ module Parsing
7
+ class ANDNode < CompositeParseNode
8
+ attr_reader :rule
9
+ attr_reader :alt_index
10
+
11
+ def initialize(anEItem, rank)
12
+ @rule = WeakRef.new(anEItem.dotted_item.rule)
13
+ @alt_index = anEItem.dotted_item.alt_index
14
+ upper_bound = rank
15
+ super(anEItem.origin, upper_bound, rule.rhs[alt_index].size)
16
+ end
17
+
18
+ def add_child(child_node, index)
19
+ if children[index].nil? # Is slot available?
20
+ super(child_node, index)
21
+ else
22
+ raise StandardError
23
+ end
24
+ end
25
+
26
+ def match(anEItem)
27
+ return false if range[0] != anEItem.origin
28
+
29
+ dotted = anEItem.dotted_item
30
+ same_rule = (rule.lhs == dotted.rule.lhs) && (alt_index == dotted.alt_index)
31
+ return false unless same_rule
32
+
33
+ dotted.initial_pos? ? true : partial?
34
+ end
35
+
36
+ def expecting?(symbol, position)
37
+ symb_seq = rule.rhs[alt_index]
38
+ symb_seq[position] == symbol
39
+ end
40
+
41
+ def partial?
42
+ children.any?(&:nil?)
43
+ end
44
+
45
+ def to_s
46
+ "#{rule.lhs} => #{rule.rhs[alt_index]} #{range}"
47
+ end
48
+
49
+ # Part of the 'visitee' role in Visitor design pattern.
50
+ # @param aVisitor[ParseTreeVisitor] the visitor
51
+ def accept(aVisitor)
52
+ aVisitor.visit_and_node(self)
53
+ end
54
+ end # class
55
+ end # module
56
+ end # module