satre 0.1.0 → 1.0.0

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