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
data/src/parser.rb
ADDED
@@ -0,0 +1,1089 @@
|
|
1
|
+
require_relative 'grammar.rb'
|
2
|
+
require_relative 'grammar rules.rb'
|
3
|
+
|
4
|
+
class Parser
|
5
|
+
def initialize
|
6
|
+
array = grammar_rules
|
7
|
+
grammar = Grammar.new array
|
8
|
+
@grammar_parser = GrammarParser.new grammar
|
9
|
+
end
|
10
|
+
|
11
|
+
def block_comment input
|
12
|
+
# returns blocks to be kept after removing block comments
|
13
|
+
blocks = []
|
14
|
+
nested = 0
|
15
|
+
position = 0
|
16
|
+
while true
|
17
|
+
opening = input.index '/#', position
|
18
|
+
closing = input.index '#/', position
|
19
|
+
last_position = position
|
20
|
+
if opening and (not closing or opening < closing) # opening is next
|
21
|
+
blocks << [position, opening-1] if nested == 0 and opening > position
|
22
|
+
nested += 1
|
23
|
+
position = opening + 2
|
24
|
+
elsif (closing and nested == 0) or (not closing and nested > 0)
|
25
|
+
raise ParseException, 'parse failed due to mismatched block comments'
|
26
|
+
elsif closing # closing is next
|
27
|
+
nested -= 1
|
28
|
+
position = closing + 2
|
29
|
+
else
|
30
|
+
blocks << [position, input.length-1] if input.length > position
|
31
|
+
break
|
32
|
+
end
|
33
|
+
end
|
34
|
+
blocks
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert_inequality relation, subtrees
|
38
|
+
case relation
|
39
|
+
when '<'
|
40
|
+
operator = :predicate
|
41
|
+
subtrees = [Tree.new('<', []), *subtrees]
|
42
|
+
when '≤', '<='
|
43
|
+
operator = :or
|
44
|
+
subtrees = [
|
45
|
+
Tree.new(:predicate, [Tree.new('<', []), *subtrees]),
|
46
|
+
Tree.new(:equals, subtrees)
|
47
|
+
]
|
48
|
+
when '>'
|
49
|
+
operator = :predicate
|
50
|
+
subtrees = [Tree.new('<', []), *subtrees.reverse]
|
51
|
+
when '≥', '>='
|
52
|
+
operator = :or
|
53
|
+
subtrees = [
|
54
|
+
Tree.new(:predicate, [Tree.new('<', []), *subtrees.reverse]),
|
55
|
+
Tree.new(:equals, subtrees)
|
56
|
+
]
|
57
|
+
end
|
58
|
+
Tree.new operator, subtrees
|
59
|
+
end
|
60
|
+
|
61
|
+
def convert_set relation, subtrees
|
62
|
+
case relation
|
63
|
+
# any of {⊆, ⊊}, {⊆, ⊂}, {⊂, ⊊} may be desired, so we do not translate
|
64
|
+
# between relations, other than flipping them
|
65
|
+
when '⊆'
|
66
|
+
operator = :predicate
|
67
|
+
subtrees = [Tree.new('⊆', []), *subtrees]
|
68
|
+
when '⊇'
|
69
|
+
operator = :predicate
|
70
|
+
subtrees = [Tree.new('⊆', []), *subtrees.reverse]
|
71
|
+
when '⊊'
|
72
|
+
operator = :predicate
|
73
|
+
subtrees = [Tree.new('⊊', []), *subtrees]
|
74
|
+
when '⊋'
|
75
|
+
operator = :predicate
|
76
|
+
subtrees = [Tree.new('⊊', []), *subtrees.reverse]
|
77
|
+
when '⊂'
|
78
|
+
operator = :predicate
|
79
|
+
subtrees = [Tree.new('⊂', []), *subtrees]
|
80
|
+
when '⊃'
|
81
|
+
operator = :predicate
|
82
|
+
subtrees = [Tree.new('⊂', []), *subtrees.reverse]
|
83
|
+
end
|
84
|
+
Tree.new operator, subtrees
|
85
|
+
end
|
86
|
+
|
87
|
+
def label_from_branch branch
|
88
|
+
if branch.value == :label or branch.value == :label_same_line
|
89
|
+
branch = branch.branches[0]
|
90
|
+
end
|
91
|
+
raise unless [:label_name, :label_name_same_line].include? branch.value
|
92
|
+
words = branch.branches.select {|branch|
|
93
|
+
next false unless branch.value.is_a? Symbol
|
94
|
+
(branch.value == :atom) ? true : raise
|
95
|
+
}
|
96
|
+
words.collect {|word| word.text}.join ' '
|
97
|
+
end
|
98
|
+
|
99
|
+
def line_comment input
|
100
|
+
# returns blocks to be kept after removing line comments
|
101
|
+
blocks = []
|
102
|
+
position = 0
|
103
|
+
while true
|
104
|
+
opening = input.index '#', position
|
105
|
+
if opening
|
106
|
+
blocks << [position, opening-1] if opening > position
|
107
|
+
position = input.index "\n", opening
|
108
|
+
break if not position
|
109
|
+
else
|
110
|
+
blocks << [position, input.length-1] if input.length > position
|
111
|
+
break
|
112
|
+
end
|
113
|
+
end
|
114
|
+
blocks
|
115
|
+
end
|
116
|
+
|
117
|
+
def normalize_whitespace! input
|
118
|
+
node = (input.is_a?(GrammarTree) ? input.root : input)
|
119
|
+
if node.value.is_a? String
|
120
|
+
# strip and contract whitespace sequences to single spaces
|
121
|
+
node.value = node.value.strip.gsub /\s+/, ' '
|
122
|
+
end
|
123
|
+
if node.branches
|
124
|
+
node.branches.each {|branch| normalize_whitespace! branch}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def parse_each input
|
129
|
+
text, line_numbers = strip_comments input
|
130
|
+
last_end = 0
|
131
|
+
begin
|
132
|
+
@grammar_parser.parse_each(text) {|grammar_tree, sentence, position|
|
133
|
+
=begin
|
134
|
+
puts "sentence: #{sentence.inspect}\n\n"
|
135
|
+
puts "grammar tree:"
|
136
|
+
puts grammar_tree
|
137
|
+
puts
|
138
|
+
=end
|
139
|
+
line = line_numbers.call position
|
140
|
+
begin
|
141
|
+
action, content, reasons, label = process_statement grammar_tree
|
142
|
+
rescue ParseException => e
|
143
|
+
raise ParseException.new e.message, line # add line number
|
144
|
+
end
|
145
|
+
=begin
|
146
|
+
if content.is_a? Tree
|
147
|
+
puts "parsed Tree:"
|
148
|
+
p content
|
149
|
+
puts
|
150
|
+
end
|
151
|
+
=end
|
152
|
+
yield sentence, action, content, reasons, label, line
|
153
|
+
last_end = position + sentence.length
|
154
|
+
}
|
155
|
+
rescue GrammarParseException => e
|
156
|
+
=begin
|
157
|
+
puts "\n", e.tree
|
158
|
+
=end
|
159
|
+
# find first character after the last successful parse
|
160
|
+
p = (last_end...text.length).find {|i| text[i].strip != ''}
|
161
|
+
# report error between that character and current position
|
162
|
+
line = line_numbers.call p
|
163
|
+
newline = text.index "\n", p
|
164
|
+
stop = (newline ? [e.position, newline].max : text.size)
|
165
|
+
context = text[p...stop].strip.gsub /\s+/, ' '
|
166
|
+
raise ParseException.new "\"#{context}\"", line
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def process_atom_list node
|
171
|
+
raise unless node.value == :atom_list
|
172
|
+
variables, conditions = [], []
|
173
|
+
node.branches.each {|branch|
|
174
|
+
next if branch.value.downcase == 'and'
|
175
|
+
new_variables, new_conditions = process_atom_block branch
|
176
|
+
variables.concat new_variables
|
177
|
+
conditions.concat new_conditions
|
178
|
+
}
|
179
|
+
[variables, conditions]
|
180
|
+
end
|
181
|
+
|
182
|
+
def process_atom_block node
|
183
|
+
raise unless [:atom_block, :atom_list_adjacent].include? node.value
|
184
|
+
properties, variables = [], []
|
185
|
+
ids = [
|
186
|
+
:word, :word_same_line, :definable, :definable_same_line, :definable_raw
|
187
|
+
]
|
188
|
+
switched = false
|
189
|
+
node.branches.each_with_index {|branch, i|
|
190
|
+
if switched
|
191
|
+
variables << branch if ids.include? branch.value
|
192
|
+
elsif i+1 < node.branches.size and ids.include? node.branches[i+1].value
|
193
|
+
properties << branch
|
194
|
+
else # this is the last id, so switch from properties to variables
|
195
|
+
variables << branch
|
196
|
+
switched = true
|
197
|
+
end
|
198
|
+
}
|
199
|
+
condition = node.branches.last if node.branches.last.value == :condition
|
200
|
+
process_conditions properties, variables, condition
|
201
|
+
end
|
202
|
+
|
203
|
+
def process_conditions properties, variables, condition
|
204
|
+
variable_trees = variables.collect {|node| tree_from_grammar node}
|
205
|
+
property_trees = properties.collect {|node| tree_from_grammar node}
|
206
|
+
right_side = tree_from_grammar condition.branches[1] if condition
|
207
|
+
conditions = variable_trees.collect {|variable_tree|
|
208
|
+
trees = property_trees.collect {|property_tree|
|
209
|
+
Tree.new :predicate, [property_tree, variable_tree]
|
210
|
+
}
|
211
|
+
if condition
|
212
|
+
trees << apply_condition(condition, right_side, variable_tree)
|
213
|
+
end
|
214
|
+
conjunction_tree trees
|
215
|
+
}
|
216
|
+
variables = variable_trees.collect {|tree| tree.operator}
|
217
|
+
[variables, conditions]
|
218
|
+
end
|
219
|
+
|
220
|
+
def apply_condition condition, right_side, variable_tree
|
221
|
+
if condition.branches[0].value == :inequality
|
222
|
+
relation = condition.branches[0].branches[0].value
|
223
|
+
convert_inequality relation, [variable_tree, right_side]
|
224
|
+
elsif condition.branches[0].value == :not_equal
|
225
|
+
subtrees = [Tree.new(:equals, [variable_tree, right_side])]
|
226
|
+
Tree.new :not, subtrees
|
227
|
+
elsif condition.branches[0].value.downcase == 'in'
|
228
|
+
subtrees = [Tree.new('in', []), variable_tree, right_side]
|
229
|
+
Tree.new :predicate, subtrees
|
230
|
+
elsif condition.branches[0].value.downcase == 'not in'
|
231
|
+
subtrees = [Tree.new('in', []), variable_tree, right_side]
|
232
|
+
Tree.new :not, [Tree.new(:predicate, subtrees)]
|
233
|
+
elsif condition.branches[0].value == :set_relation
|
234
|
+
relation = condition.branches[0].branches[0].value
|
235
|
+
convert_set relation, [variable_tree, right_side]
|
236
|
+
else
|
237
|
+
raise "unknown condition #{condition.branches[0].value.inspect}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def process_list_with_such node
|
242
|
+
variables, conditions = process_atom_list node.branches[0]
|
243
|
+
if (i = node.branches.index {|branch| branch.value == :with})
|
244
|
+
with = tree_from_grammar node.branches[i+1]
|
245
|
+
end
|
246
|
+
if (i = node.branches.index {|branch| branch.value == :such_that})
|
247
|
+
such_that = tree_from_grammar node.branches[i+1]
|
248
|
+
end
|
249
|
+
[variables, conditions, with, such_that]
|
250
|
+
end
|
251
|
+
|
252
|
+
def process_statement grammar_tree
|
253
|
+
tree = grammar_tree
|
254
|
+
normalize_whitespace! tree
|
255
|
+
raise unless tree.root.value == :start
|
256
|
+
first_branch = tree.root.branches[0]
|
257
|
+
return :empty if first_branch.value == :ending
|
258
|
+
return :now if first_branch.value == :now
|
259
|
+
return :end if first_branch.value == :end
|
260
|
+
return :exit if first_branch.value == :exit
|
261
|
+
action, content, label = nil
|
262
|
+
reasons = []
|
263
|
+
|
264
|
+
label_branch = tree.root.branches.find {|branch| branch.value == :label}
|
265
|
+
label = label_from_branch label_branch if label_branch
|
266
|
+
update_label = proc {|branch|
|
267
|
+
next unless branch.value == :label_same_line
|
268
|
+
raise ParseException, 'multiple labels' if label
|
269
|
+
label = label_from_branch branch
|
270
|
+
}
|
271
|
+
|
272
|
+
actions = [
|
273
|
+
:include, :assume, :axiom, :suppose, :take, :derive, :so, :tie_in,
|
274
|
+
:begin_assume, :end_assume, :marker
|
275
|
+
]
|
276
|
+
action_branch = tree.root.branches.find {|branch|
|
277
|
+
actions.include? branch.value
|
278
|
+
}
|
279
|
+
raise 'could not find an action!' if not action_branch
|
280
|
+
action = action_branch.value
|
281
|
+
|
282
|
+
return action if [:begin_assume, :end_assume, :marker].include? action
|
283
|
+
|
284
|
+
if action == :include
|
285
|
+
raise unless action_branch.branches.size == 4 # command, quotes around content
|
286
|
+
return :include, action_branch.branches[2].text
|
287
|
+
end
|
288
|
+
|
289
|
+
content_branch = case action
|
290
|
+
when :assume
|
291
|
+
update_label.call action_branch.branches[1]
|
292
|
+
action_branch.branches[-1]
|
293
|
+
when :axiom
|
294
|
+
update_label.call action_branch.branches[1]
|
295
|
+
action_branch.branches[-1]
|
296
|
+
when :suppose
|
297
|
+
update_label.call action_branch.branches[1]
|
298
|
+
action_branch.branches[-1]
|
299
|
+
when :take
|
300
|
+
action = :suppose
|
301
|
+
branch = action_branch.branches[0]
|
302
|
+
raise unless branch.value == :take_label
|
303
|
+
update_label.call branch.branches[1] if branch.branches.size > 1
|
304
|
+
action_branch
|
305
|
+
when :derive
|
306
|
+
action_branch.branches[0]
|
307
|
+
when :so
|
308
|
+
if action_branch.branches[1].value == :assume
|
309
|
+
action = :so_assume
|
310
|
+
action_branch = action_branch.branches[1]
|
311
|
+
update_label.call action_branch.branches[1]
|
312
|
+
if action_branch.branches[-1].value == :schema
|
313
|
+
raise ParseException, 'cannot use "so" with schema'
|
314
|
+
end
|
315
|
+
action_branch.branches[-1]
|
316
|
+
else
|
317
|
+
action = :so
|
318
|
+
update_label.call action_branch.branches[1]
|
319
|
+
action_branch.branches[-1]
|
320
|
+
end
|
321
|
+
when :tie_in
|
322
|
+
action = :derive
|
323
|
+
action_branch
|
324
|
+
end
|
325
|
+
|
326
|
+
content = Content.new tree_from_grammar content_branch, true
|
327
|
+
|
328
|
+
proof_branch = tree.root.branches.find {|branch| branch.value == :proof}
|
329
|
+
if proof_branch
|
330
|
+
if action == :so or action == :so_assume
|
331
|
+
raise ParseException, 'cannot use "so" with proof block'
|
332
|
+
end
|
333
|
+
raise unless action == :derive
|
334
|
+
action = :proof
|
335
|
+
end
|
336
|
+
|
337
|
+
by_branch = tree.root.branches.find {|branch| branch.value == :by}
|
338
|
+
if by_branch
|
339
|
+
raise ParseException, 'cannot use "by" with schema' if content.schema
|
340
|
+
by_branch.branches.each {|branch|
|
341
|
+
case branch.value
|
342
|
+
when :label_name then reasons << label_from_branch(branch)
|
343
|
+
when :question_mark then reasons << :question_mark
|
344
|
+
when Symbol then raise "unknown reason #{branch.value.inspect}"
|
345
|
+
end
|
346
|
+
}
|
347
|
+
end
|
348
|
+
|
349
|
+
if not content.schema
|
350
|
+
found = content.tie_ins.find {|tie_in|
|
351
|
+
tie_in.pattern.contains?(:substitution) or
|
352
|
+
tie_in.body.contains?(:substitution)
|
353
|
+
}
|
354
|
+
if found or content.sentence.contains? :substitution
|
355
|
+
raise ParseException, 'cannot use {...} outside schema'
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
[action, content, reasons, label]
|
360
|
+
end
|
361
|
+
|
362
|
+
def standardize_operator operator
|
363
|
+
case operator
|
364
|
+
when '/' then '÷'
|
365
|
+
when '**' then '^'
|
366
|
+
else operator
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def strip_comments input
|
371
|
+
manager = LineNumberManager.new input
|
372
|
+
blocks = block_comment manager.text
|
373
|
+
manager.use blocks
|
374
|
+
blocks = line_comment manager.text
|
375
|
+
manager.use blocks
|
376
|
+
[manager.text, manager.line_numbers]
|
377
|
+
end
|
378
|
+
|
379
|
+
def tree_for_subject subject, node
|
380
|
+
subtrees = case node.value
|
381
|
+
when :category then node.branches
|
382
|
+
when :quantified then node.branches[1].branches
|
383
|
+
when :word then [node]
|
384
|
+
end
|
385
|
+
if subtrees.length >= 2 and subtrees[-2].value == :preposition
|
386
|
+
predicate = tree_from_grammar subtrees[-3]
|
387
|
+
other = tree_from_grammar subtrees[-1]
|
388
|
+
preposition_tree = Tree.new :predicate, [
|
389
|
+
predicate, subject, other
|
390
|
+
]
|
391
|
+
subtrees = subtrees[0...-3]
|
392
|
+
end
|
393
|
+
subtrees = subtrees.collect {|subtree|
|
394
|
+
predicate = tree_from_grammar subtree
|
395
|
+
Tree.new :predicate, [predicate, subject]
|
396
|
+
}
|
397
|
+
subtrees << preposition_tree if preposition_tree
|
398
|
+
conjunction_tree subtrees
|
399
|
+
end
|
400
|
+
|
401
|
+
def tree_from_grammar node, open_to_bind = false
|
402
|
+
case node.value
|
403
|
+
when :exp, :exp_, :exp0, :exp1, :exp2, :exp3, :exp4, :exp5, :exp6
|
404
|
+
if node.branches.size == 1
|
405
|
+
return tree_from_grammar(node.branches[0], open_to_bind)
|
406
|
+
elsif node.branches.size == 3 and [:is, :is_not].include? node.branches[1].value
|
407
|
+
subject = tree_from_grammar node.branches[0]
|
408
|
+
tree = tree_for_subject subject, node.branches[2]
|
409
|
+
return case node.branches[1].value
|
410
|
+
when :is then tree
|
411
|
+
when :is_not then Tree.new :not, [tree]
|
412
|
+
end
|
413
|
+
elsif node.branches.size >= 3 and [:and, :or].include? node.branches[1].value
|
414
|
+
operator = node.branches[1].value
|
415
|
+
branches = node.branches.select.each_with_index {|branch, i| i.even?}
|
416
|
+
subtrees = branches.collect {|branch| tree_from_grammar branch}
|
417
|
+
|
418
|
+
# check that "is" is not used with "and/or" plus an atom
|
419
|
+
subtrees.each_with_index {|subtree, i|
|
420
|
+
next unless subtree.subtrees.empty? and i > 0 # atom
|
421
|
+
prev = node.branches[(i-1)*2]
|
422
|
+
is = false
|
423
|
+
is = true if prev.branches[0].value == :prefix2
|
424
|
+
tags = [:is, :is_not, :is_in, :is_not_in]
|
425
|
+
if prev.branches.size >= 2 and tags.include? prev.branches[1].value
|
426
|
+
is = true
|
427
|
+
end
|
428
|
+
raise ParseException, 'ambiguous expression' if is
|
429
|
+
}
|
430
|
+
elsif node.branches.size >= 3 and [
|
431
|
+
:plus, :union, :intersection, :times
|
432
|
+
].include? node.branches[1].value
|
433
|
+
operator = node.branches[1].branches[0].value
|
434
|
+
branches = node.branches.select.each_with_index {|branch, i| i.even?}
|
435
|
+
subtrees = branches.collect {|branch| tree_from_grammar branch}
|
436
|
+
operator_tree = Tree.new operator, []
|
437
|
+
tree = Tree.new :predicate, [operator_tree, subtrees[0], subtrees[1]]
|
438
|
+
subtrees[2..-1].each {|subtree|
|
439
|
+
tree = Tree.new :predicate, [operator_tree, tree, subtree]
|
440
|
+
}
|
441
|
+
return tree
|
442
|
+
elsif node.branches.size == 3
|
443
|
+
operator = node.branches[1].value.downcase.to_sym
|
444
|
+
subtrees = [
|
445
|
+
tree_from_grammar(node.branches[0]),
|
446
|
+
tree_from_grammar(node.branches[2])
|
447
|
+
]
|
448
|
+
if [:'-', :'/', :'÷', :'**', :'^'].include? operator
|
449
|
+
operator = standardize_operator operator.to_s
|
450
|
+
subtrees = [Tree.new(operator, [])] + subtrees
|
451
|
+
operator = :predicate
|
452
|
+
elsif operator == :'='
|
453
|
+
operator = :equals
|
454
|
+
elsif operator == :not_equal
|
455
|
+
operator = :not
|
456
|
+
subtrees = [Tree.new(:equals, subtrees)]
|
457
|
+
elsif operator == :inequality
|
458
|
+
relation = node.branches[1].branches[0].value
|
459
|
+
inequality_tree = convert_inequality relation, subtrees
|
460
|
+
operator, subtrees = inequality_tree.operator, inequality_tree.subtrees
|
461
|
+
elsif operator == :is_in
|
462
|
+
operator = :predicate
|
463
|
+
subtrees = [Tree.new('in', []), *subtrees]
|
464
|
+
elsif operator == :is_not_in
|
465
|
+
operator = :not
|
466
|
+
subsubtrees = [Tree.new('in', []), *subtrees]
|
467
|
+
subtrees = [Tree.new(:predicate, subsubtrees)]
|
468
|
+
elsif operator == :set_relation
|
469
|
+
relation = node.branches[1].branches[0].value
|
470
|
+
set_tree = convert_set relation, subtrees
|
471
|
+
operator, subtrees = set_tree.operator, set_tree.subtrees
|
472
|
+
elsif operator == :custom
|
473
|
+
operator = :predicate
|
474
|
+
custom = tree_from_grammar node.branches[1].branches[0]
|
475
|
+
subtrees = [custom, *subtrees]
|
476
|
+
end
|
477
|
+
end
|
478
|
+
when :every, :some, :no, :at_most_one
|
479
|
+
subject = Tree.new ':x', [] # can never appear in a proof
|
480
|
+
t1 = tree_for_subject subject, node.branches[1]
|
481
|
+
case node.branches[2].value
|
482
|
+
when :is, :is_not
|
483
|
+
t2 = tree_for_subject subject, node.branches[3]
|
484
|
+
t2 = Tree.new :not, [t2] if node.branches[2].value == :is_not
|
485
|
+
when :is_in, :is_not_in
|
486
|
+
t2 = tree_from_grammar node.branches[3]
|
487
|
+
t2 = Tree.new :predicate, [Tree.new('in', []), subject, t2]
|
488
|
+
t2 = Tree.new :not, [t2] if node.branches[2].value == :is_not_in
|
489
|
+
end
|
490
|
+
variables_tree = Tree.new [subject.operator], []
|
491
|
+
return case node.value
|
492
|
+
when :every
|
493
|
+
Tree.new :for_all, [variables_tree, Tree.new(:implies, [t1, t2])]
|
494
|
+
when :some
|
495
|
+
Tree.new :for_some, [variables_tree, Tree.new(:and, [t1, t2])]
|
496
|
+
when :no
|
497
|
+
t2 = Tree.new :not, [t2]
|
498
|
+
Tree.new :for_all, [variables_tree, Tree.new(:implies, [t1, t2])]
|
499
|
+
when :at_most_one
|
500
|
+
Tree.new :for_at_most_one, [variables_tree, Tree.new(:and, [t1, t2])]
|
501
|
+
end
|
502
|
+
when :operand
|
503
|
+
tree = tree_from_grammar node.branches[0]
|
504
|
+
return tree unless node.branches[0].value == :operand_base
|
505
|
+
node.branches[1..-1].each {|branch|
|
506
|
+
if branch.value == :subst
|
507
|
+
map = tree_from_grammar branch
|
508
|
+
tree = Tree.new :substitution, [tree, map]
|
509
|
+
elsif branch.value == :params
|
510
|
+
list = tree_from_grammar branch
|
511
|
+
tree = Tree.new :predicate, [tree, *(list.subtrees[1..-1])]
|
512
|
+
else
|
513
|
+
raise
|
514
|
+
end
|
515
|
+
}
|
516
|
+
return tree
|
517
|
+
when :operand_base
|
518
|
+
if node.branches.size == 3 and node.branches[0].value == '(' and node.branches[2].value == ')'
|
519
|
+
return tree_from_grammar(node.branches[1])
|
520
|
+
elsif node.branches.size == 3 and node.branches[0].value == '|' and node.branches[2].value == '|'
|
521
|
+
tree = tree_from_grammar node.branches[1]
|
522
|
+
operator = :predicate
|
523
|
+
subtrees = [Tree.new('||', []), tree]
|
524
|
+
elsif node.branches.size == 1
|
525
|
+
return tree_from_grammar(node.branches[0])
|
526
|
+
end
|
527
|
+
when :string
|
528
|
+
# for now strings are predicates, but maybe they could just be
|
529
|
+
# atoms in quotation marks?
|
530
|
+
raise unless node.branches[0].value == '"'
|
531
|
+
raise unless node.branches[-1].value == '"'
|
532
|
+
if node.branches.size == 2
|
533
|
+
return Tree.new :predicate, [
|
534
|
+
Tree.new('string', []), Tree.new('""', [])
|
535
|
+
]
|
536
|
+
end
|
537
|
+
subtrees = node.branches[1...-1].collect {|subtree|
|
538
|
+
# if subtree.value == :meta
|
539
|
+
# tree_from_grammar subtree
|
540
|
+
# else
|
541
|
+
Tree.new :predicate, [
|
542
|
+
Tree.new('string', []),
|
543
|
+
Tree.new('"' + subtree.text + '"', [])
|
544
|
+
]
|
545
|
+
# end
|
546
|
+
}
|
547
|
+
tree = subtrees[0]
|
548
|
+
subtrees[1..-1].each {|subtree|
|
549
|
+
tree = Tree.new :predicate, [Tree.new('+',[]), tree, subtree]
|
550
|
+
}
|
551
|
+
return tree
|
552
|
+
# when :meta
|
553
|
+
# raise unless node.branches.size == 2
|
554
|
+
# raise unless node.branches[0].value == '$'
|
555
|
+
# operator = :meta
|
556
|
+
# subtrees = [tree_from_grammar(node.branches[1])]
|
557
|
+
when :schema
|
558
|
+
return tree_from_grammar node.branches[1]
|
559
|
+
when :quote
|
560
|
+
return tree_from_grammar node.branches[1]
|
561
|
+
when :list, :params
|
562
|
+
if node.branches.size == 2
|
563
|
+
# empty list is not a predicate because predicates need >= 1 arguments
|
564
|
+
operator = '[]'
|
565
|
+
subtrees = []
|
566
|
+
else
|
567
|
+
operator = :predicate
|
568
|
+
branches = node.branches.select.each_with_index {|branch, i| i.odd?}
|
569
|
+
subtrees = branches.collect {|branch| tree_from_grammar branch}
|
570
|
+
subtrees.unshift Tree.new('list', [])
|
571
|
+
end
|
572
|
+
when :set
|
573
|
+
if node.branches.size == 2
|
574
|
+
# empty set is not a predicate because predicates need >= 1 arguments
|
575
|
+
operator = '{}'
|
576
|
+
subtrees = []
|
577
|
+
else
|
578
|
+
operator = :predicate
|
579
|
+
branches = node.branches.select.each_with_index {|branch, i| i.odd?}
|
580
|
+
subtrees = branches.collect {|branch| tree_from_grammar branch}
|
581
|
+
subtrees.unshift Tree.new('{}', [])
|
582
|
+
end
|
583
|
+
when :map, :subst
|
584
|
+
operator = :predicate
|
585
|
+
subtrees = node.branches[1..-1].each_slice(4).collect {|branches|
|
586
|
+
Tree.new :predicate, [
|
587
|
+
Tree.new('list', []),
|
588
|
+
tree_from_grammar(branches[0]),
|
589
|
+
tree_from_grammar(branches[2]),
|
590
|
+
]
|
591
|
+
}
|
592
|
+
subtrees.unshift Tree.new('map', [])
|
593
|
+
=begin
|
594
|
+
when :predicate
|
595
|
+
return tree_from_grammar(node.branches[0]) if node.branches.size == 1
|
596
|
+
operator = :predicate
|
597
|
+
if node.branches[0].value == '['
|
598
|
+
subtrees = node.branches.select.each_with_index {|branch, i| i.odd?}
|
599
|
+
subtrees.collect! {|subtree| tree_from_grammar subtree}
|
600
|
+
subtrees.unshift Tree.new('list', [])
|
601
|
+
else
|
602
|
+
subtrees = node.branches.select.each_with_index {|branch, i| i.even?}
|
603
|
+
subtrees.collect! {|subtree| tree_from_grammar subtree}
|
604
|
+
end
|
605
|
+
=end
|
606
|
+
when :word, :word_same_line, :definable, :definable_raw, :atom
|
607
|
+
operator = standardize_operator node.text
|
608
|
+
subtrees = []
|
609
|
+
when :boolean
|
610
|
+
case node.branches[0].text
|
611
|
+
when 'true'
|
612
|
+
return tree_for_true
|
613
|
+
when 'false', 'contradiction'
|
614
|
+
return tree_for_false
|
615
|
+
end
|
616
|
+
when :negative
|
617
|
+
operator = :predicate
|
618
|
+
if node.branches.size == 2
|
619
|
+
subtree = tree_from_grammar node.branches[1]
|
620
|
+
else
|
621
|
+
raise unless node.branches[1].text == '[' and node.branches[3].text == ']'
|
622
|
+
subtree = tree_from_grammar node.branches[2]
|
623
|
+
end
|
624
|
+
subtrees = [Tree.new('-',[]), subtree]
|
625
|
+
when :square_root
|
626
|
+
operator = :predicate
|
627
|
+
raise unless node.branches.size == 2
|
628
|
+
subtree = tree_from_grammar node.branches[1]
|
629
|
+
subtrees = [Tree.new('√',[]), subtree]
|
630
|
+
when :thesis
|
631
|
+
operator = node.text
|
632
|
+
subtrees = []
|
633
|
+
when :prefix_, :prefix0, :prefix1, :prefix2
|
634
|
+
return tree_from_grammar(node.branches[0], open_to_bind)
|
635
|
+
when :not
|
636
|
+
operator = :not
|
637
|
+
subtrees = [tree_from_grammar(node.branches[1])]
|
638
|
+
when :if
|
639
|
+
operator = :implies
|
640
|
+
subtrees = [
|
641
|
+
tree_from_grammar(node.branches[1]), tree_from_grammar(node.branches[3])
|
642
|
+
]
|
643
|
+
when :universal, :existential, :take, :for_at_most_one, :no_existential,
|
644
|
+
:define, :universal_meta
|
645
|
+
variables, conditions, with, such_that = process_list_with_such node.branches[1]
|
646
|
+
variables_tree = Tree.new variables, []
|
647
|
+
conditions_tree = conjunction_tree conditions.compact
|
648
|
+
if node.branches.size > 2
|
649
|
+
raise unless node.branches[2].value == ','
|
650
|
+
raise if node.branches.size > 4 # multiple expressions after comma
|
651
|
+
body = tree_from_grammar node.branches[3]
|
652
|
+
end
|
653
|
+
return case node.value
|
654
|
+
when :universal_meta
|
655
|
+
raise ParseException, '"with" not allowed in universal quantifier' if with
|
656
|
+
antecedent = conjunction_tree [conditions_tree, such_that].compact
|
657
|
+
Schema.new variables, antecedent, body
|
658
|
+
when :universal
|
659
|
+
raise ParseException, '"with" not allowed in universal quantifier' if with
|
660
|
+
antecedent = conjunction_tree [conditions_tree, such_that].compact
|
661
|
+
tree = antecedent ? Tree.new(:implies, [antecedent, body]) : body
|
662
|
+
Tree.new :for_all, [variables_tree, tree]
|
663
|
+
when :for_at_most_one
|
664
|
+
raise ParseException, '"with" not allowed in "at most one" quantifier' if with
|
665
|
+
tree = conjunction_tree [conditions_tree, such_that, body].compact
|
666
|
+
Tree.new :for_at_most_one, [variables_tree, tree].compact
|
667
|
+
when :no_existential
|
668
|
+
raise ParseException, '"with" not allowed in negative quantifier' if with
|
669
|
+
tree = conjunction_tree [conditions_tree, such_that, body].compact
|
670
|
+
tree = Tree.new :for_some, [variables_tree, tree].compact
|
671
|
+
tree = Tree.new :not, [tree]
|
672
|
+
when :existential, :define, :take
|
673
|
+
tie_ins = []
|
674
|
+
[variables, conditions].transpose.each {|variable, condition|
|
675
|
+
variable_tree = Tree.new variable, []
|
676
|
+
# keep these separate so they can be de-duplicated later on
|
677
|
+
tie_ins << TieIn.new([], variable_tree, condition) if condition
|
678
|
+
tie_ins << TieIn.new([], variable_tree, with) if with
|
679
|
+
}
|
680
|
+
if open_to_bind
|
681
|
+
{
|
682
|
+
:base => conjunction_tree([such_that, body].compact),
|
683
|
+
:binds => variables,
|
684
|
+
:definition? => (node.value == :define),
|
685
|
+
:tie_ins => tie_ins,
|
686
|
+
}
|
687
|
+
else
|
688
|
+
raise unless node.value == :existential
|
689
|
+
raise ParseException, '"with" not allowed in nested quantifier' if with
|
690
|
+
tree = conjunction_tree [conditions_tree, such_that, body].compact
|
691
|
+
Tree.new :for_some, [variables_tree, tree].compact
|
692
|
+
end
|
693
|
+
else raise
|
694
|
+
end
|
695
|
+
when :let
|
696
|
+
raise unless open_to_bind
|
697
|
+
left = tree_from_grammar node.branches[1]
|
698
|
+
right = tree_from_grammar node.branches[3]
|
699
|
+
return {
|
700
|
+
:base => Tree.new(:equals, [left, right]),
|
701
|
+
:binds => [left.operator],
|
702
|
+
:definition? => false,
|
703
|
+
:tie_ins => [],
|
704
|
+
}
|
705
|
+
when :tie_in
|
706
|
+
pattern = tree_from_grammar node.branches[1]
|
707
|
+
body = tree_from_grammar node.branches[3]
|
708
|
+
metas = []
|
709
|
+
if node.branches[5]
|
710
|
+
metas, conditions = process_atom_block node.branches[5]
|
711
|
+
if not conditions.compact.empty?
|
712
|
+
raise ParseException, 'tie-in variables cannot have conditions'
|
713
|
+
end
|
714
|
+
end
|
715
|
+
return TieIn.new metas, pattern, body
|
716
|
+
end
|
717
|
+
# make sure something actually made the tree
|
718
|
+
raise "#{node.value.inspect} not handled" unless operator and subtrees
|
719
|
+
Tree.new operator, subtrees
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
class Tree
|
724
|
+
attr_reader :operator, :subtrees
|
725
|
+
|
726
|
+
def initialize operator, subtrees
|
727
|
+
subtrees.each {|subtree|
|
728
|
+
next if subtree.is_a? self.class
|
729
|
+
raise "tree is a #{self.class}, but subtree is a #{subtree.class}!"
|
730
|
+
}
|
731
|
+
raise if not operator
|
732
|
+
case operator
|
733
|
+
when :not
|
734
|
+
raise unless subtrees.size == 1
|
735
|
+
when :for_all
|
736
|
+
raise unless subtrees.size == 2
|
737
|
+
raise unless subtrees[0].operator.is_a? Array
|
738
|
+
when :for_some, :for_at_most_one
|
739
|
+
raise unless [1, 2].include? subtrees.size
|
740
|
+
raise unless subtrees[0].operator.is_a? Array
|
741
|
+
when :and, :or
|
742
|
+
raise unless subtrees.size >= 2
|
743
|
+
when :implies, :iff
|
744
|
+
raise unless subtrees.size == 2
|
745
|
+
when :equals
|
746
|
+
raise unless subtrees.size == 2
|
747
|
+
if subtrees.any? {|subtree| subtree.boolean?}
|
748
|
+
raise ParseException, 'boolean operator used as a term'
|
749
|
+
end
|
750
|
+
when :predicate
|
751
|
+
raise unless subtrees.size >= 2
|
752
|
+
if subtrees.any? {|subtree| subtree.boolean?}
|
753
|
+
raise ParseException, 'boolean operator used as a term'
|
754
|
+
end
|
755
|
+
when :substitution
|
756
|
+
raise unless subtrees.size == 2
|
757
|
+
raise unless subtrees[1].operator == :predicate
|
758
|
+
raise unless subtrees[1].subtrees[0].operator == 'map'
|
759
|
+
when String, Array
|
760
|
+
if not subtrees.empty?
|
761
|
+
raise "leaf #{operator} initialized with subtrees"
|
762
|
+
end
|
763
|
+
raise "unexpected '=' instead of :equals" if operator == '='
|
764
|
+
else
|
765
|
+
raise "unknown operator #{operator.inspect}"
|
766
|
+
end
|
767
|
+
@operator = operator
|
768
|
+
@subtrees = subtrees
|
769
|
+
end
|
770
|
+
|
771
|
+
def boolean?
|
772
|
+
case @operator
|
773
|
+
when :not, :and, :or, :implies, :iff, :equals, :for_all, :for_some,
|
774
|
+
:for_at_most_one
|
775
|
+
true
|
776
|
+
when :predicate, String
|
777
|
+
false
|
778
|
+
else
|
779
|
+
raise "unexpected operator #{@operator.inspect}"
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
# def bound_variables
|
784
|
+
# result = []
|
785
|
+
# result << @subtrees[0].operator if [:for_all, :for_some].include? @operator
|
786
|
+
# result << @subtrees.collect {|subtree| subtree.bound_variables}
|
787
|
+
# result.flatten.uniq
|
788
|
+
# end
|
789
|
+
|
790
|
+
def contains? x
|
791
|
+
return true if @operator == x
|
792
|
+
@subtrees.any? {|subtree| subtree.contains? x}
|
793
|
+
end
|
794
|
+
|
795
|
+
def eql? other # hash/set equality, also used by uniq, &, -
|
796
|
+
other.is_a?(self.class) and to_s == other.to_s
|
797
|
+
# other.is_a?(self.class) and equal_up_to_variable_names? self, other
|
798
|
+
end
|
799
|
+
|
800
|
+
def free_variables
|
801
|
+
return @free_variables if @free_variables
|
802
|
+
@free_variables = case @operator
|
803
|
+
when Array then @operator
|
804
|
+
when String then [@operator]
|
805
|
+
else
|
806
|
+
subtrees = @subtrees
|
807
|
+
if @operator == :substitution
|
808
|
+
# exclude 'map' since it is at the meta level
|
809
|
+
raise unless subtrees[1].subtrees[0].operator == 'map' # sanity check
|
810
|
+
subtrees = [subtrees[0], *subtrees[1].subtrees[1..-1]]
|
811
|
+
end
|
812
|
+
sub_results = subtrees.collect {|subtree| subtree.free_variables}
|
813
|
+
result = sub_results.flatten.uniq
|
814
|
+
result -= @subtrees[0].operator if Quantifiers.include? @operator
|
815
|
+
result
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
def hash # has to be overriden along with eql?, apparently
|
820
|
+
to_s.hash
|
821
|
+
end
|
822
|
+
|
823
|
+
def inspect level = 0, result = ''
|
824
|
+
result << ' ' * level << @operator.to_s << "\n"
|
825
|
+
@subtrees.each {|subtree| subtree.inspect level+1, result}
|
826
|
+
result.chomp! if level == 0
|
827
|
+
result
|
828
|
+
end
|
829
|
+
|
830
|
+
def pretty_print level = 0
|
831
|
+
s = ' ' * level + to_s
|
832
|
+
if s.length < 80
|
833
|
+
puts s
|
834
|
+
else
|
835
|
+
puts ' ' * level << @operator.to_s
|
836
|
+
@subtrees.each {|subtree| subtree.pretty_print level+1}
|
837
|
+
end
|
838
|
+
end
|
839
|
+
|
840
|
+
Quantifiers = [:for_all, :for_some, :for_at_most_one]
|
841
|
+
|
842
|
+
def to_s
|
843
|
+
return @to_s if @to_s
|
844
|
+
infixes = [:and, :or, :implies, :iff, :equals]
|
845
|
+
@to_s = case @operator
|
846
|
+
when :not
|
847
|
+
operand = @subtrees[0].to_s
|
848
|
+
if infixes.include? @subtrees[0].operator
|
849
|
+
operand = '(' + operand + ')'
|
850
|
+
end
|
851
|
+
'not ' + operand
|
852
|
+
when *Quantifiers
|
853
|
+
if @subtrees.size == 1
|
854
|
+
"there is a #{@subtrees[0]}"
|
855
|
+
else
|
856
|
+
operator = @operator.to_s.gsub '_', ' '
|
857
|
+
variables = @subtrees[0].to_s
|
858
|
+
expression = @subtrees[1].to_s
|
859
|
+
if infixes.include? @subtrees[1].operator
|
860
|
+
expression = '(' + expression + ')'
|
861
|
+
end
|
862
|
+
"#{operator} #{variables}, #{expression}"
|
863
|
+
end
|
864
|
+
when :predicate
|
865
|
+
if @subtrees[0].operator == 'map'
|
866
|
+
arguments = @subtrees[1..-1].collect {|subtree|
|
867
|
+
raise unless subtree.operator == :predicate
|
868
|
+
raise unless subtree.subtrees.size == 3
|
869
|
+
raise unless subtree.subtrees[0].operator == 'list'
|
870
|
+
subtree.subtrees[1].to_s + ':' + subtree.subtrees[2].to_s
|
871
|
+
}.join ', '
|
872
|
+
"{#{arguments}}"
|
873
|
+
else
|
874
|
+
arguments = @subtrees[1..-1].collect {|subtree| subtree.to_s}.join ', '
|
875
|
+
if @subtrees[0].operator == '||' and @subtrees[0].subtrees.empty?
|
876
|
+
raise unless @subtrees.size == 2
|
877
|
+
"|#{arguments}|"
|
878
|
+
else
|
879
|
+
needs_parens = true
|
880
|
+
needs_parens = false if @subtrees[0].operator == :predicate
|
881
|
+
needs_parens = false if not @subtrees[0].operator.is_a? Symbol
|
882
|
+
result = (needs_parens ? "(#{@subtrees[0]})" : "#{@subtrees[0]}")
|
883
|
+
result + "[#{arguments}]"
|
884
|
+
end
|
885
|
+
end
|
886
|
+
when *infixes
|
887
|
+
operands = @subtrees.each_with_index.collect {|subtree, i|
|
888
|
+
if subtree.operator == :predicate or subtree.operator == :substitution
|
889
|
+
subtree.to_s
|
890
|
+
elsif not subtree.operator.is_a?(Symbol)
|
891
|
+
subtree.to_s
|
892
|
+
elsif i == @subtrees.size - 1 and not infixes.include?(subtree.operator)
|
893
|
+
subtree.to_s
|
894
|
+
else
|
895
|
+
'(' + subtree.to_s + ')'
|
896
|
+
end
|
897
|
+
}
|
898
|
+
operator_string = (@operator == :equals ? '=' : @operator.to_s)
|
899
|
+
operands.join(' ' + operator_string + ' ')
|
900
|
+
# when :meta
|
901
|
+
# operand = @subtrees[0].to_s
|
902
|
+
# if @subtrees[0].operator.is_a? Symbol
|
903
|
+
# operand = '(' + operand + ')'
|
904
|
+
# end
|
905
|
+
# '$' + operand
|
906
|
+
when :substitution
|
907
|
+
@subtrees[0].to_s + @subtrees[1].to_s
|
908
|
+
when Array
|
909
|
+
@operator.join ','
|
910
|
+
when String
|
911
|
+
@operator
|
912
|
+
else
|
913
|
+
raise "unexpected operator #{@operator.inspect}"
|
914
|
+
end
|
915
|
+
end
|
916
|
+
end
|
917
|
+
|
918
|
+
class Schema
|
919
|
+
attr_reader :metas, :condition, :pattern
|
920
|
+
|
921
|
+
def initialize metas, condition, pattern
|
922
|
+
if not SchemaModule.check_schema_format metas, condition, pattern
|
923
|
+
raise ParseException, 'unrecognized schema format'
|
924
|
+
end
|
925
|
+
@metas, @condition, @pattern = metas, condition, pattern
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
class TieIn
|
930
|
+
attr_reader :metas, :pattern, :body
|
931
|
+
|
932
|
+
def initialize metas, pattern, body
|
933
|
+
if pattern.boolean?
|
934
|
+
raise ParseException, 'tie-in pattern must be a term'
|
935
|
+
end
|
936
|
+
if not (metas - pattern.free_variables).empty?
|
937
|
+
raise ParseException, 'tie-in variables must appear in pattern'
|
938
|
+
end
|
939
|
+
if contains_quantifiers? body
|
940
|
+
raise ParseException, 'tie-in cannot contain quantifiers'
|
941
|
+
end
|
942
|
+
@metas, @pattern, @body = metas, pattern, body
|
943
|
+
end
|
944
|
+
|
945
|
+
def to_s # for debugging
|
946
|
+
"<TieIn @metas: #{@metas}, @pattern: #{@pattern}, @body: #{@body}>"
|
947
|
+
end
|
948
|
+
end
|
949
|
+
|
950
|
+
class Content
|
951
|
+
attr_reader :binds, :body, :schema, :sentence, :tie_ins
|
952
|
+
|
953
|
+
def initialize input
|
954
|
+
case input
|
955
|
+
when Content # needed when initializing the Line subclass
|
956
|
+
@body = input.body
|
957
|
+
@binds = input.binds
|
958
|
+
@is_definition = input.definition?
|
959
|
+
@schema = input.schema
|
960
|
+
@tie_ins = input.tie_ins
|
961
|
+
when Hash
|
962
|
+
raise unless input.keys.sort == [:base, :binds, :definition?, :tie_ins]
|
963
|
+
@binds = input[:binds]
|
964
|
+
@is_definition = input[:definition?]
|
965
|
+
@tie_ins = input[:tie_ins]
|
966
|
+
extras = @tie_ins.collect {|tie_in|
|
967
|
+
bind_variables tie_in.metas, tie_in.body, :for_all
|
968
|
+
}.uniq
|
969
|
+
@body = conjunction_tree [*extras, input[:base]].compact
|
970
|
+
when Schema
|
971
|
+
@body = nil
|
972
|
+
@binds = []
|
973
|
+
@is_definition = false
|
974
|
+
@schema = input
|
975
|
+
@tie_ins = []
|
976
|
+
when Tree
|
977
|
+
@body = input
|
978
|
+
@binds = []
|
979
|
+
@is_definition = false
|
980
|
+
@tie_ins = []
|
981
|
+
when TieIn
|
982
|
+
@body = bind_variables input.metas, input.body, :for_all
|
983
|
+
@binds = []
|
984
|
+
@is_definition = false
|
985
|
+
@tie_ins = [input]
|
986
|
+
else raise
|
987
|
+
end
|
988
|
+
@sentence = bind_variables @binds, @body, :for_some
|
989
|
+
end
|
990
|
+
|
991
|
+
def definition?
|
992
|
+
@is_definition
|
993
|
+
end
|
994
|
+
|
995
|
+
def uses
|
996
|
+
result = (@body ? @body.free_variables : []).to_set
|
997
|
+
if @schema
|
998
|
+
# schema condition does not use anything (only contains metas)
|
999
|
+
result.merge @schema.pattern.free_variables.to_set.subtract @schema.metas
|
1000
|
+
end
|
1001
|
+
@tie_ins.each {|tie_in|
|
1002
|
+
uses = tie_in.pattern.free_variables.to_set.subtract tie_in.metas
|
1003
|
+
# tie-in body is already included in @body
|
1004
|
+
result.merge uses
|
1005
|
+
}
|
1006
|
+
result.subtract @binds
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
class LineNumberManager
|
1011
|
+
# keeps track of line numbers after removing blocks (e.g. comments)
|
1012
|
+
|
1013
|
+
attr_reader :text
|
1014
|
+
|
1015
|
+
def initialize input
|
1016
|
+
@input = input
|
1017
|
+
@line_positions = []
|
1018
|
+
input.chars.each_index {|i|
|
1019
|
+
@line_positions << i if i == 0 or input[i-1] == "\n"
|
1020
|
+
}
|
1021
|
+
|
1022
|
+
@blocks = [[0, input.size-1]]
|
1023
|
+
@text = input
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
def input_positions
|
1027
|
+
passed = 0
|
1028
|
+
k = 0
|
1029
|
+
lambda {|i_in_text|
|
1030
|
+
while k < @blocks.size
|
1031
|
+
i,j = @blocks[k]
|
1032
|
+
if passed + j-i+1 > i_in_text
|
1033
|
+
return i + i_in_text - passed
|
1034
|
+
else
|
1035
|
+
passed += j-i+1
|
1036
|
+
end
|
1037
|
+
k += 1
|
1038
|
+
end
|
1039
|
+
raise "no position #{i_in_text} (length of text is #{@text.length})"
|
1040
|
+
}
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def line_numbers
|
1044
|
+
positions = input_positions
|
1045
|
+
line_index = 0
|
1046
|
+
lambda {|i_in_text|
|
1047
|
+
i_in_input = positions.call i_in_text
|
1048
|
+
line_index = (line_index...@line_positions.size).find {|i|
|
1049
|
+
next true if i == @line_positions.size-1
|
1050
|
+
@line_positions[i+1] > i_in_input
|
1051
|
+
}
|
1052
|
+
line_index + 1
|
1053
|
+
}
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
def use blocks
|
1057
|
+
# first we map the text positions in blocks to input positions
|
1058
|
+
positions = input_positions
|
1059
|
+
blocks = blocks.collect {|i,j|
|
1060
|
+
[positions.call(i), positions.call(j)]
|
1061
|
+
}
|
1062
|
+
# now we split up the blocks to fit in the existing @blocks
|
1063
|
+
new_blocks = []
|
1064
|
+
k = 0
|
1065
|
+
blocks.each {|i,j|
|
1066
|
+
# find the block which contains the start of this one
|
1067
|
+
k += 1 until @blocks[k][1] >= i
|
1068
|
+
if @blocks[k][1] >= j
|
1069
|
+
# the block we found completely contains this one
|
1070
|
+
new_blocks << [i,j]
|
1071
|
+
else
|
1072
|
+
# the block we found only partially contains this one
|
1073
|
+
new_blocks << [i, @blocks[k][1]]
|
1074
|
+
k += 1
|
1075
|
+
# find the block which contains the end of this one, adding the
|
1076
|
+
# intervening blocks
|
1077
|
+
until @blocks[k][1] >= j
|
1078
|
+
new_blocks << @blocks[k]
|
1079
|
+
k += 1
|
1080
|
+
end
|
1081
|
+
# add the block which contains the end of this one
|
1082
|
+
new_blocks << [@blocks[k][0], j]
|
1083
|
+
end
|
1084
|
+
}
|
1085
|
+
# finally, we update @blocks and @text
|
1086
|
+
@blocks = new_blocks
|
1087
|
+
@text = @blocks.collect {|i,j| @input[i..j]}.join
|
1088
|
+
end
|
1089
|
+
end
|