rley 0.4.01 → 0.4.02

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE.txt +2 -2
  4. data/README.md +3 -3
  5. data/examples/NLP/mini_en_demo.rb +1 -1
  6. data/examples/data_formats/JSON/JSON_demo.rb +1 -0
  7. data/examples/data_formats/JSON/JSON_lexer.rb +4 -4
  8. data/examples/general/calc/calc_lexer.rb +2 -2
  9. data/lib/rley.rb +1 -1
  10. data/lib/rley/constants.rb +1 -1
  11. data/lib/rley/formatter/debug.rb +2 -2
  12. data/lib/rley/formatter/json.rb +4 -4
  13. data/lib/rley/parse_tree_visitor.rb +9 -9
  14. data/lib/rley/parser/base_parser.rb +1 -1
  15. data/lib/rley/parser/gfg_parsing.rb +9 -0
  16. data/lib/rley/parser/parse_tree_builder.rb +176 -126
  17. data/lib/rley/parser/parse_tree_factory.rb +57 -0
  18. data/lib/rley/ptree/non_terminal_node.rb +10 -9
  19. data/lib/rley/ptree/parse_tree_node.rb +10 -5
  20. data/lib/rley/ptree/terminal_node.rb +14 -6
  21. data/lib/rley/sppf/sppf_node.rb +2 -2
  22. data/lib/rley/{parser → tokens}/token.rb +1 -4
  23. data/lib/rley/{ptree → tokens}/token_range.rb +1 -1
  24. data/spec/rley/formatter/debug_spec.rb +16 -16
  25. data/spec/rley/formatter/json_spec.rb +8 -8
  26. data/spec/rley/parse_forest_visitor_spec.rb +1 -1
  27. data/spec/rley/parse_tree_visitor_spec.rb +28 -28
  28. data/spec/rley/parser/error_reason_spec.rb +3 -3
  29. data/spec/rley/parser/gfg_chart_spec.rb +2 -2
  30. data/spec/rley/parser/gfg_earley_parser_spec.rb +2 -2
  31. data/spec/rley/parser/gfg_parsing_spec.rb +2 -2
  32. data/spec/rley/parser/groucho_spec.rb +1 -1
  33. data/spec/rley/parser/parse_tracer_spec.rb +2 -2
  34. data/spec/rley/parser/parse_tree_builder_spec.rb +213 -140
  35. data/spec/rley/parser/parse_tree_factory_spec.rb +85 -0
  36. data/spec/rley/parser/parse_walker_factory_spec.rb +11 -10
  37. data/spec/rley/ptree/non_terminal_node_spec.rb +23 -20
  38. data/spec/rley/ptree/terminal_node_spec.rb +7 -12
  39. data/spec/rley/sppf/alternative_node_spec.rb +2 -2
  40. data/spec/rley/sppf/non_terminal_node_spec.rb +2 -2
  41. data/spec/rley/support/ambiguous_grammar_helper.rb +2 -2
  42. data/spec/rley/support/expectation_helper.rb +1 -1
  43. data/spec/rley/support/grammar_ambig01_helper.rb +2 -2
  44. data/spec/rley/support/grammar_b_expr_helper.rb +2 -2
  45. data/spec/rley/support/grammar_helper.rb +3 -3
  46. data/spec/rley/support/grammar_l0_helper.rb +2 -2
  47. data/spec/rley/support/grammar_pb_helper.rb +2 -2
  48. data/spec/rley/{ptree → tokens}/token_range_spec.rb +2 -2
  49. data/spec/rley/{parser → tokens}/token_spec.rb +2 -2
  50. metadata +11 -17
  51. data/lib/rley/parser/chart.rb +0 -82
  52. data/lib/rley/parser/earley_parser.rb +0 -203
  53. data/lib/rley/parser/parsing.rb +0 -265
  54. data/spec/rley/parser/chart_spec.rb +0 -120
  55. data/spec/rley/parser/earley_parser_spec.rb +0 -710
  56. data/spec/rley/parser/parsing_spec.rb +0 -408
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 17e935e339154ef6729bc4e4679c9ee9c2e81bba
4
- data.tar.gz: 479a0cbdc90d6137780983e4eb5b64026e5e844b
3
+ metadata.gz: cd7c7f9e941ac7139080960dca0cb2a16665bcbd
4
+ data.tar.gz: dd4e2a8dae9fd1958427beab6c63b0e9206834e7
5
5
  SHA512:
6
- metadata.gz: 453d7539813b80a7664d71dbfedc2cda89efd09be1db52515a3f5dd7fd314f61e89b7659b88c48ce08f947f8cd614fbf0cab8c9193c653a92da8cf9b76d5faf0
7
- data.tar.gz: c14955b02a089aa48dba40b5d97e42aeed880e16dd846d5451af85dad387e581060c0c742b58f3365f7c0305144a1f48a50a3ad7af8feb9b9f4336faff839d85
6
+ metadata.gz: 7611109ca8b7d99a090e88de7eea31cc08c22250d790c2befd3b3aaa9e222450ac900d10ab733d0ee72e831da0bf7a7ad07da6f6e33e20724879659a2e45d620
7
+ data.tar.gz: f81cc80909844e4cbd50ec85ad803b08483e714f78734b234de13b544f891f77759cdb8df4349dea1c68a8de250d44ef860e0a8bf27dd6313da08f0cc3c49b34
@@ -1,3 +1,7 @@
1
+ ### 0.4.02 / 2017-04-09
2
+ * [NEW] Module re-organization for clearer dependencies: Classes `Token` and `TokenRange` are moved to a separate module `Tokens`.
3
+ * [CHANGE] Code, specs, examples and `README.md` adapted to reflect the module re-organization.
4
+
1
5
  ### 0.4.01 / 2016-12-21
2
6
  * [NEW] File `appveyor.yml`. Add AppVeyor CI to Github commits. AppVeyor complements Travis by running builds under Windows OS.
3
7
  This permits to test the portability across operating systems.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2016 Dimitri Geshef
1
+ Copyright (c) 2014-2017 Dimitri Geshef
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
@@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
16
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
17
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
18
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- THE SOFTWARE.
19
+ THE SOFTWARE.
data/README.md CHANGED
@@ -75,7 +75,7 @@ Installing the latest stable version is simple:
75
75
  ## A whirlwind tour of Rley
76
76
  The purpose of this section is show how to create a parser for a minimalistic
77
77
  English language subset.
78
- The tour is organized into the following steps:
78
+ The tour is organized as follows:
79
79
  1. [Defining the language grammar](#defining-the-language-grammar)
80
80
  2. [Creating a lexicon](#creating-a-lexicon)
81
81
  3. [Creating a tokenizer](#creating-a-tokenizer)
@@ -152,7 +152,7 @@ The subset of English grammar is based on an example from the NLTK book.
152
152
  raise StandardError, "Word '#{word}' not found in lexicon"
153
153
  end
154
154
  terminal = aGrammar.name2symbol[term_name]
155
- Rley::Parser::Token.new(word, terminal)
155
+ Rley::Tokens::Token.new(word, terminal)
156
156
  end
157
157
 
158
158
  return tokens
@@ -283,5 +283,5 @@ Here are a few other ones:
283
283
 
284
284
  Copyright
285
285
  ---------
286
- Copyright (c) 2014-2016, Dimitri Geshef.
286
+ Copyright (c) 2014-2017, Dimitri Geshef.
287
287
  __Rley__ is released under the MIT License see [LICENSE.txt](https://github.com/famished-tiger/Rley/blob/master/LICENSE.txt) for details.
@@ -64,7 +64,7 @@ def tokenizer(aTextToParse, aGrammar)
64
64
  raise StandardError, "Word '#{word}' not found in lexicon"
65
65
  end
66
66
  terminal = aGrammar.name2symbol[term_name]
67
- Rley::Parser::Token.new(word, terminal)
67
+ Rley::Tokens::Token.new(word, terminal)
68
68
  end
69
69
 
70
70
  return tokens
@@ -29,4 +29,5 @@ end
29
29
 
30
30
  # Generate a parse forest from the parse result
31
31
  pforest = result.parse_forest
32
+ # puts pforest.ambiguous?
32
33
  # End of file
@@ -51,7 +51,7 @@ private
51
51
  when '{', '}', '[', ']', ',', ':'
52
52
  type_name = @@lexeme2name[curr_ch]
53
53
  token_type = name2symbol[type_name]
54
- token = Rley::Parser::Token.new(curr_ch, token_type)
54
+ token = Rley::Tokens::Token.new(curr_ch, token_type)
55
55
 
56
56
  # LITERALS
57
57
  when '"' # Start string delimiter found
@@ -59,7 +59,7 @@ private
59
59
  end_delimiter = scanner.getch()
60
60
  raise ScanError.new('No closing quotes (") found') if end_delimiter.nil?
61
61
  token_type = name2symbol['JSON_STRING']
62
- token = Rley::Parser::Token.new(value, token_type)
62
+ token = Rley::Tokens::Token.new(value, token_type)
63
63
 
64
64
  when /[ftn]/ # First letter of keywords
65
65
  @scanner.pos = scanner.pos - 1 # Simulate putback
@@ -69,7 +69,7 @@ private
69
69
  raise ScanError.new("Invalid keyword: #{invalid_keyw}")
70
70
  else
71
71
  token_type = name2symbol['KEYWORD']
72
- token = Rley::Parser::Token.new(keyw, token_type)
72
+ token = Rley::Tokens::Token.new(keyw, token_type)
73
73
  end
74
74
 
75
75
 
@@ -77,7 +77,7 @@ private
77
77
  @scanner.pos = scanner.pos - 1 # Simulate putback
78
78
  value = scanner.scan(/-?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9])?/)
79
79
  token_type = name2symbol['JSON_NUMBER']
80
- token = Rley::Parser::Token.new(value, token_type)
80
+ token = Rley::Tokens::Token.new(value, token_type)
81
81
 
82
82
 
83
83
  else # Unknown token
@@ -51,14 +51,14 @@ private
51
51
  when '(', ')', '+', '-', '*', '/'
52
52
  type_name = @@lexeme2name[curr_ch]
53
53
  token_type = name2symbol[type_name]
54
- token = Rley::Parser::Token.new(curr_ch, token_type)
54
+ token = Rley::Tokens::Token.new(curr_ch, token_type)
55
55
 
56
56
  # LITERALS
57
57
  when /[-0-9]/ # Start character of number literal found
58
58
  @scanner.pos = scanner.pos - 1 # Simulate putback
59
59
  value = scanner.scan(/-?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9])?/)
60
60
  token_type = name2symbol['NUMBER']
61
- token = Rley::Parser::Token.new(value, token_type)
61
+ token = Rley::Tokens::Token.new(value, token_type)
62
62
 
63
63
 
64
64
  else # Unknown token
@@ -4,7 +4,7 @@
4
4
 
5
5
  require_relative './rley/constants'
6
6
  require_relative './rley/syntax/grammar_builder'
7
- require_relative './rley/parser/token'
7
+ require_relative './rley/tokens/token'
8
8
  require_relative './rley/parser/earley_parser'
9
9
  require_relative './rley/parser/gfg_earley_parser'
10
10
  require_relative './rley/parse_tree_visitor'
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Rley # Module used as a namespace
5
5
  # The version number of the gem.
6
- Version = '0.4.01'.freeze
6
+ Version = '0.4.02'.freeze
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = "Ruby implementation of the Earley's parsing algorithm".freeze
@@ -41,7 +41,7 @@ module Rley # This module is used as a namespace
41
41
  # the children of a non-terminal node
42
42
  # @param _parent [NonTerminalNode]
43
43
  # @param _children [Array] array of children nodes
44
- def before_children(_parent, _children)
44
+ def before_subnodes(_parent, _children)
45
45
  output_event(__method__, indentation)
46
46
  indent
47
47
  end
@@ -76,7 +76,7 @@ module Rley # This module is used as a namespace
76
76
  # the children of a non-terminal node.
77
77
  # @param _parent [NonTerminalNode]
78
78
  # @param _children [Array] array of children nodes
79
- def after_children(_parent, _children)
79
+ def after_subnodes(_parent, _children)
80
80
  dedent
81
81
  output_event(__method__, indentation)
82
82
  end
@@ -48,8 +48,8 @@ module Rley # This module is used as a namespace
48
48
  # Notification of a visit event: the visitor is about to visit
49
49
  # the children of a non-terminal node
50
50
  # @param _parent [NonTerminalNode]
51
- # @param _children [Array] array of children nodes
52
- def before_children(_parent, _children)
51
+ # @param _subnodes [Array] array of children nodes
52
+ def before_subnodes(_parent, _subnodes)
53
53
  print_text('[', nil)
54
54
  indent
55
55
  sibling_flags.push(false)
@@ -73,8 +73,8 @@ module Rley # This module is used as a namespace
73
73
  # Notification of a visit event: the visitor completed the visit of
74
74
  # the children of a non-terminal node.
75
75
  # @param _parent [NonTerminalNode]
76
- # @param _children [Array] array of children nodes
77
- def after_children(_parent, _children)
76
+ # @param _subnodes [Array] array of children nodes
77
+ def after_subnodes(_parent, _subnodes)
78
78
  sibling_flags.pop
79
79
  print_text("\n", ']')
80
80
  dedent
@@ -50,9 +50,9 @@ module Rley # This module is used as a namespace
50
50
  def visit_nonterminal(aNonTerminalNode)
51
51
  if @traversal == :post_order
52
52
  broadcast(:before_non_terminal, aNonTerminalNode)
53
- traverse_children(aNonTerminalNode)
53
+ traverse_subnodes(aNonTerminalNode)
54
54
  else
55
- traverse_children(aNonTerminalNode)
55
+ traverse_subnodes(aNonTerminalNode)
56
56
  broadcast(:before_non_terminal, aNonTerminalNode)
57
57
  end
58
58
  broadcast(:after_non_terminal, aNonTerminalNode)
@@ -82,17 +82,17 @@ module Rley # This module is used as a namespace
82
82
 
83
83
  private
84
84
 
85
- # Visit event. The visitor is about to visit the children of a non
85
+ # Visit event. The visitor is about to visit the subnodes of a non
86
86
  # terminal node.
87
87
  # @param aParentNode [NonTeminalNode] the (non-terminal) parent node.
88
- def traverse_children(aParentNode)
89
- children = aParentNode.children
90
- broadcast(:before_children, aParentNode, children)
88
+ def traverse_subnodes(aParentNode)
89
+ subnodes = aParentNode.subnodes
90
+ broadcast(:before_subnodes, aParentNode, subnodes)
91
91
 
92
- # Let's proceed with the visit of children
93
- children.each { |a_node| a_node.accept(self) }
92
+ # Let's proceed with the visit of subnodes
93
+ subnodes.each { |a_node| a_node.accept(self) }
94
94
 
95
- broadcast(:after_children, aParentNode, children)
95
+ broadcast(:after_subnodes, aParentNode, subnodes)
96
96
  end
97
97
 
98
98
  # Send a notification to all subscribers.
@@ -1,7 +1,7 @@
1
1
  require_relative '../syntax/grammar'
2
2
  require_relative 'grm_items_builder' # Use mix-in module
3
3
  require_relative 'parse_tracer'
4
- require_relative 'parsing'
4
+
5
5
 
6
6
  module Rley # This module is used as a namespace
7
7
  module Parser # This module is used as a namespace
@@ -2,6 +2,7 @@ require_relative 'gfg_chart'
2
2
  require_relative 'error_reason'
3
3
  require_relative 'parse_entry_tracker'
4
4
  require_relative 'parse_forest_factory'
5
+ require_relative 'parse_tree_factory'
5
6
 
6
7
 
7
8
  module Rley # This module is used as a namespace
@@ -148,6 +149,14 @@ module Rley # This module is used as a namespace
148
149
 
149
150
  return factory.build_parse_forest
150
151
  end
152
+
153
+ # Factory method. Builds a ParseTree from the parse result.
154
+ # @return [ParseTree]
155
+ def parse_tree()
156
+ factory = ParseTreeFactory.new(self)
157
+
158
+ return factory.build_parse_tree
159
+ end
151
160
 
152
161
  # Retrieve the very first parse entry added to the chart.
153
162
  # This entry corresponds to the start vertex of the GF graph
@@ -1,174 +1,224 @@
1
- require_relative '../ptree/terminal_node'
1
+ require_relative '../syntax/terminal'
2
+ require_relative '../syntax/non_terminal'
3
+ require_relative '../gfg/end_vertex'
4
+ require_relative '../gfg/item_vertex'
5
+ require_relative '../gfg/start_vertex'
2
6
  require_relative '../ptree/non_terminal_node'
7
+ require_relative '../ptree/terminal_node'
3
8
  require_relative '../ptree/parse_tree'
4
9
 
5
-
6
10
  module Rley # This module is used as a namespace
7
11
  module Parser # This module is used as a namespace
8
12
  # Builder GoF pattern. Builder pattern builds a complex object
9
13
  # (say, a parse tree) from simpler objects (terminal and non-terminal
10
14
  # nodes) and using a step by step approach.
11
15
  class ParseTreeBuilder
12
- attr_reader(:root)
13
- attr_reader(:current_path)
14
-
15
- def initialize(aStartProduction, aRange)
16
- @current_path = []
17
- start_symbol = aStartProduction.lhs
18
- add_node(start_symbol, aRange)
19
- use_production(aStartProduction, aRange)
20
- move_down
21
- end
16
+ # The sequence of input tokens
17
+ attr_reader(:tokens)
22
18
 
23
- # Return the active node.
24
- def current_node()
25
- return current_path.last
26
- end
19
+ # Link to tree object
20
+ attr_reader(:tree)
27
21
 
28
- # Factory method.
29
- def parse_tree()
30
- return PTree::ParseTree.new(root)
31
- end
22
+ # Link to current path
23
+ attr_reader(:curr_path)
32
24
 
25
+ # The last parse entry visited
26
+ attr_reader(:last_visitee)
33
27
 
34
- # Given that the current node is also lhs of the production
35
- # associated with the complete parse state,
36
- # Then add the rhs constituents as child nodes of the current node.
37
- # Assumption: current node is lhs of the production association
38
- # with the parse state.
39
- # @param aCompleteState [ParseState] A complete parse state
40
- # (dot is at end of rhs)
41
- def use_complete_state(aCompleteState)
42
- prod = aCompleteState.dotted_rule.production
43
- use_production(prod, low: aCompleteState.origin)
44
- end
28
+ # A hash with pairs of the form: visited parse entry => tree node
29
+ attr_reader(:entry2node)
45
30
 
46
- # Given that the current node is a non-terminal
47
- # Make its last child node the current node.
48
- def move_down()
49
- curr_node = current_node
50
- unless curr_node.is_a?(PTree::NonTerminalNode)
51
- msg = "Current node isn't a non-terminal node #{curr_node.class}"
52
- raise StandardError, msg
53
- end
54
- children = curr_node.children
55
- path_increment = [children.size - 1, children.last]
56
- @current_path.concat(path_increment)
57
- end
58
31
 
32
+ def initialize(theTokens)
33
+ @tokens = theTokens
34
+ @curr_path = []
35
+ @entry2node = {}
36
+ end
59
37
 
60
- # Make the predecessor of current node the
61
- # new current node.
62
- def move_back()
63
- loop do
64
- break if current_path.length == 1
65
- (parent, pos) = current_path[-3, 2]
66
- current_path.pop(2)
67
- if pos > 0
68
- new_pos = pos - 1
69
- new_curr_node = parent.children[new_pos]
70
- current_path << new_pos
71
- current_path << new_curr_node
72
- end
73
- break if pos > 0 || new_curr_node.is_a?(PTree::TerminalNode)
38
+ def receive_event(anEvent, anEntry, anIndex)
39
+ # puts "Event: #{anEvent} #{anEntry} #{anIndex}"
40
+ if anEntry.dotted_entry?
41
+ process_item_entry(anEvent, anEntry, anIndex)
42
+ elsif anEntry.start_entry?
43
+ process_start_entry(anEvent, anEntry, anIndex)
44
+ elsif anEntry.end_entry?
45
+ process_end_entry(anEvent, anEntry, anIndex)
46
+ else
47
+ raise NotImplementedError
74
48
  end
49
+
50
+ @last_visitee = anEntry
75
51
  end
76
52
 
53
+ # Return the current_parent node
54
+ def curr_parent()
55
+ return curr_path.last
56
+ end
77
57
 
78
- # Add a child node to the current node.
79
- def add_node(aSymbol, aRange)
80
- # Create the node
81
- a_node = new_node(aSymbol, aRange)
58
+ private
82
59
 
83
- # Add it to the current node
84
- add_child(a_node)
60
+ def process_start_entry(_anEvent, _anEntry, _anIndex)
61
+ curr_path.pop
85
62
  end
86
63
 
87
- # Set unbound endpoints of current node range
88
- # to the given range.
89
- def range=(aRange)
90
- curr_node = current_node
91
- return if curr_node.nil?
92
- lower = low_bound(aRange)
93
- unless lower.nil?
94
- current_node.range = lower
95
- if curr_node.is_a?(PTree::TerminalNode) && lower[:low]
96
- current_node.range = high_bound(lower[:low] + 1)
97
- end
64
+ def process_end_entry(anEvent, anEntry, anIndex)
65
+ case anEvent
66
+ when :visit
67
+ # create a node with the non-terminal
68
+ # with same right extent as curr_entry_set_index
69
+ # add the new node as first child of current_parent
70
+ # append the new node to the curr_path
71
+ range = { low: anEntry.origin, high: anIndex }
72
+ non_terminal = anEntry.vertex.non_terminal
73
+ create_non_terminal_node(anEntry, range, non_terminal)
74
+ @tree = create_tree(curr_parent) unless @last_visitee
75
+ else
76
+ raise NotImplementedError
98
77
  end
99
- upper = high_bound(aRange)
100
- current_node.range = upper unless upper.nil?
101
78
  end
102
79
 
103
- private
104
80
 
105
- def new_node(aSymbol, aRange)
106
- case aSymbol
107
- when Syntax::Terminal
108
- new_node = PTree::TerminalNode.new(aSymbol, aRange)
109
- when Syntax::NonTerminal
110
- new_node = PTree::NonTerminalNode.new(aSymbol, aRange)
81
+ def process_item_entry(anEvent, anEntry, anIndex)
82
+ case anEvent
83
+ when :visit
84
+ if anEntry.exit_entry?
85
+ # Previous entry was an end entry (X. pattern)
86
+ # Does the previous entry have multiple antecedent?
87
+ if last_visitee.end_entry? && last_visitee.antecedents.size > 1
88
+ # Store current path for later backtracking
89
+ # puts "Store backtrack context #{last_visitee}"
90
+ # puts "path [#{curr_path.map{|e|e.to_string(0)}.join(', ')}]"
91
+ entry2path_to_alt[last_visitee] = curr_path.dup
92
+ curr_parent.refinement = :or
93
+
94
+ create_alternative_node(anEntry)
95
+ end
96
+ end
97
+
98
+ # Does this entry have multiple antecedent?
99
+ if anEntry.antecedents.size > 1
100
+ # Store current path for later backtracking
101
+ # puts "Store backtrack context #{anEntry}"
102
+ # puts "path [#{curr_path.map{|e|e.to_string(0)}.join(', ')}]"
103
+ entry2path_to_alt[anEntry] = curr_path.dup
104
+ # curr_parent.refinement = :or
105
+
106
+ create_alternative_node(anEntry)
107
+ end
108
+
109
+ # Retrieve the grammar symbol before the dot (if any)
110
+ prev_symbol = anEntry.prev_symbol
111
+ case prev_symbol
112
+ when Syntax::Terminal
113
+ # Add node without changing current path
114
+ create_token_node(anEntry, anIndex)
115
+
116
+ when NilClass # Dot at the beginning of production
117
+ if anEntry.vertex.dotted_item.production.empty?
118
+ # Empty rhs => create an epsilon node ...
119
+ # ... without changing current path
120
+ create_epsilon_node(anEntry, anIndex)
121
+ end
122
+ curr_path.pop if curr_parent.kind_of?(SPPF::AlternativeNode)
123
+ end
124
+
125
+ when :backtrack
126
+ # # Restore path
127
+ # @curr_path = entry2path_to_alt[anEntry].dup
128
+ # # puts "Special restore path [#{curr_path.map{|e|e.to_string(0)}.join(', ')}]"
129
+ # antecedent_index = curr_parent.subnodes.size
130
+ # # puts "Current parent #{curr_parent.to_string(0)}"
131
+ # # puts "Antecedent index #{antecedent_index}"
132
+
133
+ # create_alternative_node(anEntry)
134
+
135
+ when :revisit
136
+ # # Retrieve the grammar symbol before the dot (if any)
137
+ # prev_symbol = anEntry.prev_symbol
138
+ # case prev_symbol
139
+ # when Syntax::Terminal
140
+ # # Add node without changing current path
141
+ # create_token_node(anEntry, anIndex)
142
+
143
+ # when NilClass # Dot at the beginning of production
144
+ # if anEntry.vertex.dotted_item.production.empty?
145
+ # # Empty rhs => create an epsilon node ...
146
+ # # ... without changing current path
147
+ # create_epsilon_node(anEntry, anIndex)
148
+ # end
149
+ # curr_path.pop if curr_parent.kind_of?(PTree::AlternativeNode)
150
+ # end
111
151
  end
152
+ end
112
153
 
113
- return new_node
154
+ # Create an empty parse tree
155
+ def create_tree(aRootNode)
156
+ return Rley::PTree::ParseTree.new(aRootNode)
114
157
  end
115
158
 
116
- # Add children nodes to current one.
117
- # The children correspond to the members of the rhs of the production.
118
- def use_production(aProduction, aRange)
119
- prod = aProduction
120
- curr_node = current_node
121
159
 
122
- if curr_node.symbol != prod.lhs
123
- snapshot = root.to_string(0)
124
- msg = "Current node is a #{curr_node.symbol} instead of #{prod.lhs}."
125
- raise StandardError, msg + "\n" + snapshot
126
- end
127
- self.range = aRange
128
- prod.rhs.each { |symb| add_node(symb, {}) }
160
+ # Factory method. Build and return an PTree non-terminal node.
161
+ def create_non_terminal_node(anEntry, aRange, nonTSymb = nil)
162
+ non_terminal = nonTSymb.nil? ? anEntry.vertex.non_terminal : nonTSymb
163
+ new_node = Rley::PTree::NonTerminalNode.new(non_terminal, aRange)
164
+ entry2node[anEntry] = new_node
165
+ add_subnode(new_node)
166
+ # puts "FOREST ADD #{curr_parent.key if curr_parent}/#{new_node.key}"
129
167
 
130
- return if curr_node.children.empty?
131
- curr_node.children.first.range.assign(low: curr_node.range.low)
132
- curr_node.children.last.range.assign(high: curr_node.range.high)
168
+ return new_node
133
169
  end
134
170
 
135
- # Add the given node as child node of current node
136
- def add_child(aNode)
137
- curr_node = current_node
138
171
 
139
- if curr_node.nil?
140
- self.root = aNode
141
- else
142
- curr_node.children << aNode
143
- end
172
+ # Add an alternative node to the tree
173
+ def create_alternative_node(anEntry)
174
+ vertex = anEntry.vertex
175
+ range = curr_parent.range
176
+ alternative = Rley::PTree::AlternativeNode.new(vertex, range)
177
+ add_subnode(alternative)
178
+ tree.is_ambiguous = true
179
+ # puts "FOREST ADD #{alternative.key}"
180
+
181
+ return alternative
144
182
  end
145
183
 
146
- # Set the root node of the tree.
147
- def root=(aNode)
148
- @root = aNode
149
- @current_path = [ @root ]
150
- root.range = low_bound(0)
184
+ # create a token node,
185
+ # with same origin as token,
186
+ # with same right extent = origin + 1
187
+ # add the new node as first child of current_parent
188
+ def create_token_node(anEntry, anIndex)
189
+ token_position = anIndex - 1
190
+ curr_token = tokens[token_position]
191
+ new_node = PTree::TerminalNode.new(curr_token, token_position)
192
+ candidate = add_node_to_tree(new_node)
193
+ entry2node[anEntry] = candidate
194
+
195
+ return candidate
151
196
  end
152
197
 
153
198
 
154
- def low_bound(aRange)
155
- result = case aRange
156
- when Integer then aRange
157
- when Hash then aRange[:low]
158
- when PTree::TokenRange then aRange.low
159
- end
199
+ def create_epsilon_node(anEntry, anIndex)
200
+ new_node = PTree::EpsilonNode.new(anIndex)
201
+ candidate = add_node_to_tree(new_node)
202
+ entry2node[anEntry] = candidate
203
+
204
+ return candidate
205
+ end
206
+
207
+ # Add the given node if not yet present in parse tree
208
+ def add_node_to_tree(aNode)
209
+ new_node = aNode
210
+ # puts "FOREST ADD #{key_node}"
211
+ add_subnode(new_node, false)
160
212
 
161
- return { low: result }
213
+ return new_node
162
214
  end
163
215
 
164
- def high_bound(aRange)
165
- result = case aRange
166
- when Integer then aRange
167
- when Hash then aRange[:high]
168
- when PTree::TokenRange then aRange.high
169
- end
170
216
 
171
- return { high: result }
217
+ # Add the given node as sub-node of current parent node
218
+ # Optionally add the node to the current path
219
+ def add_subnode(aNode, addToPath = true)
220
+ curr_parent.add_subnode(aNode) unless curr_path.empty?
221
+ curr_path << aNode if addToPath
172
222
  end
173
223
  end # class
174
224
  end # module