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