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.
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