rley 0.1.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NTc2NGE4MzYxOTc1ZDUyMDVkYjdmNGFhODllNmEwM2YxMjVkZDk1OQ==
4
+ ZTAxMjk3Yzc3YTFjZmUxY2Q2OGI1MDFhZjMxMTk5M2Q1YmMzZWMwNw==
5
5
  data.tar.gz: !binary |-
6
- ZDBkMTdmZWM2NTMwYWMwNDFkNDQ4NGI2YzdkNjk3NDU0ZGExMGYzNA==
6
+ ODMxNDQ0ODZjYmI0YWRlYTE5NWQwZjE3Njg2ZGI2MzBjMjc5ZmVlMQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- YWZjYmQxNDNiNjVmMDYyYWI1YzM0YzMyN2VjMzk4ZjUxOTIyMmQxNTE4Y2Y4
10
- YjM4NTAyNGNhNjhiOWRhNWMyZWVmYzRhYjFjNzhhOWEzMDY1ZTgzMzRiMTVh
11
- Njg1MDRjMjQ5NTlhYmU3NTk2MDBiMWQyZmI3MDIyMWUwNGM1NjM=
9
+ NmVkNTY4MzE3ZWNhMDhlNWI5OTJlODBkM2ZmOTg1Y2RjODViOThiMzAyYmUy
10
+ ZTY5N2RhN2EyNzFhMmY3MjhlNmJkNjI4MGVlOTBjZDgwYmM4ZTY0ZjhlNjE2
11
+ OGU5ZTRlNzAzOGRiOTlmMmZkZDg0NzcyMGQwZDFlYjVmYjAwMzI=
12
12
  data.tar.gz: !binary |-
13
- ODZiMmY1ZDMwNTVlNmM3ZGJmZDIzOTAzYjQ1MTFlMmY4OTBlODhhZWZhN2M3
14
- MjEzYjI0YjZhNTA4NzkzYjJiMzMwY2Y0NzliMGMyZDdlMjI3NjkzMTliYWNh
15
- YzQ2MGVmNWM1NTA5M2IxYjcxNzliNzhhMzViMDE4OTM3NWY1NjI=
13
+ N2E0OGJhNjVjOWM3YTMzMzk5YjdjZDdiY2IzMGE5N2RkNTJkNDlkMDcyYTI3
14
+ YzY5Y2YzZTE4NjcxYjFlNzhhMmM4NDExYzk0NGEzMTE1ZmMwNDUzNzExMTA5
15
+ OGI3MDFlMzFkYWQ2Y2UzNmI1MGQ5ZGEwNGYyNTg4N2MzMTk3NTk=
@@ -1,3 +1,12 @@
1
+ ### 0.2.00 / 2015-01-03
2
+ Version number bump: major re-design of the parse tree generation.
3
+ * [NEW] Class `ParseTreeBuilder`: builder for creating parse tree.
4
+ * [NEW] Class `ParseStateTracker`: helper class used in parse tree generation.
5
+ * [NEW] Examplar file `parsing_L0.rb`: demo using a (highly simplified) English grammar.
6
+ * [CHANGE] Class `ParseTree`: construction methods removed.
7
+ * [CHANGE] Method `Parsing#parse_tree` completely rewritten.
8
+ * [FIX] Method `Parsing#parse_tree` now handles situations where there are multiple complete parse states for a non-terminal.
9
+
1
10
  ### 0.1.12 / 2014-12-22
2
11
  * [FIX] Fixed `Parsing#parse_tree`: code couldn't cope with parse state set containing more
3
12
  than one parse state that expected the same symbol.
@@ -0,0 +1,125 @@
1
+ # Purpose: to demonstrate how to build and render a parse tree for the L0
2
+ # language
3
+ require 'pp'
4
+ require 'rley' # Load the gem
5
+
6
+ # Steps to render a parse tree (of a valid parsed input):
7
+ # 1. Define a grammar
8
+ # 2. Create a tokenizer for the language
9
+ # 3. Create a parser for that grammar
10
+ # 4. Tokenize the input
11
+ # 5. Let the parser process the input
12
+ # 6. Generate a parse tree from the parse result
13
+ # 7. Render the parse tree (in JSON)
14
+
15
+ ########################################
16
+ # Step 1. Define a grammar for a micro English-like language
17
+ # based on Jurafky & Martin L0 language.
18
+ # It defines the syntax of a sentence in a language with a
19
+ # very limited syntax and lexicon in the context of airline reservation.
20
+ builder = Rley::Syntax::GrammarBuilder.new
21
+ builder.add_terminals('Noun', 'Verb', 'Pronoun', 'Proper-Noun')
22
+ builder.add_terminals('Determiner', 'Preposition', )
23
+ builder.add_production('S' => %w[NP VP])
24
+ builder.add_production('NP' => 'Pronoun')
25
+ builder.add_production('NP' => 'Proper-Noun')
26
+ builder.add_production('NP' => %w[Determiner Nominal])
27
+ builder.add_production('Nominal' => %w[Nominal Noun])
28
+ builder.add_production('Nominal' => 'Noun')
29
+ builder.add_production('VP' => 'Verb')
30
+ builder.add_production('VP' => %w[Verb NP])
31
+ builder.add_production('VP' => %w[Verb NP PP])
32
+ builder.add_production('VP' => %w[Verb PP])
33
+ builder.add_production('PP' => %w[Preposition PP])
34
+
35
+ # And now build the grammar...
36
+ grammar_l0 = builder.grammar
37
+
38
+
39
+ ########################################
40
+ # 2. Create a tokenizer for the language
41
+ # The tokenizer transforms the input into an array of tokens
42
+ # This is a very simplistic implementation for demo purposes.
43
+
44
+ # The lexicon is just a Hash with pairs of the form:
45
+ # word =>terminal symbol name
46
+ L0_lexicon = {
47
+ 'flight' => 'Noun',
48
+ 'breeze' => 'Noun',
49
+ 'trip' => 'Noun',
50
+ 'morning' => 'Noun',
51
+ 'is' => 'Verb',
52
+ 'prefer' => 'Verb',
53
+ 'like' => 'Verb',
54
+ 'need' => 'Verb',
55
+ 'want' => 'Verb',
56
+ 'fly' => 'Verb',
57
+ 'me' => 'Pronoun',
58
+ 'I' => 'Pronoun',
59
+ 'you' => 'Pronoun',
60
+ 'it' => 'Pronoun',
61
+ 'Alaska' => 'Proper-Noun',
62
+ 'Baltimore' => 'Proper-Noun',
63
+ 'Chicago' => 'Proper-Noun',
64
+ 'United' => 'Proper-Noun',
65
+ 'American' => 'Proper-Noun',
66
+ 'the' => 'Determiner',
67
+ 'a' => 'Determiner',
68
+ 'an' => 'Determiner',
69
+ 'this' => 'Determiner',
70
+ 'these' => 'Determiner',
71
+ 'that' => 'Determiner',
72
+ 'from' => 'Preposition',
73
+ 'to' => 'Preposition',
74
+ 'on' => 'Preposition',
75
+ 'near' => 'Preposition'
76
+ }
77
+
78
+ # Highly simplified tokenizer implementation.
79
+ def tokenizer(aText, aGrammar)
80
+ tokens = aText.scan(/\S+/).map do |word|
81
+ term_name = L0_lexicon[word]
82
+ if term_name.nil?
83
+ fail StandardError, "Word '#{word}' not found in lexicon"
84
+ end
85
+ terminal = aGrammar.name2symbol[term_name]
86
+ Rley::Parser::Token.new(word, terminal)
87
+ end
88
+
89
+ return tokens
90
+ end
91
+
92
+ ########################################
93
+ # Step 3. Create a parser for that grammar
94
+ parser = Rley::Parser::EarleyParser.new(grammar_l0)
95
+
96
+ ########################################
97
+ # Step 3. Tokenize the input
98
+ valid_input = 'I prefer a morning flight'
99
+ # Another sentence: it is a flight from Chicago
100
+ tokens = tokenizer(valid_input, grammar_l0)
101
+
102
+ ########################################
103
+ # Step 5. Let the parser process the input
104
+ result = parser.parse(tokens)
105
+
106
+ puts "Parsing success? #{result.success?}"
107
+
108
+
109
+ ########################################
110
+ # Step 6. Generate a parse tree from the parse result
111
+ ptree = result.parse_tree
112
+ pp ptree
113
+
114
+ ########################################
115
+ # Step 7. Render the parse tree (in JSON)
116
+ # Let's create a parse tree visitor
117
+ visitor = Rley::ParseTreeVisitor.new(ptree)
118
+
119
+ #Here we create a renderer object...
120
+ renderer = Rley::Formatter::Json.new(STDOUT)
121
+
122
+ # Now emit the parse tree as JSON on the console output
123
+ puts "JSON rendering of the parse tree for '#{valid_input}' input:"
124
+ renderer.render(visitor)
125
+ # End of file
@@ -1,85 +1,85 @@
1
- # Purpose: to demonstrate how to parse basic arithmetic expressions
2
- # and render a parse tree
3
- require 'pp' # TODO remove this dependency
4
- require 'rley' # Load the gem
5
-
6
- # Steps to render a parse tree (of a valid parsed input):
7
- # 1. Define a grammar
8
- # 2. Create a tokenizer for the language
9
- # 3. Create a parser for that grammar
10
- # 4. Tokenize the input
11
- # 5. Let the parser process the input
12
- # 6. Generate a parse tree from the parse result
13
- # 7. Render the parse tree (in JSON)
14
-
15
- ########################################
16
- # Step 1. Define a grammar for a very simple arithmetic expression language
17
- # (based on example in article on Earley's algorithm in Wikipedia)
18
-
19
- # Let's create the grammar piece by piece
20
- builder = Rley::Syntax::GrammarBuilder.new
21
- builder.add_terminals('+', '*', 'integer')
22
- builder.add_production('P' => 'S')
23
- builder.add_production('S' => %w(S + M))
24
- builder.add_production('S' => 'M')
25
- builder.add_production('M' => %w(M * T))
26
- builder.add_production('M' => 'T')
27
- builder.add_production('T' => 'integer')
28
-
29
- # And now build the grammar...
30
- grammar_s_expr = builder.grammar
31
-
32
-
33
- ########################################
34
- # 2. Create a tokenizer for the language
35
- # The tokenizer transforms the input into an array of tokens
36
- def tokenizer(aText, aGrammar)
37
- tokens = aText.scan(/\S+/).map do |lexeme|
38
- case lexeme
39
- when '+', '*'
40
- terminal = aGrammar.name2symbol[lexeme]
41
- when /^[-+]?\d+$/
42
- terminal = aGrammar.name2symbol['integer']
43
- else
44
- msg = "Unknown input text '#{lexeme}'"
45
- fail StandardError, msg
46
- end
47
- Rley::Parser::Token.new(lexeme, terminal)
48
- end
49
-
50
- return tokens
51
- end
52
-
53
- ########################################
54
- # Step 3. Create a parser for that grammar
55
- parser = Rley::Parser::EarleyParser.new(grammar_s_expr)
56
-
57
- ########################################
58
- # Step 3. Tokenize the input
59
- valid_input = '2 + 3 * 4'
60
- tokens = tokenizer(valid_input, grammar_s_expr)
61
-
62
- ########################################
63
- # Step 5. Let the parser process the input
64
- result = parser.parse(tokens)
65
- puts "Parse successful? #{result.success?}"
66
- pp result
67
-
68
-
69
- ########################################
70
- # Step 6. Generate a parse tree from the parse result
71
- ptree = result.parse_tree
72
- =begin
73
- ########################################
74
- # Step 7. Render the parse tree (in JSON)
75
- # Let's create a parse tree visitor
76
- visitor = Rley::ParseTreeVisitor.new(ptree)
77
-
78
- #Here we create a renderer object...
79
- renderer = Rley::Formatter::Json.new(STDOUT)
80
-
81
- # Now emit the parse tree as JSON on the console output
82
- puts "JSON rendering of the parse tree for '#{valid_input}' input:"
83
- renderer.render(visitor)
84
- =end
1
+ # Purpose: to demonstrate how to parse basic arithmetic expressions
2
+ # and render a parse tree
3
+ require 'pp' # TODO remove this dependency
4
+ require 'rley' # Load the gem
5
+
6
+ # Steps to render a parse tree (of a valid parsed input):
7
+ # 1. Define a grammar
8
+ # 2. Create a tokenizer for the language
9
+ # 3. Create a parser for that grammar
10
+ # 4. Tokenize the input
11
+ # 5. Let the parser process the input
12
+ # 6. Generate a parse tree from the parse result
13
+ # 7. Render the parse tree (in JSON)
14
+
15
+ ########################################
16
+ # Step 1. Define a grammar for a very simple arithmetic expression language
17
+ # (based on example in article on Earley's algorithm in Wikipedia)
18
+
19
+ # Let's create the grammar piece by piece
20
+ builder = Rley::Syntax::GrammarBuilder.new
21
+ builder.add_terminals('+', '*', 'integer')
22
+ builder.add_production('P' => 'S')
23
+ builder.add_production('S' => %w(S + M))
24
+ builder.add_production('S' => 'M')
25
+ builder.add_production('M' => %w(M * T))
26
+ builder.add_production('M' => 'T')
27
+ builder.add_production('T' => 'integer')
28
+
29
+ # And now build the grammar...
30
+ grammar_s_expr = builder.grammar
31
+
32
+
33
+ ########################################
34
+ # 2. Create a tokenizer for the language
35
+ # The tokenizer transforms the input into an array of tokens
36
+ def tokenizer(aText, aGrammar)
37
+ tokens = aText.scan(/\S+/).map do |lexeme|
38
+ case lexeme
39
+ when '+', '*'
40
+ terminal = aGrammar.name2symbol[lexeme]
41
+ when /^[-+]?\d+$/
42
+ terminal = aGrammar.name2symbol['integer']
43
+ else
44
+ msg = "Unknown input text '#{lexeme}'"
45
+ fail StandardError, msg
46
+ end
47
+ Rley::Parser::Token.new(lexeme, terminal)
48
+ end
49
+
50
+ return tokens
51
+ end
52
+
53
+ ########################################
54
+ # Step 3. Create a parser for that grammar
55
+ parser = Rley::Parser::EarleyParser.new(grammar_s_expr)
56
+
57
+ ########################################
58
+ # Step 3. Tokenize the input
59
+ valid_input = '2 + 3 * 4'
60
+ tokens = tokenizer(valid_input, grammar_s_expr)
61
+
62
+ ########################################
63
+ # Step 5. Let the parser process the input
64
+ result = parser.parse(tokens)
65
+ puts "Parse successful? #{result.success?}"
66
+
67
+
68
+ ########################################
69
+ # Step 6. Generate a parse tree from the parse result
70
+ ptree = result.parse_tree
71
+ pp ptree
72
+
73
+ ########################################
74
+ # Step 7. Render the parse tree (in JSON)
75
+ # Let's create a parse tree visitor
76
+ visitor = Rley::ParseTreeVisitor.new(ptree)
77
+
78
+ #Here we create a renderer object...
79
+ renderer = Rley::Formatter::Json.new(STDOUT)
80
+
81
+ # Now emit the parse tree as JSON on the console output
82
+ puts "JSON rendering of the parse tree for '#{valid_input}' input:"
83
+ renderer.render(visitor)
84
+
85
85
  # End of file
@@ -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.1.12'
6
+ Version = '0.2.00'
7
7
 
8
8
  # Brief description of the gem.
9
9
  Description = "Ruby implementation of the Earley's parsing algorithm"
@@ -64,6 +64,10 @@ module Rley # This module is used as a namespace
64
64
  def before_terminal(term_node)
65
65
  separator = sibling_flags[-1] ? ",\n" : "\n"
66
66
  name = term_node.symbol.name
67
+ if term_node.token.nil?
68
+ msg = "No token associated with #{name}"
69
+ fail StandardError, msg
70
+ end
67
71
  lexeme = term_node.token.lexeme
68
72
  print_text(separator, "{\"#{name}\": \"#{lexeme}\"}")
69
73
  sibling_flags[-1] = true
@@ -24,6 +24,18 @@ module Rley # This module is used as a namespace
24
24
  def [](index)
25
25
  return state_sets[index]
26
26
  end
27
+
28
+ # Return the index value of the last non-empty state set.
29
+ def last_index()
30
+ first_empty = state_sets.find_index { |set| set.empty? }
31
+ if first_empty.nil?
32
+ index = state_sets.size - 1
33
+ else
34
+ index = first_empty == 0 ? 0 : first_empty - 1
35
+ end
36
+
37
+ return index
38
+ end
27
39
 
28
40
  # Push a parse state for the chart entry with given index
29
41
  def push_state(aDottedItem, anOrigin, anIndex)
@@ -0,0 +1,55 @@
1
+ module Rley # This module is used as a namespace
2
+ module Parser # This module is used as a namespace
3
+ # Helper class that keeps track of the parse states used
4
+ # while a Parsing instance is constructing a parse tree.
5
+ class ParseStateTracker
6
+ # The index of the current state set
7
+ attr_reader(:state_set_index)
8
+
9
+ # The current parse state
10
+ attr_reader(:parse_state)
11
+
12
+ # The already processed states from current state set
13
+ attr_reader(:processed_states)
14
+
15
+ # Constructor. Refined variant of the inherited constructor.
16
+ def initialize(aStateSetIndex)
17
+ self.state_set_index = aStateSetIndex
18
+ end
19
+
20
+ # Write accessor. Sets the value of the state set index
21
+ def state_set_index=(anIndex)
22
+ @state_set_index = anIndex
23
+ @processed_states = {}
24
+ end
25
+
26
+ # Write accessor. Set the given parse state as the current one.
27
+ def parse_state=(aParseState)
28
+ @parse_state = aParseState
29
+ self.processed_states[parse_state] = true
30
+ end
31
+
32
+ # Take the first provided state that wasn't processed yet.
33
+ def select_state(theStates)
34
+ a_state = theStates.find { |st| ! processed_states.include?(st) }
35
+ self.parse_state = a_state
36
+ end
37
+
38
+ # The dotted item for the current parse state.
39
+ def curr_dotted_item()
40
+ parse_state.dotted_rule
41
+ end
42
+
43
+ def symbol_on_left()
44
+ return curr_dotted_item.prev_symbol
45
+ end
46
+
47
+ # Notification that one begins with the previous state set
48
+ def to_prev_state_set()
49
+ self.state_set_index = self.state_set_index - 1
50
+ end
51
+ end # class
52
+ end # module
53
+ end # module
54
+
55
+ # End of file
@@ -0,0 +1,181 @@
1
+ require 'ostruct' # TODO delete this
2
+ require_relative '../ptree/terminal_node'
3
+ require_relative '../ptree/non_terminal_node'
4
+ require_relative '../ptree/parse_tree'
5
+
6
+
7
+ module Rley # This module is used as a namespace
8
+ module Parser # This module is used as a namespace
9
+ # Builder GoF pattern. Builder pattern builds a complex object
10
+ # (say, a parse tree) from simpler objects (terminal and non-terminal
11
+ # nodes) and using a step by step approach.
12
+ class ParseTreeBuilder
13
+ attr_reader(:root)
14
+ attr_reader(:current_path)
15
+
16
+ def initialize(aStartProduction, aRange)
17
+ @current_path = []
18
+ start_symbol = aStartProduction.lhs
19
+ add_node(start_symbol, aRange)
20
+ use_production(aStartProduction, aRange)
21
+ move_down
22
+ end
23
+
24
+ # Return the active node.
25
+ def current_node()
26
+ return current_path.last
27
+ end
28
+
29
+ # Factory method.
30
+ def parse_tree()
31
+ return PTree::ParseTree.new(root)
32
+ end
33
+
34
+
35
+ # Given that the current node is also lhs of the production
36
+ # associated with the complete parse state,
37
+ # Then add the rhs constituents as child nodes of the current node.
38
+ # Assumption: current node is lhs of the production association
39
+ # with the parse state.
40
+ # @param aCompleteState [ParseState] A complete parse state
41
+ # (dot is at end of rhs)
42
+ def use_complete_state(aCompleteState)
43
+ prod = aCompleteState.dotted_rule.production
44
+ use_production(prod, {low: aCompleteState.origin})
45
+ end
46
+
47
+ # Given that the current node is a non-terminal
48
+ # Make its last child node the current node.
49
+ def move_down()
50
+ curr_node = current_node
51
+ unless curr_node.is_a?(PTree::NonTerminalNode)
52
+ msg = "Current node isn't a non-terminal node #{curr_node.class}"
53
+ fail StandardError, msg
54
+ end
55
+ children = curr_node.children
56
+ path_increment = [children.size - 1, children.last]
57
+ @current_path.concat(path_increment)
58
+ end
59
+
60
+
61
+ # Make the predecessor of current node the
62
+ # new current node.
63
+ def move_back()
64
+ begin
65
+ if current_path.length == 1
66
+ msg = 'Cannot move further back'
67
+ fail StandardError, msg
68
+ end
69
+ (parent, pos, child_node) = current_path[-3, 3]
70
+ current_path.pop(2)
71
+ if pos > 0
72
+ new_pos = pos - 1
73
+ new_curr_node = parent.children[new_pos]
74
+ current_path << new_pos
75
+ current_path << new_curr_node
76
+ range = high_bound(child_node.range.low)
77
+ end
78
+ end while pos == 0 && new_curr_node.is_a?(PTree::NonTerminalNode)
79
+ end
80
+
81
+
82
+ # Add a child node to the current node.
83
+ def add_node(aSymbol, aRange)
84
+ # Create the node
85
+ a_node = new_node(aSymbol, aRange)
86
+
87
+ # Add it to the current node
88
+ add_child(a_node)
89
+ end
90
+
91
+ # Set unbound endpoints of current node range
92
+ # to the given range.
93
+ def range=(aRange)
94
+ curr_node = current_node
95
+ return if curr_node.nil?
96
+ lower = low_bound(aRange)
97
+ unless lower.nil?
98
+ current_node.range = lower
99
+ if curr_node.is_a?(PTree::TerminalNode)
100
+ current_node.range = high_bound(lower[:low] + 1)
101
+ end
102
+ end
103
+ upper = high_bound(aRange)
104
+ current_node.range = upper unless upper.nil?
105
+ end
106
+
107
+ private
108
+
109
+ def new_node(aSymbol, aRange)
110
+ case aSymbol
111
+ when Syntax::Terminal
112
+ new_node = PTree::TerminalNode.new(aSymbol, aRange)
113
+ when Syntax::NonTerminal
114
+ new_node = PTree::NonTerminalNode.new(aSymbol, aRange)
115
+ end
116
+
117
+ return new_node
118
+ end
119
+
120
+ # Add children nodes to current one.
121
+ # The children correspond to the members of the rhs of the production.
122
+ def use_production(aProduction, aRange)
123
+ prod = aProduction
124
+ curr_node = current_node
125
+
126
+ if curr_node.symbol != prod.lhs
127
+ msg = "Current node is a #{curr_node.symbol} instead of #{prod.lhs}"
128
+ fail StandardError, msg
129
+ end
130
+ self.range = aRange
131
+ prod.rhs.each { |symb| add_node(symb, {}) }
132
+
133
+ unless curr_node.children.empty?
134
+ curr_node.children.first.range.assign({ low: curr_node.range.low })
135
+ curr_node.children.last.range.assign({ high: curr_node.range.high })
136
+ end
137
+ end
138
+
139
+ # Add the given node as child node of current node
140
+ def add_child(aNode)
141
+ curr_node = current_node
142
+
143
+ if curr_node.nil?
144
+ self.root = aNode
145
+ else
146
+ curr_node.children << aNode
147
+ end
148
+ end
149
+
150
+ # Set the root node of the tree.
151
+ def root=(aNode)
152
+ @root = aNode
153
+ @current_path = [ @root ]
154
+ root.range = low_bound(0)
155
+ end
156
+
157
+
158
+ def low_bound(aRange)
159
+ result = case aRange
160
+ when Fixnum then aRange
161
+ when Hash then aRange[:low]
162
+ when PTree::TokenRange then aRange.low
163
+ end
164
+
165
+ return { low: result }
166
+ end
167
+
168
+ def high_bound(aRange)
169
+ result = case aRange
170
+ when Fixnum then aRange
171
+ when Hash then aRange[:high]
172
+ when PTree::TokenRange then aRange.high
173
+ end
174
+
175
+ return { high: result }
176
+ end
177
+ end # class
178
+ end # module
179
+ end # module
180
+
181
+ # End of file