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