satre 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -1
  3. data/README.md +9 -7
  4. data/bin/exercise +64 -8
  5. data/lib/satre.rb +1 -3
  6. data/lib/satre/errors.rb +3 -0
  7. data/lib/satre/errors/argument_error.rb +2 -0
  8. data/lib/satre/errors/expression_error.rb +2 -0
  9. data/lib/satre/errors/parser_error.rb +2 -0
  10. data/lib/satre/formula.rb +3 -9
  11. data/lib/satre/formula/first_order_logic.rb +4 -0
  12. data/lib/satre/formula/first_order_logic/exists.rb +32 -0
  13. data/lib/satre/formula/first_order_logic/fol_formula.rb +35 -0
  14. data/lib/satre/formula/first_order_logic/forall.rb +32 -0
  15. data/lib/satre/formula/first_order_logic/relation.rb +38 -0
  16. data/lib/satre/formula/formula.rb +1 -13
  17. data/lib/satre/formula/propositional_logic.rb +9 -0
  18. data/lib/satre/formula/propositional_logic/and.rb +37 -0
  19. data/lib/satre/formula/propositional_logic/atom.rb +44 -0
  20. data/lib/satre/formula/propositional_logic/entails.rb +42 -0
  21. data/lib/satre/formula/{false.rb → propositional_logic/false.rb} +9 -2
  22. data/lib/satre/formula/propositional_logic/iff.rb +38 -0
  23. data/lib/satre/formula/propositional_logic/imp.rb +38 -0
  24. data/lib/satre/formula/propositional_logic/not.rb +34 -0
  25. data/lib/satre/formula/propositional_logic/or.rb +37 -0
  26. data/lib/satre/formula/{true.rb → propositional_logic/true.rb} +11 -2
  27. data/lib/satre/formula/term.rb +3 -0
  28. data/lib/satre/formula/term/function.rb +33 -0
  29. data/lib/satre/formula/term/term.rb +6 -0
  30. data/lib/satre/formula/term/variable.rb +24 -0
  31. data/lib/satre/parser.rb +4 -15
  32. data/lib/satre/parser/formula_parser.rb +74 -0
  33. data/lib/satre/parser/lexer.rb +55 -0
  34. data/lib/satre/parser/parser.rb +80 -0
  35. data/lib/satre/parser/term_parser.rb +85 -0
  36. data/lib/satre/version.rb +1 -1
  37. data/satre.gemspec +1 -0
  38. metadata +43 -20
  39. data/lib/satre/expression.rb +0 -6
  40. data/lib/satre/expression/add.rb +0 -26
  41. data/lib/satre/expression/const.rb +0 -23
  42. data/lib/satre/expression/expression.rb +0 -22
  43. data/lib/satre/expression/expression_parser.rb +0 -56
  44. data/lib/satre/expression/mul.rb +0 -30
  45. data/lib/satre/expression/var.rb +0 -14
  46. data/lib/satre/formula/and.rb +0 -26
  47. data/lib/satre/formula/atom.rb +0 -28
  48. data/lib/satre/formula/entails.rb +0 -29
  49. data/lib/satre/formula/iff.rb +0 -27
  50. data/lib/satre/formula/imp.rb +0 -25
  51. data/lib/satre/formula/not.rb +0 -22
  52. data/lib/satre/formula/or.rb +0 -25
  53. data/lib/satre/formula_parser.rb +0 -92
  54. data/lib/satre/lexer.rb +0 -47
@@ -0,0 +1,55 @@
1
+ module Satre
2
+ class Lexer
3
+ class << self
4
+ def matches
5
+ lambda { |pattern, str| str.split("").all? { |c| pattern.include? c } }
6
+ end
7
+
8
+ def matches(pattern, str)
9
+ str.split("").all? { |c| pattern.include? c }
10
+ end
11
+
12
+ def space?(str)
13
+ matches(" \t\n\r", str)
14
+ end
15
+
16
+ def punctuation?(str)
17
+ matches("()[]{}", str)
18
+ end
19
+
20
+ def symbolic?(str)
21
+ matches("~`!@#%$^&*-+<=>\\/|", str)
22
+ end
23
+
24
+ def numeric?(str)
25
+ matches("0123456789", str)
26
+ end
27
+
28
+ def alpanumeric?(str)
29
+ matches("abcdefghijklmnopqrstuvwxyz_'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", str)
30
+ end
31
+
32
+ def refute(*)
33
+ false
34
+ end
35
+
36
+ def lexwhile(prop, inp)
37
+ tokl = inp.split("").take_while { |c| self.send(prop, c) }.inject(:+)
38
+ tokl = "" if tokl.nil?
39
+ return tokl, inp[tokl.length..-1]
40
+ end
41
+
42
+ def lex(inp)
43
+ inp, rest = self.lexwhile(:space?, inp)
44
+ return [] if rest == "" || rest.nil?
45
+ c = rest[0]
46
+ cs = rest[1..-1]
47
+ prop = if self.alpanumeric?(c) then :alpanumeric?
48
+ elsif self.symbolic?(c) then :symbolic?
49
+ else :refute end
50
+ toktl, rest = self.lexwhile(prop, cs)
51
+ [c+toktl] + lex(rest)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,80 @@
1
+ require 'satre/parser/lexer'
2
+ require 'satre/formula'
3
+ require 'satre/errors'
4
+
5
+ module Satre
6
+ class Parser
7
+ class << self
8
+ def make_parser(parsing_function, input)
9
+ expr, rest = parsing_function.call(Lexer.lex(input))
10
+ fail(ParserError, "Unparsed input: #{rest}") unless rest == []
11
+ return expr
12
+ end
13
+
14
+ def is_const_name(string)
15
+ string.split('').all? { |c| Lexer.numeric?(c) } || string.nil?
16
+ end
17
+
18
+ ###
19
+ # Generic parsing function
20
+ # sof: function -> takes the current input and combines it in some way with the items arravied at so far
21
+ # opupdate: function -> modifies the function appropriately when an new item is parsed.
22
+ ###
23
+ def parse_ginfix(opsym, opupdate, sof, subparser, inp)
24
+ e1, inp1 = subparser.call(inp)
25
+ if inp1 != [] && inp1[0] == opsym
26
+ parse_ginfix(opsym, opupdate, opupdate.curry.call(sof, e1), subparser, inp1[1..-1])
27
+ else
28
+ return sof.call(e1), inp1
29
+ end
30
+ end
31
+
32
+ # parse a list of items and combine them in a left associated manner
33
+ # returns a lambda
34
+ def parse_left_infix(opsym, opcon)
35
+ opupdate = ->(f, e1 ,e2) {opcon.call(f.call(e1), e2) }
36
+ sof = ->(x) { x }
37
+ method(:parse_ginfix).curry.call(opsym, opupdate, sof)
38
+ end
39
+
40
+ # parse a list of items and combine them in a right associated manner
41
+ # returns a lambda
42
+ def parse_right_infix(opsym, opcon)
43
+ opupdate = ->(f,e1,e2) { f.call(opcon.call(e1,e2)) }
44
+ sof = ->(x) { x }
45
+ method(:parse_ginfix).curry.call(opsym, opupdate, sof)
46
+ end
47
+
48
+ # parse a list of items and collect them in a list
49
+ def parse_list(opsym)
50
+ opupdate = ->(f,e1,e2) { f.call(e1) << e2 }
51
+ sof = ->(x) { [x] }
52
+ method(:parse_ginfix).curry.call(opsym, opupdate, sof)
53
+ end
54
+
55
+ # Applies a function to the first element of a pair.
56
+ # The idea being to modify the returned abstract syntax tree while leaving
57
+ # the `unparsed input` alone
58
+ def papply(f, ast, rest)
59
+ return f.call(ast), rest
60
+ end
61
+
62
+ # Checks if the head of a list (typically the list of unparsed input)
63
+ # is some particular item, but also first checks that the list is nonempty
64
+ # before looking at its head
65
+ def nextin(inp, tok)
66
+ inp != [] && inp.first == tok
67
+ end
68
+
69
+ # Deals with the common situation of syntastic items enclosed in brackets
70
+ # It simply calls the subparser and then checks and eliminates
71
+ # the closing bracket. In principle, the therminating character can be anything,
72
+ # so this function could equally be used for other purposes.
73
+ def parse_bracketed(subparser, closing_character, inp)
74
+ ast, rest = subparser.call(inp)
75
+ return ast, rest[1..-1] if nextin(rest, closing_character)
76
+ fail(ExpressionError, "Expected closing character '#{closing_character}'")
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,85 @@
1
+ require 'satre/parser/parser'
2
+
3
+ module Satre
4
+ class TermParser < Parser
5
+ class << self
6
+
7
+ # vs: set of bounds variables in the current scope
8
+ # inp: current input
9
+ def parse_atomic_term(vs, inp)
10
+ fail(ArgumentError, 'Term expected') if inp == []
11
+ head = inp.first
12
+ rest = inp[1..-1]
13
+ if head == '(' then parse_bracketed(method(:parse_term).curry.call(vs), ")", rest)
14
+ elsif head == '-' then papply( ->(t) { Function.new("-", [t])}, method(:parse_atomic_term).curry.call(vs), rest )
15
+ elsif Lexer.alpanumeric?(head) && rest[0] == '('
16
+ if rest[1] == ')'
17
+ papply( -> { Function.new(head, []) }, rest[2..-1])
18
+ else
19
+ ast, rest = parse_bracketed(parse_list(',').call(method(:parse_term).curry.call(vs)) , ')', rest[1..-1])
20
+ papply(->(args) { Function.new(head, args) }, ast, rest)
21
+ end
22
+ else
23
+ a = if is_const_name(head) && !vs.include?(head) then Function.new(head,[]) else Variable.new(head) end
24
+ return a, rest
25
+ end
26
+ end
27
+
28
+ # build up parsing of general terms via parsing of the various infix operations
29
+ def parse_term(vs, inp)
30
+ colon = parse_right_infix("::", ->(e1,e2) { Function.new('::', [e1, e2]) })
31
+ plus = parse_right_infix("+", ->(e1,e2) { Function.new('+', [e1, e2]) })
32
+ minus = parse_left_infix("-", ->(e1,e2) { Function.new('-', [e1, e2]) })
33
+ mul = parse_right_infix("*", ->(e1,e2) { Function.new('*', [e1, e2]) })
34
+ div = parse_left_infix("/", ->(e1,e2) { Function.new('/', [e1, e2]) })
35
+ power = parse_left_infix("^", ->(e1,e2) { Function.new('^', [e1, e2]) })
36
+ atom = method(:parse_atomic_term).curry.call(vs)
37
+ colon.call(plus.call(minus.call(mul.call(div.call(power.call(atom))))), inp)
38
+ end
39
+
40
+ def is_const_name(string)
41
+ string.split('').all? { |c| Lexer.numeric?(c) } || string.nil?
42
+ end
43
+
44
+
45
+ def parse_atom(vs, inp)
46
+ FormulaParser.parse_infix_atom(vs, inp)
47
+ rescue ParserError
48
+ head = inp.first
49
+ rest = inp[1..-1]
50
+ if rest[0] == '('
51
+ rest = rest[1..-1]
52
+ if rest[0] == ')'
53
+ return Atom.new(Relation.new(head, [])), rest[1..-1]
54
+ else
55
+ ast, rest = parse_bracketed(parse_list(',').call(method(:parse_term).curry.call(vs)),')', rest)
56
+ papply( ->(args) { Atom.new(Relation.new(head,args))}, ast, rest)
57
+ end
58
+ else
59
+ return Atom.new(Relation.new(head, [])), rest if head != '('
60
+ fail(ParserError, 'Parse Atom')
61
+ end
62
+ end
63
+
64
+ def parse_formula(ifn, afn, vs, inp)
65
+ parse_Entails = parse_right_infix("|=", ->(p,q) { Entails.new(p,q)})
66
+ parse_Iff = parse_right_infix("<=>", ->(p,q) { Iff.new(p,q)})
67
+ parse_Imp = parse_right_infix("==>", ->(p,q) { Imp.new(p,q)})
68
+ parse_Or = parse_right_infix("\\/", ->(p,q) { Or.new(p,q)})
69
+ parse_And = parse_right_infix("/\\", ->(p,q) { And.new(p,q)})
70
+ parse_Atom = method(:parse_atomic_formula).curry.call(ifn, afn, vs)
71
+ parse_Entails.call(parse_Imp.call(parse_Or.call(parse_And.call(parse_Iff.call(parse_Atom)))),inp)
72
+ end
73
+
74
+ def parse(inp)
75
+ make_parser(method(:parse_term).curry.call([]), inp)
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ class String
82
+ def to_term
83
+ Satre::TermParser.parse(self)
84
+ end
85
+ end
@@ -1,3 +1,3 @@
1
1
  module Satre
2
- VERSION = '0.1.0'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -37,4 +37,5 @@ Gem::Specification.new do |spec|
37
37
  spec.add_development_dependency 'pry'
38
38
  spec.add_development_dependency 'rubocop'
39
39
  spec.add_development_dependency 'guard-rubocop'
40
+ spec.add_development_dependency 'codeclimate-test-reporter'
40
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: satre
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman C. Podolski
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-01-10 00:00:00.000000000 Z
11
+ date: 2016-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: terminal-table
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: codeclimate-test-reporter
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  description: "\n I think therefore I am.\n \n Satre is a library for proportional
168
182
  and first order logic.\n It was inspired by the book 'Handbook of practical logic
169
183
  and automated reasoning' by Harrison, J (2009).\n \n This project originated at
@@ -188,27 +202,36 @@ files:
188
202
  - bin/exercise
189
203
  - bin/setup
190
204
  - lib/satre.rb
191
- - lib/satre/expression.rb
192
- - lib/satre/expression/add.rb
193
- - lib/satre/expression/const.rb
194
- - lib/satre/expression/expression.rb
195
- - lib/satre/expression/expression_parser.rb
196
- - lib/satre/expression/mul.rb
197
- - lib/satre/expression/var.rb
205
+ - lib/satre/errors.rb
206
+ - lib/satre/errors/argument_error.rb
207
+ - lib/satre/errors/expression_error.rb
208
+ - lib/satre/errors/parser_error.rb
198
209
  - lib/satre/formula.rb
199
- - lib/satre/formula/and.rb
200
- - lib/satre/formula/atom.rb
201
- - lib/satre/formula/entails.rb
202
- - lib/satre/formula/false.rb
210
+ - lib/satre/formula/first_order_logic.rb
211
+ - lib/satre/formula/first_order_logic/exists.rb
212
+ - lib/satre/formula/first_order_logic/fol_formula.rb
213
+ - lib/satre/formula/first_order_logic/forall.rb
214
+ - lib/satre/formula/first_order_logic/relation.rb
203
215
  - lib/satre/formula/formula.rb
204
- - lib/satre/formula/iff.rb
205
- - lib/satre/formula/imp.rb
206
- - lib/satre/formula/not.rb
207
- - lib/satre/formula/or.rb
208
- - lib/satre/formula/true.rb
209
- - lib/satre/formula_parser.rb
210
- - lib/satre/lexer.rb
216
+ - lib/satre/formula/propositional_logic.rb
217
+ - lib/satre/formula/propositional_logic/and.rb
218
+ - lib/satre/formula/propositional_logic/atom.rb
219
+ - lib/satre/formula/propositional_logic/entails.rb
220
+ - lib/satre/formula/propositional_logic/false.rb
221
+ - lib/satre/formula/propositional_logic/iff.rb
222
+ - lib/satre/formula/propositional_logic/imp.rb
223
+ - lib/satre/formula/propositional_logic/not.rb
224
+ - lib/satre/formula/propositional_logic/or.rb
225
+ - lib/satre/formula/propositional_logic/true.rb
226
+ - lib/satre/formula/term.rb
227
+ - lib/satre/formula/term/function.rb
228
+ - lib/satre/formula/term/term.rb
229
+ - lib/satre/formula/term/variable.rb
211
230
  - lib/satre/parser.rb
231
+ - lib/satre/parser/formula_parser.rb
232
+ - lib/satre/parser/lexer.rb
233
+ - lib/satre/parser/parser.rb
234
+ - lib/satre/parser/term_parser.rb
212
235
  - lib/satre/version.rb
213
236
  - satre.gemspec
214
237
  homepage: http://in.tum.de
@@ -1,6 +0,0 @@
1
- require 'satre/expression/expression'
2
- require 'satre/expression/add'
3
- require 'satre/expression/const'
4
- require 'satre/expression/expression_parser'
5
- require 'satre/expression/mul'
6
- require 'satre/expression/var'
@@ -1,26 +0,0 @@
1
- require 'satre/expression'
2
-
3
- # A parster for propositional statements
4
- # and simple mathematical expressions
5
- module Satre
6
- class Add < Expression
7
- attr_reader :p
8
- attr_reader :q
9
-
10
- def initialize(p,q)
11
- fail(ArgumentError, 'p must be a expression') unless p.is_a?(Expression)
12
- fail(ArgumentError, 'q must be a expression') unless p.is_a?(Expression)
13
- @p = p.dup.freeze
14
- @q = q.dup.freeze
15
- super "Add (#{@p},#{@q})"
16
- end
17
-
18
- def eval
19
- p.eval + q.eval
20
- end
21
-
22
- def self.parse(e)
23
- fail 'not yet implemented'
24
- end
25
- end
26
- end
@@ -1,23 +0,0 @@
1
- require 'satre/expression'
2
-
3
- # A parster for propositional statements
4
- # and simple mathematical expressions
5
- module Satre
6
- class Const < Expression
7
- attr_reader :i
8
-
9
- def initialize(i)
10
- ArgumentError 'argument must be an integer' unless i.is_a? Integer
11
- @i = i
12
- super "Const #{i}"
13
- end
14
-
15
- def eval
16
- i
17
- end
18
-
19
- def self.parse(e)
20
- fail 'not yet implemented'
21
- end
22
- end
23
- end
@@ -1,22 +0,0 @@
1
- module Satre
2
- class Expression
3
- attr_reader :base
4
-
5
- def initialize(base)
6
- fail(ArgumentError, 'argument must be a string') unless base.is_a?(String)
7
- @base = base.dup.freeze
8
- end
9
-
10
- def to_s
11
- base
12
- end
13
-
14
- def eval # abstract method
15
- fail 'abstract'
16
- end
17
-
18
- def simplify
19
- fail 'not yet implemented'
20
- end
21
- end
22
- end
@@ -1,56 +0,0 @@
1
- require 'satre/parser'
2
-
3
- module Satre
4
- class ExpressionParser < Parser
5
- class << self
6
- def parse_expression
7
- lambda do |inp|
8
- e1, i1 = parse_product(inp)
9
- if i1[0] == '+'
10
- e2, i2 = parse_expression.call(i1.drop(1))
11
- return Add.new(e1, e2), i2
12
- end
13
- return e1, i1
14
- end
15
- end
16
-
17
- def parse_product(inp)
18
- e1, i1 = parse_atom(inp)
19
- if i1[0] == '*'
20
- e2,i2 = parse_expression.call(i1.drop(1))
21
- return Mul.new(e1, e2), i2
22
- end
23
- return e1, i1
24
- end
25
-
26
- def parse_atom(inp)
27
- fail(ArgumentError, 'Expected an expression at end of input') if inp == []
28
- if inp[0] == '('
29
- e2, i2 = parse_expression.call(inp.drop(1))
30
- if i2[0] == ')'
31
- return e2, i2.drop(1)
32
- else
33
- fail(ExpressionError, 'Expected closing bracket')
34
- end
35
- else
36
- if Lexer.numeric?.call(inp[0])
37
- return Const.new(inp[0].to_i), inp.drop(1)
38
- else
39
- return Var.new(inp[0]), inp.drop(1)
40
- end
41
- end
42
- end
43
-
44
- def parse(inp)
45
- make_parser.curry.call(parse_expression).call(inp)
46
- end
47
- end
48
-
49
- end
50
- end
51
-
52
- class String
53
- def to_expression
54
- Satre::ExpressionParser.parse(self)
55
- end
56
- end