rley 0.1.12 → 0.2.00

Sign up to get free protection for your applications and to get access to all the features.
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