oakproof 0.7.1
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 +7 -0
- data/eprover/eprover-2.3-linux +0 -0
- data/eprover/eprover-2.3-mac +0 -0
- data/eprover/eprover-2.3-windows.exe +0 -0
- data/eprover/eprover.txt +5 -0
- data/oak +3 -0
- data/src/bindings.rb +464 -0
- data/src/commands.rb +349 -0
- data/src/external prover.rb +189 -0
- data/src/grammar rules.rb +439 -0
- data/src/grammar.rb +218 -0
- data/src/oak.rb +56 -0
- data/src/parser.rb +1089 -0
- data/src/proof.rb +471 -0
- data/src/schema.rb +170 -0
- data/src/utilities.rb +361 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f15b0ead7b7f0d5f1dade59eb2a777769b8bc50f006e976fe99befea8aeae4b9
|
4
|
+
data.tar.gz: f49f7228719fa47a580bfeef65c3b1139f721afbd24f059d7ede21e6e2a6447b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9a57aedc97b65aef33feae93b2afd8e3bba209fd8f7ab6a012dd316fec287c086279caa676fd293432a4d57e7df819a4d223543a763eae14fefaf8ab5a8abbd0
|
7
|
+
data.tar.gz: c0091be99a7d8f7370b4f61c2d5b965a8076a4bb1d09f1661fab8cef4e7a3db818c4e5aa63f522c32d25e769d419747ee041bdbbb2f4cde50299ebd84720e6a9
|
Binary file
|
Binary file
|
Binary file
|
data/eprover/eprover.txt
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
E is an equational theorem prover created and maintained by Stephan Schulz (<schulz@eprover.org>), with the help of several contributors.
|
2
|
+
|
3
|
+
E is free software, developed and distributed under the GNU General Public License.
|
4
|
+
|
5
|
+
The E homepage, including source code for E and further licensing information, can be found at <http://www.eprover.org>.
|
data/src/bindings.rb
ADDED
@@ -0,0 +1,464 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This class supports "scopes" and "blocks".
|
4
|
+
|
5
|
+
Scopes are tracked automatically, while blocks are set manually.
|
6
|
+
|
7
|
+
When a variable is bound, its scope begins. Whenever it is used, its scope is
|
8
|
+
extended through that point. Its scope can end anywhere after its last use, as
|
9
|
+
long as it is (1) before the block it was bound in ends, and (2) before it is
|
10
|
+
rebound.
|
11
|
+
|
12
|
+
Blocks are set with begin_block and end_block.
|
13
|
+
|
14
|
+
Scopes cannot interleave blocks. That is, a scope cannot begin outside a block
|
15
|
+
and end inside it, or begin inside a block and end outside it.
|
16
|
+
|
17
|
+
This guarantees that if you bind a variable outside a block, and extend its
|
18
|
+
scope into the block (by using it in the block), it will "mean" the same thing
|
19
|
+
for the whole block.
|
20
|
+
|
21
|
+
Definitions are handled automatically with internal blocks.
|
22
|
+
|
23
|
+
=end
|
24
|
+
|
25
|
+
class Proof
|
26
|
+
|
27
|
+
class Bindings
|
28
|
+
class Node
|
29
|
+
attr_reader :line, :branches, :depth
|
30
|
+
attr_accessor :parent, :active
|
31
|
+
|
32
|
+
def initialize line, parent
|
33
|
+
@line, @parent = line, parent
|
34
|
+
@branches = []
|
35
|
+
@active = true
|
36
|
+
@depth = parent ? (parent.depth + 1) : 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s prefix = ''
|
40
|
+
type = case
|
41
|
+
when not(@line) then 'block'
|
42
|
+
when @line.definition? then "define block (line #{@line.fileline})"
|
43
|
+
else "line #{@line.fileline}"
|
44
|
+
end
|
45
|
+
variables = @line ? @line.binds : []
|
46
|
+
me = "#{prefix}#{type}, variables #{variables}, active #{@active}"
|
47
|
+
them = branches.collect {|node| node.to_s prefix + ' '}
|
48
|
+
[me, *them].join "\n"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Bindings
|
54
|
+
def initialize
|
55
|
+
@constants = Set[]
|
56
|
+
@bound = {}
|
57
|
+
@unbound = Set[]
|
58
|
+
@tie_ins = {}
|
59
|
+
@root = Node.new nil, nil
|
60
|
+
@blocks = [@root]
|
61
|
+
@node_for_line = Hash.new {raise}
|
62
|
+
end
|
63
|
+
|
64
|
+
def admit line # is nil when called by begin_block
|
65
|
+
# assumes check_admission has already been called!
|
66
|
+
|
67
|
+
binds = line ? line.binds : []
|
68
|
+
uses = line ? line.uses : []
|
69
|
+
|
70
|
+
# unbind any of the variables to be bound which were already bound
|
71
|
+
binds.each {|variable| deactivate_node @bound[variable] if @bound[variable]}
|
72
|
+
|
73
|
+
# add the new bindings
|
74
|
+
node = Node.new line, @blocks.last
|
75
|
+
@blocks.last.branches << node
|
76
|
+
binds.each {|variable| @bound[variable] = node}
|
77
|
+
@unbound.subtract binds
|
78
|
+
|
79
|
+
uses.each {|variable|
|
80
|
+
if @bound[variable]
|
81
|
+
# shift so that this appearance of variable will be in the binding scope
|
82
|
+
shift_under_node @bound[variable]
|
83
|
+
else
|
84
|
+
# variable was never bound, so register it as a constant
|
85
|
+
@constants << variable
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
# set tie-ins to start retroactively, as far back as possible
|
90
|
+
if line and not line.tie_ins.empty?
|
91
|
+
starter = most_recent_binding uses + binds
|
92
|
+
@tie_ins[starter] = [] if not @tie_ins[starter]
|
93
|
+
@tie_ins[starter] << node
|
94
|
+
end
|
95
|
+
|
96
|
+
# Tie-ins that are part of bindings only apply to the variables being bound,
|
97
|
+
# so their position in the tree is handled by the "uses" logic above. For
|
98
|
+
# other tie-ins, we cannot easily tell (due to their mutually recursive
|
99
|
+
# nature) whether they apply to the line being added or not. If they do, we
|
100
|
+
# need to call shift_under_node to put the line in their scope. If they
|
101
|
+
# don't, we should leave the line out of their scope, so that it will not be
|
102
|
+
# deactivated along with the tie-in. We use the following heuristic to make
|
103
|
+
# this decision:
|
104
|
+
# * If the line is a schema, it doesn't need tie-ins, and also it can't be
|
105
|
+
# cited if it is inactive, so we had better not deactivate it too soon.
|
106
|
+
# So we leave it out of the tie-in scope.
|
107
|
+
# * Otherwise, if the line doesn't bind anything, then hopefully it won't
|
108
|
+
# matter if it gets deactivated early, so we shift it under the tie-in.
|
109
|
+
# * Otherwise, we leave it out of the tie-in scope.
|
110
|
+
if line and line.binds.empty? and not line.schema
|
111
|
+
@tie_ins.each {|starter, tie_ins|
|
112
|
+
tie_ins.each {|tie_in| shift_under_node tie_in}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
@node_for_line[line] = node if line
|
117
|
+
@blocks << node if not line or line.definition?
|
118
|
+
end
|
119
|
+
|
120
|
+
def begin_block
|
121
|
+
admit nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def check_admission content
|
125
|
+
binds = content.binds
|
126
|
+
uses = content.uses
|
127
|
+
|
128
|
+
unless (@constants & binds).empty?
|
129
|
+
variable = (@constants & binds).first
|
130
|
+
msg = "cannot bind #{variable}: already occurred as a constant"
|
131
|
+
raise ProofException, msg
|
132
|
+
end
|
133
|
+
|
134
|
+
unless (@unbound & uses).empty?
|
135
|
+
variable = (@unbound & uses).first
|
136
|
+
raise ProofException, "variable #{variable} is no longer in scope"
|
137
|
+
end
|
138
|
+
|
139
|
+
binds.each {|variable|
|
140
|
+
next unless @bound[variable]
|
141
|
+
begin
|
142
|
+
to_unbind = variables_for_unbinding @bound[variable]
|
143
|
+
rescue ProofException
|
144
|
+
msg = "binding of variable #{variable} causes scope interleaving"
|
145
|
+
raise ProofException, msg
|
146
|
+
end
|
147
|
+
unless (uses & to_unbind).empty?
|
148
|
+
culprit = (uses & to_unbind).first
|
149
|
+
msg = "placement of variable #{culprit} causes scope interleaving"
|
150
|
+
raise ProofException, msg
|
151
|
+
end
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def end_block
|
156
|
+
@blocks.pop while @blocks.last.line # pop define blocks
|
157
|
+
node = @blocks.pop
|
158
|
+
deactivate_node node
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_check content, cited_lines
|
162
|
+
# adjust if schema is being cited
|
163
|
+
schema_lines = cited_lines.select {|line| line.schema}
|
164
|
+
if schema_lines.size > 1
|
165
|
+
raise ProofException, 'cannot cite multiple schemas'
|
166
|
+
elsif not schema_lines.empty?
|
167
|
+
if not @node_for_line[schema_lines[0]].active
|
168
|
+
raise ProofException, 'cannot cite inactive schema'
|
169
|
+
end
|
170
|
+
cited_lines -= [schema_lines[0]]
|
171
|
+
schema = schema_lines[0].schema
|
172
|
+
end
|
173
|
+
|
174
|
+
# handle cases that don't need tie-ins
|
175
|
+
cited = cited_tree cited_lines, false
|
176
|
+
tree = implication_tree [cited], content.sentence
|
177
|
+
if schema
|
178
|
+
return {:schema => schema, :tree => tree}
|
179
|
+
elsif self_implication? tree
|
180
|
+
tree = tree_for_true # make things easy for external prover
|
181
|
+
return {:tree => tree}
|
182
|
+
end
|
183
|
+
|
184
|
+
# finally, handle tie-ins
|
185
|
+
cited = cited_tree cited_lines, true
|
186
|
+
tree = nil
|
187
|
+
if content.body
|
188
|
+
# apply active tie-ins to the body, as long as they do not use or bind
|
189
|
+
# vars which content is rebinding
|
190
|
+
tie_ins = @tie_ins.values.flatten.select {|tie_in|
|
191
|
+
uses_or_binds = tie_in.line.uses + tie_in.line.binds
|
192
|
+
(uses_or_binds & content.binds).empty?
|
193
|
+
}
|
194
|
+
tied_in = tied_in_for content.body, tie_ins
|
195
|
+
tied_in = tied_in.collect {|match, used| match}
|
196
|
+
tree = implication_tree tied_in, content.body
|
197
|
+
end
|
198
|
+
tree = bind_variables content.binds, tree, :for_some
|
199
|
+
tree = implication_tree [cited], tree
|
200
|
+
tree = deduplicate tree
|
201
|
+
{:tree => tree}
|
202
|
+
end
|
203
|
+
|
204
|
+
def to_s
|
205
|
+
@root.to_s
|
206
|
+
end
|
207
|
+
|
208
|
+
private ###################################################################
|
209
|
+
|
210
|
+
# tie-ins are a bit tricky. here are two of the main ideas.
|
211
|
+
#
|
212
|
+
# (1) for any line, there are two classes of tie-ins that can apply to it:
|
213
|
+
# those that were in effect at the time the line was added, and those which
|
214
|
+
# are in effect now. to handle the second class, we place them
|
215
|
+
# "retroactively" at the earliest nodes in the tree they can apply to. these
|
216
|
+
# placements are kept in @tie_ins. they stay there for as long as they are
|
217
|
+
# active, then fall back to their placement in the line that created them.
|
218
|
+
#
|
219
|
+
# (2) matches for a tie-in cannot be computed "high" in the tree, because
|
220
|
+
# there may be lower tie-ins that could apply to them. but they cannot be
|
221
|
+
# added to the tree "low", because they may then be encompassed in a binding
|
222
|
+
# or supposition context which need not apply to them. so we have to compute
|
223
|
+
# them "low" and add them "high". to do this, we pass the tie-ins down
|
224
|
+
# through the tree, compute their matches on the way back up, but store the
|
225
|
+
# results as pending and only add them when we cannot retract them any further
|
226
|
+
# up the tree.
|
227
|
+
|
228
|
+
def cited_tree cited_lines, add_tied_in
|
229
|
+
cited_nodes = cited_lines.collect {|line| @node_for_line[line]}
|
230
|
+
ancestors = get_ancestors cited_nodes
|
231
|
+
tie_ins = (add_tied_in ? [] : nil)
|
232
|
+
tree, pending = cited_tree_internal @root, cited_nodes, ancestors, tie_ins
|
233
|
+
raise if not pending.empty? # sanity check
|
234
|
+
tree
|
235
|
+
end
|
236
|
+
|
237
|
+
def cited_tree_internal scope_tree, cited_nodes, ancestors, tie_ins
|
238
|
+
# if nothing under it was cited, there will be nothing to return
|
239
|
+
return [nil, []] unless ancestors.include? scope_tree
|
240
|
+
|
241
|
+
active = scope_tree.active
|
242
|
+
line = scope_tree.line
|
243
|
+
delay = (line and line.supposition? and not active) # handle next level up
|
244
|
+
|
245
|
+
if tie_ins and not delay
|
246
|
+
tie_in_precount = tie_ins.size
|
247
|
+
# add tie-ins defined here, unless they are active, in which case they
|
248
|
+
# are handled by the retroactive placement
|
249
|
+
tie_ins += [scope_tree] if line and not line.tie_ins.empty? and not active
|
250
|
+
# add any tie-ins that have been retroactively placed here
|
251
|
+
tie_ins += @tie_ins[scope_tree] if @tie_ins[scope_tree]
|
252
|
+
end
|
253
|
+
|
254
|
+
# make trees recursively
|
255
|
+
trees, pending = [], []
|
256
|
+
scope_tree.branches.each {|branch|
|
257
|
+
result = cited_tree_internal branch, cited_nodes, ancestors, tie_ins
|
258
|
+
trees << result[0] if result[0]
|
259
|
+
pending.concat result[1]
|
260
|
+
}
|
261
|
+
|
262
|
+
return [conjunction_tree(trees), pending] if delay
|
263
|
+
|
264
|
+
# supposition start block (first branch is the supposition itself)
|
265
|
+
# handled "one level up" because supposition may be wider than its binding
|
266
|
+
branch = scope_tree.branches[0]
|
267
|
+
if branch and branch.line and branch.line.supposition? and not active
|
268
|
+
raise if line # should be a supposition block
|
269
|
+
line = branch.line
|
270
|
+
tree = conjunction_tree trees
|
271
|
+
tree = Tree.new :implies, [line.body, tree] if line.body and tree
|
272
|
+
pending.concat tied_in_for line.body, tie_ins if line.body and tie_ins
|
273
|
+
readies = release_pending pending, line, tie_in_precount if tie_ins
|
274
|
+
tree = conjunction_tree [*readies, tree].compact
|
275
|
+
# generalize the binding
|
276
|
+
tree = bind_variables line.binds, tree, :for_all if tree
|
277
|
+
return [tree, pending]
|
278
|
+
end
|
279
|
+
|
280
|
+
if line and line.body and cited_nodes.include? scope_tree
|
281
|
+
trees.unshift line.body
|
282
|
+
pending.concat tied_in_for line.body, tie_ins if tie_ins
|
283
|
+
end
|
284
|
+
trees.unshift *release_pending(pending, line, tie_in_precount) if tie_ins
|
285
|
+
tree = conjunction_tree trees
|
286
|
+
tree = bind_variables line.binds, tree, :for_some if line and not active
|
287
|
+
[tree, pending]
|
288
|
+
end
|
289
|
+
|
290
|
+
def deactivate_node n
|
291
|
+
return if not n.active
|
292
|
+
n.branches.each {|branch| deactivate_node branch}
|
293
|
+
if n.line
|
294
|
+
n.line.binds.each {|v| @bound.delete v}
|
295
|
+
@unbound.merge n.line.binds
|
296
|
+
@tie_ins.each {|starter, tie_ins| tie_ins.delete n}
|
297
|
+
end
|
298
|
+
n.active = false
|
299
|
+
end
|
300
|
+
|
301
|
+
def deduplicate tree, facts = [], top = true
|
302
|
+
# does some simplifying to reduce duplication in the tree
|
303
|
+
tree = case tree.operator
|
304
|
+
when :for_all, :for_some
|
305
|
+
vars, body = tree.subtrees
|
306
|
+
return tree if not body
|
307
|
+
facts = facts.select {|fact|
|
308
|
+
(fact.free_variables & vars.operator).empty?
|
309
|
+
}
|
310
|
+
body = deduplicate body, facts, false
|
311
|
+
Tree.new tree.operator, [vars, body] if body
|
312
|
+
when :and
|
313
|
+
subtrees = tree.subtrees.uniq - facts.to_a
|
314
|
+
subtrees = subtrees.collect.with_index {|subtree, i|
|
315
|
+
new_facts = facts + subtrees[0...i] + subtrees[i+1..-1]
|
316
|
+
deduplicate subtree, new_facts, false
|
317
|
+
}.compact
|
318
|
+
if subtrees.empty?
|
319
|
+
nil
|
320
|
+
elsif subtrees.size == 1
|
321
|
+
subtrees[0]
|
322
|
+
else
|
323
|
+
Tree.new :and, subtrees
|
324
|
+
end
|
325
|
+
when :implies
|
326
|
+
antecedent = deduplicate tree.subtrees[0], facts, false
|
327
|
+
if tree.subtrees[0].operator == :and
|
328
|
+
new_facts = facts + tree.subtrees[0].subtrees
|
329
|
+
else
|
330
|
+
new_facts = facts + [tree.subtrees[0]]
|
331
|
+
end
|
332
|
+
consequent = deduplicate tree.subtrees[1], new_facts, false
|
333
|
+
consequent ? implication_tree([antecedent], consequent) : nil
|
334
|
+
else
|
335
|
+
tree
|
336
|
+
end
|
337
|
+
(top and not tree) ? tree_for_true : tree
|
338
|
+
end
|
339
|
+
|
340
|
+
def get_ancestors nodes
|
341
|
+
result = Set[]
|
342
|
+
nodes.each {|node|
|
343
|
+
while node
|
344
|
+
result << node
|
345
|
+
node = node.parent
|
346
|
+
end
|
347
|
+
}
|
348
|
+
result
|
349
|
+
end
|
350
|
+
|
351
|
+
def matches_in tie_in, tree
|
352
|
+
# find all matches in the tree and its subtrees for the tie-in
|
353
|
+
matches = matches_in_internal tie_in, tree, Set[]
|
354
|
+
matches.uniq
|
355
|
+
end
|
356
|
+
|
357
|
+
def matches_in_internal tie_in, tree, quantified
|
358
|
+
if Tree::Quantifiers.include? tree.operator
|
359
|
+
return [] if tree.subtrees.size == 1
|
360
|
+
# start = Time.now
|
361
|
+
vars = tree.subtrees[0].operator
|
362
|
+
# if pattern contains quantified variables, it cannot match
|
363
|
+
return [] if not (vars & tie_in.pattern.free_variables).empty?
|
364
|
+
quantified += Set[*vars]
|
365
|
+
return matches_in_internal tie_in, tree.subtrees[1], quantified
|
366
|
+
end
|
367
|
+
matches = []
|
368
|
+
tree.subtrees.each {|subtree|
|
369
|
+
matches.concat matches_in_internal tie_in, subtree, quantified
|
370
|
+
}
|
371
|
+
begin # check tree as a whole
|
372
|
+
params = [tie_in.pattern, tree, tie_in.metas]
|
373
|
+
constraints = SchemaModule.find_constraints *params
|
374
|
+
next if not constraints
|
375
|
+
|
376
|
+
resolved = SchemaModule.resolve_constraints constraints
|
377
|
+
next if not resolved
|
378
|
+
next unless (tie_in.metas - resolved.keys).empty?
|
379
|
+
|
380
|
+
# tie-ins must be terms
|
381
|
+
next unless resolved.values.none? {|v| v.boolean?}
|
382
|
+
|
383
|
+
# tie-ins cannot instantiate quantified variables
|
384
|
+
used_metas = tie_in.metas & tie_in.body.free_variables
|
385
|
+
found = used_metas.find {|meta|
|
386
|
+
not (quantified & resolved[meta].free_variables).empty?
|
387
|
+
}
|
388
|
+
next if found
|
389
|
+
|
390
|
+
matches << substitute(tie_in.body, resolved)
|
391
|
+
rescue ProofException # tree as a whole didn't match
|
392
|
+
end while false # for next to work!
|
393
|
+
matches
|
394
|
+
end
|
395
|
+
|
396
|
+
def most_recent_binding vars
|
397
|
+
# assumes bound vars are on same root-to-leaf path
|
398
|
+
nodes = vars.map {|v| @bound[v]}.compact
|
399
|
+
return @root if nodes.empty?
|
400
|
+
nodes.max_by {|node| node.depth}
|
401
|
+
end
|
402
|
+
|
403
|
+
def shift_under_node n
|
404
|
+
# shift everything after n to be under n
|
405
|
+
holder = n
|
406
|
+
while holder != @root
|
407
|
+
parent = holder.parent
|
408
|
+
i = parent.branches.index holder
|
409
|
+
nodes = parent.branches[i+1..-1]
|
410
|
+
parent.branches.slice! i+1..-1
|
411
|
+
n.branches.concat nodes
|
412
|
+
nodes.each {|node| node.parent = n}
|
413
|
+
holder = parent
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def tied_in_for tree, tie_in_nodes, used = [], calls = {}
|
418
|
+
# find all tie-ins for the tree
|
419
|
+
calls[tree] = [] if not calls[tree]
|
420
|
+
# skip if we already made this call with fewer restrictions
|
421
|
+
return [] if calls[tree].find {|call| (call - used).empty?}
|
422
|
+
calls[tree] << used
|
423
|
+
results = []
|
424
|
+
tie_in_nodes.each_with_index {|node, i|
|
425
|
+
node.line.tie_ins.each_with_index {|tie_in, j|
|
426
|
+
# skip tie-ins used in making the tree
|
427
|
+
next if used.include? [i,j]
|
428
|
+
matches_in(tie_in, tree).each {|match|
|
429
|
+
new_used = used + [[i,j]]
|
430
|
+
results << [match, new_used.transpose[0]]
|
431
|
+
results.concat tied_in_for match, tie_in_nodes, new_used, calls
|
432
|
+
}
|
433
|
+
}
|
434
|
+
}
|
435
|
+
results
|
436
|
+
end
|
437
|
+
|
438
|
+
def release_pending pending, line, threshold
|
439
|
+
# find any pending tie-in matches that cannot be retracted further upward,
|
440
|
+
# remove them from pending, and return them
|
441
|
+
readies = []
|
442
|
+
vars = line ? line.binds : []
|
443
|
+
pending.delete_if {|match, used|
|
444
|
+
ready = false
|
445
|
+
# match uses a variable that this line binds
|
446
|
+
ready = true if not (match.free_variables & vars).empty?
|
447
|
+
# match uses a tie-in added by this line
|
448
|
+
ready = true if used.max >= threshold
|
449
|
+
readies << match if ready
|
450
|
+
ready
|
451
|
+
}
|
452
|
+
readies
|
453
|
+
end
|
454
|
+
|
455
|
+
def variables_for_unbinding n
|
456
|
+
return [] if not n.active
|
457
|
+
raise ProofException if @blocks.include? n # can't unbind a block
|
458
|
+
result = n.line.binds.dup
|
459
|
+
n.branches.each {|branch| result.concat variables_for_unbinding(branch)}
|
460
|
+
result
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|