regex-treetop 1.4.8
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.
- data/LICENSE +19 -0
- data/README.md +164 -0
- data/Rakefile +19 -0
- data/bin/tt +112 -0
- data/doc/contributing_and_planned_features.markdown +103 -0
- data/doc/grammar_composition.markdown +65 -0
- data/doc/index.markdown +90 -0
- data/doc/pitfalls_and_advanced_techniques.markdown +51 -0
- data/doc/semantic_interpretation.markdown +189 -0
- data/doc/site.rb +112 -0
- data/doc/sitegen.rb +65 -0
- data/doc/syntactic_recognition.markdown +100 -0
- data/doc/using_in_ruby.markdown +21 -0
- data/examples/lambda_calculus/arithmetic.rb +551 -0
- data/examples/lambda_calculus/arithmetic.treetop +97 -0
- data/examples/lambda_calculus/arithmetic_node_classes.rb +7 -0
- data/examples/lambda_calculus/arithmetic_test.rb +54 -0
- data/examples/lambda_calculus/lambda_calculus +0 -0
- data/examples/lambda_calculus/lambda_calculus.rb +718 -0
- data/examples/lambda_calculus/lambda_calculus.treetop +132 -0
- data/examples/lambda_calculus/lambda_calculus_node_classes.rb +5 -0
- data/examples/lambda_calculus/lambda_calculus_test.rb +89 -0
- data/examples/lambda_calculus/test_helper.rb +18 -0
- data/lib/treetop.rb +16 -0
- data/lib/treetop/bootstrap_gen_1_metagrammar.rb +45 -0
- data/lib/treetop/compiler.rb +6 -0
- data/lib/treetop/compiler/grammar_compiler.rb +44 -0
- data/lib/treetop/compiler/lexical_address_space.rb +17 -0
- data/lib/treetop/compiler/metagrammar.rb +3392 -0
- data/lib/treetop/compiler/metagrammar.treetop +454 -0
- data/lib/treetop/compiler/node_classes.rb +21 -0
- data/lib/treetop/compiler/node_classes/anything_symbol.rb +18 -0
- data/lib/treetop/compiler/node_classes/atomic_expression.rb +14 -0
- data/lib/treetop/compiler/node_classes/character_class.rb +28 -0
- data/lib/treetop/compiler/node_classes/choice.rb +31 -0
- data/lib/treetop/compiler/node_classes/declaration_sequence.rb +24 -0
- data/lib/treetop/compiler/node_classes/grammar.rb +28 -0
- data/lib/treetop/compiler/node_classes/inline_module.rb +27 -0
- data/lib/treetop/compiler/node_classes/nonterminal.rb +13 -0
- data/lib/treetop/compiler/node_classes/optional.rb +19 -0
- data/lib/treetop/compiler/node_classes/parenthesized_expression.rb +9 -0
- data/lib/treetop/compiler/node_classes/parsing_expression.rb +146 -0
- data/lib/treetop/compiler/node_classes/parsing_rule.rb +55 -0
- data/lib/treetop/compiler/node_classes/predicate.rb +45 -0
- data/lib/treetop/compiler/node_classes/predicate_block.rb +16 -0
- data/lib/treetop/compiler/node_classes/regex.rb +23 -0
- data/lib/treetop/compiler/node_classes/repetition.rb +55 -0
- data/lib/treetop/compiler/node_classes/sequence.rb +71 -0
- data/lib/treetop/compiler/node_classes/terminal.rb +20 -0
- data/lib/treetop/compiler/node_classes/transient_prefix.rb +9 -0
- data/lib/treetop/compiler/node_classes/treetop_file.rb +9 -0
- data/lib/treetop/compiler/ruby_builder.rb +113 -0
- data/lib/treetop/ruby_extensions.rb +2 -0
- data/lib/treetop/ruby_extensions/string.rb +42 -0
- data/lib/treetop/runtime.rb +5 -0
- data/lib/treetop/runtime/compiled_parser.rb +118 -0
- data/lib/treetop/runtime/interval_skip_list.rb +4 -0
- data/lib/treetop/runtime/interval_skip_list/head_node.rb +15 -0
- data/lib/treetop/runtime/interval_skip_list/interval_skip_list.rb +200 -0
- data/lib/treetop/runtime/interval_skip_list/node.rb +164 -0
- data/lib/treetop/runtime/syntax_node.rb +114 -0
- data/lib/treetop/runtime/terminal_parse_failure.rb +16 -0
- data/lib/treetop/runtime/terminal_syntax_node.rb +17 -0
- data/lib/treetop/version.rb +9 -0
- metadata +138 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
class IntervalSkipList
|
2
|
+
class HeadNode
|
3
|
+
attr_reader :height, :forward, :forward_markers
|
4
|
+
|
5
|
+
def initialize(height)
|
6
|
+
@height = height
|
7
|
+
@forward = Array.new(height, nil)
|
8
|
+
@forward_markers = Array.new(height) {|i| []}
|
9
|
+
end
|
10
|
+
|
11
|
+
def top_level
|
12
|
+
height - 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
class IntervalSkipList
|
2
|
+
attr_reader :probability
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@head = HeadNode.new(max_height)
|
6
|
+
@ranges = {}
|
7
|
+
@probability = 0.5
|
8
|
+
end
|
9
|
+
|
10
|
+
def max_height
|
11
|
+
3
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
head.forward[0].nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def expire(range, length_change)
|
19
|
+
expired_markers, first_node_after_range = overlapping(range)
|
20
|
+
expired_markers.each { |marker| delete(marker) }
|
21
|
+
first_node_after_range.propagate_length_change(length_change)
|
22
|
+
end
|
23
|
+
|
24
|
+
def overlapping(range)
|
25
|
+
markers, first_node = containing_with_node(range.first)
|
26
|
+
|
27
|
+
cur_node = first_node
|
28
|
+
begin
|
29
|
+
markers.concat(cur_node.forward_markers.flatten)
|
30
|
+
cur_node = cur_node.forward[0]
|
31
|
+
end while cur_node.key < range.last
|
32
|
+
|
33
|
+
return markers.uniq, cur_node
|
34
|
+
end
|
35
|
+
|
36
|
+
def containing(n)
|
37
|
+
containing_with_node(n).first
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert(range, marker)
|
41
|
+
ranges[marker] = range
|
42
|
+
first_node = insert_node(range.first)
|
43
|
+
first_node.endpoint_of.push(marker)
|
44
|
+
last_node = insert_node(range.last)
|
45
|
+
last_node.endpoint_of.push(marker)
|
46
|
+
|
47
|
+
cur_node = first_node
|
48
|
+
cur_level = first_node.top_level
|
49
|
+
while next_node_at_level_inside_range?(cur_node, cur_level, range)
|
50
|
+
while can_ascend_from?(cur_node, cur_level) && next_node_at_level_inside_range?(cur_node, cur_level + 1, range)
|
51
|
+
cur_level += 1
|
52
|
+
end
|
53
|
+
cur_node = mark_forward_path_at_level(cur_node, cur_level, marker)
|
54
|
+
end
|
55
|
+
|
56
|
+
while node_inside_range?(cur_node, range)
|
57
|
+
while can_descend_from?(cur_level) && next_node_at_level_outside_range?(cur_node, cur_level, range)
|
58
|
+
cur_level -= 1
|
59
|
+
end
|
60
|
+
cur_node = mark_forward_path_at_level(cur_node, cur_level, marker)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete(marker)
|
65
|
+
range = ranges[marker]
|
66
|
+
path_to_first_node = make_path
|
67
|
+
first_node = find(range.first, path_to_first_node)
|
68
|
+
|
69
|
+
cur_node = first_node
|
70
|
+
cur_level = first_node.top_level
|
71
|
+
while next_node_at_level_inside_range?(cur_node, cur_level, range)
|
72
|
+
while can_ascend_from?(cur_node, cur_level) && next_node_at_level_inside_range?(cur_node, cur_level + 1, range)
|
73
|
+
cur_level += 1
|
74
|
+
end
|
75
|
+
cur_node = unmark_forward_path_at_level(cur_node, cur_level, marker)
|
76
|
+
end
|
77
|
+
|
78
|
+
while node_inside_range?(cur_node, range)
|
79
|
+
while can_descend_from?(cur_level) && next_node_at_level_outside_range?(cur_node, cur_level, range)
|
80
|
+
cur_level -= 1
|
81
|
+
end
|
82
|
+
cur_node = unmark_forward_path_at_level(cur_node, cur_level, marker)
|
83
|
+
end
|
84
|
+
last_node = cur_node
|
85
|
+
|
86
|
+
first_node.endpoint_of.delete(marker)
|
87
|
+
if first_node.endpoint_of.empty?
|
88
|
+
first_node.delete(path_to_first_node)
|
89
|
+
end
|
90
|
+
|
91
|
+
last_node.endpoint_of.delete(marker)
|
92
|
+
if last_node.endpoint_of.empty?
|
93
|
+
path_to_last_node = make_path
|
94
|
+
find(range.last, path_to_last_node)
|
95
|
+
last_node.delete(path_to_last_node)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
attr_reader :head, :ranges
|
101
|
+
|
102
|
+
def insert_node(key)
|
103
|
+
path = make_path
|
104
|
+
found_node = find(key, path)
|
105
|
+
if found_node && found_node.key == key
|
106
|
+
return found_node
|
107
|
+
else
|
108
|
+
return Node.new(key, next_node_height, path)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def containing_with_node(n)
|
113
|
+
containing = []
|
114
|
+
cur_node = head
|
115
|
+
(max_height - 1).downto(0) do |cur_level|
|
116
|
+
while (next_node = cur_node.forward[cur_level]) && next_node.key <= n
|
117
|
+
cur_node = next_node
|
118
|
+
if cur_node.key == n
|
119
|
+
return containing + (cur_node.markers - cur_node.endpoint_of), cur_node
|
120
|
+
end
|
121
|
+
end
|
122
|
+
containing.concat(cur_node.forward_markers[cur_level])
|
123
|
+
end
|
124
|
+
|
125
|
+
return containing, cur_node
|
126
|
+
end
|
127
|
+
|
128
|
+
def delete_node(key)
|
129
|
+
path = make_path
|
130
|
+
found_node = find(key, path)
|
131
|
+
found_node.delete(path) if found_node.key == key
|
132
|
+
end
|
133
|
+
|
134
|
+
def find(key, path)
|
135
|
+
cur_node = head
|
136
|
+
(max_height - 1).downto(0) do |cur_level|
|
137
|
+
while (next_node = cur_node.forward[cur_level]) && next_node.key < key
|
138
|
+
cur_node = next_node
|
139
|
+
end
|
140
|
+
path[cur_level] = cur_node
|
141
|
+
end
|
142
|
+
cur_node.forward[0]
|
143
|
+
end
|
144
|
+
|
145
|
+
def make_path
|
146
|
+
Array.new(max_height, nil)
|
147
|
+
end
|
148
|
+
|
149
|
+
def next_node_height
|
150
|
+
height = 1
|
151
|
+
while rand < probability && height < max_height
|
152
|
+
height += 1
|
153
|
+
end
|
154
|
+
height
|
155
|
+
end
|
156
|
+
|
157
|
+
def can_ascend_from?(node, level)
|
158
|
+
level < node.top_level
|
159
|
+
end
|
160
|
+
|
161
|
+
def can_descend_from?(level)
|
162
|
+
level > 0
|
163
|
+
end
|
164
|
+
|
165
|
+
def node_inside_range?(node, range)
|
166
|
+
node.key < range.last
|
167
|
+
end
|
168
|
+
|
169
|
+
def next_node_at_level_inside_range?(node, level, range)
|
170
|
+
node.forward[level] && node.forward[level].key <= range.last
|
171
|
+
end
|
172
|
+
|
173
|
+
def next_node_at_level_outside_range?(node, level, range)
|
174
|
+
(node.forward[level].nil? || node.forward[level].key > range.last)
|
175
|
+
end
|
176
|
+
|
177
|
+
def mark_forward_path_at_level(node, level, marker)
|
178
|
+
node.forward_markers[level].push(marker)
|
179
|
+
next_node = node.forward[level]
|
180
|
+
next_node.markers.push(marker)
|
181
|
+
node = next_node
|
182
|
+
end
|
183
|
+
|
184
|
+
def unmark_forward_path_at_level(node, level, marker)
|
185
|
+
node.forward_markers[level].delete(marker)
|
186
|
+
next_node = node.forward[level]
|
187
|
+
next_node.markers.delete(marker)
|
188
|
+
node = next_node
|
189
|
+
end
|
190
|
+
|
191
|
+
def nodes
|
192
|
+
nodes = []
|
193
|
+
cur_node = head.forward[0]
|
194
|
+
until cur_node.nil?
|
195
|
+
nodes << cur_node
|
196
|
+
cur_node = cur_node.forward[0]
|
197
|
+
end
|
198
|
+
nodes
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
class IntervalSkipList
|
2
|
+
class Node < HeadNode
|
3
|
+
attr_accessor :key
|
4
|
+
attr_reader :markers, :endpoint_of
|
5
|
+
|
6
|
+
def initialize(key, height, path)
|
7
|
+
super(height)
|
8
|
+
@key = key
|
9
|
+
@markers = []
|
10
|
+
@endpoint_of = []
|
11
|
+
update_forward_pointers(path)
|
12
|
+
promote_markers(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def all_forward_markers
|
16
|
+
markers.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(path)
|
20
|
+
0.upto(top_level) do |i|
|
21
|
+
path[i].forward[i] = forward[i]
|
22
|
+
end
|
23
|
+
demote_markers(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def propagate_length_change(length_change)
|
27
|
+
cur_node = self
|
28
|
+
while cur_node do
|
29
|
+
cur_node.key += length_change
|
30
|
+
cur_node = cur_node.forward[0]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def update_forward_pointers(path)
|
37
|
+
0.upto(top_level) do |i|
|
38
|
+
forward[i] = path[i].forward[i]
|
39
|
+
path[i].forward[i] = self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def promote_markers(path)
|
44
|
+
promoted = []
|
45
|
+
new_promoted = []
|
46
|
+
0.upto(top_level) do |i|
|
47
|
+
incoming_markers = path[i].forward_markers[i]
|
48
|
+
markers.concat(incoming_markers)
|
49
|
+
|
50
|
+
incoming_markers.each do |marker|
|
51
|
+
if can_be_promoted_higher?(marker, i)
|
52
|
+
new_promoted.push(marker)
|
53
|
+
forward[i].delete_marker_from_path(marker, i, forward[i+1])
|
54
|
+
else
|
55
|
+
forward_markers[i].push(marker)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
promoted.each do |marker|
|
60
|
+
if can_be_promoted_higher?(marker, i)
|
61
|
+
new_promoted.push(marker)
|
62
|
+
forward[i].delete_marker_from_path(marker, i, forward[i+1])
|
63
|
+
else
|
64
|
+
forward_markers[i].push(marker)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
promoted = new_promoted
|
69
|
+
new_promoted = []
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def can_be_promoted_higher?(marker, level)
|
75
|
+
level < top_level && forward[level + 1] && forward[level + 1].markers.include?(marker)
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete_marker_from_path(marker, level, terminus)
|
79
|
+
cur_node = self
|
80
|
+
until cur_node == terminus
|
81
|
+
cur_node.forward_markers[level].delete(marker)
|
82
|
+
cur_node.markers.delete(marker)
|
83
|
+
cur_node = cur_node.forward[level]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def demote_markers(path)
|
88
|
+
demote_inbound_markers(path)
|
89
|
+
demote_outbound_markers(path)
|
90
|
+
end
|
91
|
+
|
92
|
+
def demote_inbound_markers(path)
|
93
|
+
demoted = []
|
94
|
+
new_demoted = []
|
95
|
+
|
96
|
+
top_level.downto(0) do |i|
|
97
|
+
incoming_markers = path[i].forward_markers[i].dup
|
98
|
+
incoming_markers.each do |marker|
|
99
|
+
unless forward_node_with_marker_at_or_above_level?(marker, i)
|
100
|
+
path[i].forward_markers[i].delete(marker)
|
101
|
+
new_demoted.push(marker)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
demoted.each do |marker|
|
106
|
+
path[i + 1].place_marker_on_inbound_path(marker, i, path[i])
|
107
|
+
|
108
|
+
if forward[i].markers.include?(marker)
|
109
|
+
path[i].forward_markers[i].push(marker)
|
110
|
+
else
|
111
|
+
new_demoted.push(marker)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
demoted = new_demoted
|
116
|
+
new_demoted = []
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def demote_outbound_markers(path)
|
121
|
+
demoted = []
|
122
|
+
new_demoted = []
|
123
|
+
|
124
|
+
top_level.downto(0) do |i|
|
125
|
+
forward_markers[i].each do |marker|
|
126
|
+
new_demoted.push(marker) unless path[i].forward_markers[i].include?(marker)
|
127
|
+
end
|
128
|
+
|
129
|
+
demoted.each do |marker|
|
130
|
+
forward[i].place_marker_on_outbound_path(marker, i, forward[i + 1])
|
131
|
+
new_demoted.push(marker) unless path[i].forward_markers[i].include?(marker)
|
132
|
+
end
|
133
|
+
|
134
|
+
demoted = new_demoted
|
135
|
+
new_demoted = []
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def forward_node_with_marker_at_or_above_level?(marker, level)
|
140
|
+
level.upto(top_level) do |i|
|
141
|
+
return true if forward[i].markers.include?(marker)
|
142
|
+
end
|
143
|
+
false
|
144
|
+
end
|
145
|
+
|
146
|
+
def place_marker_on_outbound_path(marker, level, terminus)
|
147
|
+
cur_node = self
|
148
|
+
until cur_node == terminus
|
149
|
+
cur_node.forward_markers[level].push(marker)
|
150
|
+
cur_node.markers.push(marker)
|
151
|
+
cur_node = cur_node.forward[level]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def place_marker_on_inbound_path(marker, level, terminus)
|
156
|
+
cur_node = self
|
157
|
+
until cur_node == terminus
|
158
|
+
cur_node.forward_markers[level].push(marker)
|
159
|
+
cur_node = cur_node.forward[level]
|
160
|
+
cur_node.markers.push(marker)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Treetop
|
2
|
+
module Runtime
|
3
|
+
class SyntaxNode
|
4
|
+
attr_reader :input, :interval
|
5
|
+
attr_accessor :parent
|
6
|
+
attr_reader :dot_id
|
7
|
+
|
8
|
+
@@dot_id_counter = 0
|
9
|
+
|
10
|
+
def initialize(input, interval, elements = nil)
|
11
|
+
@input = input
|
12
|
+
@interval = interval
|
13
|
+
@elements = elements
|
14
|
+
end
|
15
|
+
|
16
|
+
def elements
|
17
|
+
return @elements if terminal?
|
18
|
+
# replace the character class placeholders in the sequence (lazy instantiation)
|
19
|
+
last_element = nil
|
20
|
+
@comprehensive_elements ||= @elements.map do |element|
|
21
|
+
if element == true
|
22
|
+
index = last_element ? last_element.interval.last : interval.first
|
23
|
+
element = SyntaxNode.new(input, index...(index + 1))
|
24
|
+
end
|
25
|
+
element.parent = self
|
26
|
+
last_element = element
|
27
|
+
end
|
28
|
+
|
29
|
+
@dot_id = @@dot_id_counter
|
30
|
+
@@dot_id_counter += 1
|
31
|
+
|
32
|
+
@comprehensive_elements
|
33
|
+
end
|
34
|
+
|
35
|
+
def terminal?
|
36
|
+
@elements.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def nonterminal?
|
40
|
+
!terminal?
|
41
|
+
end
|
42
|
+
|
43
|
+
def text_value
|
44
|
+
input[interval]
|
45
|
+
end
|
46
|
+
|
47
|
+
def empty?
|
48
|
+
interval.first == interval.last && interval.exclude_end?
|
49
|
+
end
|
50
|
+
|
51
|
+
def <=>(other)
|
52
|
+
self.interval.first <=> other.interval.first
|
53
|
+
end
|
54
|
+
|
55
|
+
def extension_modules
|
56
|
+
local_extensions =
|
57
|
+
class <<self
|
58
|
+
included_modules-Object.included_modules
|
59
|
+
end
|
60
|
+
if local_extensions.size > 0
|
61
|
+
local_extensions
|
62
|
+
else
|
63
|
+
[] # There weren't any; must be a literal node
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect(indent="")
|
68
|
+
em = extension_modules
|
69
|
+
interesting_methods = methods-[em.last ? em.last.methods : nil]-self.class.instance_methods
|
70
|
+
im = interesting_methods.size > 0 ? " (#{interesting_methods.join(",")})" : ""
|
71
|
+
tv = text_value
|
72
|
+
tv = "...#{tv[-20..-1]}" if tv.size > 20
|
73
|
+
|
74
|
+
indent +
|
75
|
+
self.class.to_s.sub(/.*:/,'') +
|
76
|
+
em.map{|m| "+"+m.to_s.sub(/.*:/,'')}*"" +
|
77
|
+
" offset=#{interval.first}" +
|
78
|
+
", #{tv.inspect}" +
|
79
|
+
im +
|
80
|
+
(elements && elements.size > 0 ?
|
81
|
+
":" +
|
82
|
+
(elements||[]).map{|e|
|
83
|
+
begin
|
84
|
+
"\n"+e.inspect(indent+" ")
|
85
|
+
rescue # Defend against inspect not taking a parameter
|
86
|
+
"\n"+indent+" "+e.inspect
|
87
|
+
end
|
88
|
+
}.join("") :
|
89
|
+
""
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def write_dot(io)
|
94
|
+
io.puts "node#{dot_id} [label=\"#{text_value}\"];"
|
95
|
+
if nonterminal? then
|
96
|
+
elements.each do
|
97
|
+
|x|
|
98
|
+
io.puts "node#{dot_id} -> node#{x.dot_id};"
|
99
|
+
x.write_dot(io)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def write_dot_file(fname)
|
105
|
+
File.open(fname + ".dot","w") do
|
106
|
+
|file|
|
107
|
+
file.puts "digraph G {"
|
108
|
+
write_dot(file)
|
109
|
+
file.puts "}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|