oakproof 0.7.1

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