satre 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +110 -0
- data/Guardfile +42 -0
- data/LICENSE +22 -0
- data/README.md +82 -0
- data/Rakefile +17 -0
- data/bin/console +9 -0
- data/bin/exercise +70 -0
- data/bin/setup +7 -0
- data/lib/satre.rb +6 -0
- data/lib/satre/expression.rb +6 -0
- data/lib/satre/expression/add.rb +26 -0
- data/lib/satre/expression/const.rb +23 -0
- data/lib/satre/expression/expression.rb +22 -0
- data/lib/satre/expression/expression_parser.rb +56 -0
- data/lib/satre/expression/mul.rb +30 -0
- data/lib/satre/expression/var.rb +14 -0
- data/lib/satre/formula.rb +10 -0
- data/lib/satre/formula/and.rb +26 -0
- data/lib/satre/formula/atom.rb +28 -0
- data/lib/satre/formula/entails.rb +29 -0
- data/lib/satre/formula/false.rb +19 -0
- data/lib/satre/formula/formula.rb +61 -0
- data/lib/satre/formula/iff.rb +27 -0
- data/lib/satre/formula/imp.rb +25 -0
- data/lib/satre/formula/not.rb +22 -0
- data/lib/satre/formula/or.rb +25 -0
- data/lib/satre/formula/true.rb +17 -0
- data/lib/satre/formula_parser.rb +92 -0
- data/lib/satre/lexer.rb +47 -0
- data/lib/satre/parser.rb +15 -0
- data/lib/satre/version.rb +3 -0
- data/satre.gemspec +40 -0
- metadata +239 -0
@@ -0,0 +1,26 @@
|
|
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
|
@@ -0,0 +1,23 @@
|
|
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
|
@@ -0,0 +1,22 @@
|
|
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
|
@@ -0,0 +1,56 @@
|
|
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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'satre/expression'
|
2
|
+
|
3
|
+
# A parster for propositional statements
|
4
|
+
# and simple mathematical expressions
|
5
|
+
module Satre
|
6
|
+
class Mul < 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 "Mul(#{@p},#{@q})"
|
16
|
+
end
|
17
|
+
|
18
|
+
def eval
|
19
|
+
p.eval * q.eval
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse(e)
|
23
|
+
e1, i1 = Expression.parse(inp)
|
24
|
+
if e1[-1] == '*'
|
25
|
+
_e2,i2 = parse_expression(i1)
|
26
|
+
return Mul.new(e1,i1), i2
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'satre/expression'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Var < Expression
|
5
|
+
def initialize(s)
|
6
|
+
fail(ArgumentError, 's must be a string') unless s.is_a?(String)
|
7
|
+
super "Var \"#{s}\""
|
8
|
+
end
|
9
|
+
|
10
|
+
def eval(_valudation)
|
11
|
+
fail 'not implemented'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'satre/formula/formula'
|
2
|
+
require 'satre/formula/and'
|
3
|
+
require 'satre/formula/atom'
|
4
|
+
require 'satre/formula/entails'
|
5
|
+
require 'satre/formula/false'
|
6
|
+
require 'satre/formula/iff'
|
7
|
+
require 'satre/formula/imp'
|
8
|
+
require 'satre/formula/not'
|
9
|
+
require 'satre/formula/or'
|
10
|
+
require 'satre/formula/true'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class And < Formula
|
5
|
+
attr_reader :p
|
6
|
+
attr_reader :q
|
7
|
+
|
8
|
+
def initialize(p,q)
|
9
|
+
fail(ArgumentError, 'Argument must be a Formula') unless p.is_a?(Formula)
|
10
|
+
fail(ArgumentError, 'Argument must be a Formula') unless q.is_a?(Formula)
|
11
|
+
@p = p.dup.freeze
|
12
|
+
@q = q.dup.freeze
|
13
|
+
super "(#{@p} ∧ #{@q})"
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval(v)
|
17
|
+
p.eval(v) && q.eval(v)
|
18
|
+
end
|
19
|
+
|
20
|
+
def atoms
|
21
|
+
atoms = p.atoms + q.atoms
|
22
|
+
atoms.uniq || []
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
# A parster for propositional statements
|
4
|
+
# and simple mathematical expressions
|
5
|
+
module Satre
|
6
|
+
# A propositional 'atomic' value
|
7
|
+
class Atom < Formula
|
8
|
+
|
9
|
+
attr_reader :base
|
10
|
+
|
11
|
+
def initialize(base)
|
12
|
+
super base
|
13
|
+
end
|
14
|
+
|
15
|
+
def eval(valudation)
|
16
|
+
fail(Error, 'valudation must be a hash') unless valudation.is_a?(Hash)
|
17
|
+
fail(Error, 'all validations must be booleans') unless valudation.values.all?{|b|!!b == b}
|
18
|
+
valudation = valudation.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
19
|
+
fail(Error, "valudation for Atom #{base} not given") unless valudation.keys.include?(base.to_sym)
|
20
|
+
valudation[base.to_sym]
|
21
|
+
end
|
22
|
+
|
23
|
+
def atoms
|
24
|
+
[base]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
# A parster for propositional statements
|
4
|
+
# and simple mathematical expressions
|
5
|
+
module Satre
|
6
|
+
# A propositional logic 'and' value
|
7
|
+
class Entails < Formula
|
8
|
+
attr_reader :p
|
9
|
+
attr_reader :q
|
10
|
+
|
11
|
+
def initialize(p,q)
|
12
|
+
fail(ArgumentError, 'Argument must be a Formula') unless p.is_a?(Formula)
|
13
|
+
fail(ArgumentError, 'Argument must be a Formula') unless q.is_a?(Formula)
|
14
|
+
@p = p.dup.freeze
|
15
|
+
@q = q.dup.freeze
|
16
|
+
super "(#{@p} ⊨ #{@q})"
|
17
|
+
end
|
18
|
+
|
19
|
+
def eval(*)
|
20
|
+
p.entails?(q)
|
21
|
+
end
|
22
|
+
|
23
|
+
def atoms
|
24
|
+
atoms = p.atoms + q.atoms
|
25
|
+
atoms.uniq || []
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Satre
|
2
|
+
class Formula
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def <=>(other)
|
6
|
+
base <=> other.base
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :base
|
10
|
+
|
11
|
+
def initialize(base)
|
12
|
+
fail(ArgumentError, 'Argument must be a String') unless base.is_a?(String)
|
13
|
+
@base = base.dup.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
base
|
18
|
+
end
|
19
|
+
|
20
|
+
def atoms
|
21
|
+
fail 'abstract'
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_all_valuations?
|
25
|
+
valudation = Hash[self.atoms.map { |x| [x, true] }]
|
26
|
+
truthtable = [valudation]
|
27
|
+
valudation.length.times do |i|
|
28
|
+
v = {}
|
29
|
+
(valudation.length - i).times do |j|
|
30
|
+
v = valudation.dup
|
31
|
+
v[v.keys[j]] = ! v[v.keys[j]]
|
32
|
+
truthtable << v
|
33
|
+
end
|
34
|
+
valudation = v
|
35
|
+
end
|
36
|
+
truthtable.all? { |v| self.eval(v) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def eval(_valudation)
|
40
|
+
fail 'abstract'
|
41
|
+
end
|
42
|
+
|
43
|
+
def tautology?
|
44
|
+
on_all_valuations?
|
45
|
+
end
|
46
|
+
|
47
|
+
def unsatifiable?
|
48
|
+
Not.new(self).tautology?
|
49
|
+
end
|
50
|
+
|
51
|
+
def satifiable?
|
52
|
+
not unsatifiable?
|
53
|
+
end
|
54
|
+
|
55
|
+
def entails?(other)
|
56
|
+
#Imp.new(self, other).tautology?
|
57
|
+
And.new(self, Not.new(other)).unsatifiable?
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Iff < Formula
|
5
|
+
attr_reader :p
|
6
|
+
attr_reader :q
|
7
|
+
|
8
|
+
def initialize(p,q)
|
9
|
+
fail(ArgumentError, 'Argument must be a Formula') unless p.is_a?(Formula)
|
10
|
+
fail(ArgumentError, 'Argument must be a Formula') unless q.is_a?(Formula)
|
11
|
+
@p = p.dup.freeze
|
12
|
+
@q = q.dup.freeze
|
13
|
+
super "(#{@p} ⇔ #{@q})"
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval(valudation)
|
17
|
+
p.eval(valudation) == q.eval(valudation)
|
18
|
+
end
|
19
|
+
|
20
|
+
def atoms
|
21
|
+
atoms = p.atoms + q.atoms
|
22
|
+
atoms.uniq || []
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Imp < Formula
|
5
|
+
attr_reader :p
|
6
|
+
attr_reader :q
|
7
|
+
|
8
|
+
def initialize(p,q)
|
9
|
+
fail(ArgumentError, "Argument must be a Formula p:#{p}") unless p.is_a?(Formula)
|
10
|
+
fail(ArgumentError, "Argument must be a Formula q:#{q}") unless q.is_a?(Formula)
|
11
|
+
@p = p.dup.freeze
|
12
|
+
@q = q.dup.freeze
|
13
|
+
super "(#{@p} → #{@q})"
|
14
|
+
end
|
15
|
+
|
16
|
+
def eval(valudation)
|
17
|
+
(! p.eval(valudation)) or (q.eval(valudation))
|
18
|
+
end
|
19
|
+
|
20
|
+
def atoms
|
21
|
+
atoms = p.atoms + q.atoms
|
22
|
+
atoms.uniq || []
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Not < Formula
|
5
|
+
attr_reader :p
|
6
|
+
|
7
|
+
def initialize(p)
|
8
|
+
fail(ArgumentError, 'Argument must be a Formula') unless p.is_a?(Formula)
|
9
|
+
@p = p.dup.freeze
|
10
|
+
super "(¬#{@p})"
|
11
|
+
end
|
12
|
+
|
13
|
+
def eval(valudation)
|
14
|
+
! p.eval(valudation)
|
15
|
+
end
|
16
|
+
|
17
|
+
def atoms
|
18
|
+
p.atoms || []
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|