korekto 1.0.210306

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 23a256a294cc625c6d701521f8ac80436501dbad3cfcaddc361418454a2aa5d3
4
+ data.tar.gz: ee6ed801ec1619b0c3c41946f3d1c1831181ab6f8bfbe798174ab3638e021d7b
5
+ SHA512:
6
+ metadata.gz: ea659d6feef95bdb03e76e80dc974ba9a8b6288239525ff6bd141f53e7cdedde1432ab816e4831b35c4b7728ff1b14896ed10915a31badd2093e4b50dd33b1f3
7
+ data.tar.gz: d191041a84be4e648e4ebb18c376090e6a6ec5c375fe69dea7224532bad19a71d3c9e37db6c07d96243a3206545638bc72d9fb4359cb252ba7d825656aafe8c6
data/bin/korekto ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ require 'help_parser'
3
+
4
+ OPTIONS = HelpParser['1.0.210306', <<HELP]
5
+ Usage:
6
+ korekto [:options+]
7
+ Options:
8
+ -h --help
9
+ -v --version
10
+ --install \tInstalls the korekto neovim ruby plugin
11
+ --readme \tOpen korekto github page
12
+ # Example usage:
13
+ # cat MARKDOWN.md | korekto
14
+ # korekto < MARKDOWN.md
15
+ HELP
16
+
17
+ if OPTIONS.install?
18
+ require 'fileutils'
19
+ # pack
20
+ dest = File.expand_path '~/.local/share/nvim/site/pack/korekto'
21
+ FileUtils.mkdir_p dest
22
+ src = File.join File.dirname(__dir__), 'start'
23
+ FileUtils.cp_r src, dest
24
+ # rplugin
25
+ dest = File.expand_path '~/.config/nvim'
26
+ src = File.join File.dirname(__dir__), 'rplugin'
27
+ FileUtils.cp_r src, dest
28
+ system "nvim -c ':UpdateRemotePlugins|:quit' > /dev/null"
29
+ exit
30
+ end
31
+ if OPTIONS.readme?
32
+ Process.detach spawn('xdg-open', 'https://www.github.com/carlosjhr64/korekto')
33
+ exit
34
+ end
35
+
36
+ require 'korekto'
37
+ Korekto.run
data/lib/korekto.rb ADDED
@@ -0,0 +1,15 @@
1
+ module Korekto
2
+ VERSION = '1.0.210306'
3
+ class Error < Exception; end
4
+ require 'korekto/symbols'
5
+ require 'korekto/syntax'
6
+ require 'korekto/heap'
7
+ require 'korekto/statement'
8
+ require 'korekto/statements'
9
+ require 'korekto/main'
10
+ def Korekto.run = Korekto::Main.new.run
11
+ end
12
+ # Requires:
13
+ # `ruby`
14
+ # `nvim`
15
+ # `xdg-open`
@@ -0,0 +1,26 @@
1
+ module Korekto
2
+ class Heap
3
+ def initialize(limit)
4
+ @limit = limit
5
+ @combos = (0...limit).to_a.combination(2)
6
+ .sort{|ij, kl| ij[0]**2 + ij[1]**2 <=> kl[0]**2 + kl[1]**2}
7
+ .map{|i, j| [[i,j], [j,i]]}
8
+ .inject([]){|a, ij_kl| a<<ij_kl[0]; a<<ij_kl[1]}
9
+ @a = []
10
+ end
11
+
12
+ def add(s)
13
+ @a.unshift s
14
+ @a.pop if @a.length > @limit
15
+ end
16
+
17
+ def combos
18
+ @combos.each do |i,j|
19
+ next if [i,j].max >= @a.length
20
+ yield(@a[i], @a[j])
21
+ end
22
+ end
23
+
24
+ def each = @a.each{yield _1}
25
+ end
26
+ end
@@ -0,0 +1,141 @@
1
+ module Korekto
2
+ class Main
3
+ MD_STATEMENT_CODE_TITLE = %r{^(?<statement>.*)\s#(?<code>[A-Z](\d+(/[\w,.]+)?)?)( (?<title>\S.*?\S))?$}
4
+ MD_FILENAME = %r{^< (?<filename>[/\w\-.]+)$}
5
+ MD_KLASS_METHOD_DEFINITION = /^(?<klass>::[A-Z]\w+)#(?<method>\w+)(?<definition>[^=]*=.+)$/ # patch
6
+ MD_RULE = /^[?] (?<rule>\S.*)$/
7
+ MD_TYPE_PATTERN = %r{^! (?<type>\S+)\s*/(?<pattern>.*)/$}
8
+ MD_TYPE_VARIABLES = /^! (?<type>\S+)\s*\{(?<variables>\S+( \S+)*)\}$/
9
+ MD_KEY_VALUE = /^! (?<key>\w+):\s*'(?<value>.*)'$/
10
+
11
+ M_FENCE = /^```\s*$/
12
+ M_COMMENT_LINE = /^\s*#/
13
+
14
+ BACKUPS = {}
15
+
16
+ def initialize(filename='-', statements: Statements.new, imports: [])
17
+ @filename,@statements,@imports = filename,statements,imports
18
+ @imports.push @filename
19
+ @line,@active = nil,false
20
+ @m_fence_korekto = /^```korekto$/
21
+ end
22
+
23
+ def type_pattern(type, pattern)
24
+ t2p = @statements.symbols.t2p
25
+ raise Error, "type #{type} in use" if t2p.has_key? type
26
+ t2p[type] = pattern
27
+ end
28
+
29
+ def type_variables(type, variables)
30
+ v2t,t2p = @statements.symbols.v2t,@statements.symbols.t2p
31
+ pattern = t2p[type]
32
+ raise Error, "type #{type} not defined" unless pattern
33
+ variables.each do |variable|
34
+ raise Error, "variable #{variable} in use" if v2t.has_key? variable
35
+ v2t[variable] = type
36
+ end
37
+ end
38
+
39
+ def active?
40
+ case @line
41
+ when @m_fence_korekto
42
+ raise Error, 'unexpected fence' if @active
43
+ @active = true
44
+ false
45
+ when M_FENCE
46
+ @active = false
47
+ false
48
+ else
49
+ @active and not M_COMMENT_LINE.match?(@line)
50
+ end
51
+ end
52
+
53
+ def patch(klass, method, definition)
54
+ raise Error, "overrides: #{klass}##{method}" if eval(klass).method_defined? method
55
+ eval <<~EVAL
56
+ class #{klass}
57
+ def #{method}#{definition}
58
+ end
59
+ EVAL
60
+ end
61
+
62
+ def key_value(key, value)
63
+ case key
64
+ when 'scanner'
65
+ @statements.symbols.set_scanner value
66
+ when 'fence'
67
+ @m_fence_korekto = Regexp.new "^```#{value}$"
68
+ when 'save'
69
+ BACKUPS[value] = Marshal.dump(@statements)
70
+ when 'restore'
71
+ if backup = BACKUPS[value]
72
+ @statements = Marshal.load(backup)
73
+ else
74
+ raise Error, "nothing saved as '#{value}'"
75
+ end
76
+ else
77
+ raise Error, "key '#{key}' not implemented"
78
+ end
79
+ end
80
+
81
+ def preprocess?
82
+ case @line
83
+ when MD_FILENAME
84
+ filename = $~[:filename].strip
85
+ unless @imports.include? filename
86
+ Main.new(filename, statements:@statements, imports:@imports).run
87
+ end
88
+ when MD_KLASS_METHOD_DEFINITION
89
+ patch($~[:klass],$~[:method],$~[:definition])
90
+ when MD_RULE
91
+ @statements.syntax.push $~[:rule].strip
92
+ when MD_TYPE_PATTERN
93
+ type_pattern($~[:type], $~[:pattern])
94
+ when MD_TYPE_VARIABLES
95
+ type_variables($~[:type], $~[:variables].split)
96
+ when MD_KEY_VALUE
97
+ key_value($~[:key], $~[:value])
98
+ else
99
+ return false
100
+ end
101
+ true
102
+ end
103
+
104
+ def parse(lines)
105
+ statement_number = line_number = 0
106
+ while @line = lines.shift
107
+ begin
108
+ line_number += 1
109
+ next unless active?
110
+ next if preprocess?
111
+ if md = MD_STATEMENT_CODE_TITLE.match(@line)
112
+ statement_number += 1
113
+ code,title = @statements.add(md[:statement].strip,
114
+ md[:code],
115
+ md[:title],
116
+ @filename,
117
+ statement_number)
118
+ puts "#{@filename}:#{line_number}:#{code}:#{title}"
119
+ else
120
+ raise Error, 'unrecognized korekto line'
121
+ end
122
+ rescue Error
123
+ puts "#{@filename}:#{line_number}:!:#{$!.message}"
124
+ exit 65
125
+ rescue
126
+ puts "#{@filename}:#{line_number}:?:#{$!.message}"
127
+ $stderr.puts $!.backtrace
128
+ exit 1
129
+ end
130
+ end
131
+ end
132
+
133
+ def run
134
+ if @filename=='-'
135
+ parse $stdin.readlines(chomp: true)
136
+ else
137
+ parse IO.readlines(@filename, chomp: true)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,196 @@
1
+ module Korekto
2
+ class Statement
3
+ attr_reader :code,:title,:regexp,:filename,:statement_number
4
+ def initialize(statement,code,title,filename,statement_number,context)
5
+ @statement,@code,@title,@statement_number,@context = statement,code,title,statement_number,context
6
+ @filename = File.basename(filename,'.*')
7
+ @statement.freeze; @filename.freeze; @statement_number.freeze
8
+ syntax_check
9
+ @regexp = nil
10
+ set_acceptance_code
11
+ @code.freeze; @title.freeze; @regexp.freeze
12
+ end
13
+
14
+ def type = @code[0]
15
+ def to_s = @statement
16
+ def to_str = @statement
17
+ def match?(statement) = @regexp.match?(statement)
18
+ def scan(regex, &blk) = @statement.scan(regex, &blk)
19
+ def pattern? = !@regexp.nil?
20
+ def literal_regexp? = @statement[0]=='/' && @statement[-1]=='/'
21
+
22
+ private
23
+
24
+ def syntax_check
25
+ @context.syntax.each do |rule|
26
+ raise Error, "syntax: #{rule}" unless @statement.instance_eval(rule)
27
+ rescue # other than Korekto::Error < Exception
28
+ raise Error, "#{$!.class}: #{rule}"
29
+ end
30
+ end
31
+
32
+ def set_acceptance_code
33
+ case @code[0] # type
34
+ when 'P'
35
+ postulate
36
+ when 'D'
37
+ definition
38
+ when 'C'
39
+ conclusion
40
+ when 'X'
41
+ instantiation
42
+ when 'R'
43
+ result
44
+ when 'S'
45
+ set
46
+ when 'T'
47
+ tautology
48
+ when 'A', 'L'
49
+ # Axiom=>Tautoloty, Let=>Set,
50
+ pattern_type(0)
51
+ when 'M', 'E'
52
+ # Map=>Result, Existential=>Instantiation(X)
53
+ pattern_type(1)
54
+ when 'I'
55
+ # Inference=>Conclusion
56
+ pattern_type(2)
57
+ when 'W'
58
+ ['T','S','R','X','C'].any? do |code|
59
+ begin
60
+ @code[0]=code
61
+ set_acceptance_code
62
+ true
63
+ rescue Error
64
+ false
65
+ end
66
+ end or raise Error, 'did not match any statement pattern'
67
+ else
68
+ raise Error, "statement type #{@code[0]} not implemented"
69
+ end
70
+ end
71
+
72
+ # Common helper
73
+
74
+ def set_statement(support=nil, title=nil)
75
+ @code = "#{@code[0]}#{@statement_number}"
76
+ @code += "/#{support}" if support
77
+ @title = title.split(':',2).first if title
78
+ end
79
+
80
+ def support(*s)
81
+ support = []
82
+ s.each do |s|
83
+ c = s.code.split('/',2)[0]
84
+ c = s.filename+'.'+c unless s.filename=='-'
85
+ support.push(c)
86
+ end
87
+ return support.join(',')
88
+ end
89
+
90
+ # Pattern helpers
91
+
92
+ def newlines_count(n)
93
+ raise Error, "expected #{n} newlines" unless n==@regexp.inspect.gsub('\\\\','').scan('\\n').length
94
+ end
95
+
96
+ def set_regexp
97
+ @regexp = @context.symbols.s2r(@statement)
98
+ end
99
+
100
+ # Searches
101
+
102
+ def detect_statement(type)
103
+ statement = @context.type(type).detect do |statement|
104
+ statement.match? @statement
105
+ end
106
+ raise Error, "does not match any '#{type}' statement" unless statement
107
+ return statement
108
+ end
109
+
110
+ def heap_combos_search(type)
111
+ @context.heap.combos do |s1, s2|
112
+ compound = [s1,s2,@statement].join("\n")
113
+ @context.type(type).each do |inference|
114
+ return inference,s1,s2 if inference.match?(compound)
115
+ end
116
+ end
117
+ raise Error, "does not match any '#{type}' statement"
118
+ end
119
+
120
+ def heap_search(type)
121
+ @context.heap.each do |s1|
122
+ compound = [s1,@statement].join("\n")
123
+ @context.type(type).each do |s0|
124
+ return s0,s1 if s0.match?(compound)
125
+ end
126
+ end
127
+ raise Error, "does not match any '#{type}' statement"
128
+ end
129
+
130
+ # Defined/Undefined
131
+
132
+ def expected_instantiations(title=nil, n:nil)
133
+ undefined = @context.symbols.undefined(self)
134
+ if n ||= title&.match(/[1-9]\d*/)&.to_s&.to_i
135
+ raise Error, "expected #{n} undefined: #{undefined.join(' ')}" unless n==undefined.length
136
+ else
137
+ raise Error, 'nothing was undefined' if undefined.empty?
138
+ end
139
+ end
140
+
141
+ def undefined_in_pattern
142
+ @title = @title.split(':',2).first if @title
143
+ undefined = @context.symbols.undefined(self)
144
+ @title = "#{@title}: #{undefined.join(' ')}" unless undefined.empty?
145
+ end
146
+
147
+ # Statement type processing
148
+
149
+ def pattern_type(nl)
150
+ set_regexp
151
+ newlines_count(nl)
152
+ undefined_in_pattern
153
+ set_statement
154
+ end
155
+
156
+ def tautology
157
+ expected_instantiations(n:0)
158
+ axiom = detect_statement('A')
159
+ set_statement(support(axiom), axiom.title)
160
+ end
161
+
162
+ def set
163
+ let = detect_statement('L')
164
+ expected_instantiations(let.title)
165
+ set_statement(support(let), let.title)
166
+ end
167
+
168
+ def result
169
+ expected_instantiations(n:0)
170
+ mapping,s1 = heap_search('M')
171
+ set_statement(support(mapping,s1), mapping.title)
172
+ end
173
+
174
+ def instantiation
175
+ existential,s1 = heap_search('E')
176
+ expected_instantiations(existential.title)
177
+ set_statement(support(existential,s1), existential.title)
178
+ end
179
+
180
+ def conclusion
181
+ expected_instantiations(n:0)
182
+ inference,s1,s2 = heap_combos_search('I')
183
+ set_statement(support(inference,s1,s2), inference.title)
184
+ end
185
+
186
+ def definition
187
+ expected_instantiations(@title)
188
+ set_statement
189
+ end
190
+
191
+ def postulate
192
+ expected_instantiations(n:0)
193
+ set_statement
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,41 @@
1
+ module Korekto
2
+ class Statements
3
+ attr_reader :heap,:symbols,:syntax
4
+ def initialize
5
+ @statements = []
6
+ @heap = Heap.new(13)
7
+ @symbols = Symbols.new
8
+ @syntax = Syntax.new
9
+ end
10
+
11
+ def type(c) = @statements.select{_1.type==c}
12
+ def length = @statements.length
13
+
14
+ def add(statement,code,title,filename,statement_number)
15
+ c = code[0]; w = c=='W'
16
+ if restatement = @statements.detect{(w or _1.type==c) and _1.to_s==statement}
17
+ case restatement.type
18
+ when 'D','X','S','P','T','C','R'
19
+ @heap.add restatement
20
+ else
21
+ raise Error, "restatement: #{restatement.code}"
22
+ end
23
+ code,_ = restatement.code
24
+ title ||= restatement.title
25
+ return code, title
26
+ end
27
+ statement = Statement.new(statement,code,title,filename,statement_number,self)
28
+ @statements.push statement
29
+ case statement.type
30
+ when 'A','I','E','M','L'
31
+ @symbols.define! statement
32
+ when 'D','X','S'
33
+ @symbols.define! statement
34
+ @heap.add statement
35
+ when 'P','T','C','R'
36
+ @heap.add statement
37
+ end
38
+ return statement.code, statement.title
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ module Korekto
2
+ class Symbols
3
+ attr_reader :t2p, :v2t
4
+ def initialize
5
+ @h = {}
6
+ @t2p = {}
7
+ @v2t = {}
8
+ @scanner = /:\w+|./
9
+ end
10
+
11
+ def set_scanner(value) = @scanner=Regexp.new(value)
12
+
13
+ def undefined(statement)
14
+ undefined = []
15
+ if statement.pattern?
16
+ unless statement.literal_regexp?
17
+ statement.scan(@scanner){|w| undefined.push(w) unless @v2t.include?(w) or @h.include?(w)}
18
+ end
19
+ else
20
+ statement.scan(@scanner){|w| undefined.push(w) unless @h.include?(w)}
21
+ end
22
+ return undefined.uniq
23
+ end
24
+
25
+ def define!(statement)
26
+ if statement.pattern?
27
+ unless statement.literal_regexp?
28
+ statement.scan(@scanner){|w| @h[w]=nil unless @v2t.include?(w) or @h.include?(w)}
29
+ end
30
+ else
31
+ statement.scan(@scanner){|w| @h[w]=nil unless @h.include?(w)}
32
+ end
33
+ end
34
+
35
+ def s2r(statement)
36
+ if statement[0]=='/' and statement[-1]=='/'
37
+ Regexp.new(statement[1..-2])
38
+ else
39
+ pattern,count,seen = '^',0,{}
40
+ Regexp.quote(statement).scan(@scanner) do |v|
41
+ if n=seen[v]
42
+ pattern << '\\'+n
43
+ elsif type = @v2t[v]
44
+ regex = @t2p[type]
45
+ if regex=='\n'
46
+ pattern << regex
47
+ else
48
+ count += 1
49
+ seen[v]=count.to_s
50
+ pattern << '('+regex+')'
51
+ end
52
+ else
53
+ # To avoid collisions with back-references,
54
+ # isolate digit in square brackets:
55
+ '0123456789'.include?(_=v[0]) and v[0]='['+_+']'
56
+ pattern << v
57
+ end
58
+ end
59
+ raise Error, 'pattern with no captures' if count < 1
60
+ pattern << '$'
61
+ Regexp.new(pattern)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,15 @@
1
+ module Korekto
2
+ class Syntax
3
+ def initialize = @a=[]
4
+ def each = @a.each{|s| yield s}
5
+
6
+ def push(s)
7
+ # ensure it'll eval on string and returns boolean
8
+ b = ''.instance_eval(s)
9
+ raise Error, 'syntax rule must eval boolean' unless b==!!b
10
+ @a.push(s)
11
+ rescue
12
+ raise Error, "#{$!.class}: #{s}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ Neovim.plugin do |plug|
2
+ plug.command(:Korekto) do |nvim|
3
+ validations = nvim.command_output('w !korekto').strip.split("\n").map(&:strip)
4
+ msg = 'OK'
5
+ unless validations.empty?
6
+ buf = nvim.get_current_buf
7
+ while validation = validations.shift
8
+ fields = validation.split(':',4)
9
+ n = fields[1].to_i
10
+ if n > 0
11
+ fn,ln,code,title = fields
12
+ if code=='?' or code=='!'
13
+ # move onto error if on page
14
+ nvim.get_current_win.set_cursor([n,0]) if fn=='-'
15
+ # echo error message
16
+ msg = "#{fn}:#{ln}: #{title}"
17
+ # and stop.
18
+ break
19
+ elsif fn=='-'
20
+ line = buf[n].sub(/#.*$/,'').rstrip
21
+ line << "\t##{code}"
22
+ line << " #{title}" unless title==''
23
+ buf[n] = line unless line == buf[n]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ nvim.command "echom #{msg.inspect}"
29
+ end
30
+ end
@@ -0,0 +1 @@
1
+ autocmd BufRead,BufNewFile *.korekto set filetype=korekto
@@ -0,0 +1,12 @@
1
+ syntax match Comment /^\s*#.\+$/
2
+ syntax match Pass /\s#[A-Z][^#]*$/
3
+ syntax match Syntax /^[?] \w.\+$/
4
+ syntax match Patch /^::[A-Z]\w\+#\w\+[^=]\+=.\+$/
5
+ syntax match Import /^< [\/A-Za-z\_\-\.]\+$/
6
+ highlight Comment ctermfg=darkblue
7
+ highlight Pass ctermfg=darkgreen
8
+ highlight Syntax ctermfg=darkmagenta
9
+ highlight Patch ctermfg=darkgrey
10
+ highlight Import ctermfg=darkyellow
11
+ setlocal tabstop=23
12
+ map <F7> :Korekto<CR>
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: korekto
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.210306
5
+ platform: ruby
6
+ authors:
7
+ - carlosjhr64
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: help_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 7.0.200907
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '7.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 7.0.200907
33
+ description: |
34
+ A general proof checker.
35
+
36
+ Works with neovim(nvim).
37
+ email: carlosjhr64@gmail.com
38
+ executables:
39
+ - korekto
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - bin/korekto
44
+ - lib/korekto.rb
45
+ - lib/korekto/heap.rb
46
+ - lib/korekto/main.rb
47
+ - lib/korekto/statement.rb
48
+ - lib/korekto/statements.rb
49
+ - lib/korekto/symbols.rb
50
+ - lib/korekto/syntax.rb
51
+ - rplugin/ruby/korekto.rb
52
+ - start/korekto/ftdetect/korekto.vim
53
+ - start/korekto/syntax/korekto.vim
54
+ homepage: https://github.com/carlosjhr64/korekto
55
+ licenses:
56
+ - MIT
57
+ metadata: {}
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements:
73
+ - 'ruby: ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]'
74
+ - 'nvim: NVIM v0.4.4'
75
+ - 'xdg-open: xdg-open 1.1.3+'
76
+ rubygems_version: 3.2.3
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: A general proof checker.
80
+ test_files: []