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.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -1
- data/README.md +9 -7
- data/bin/exercise +64 -8
- data/lib/satre.rb +1 -3
- data/lib/satre/errors.rb +3 -0
- data/lib/satre/errors/argument_error.rb +2 -0
- data/lib/satre/errors/expression_error.rb +2 -0
- data/lib/satre/errors/parser_error.rb +2 -0
- data/lib/satre/formula.rb +3 -9
- data/lib/satre/formula/first_order_logic.rb +4 -0
- data/lib/satre/formula/first_order_logic/exists.rb +32 -0
- data/lib/satre/formula/first_order_logic/fol_formula.rb +35 -0
- data/lib/satre/formula/first_order_logic/forall.rb +32 -0
- data/lib/satre/formula/first_order_logic/relation.rb +38 -0
- data/lib/satre/formula/formula.rb +1 -13
- data/lib/satre/formula/propositional_logic.rb +9 -0
- data/lib/satre/formula/propositional_logic/and.rb +37 -0
- data/lib/satre/formula/propositional_logic/atom.rb +44 -0
- data/lib/satre/formula/propositional_logic/entails.rb +42 -0
- data/lib/satre/formula/{false.rb → propositional_logic/false.rb} +9 -2
- data/lib/satre/formula/propositional_logic/iff.rb +38 -0
- data/lib/satre/formula/propositional_logic/imp.rb +38 -0
- data/lib/satre/formula/propositional_logic/not.rb +34 -0
- data/lib/satre/formula/propositional_logic/or.rb +37 -0
- data/lib/satre/formula/{true.rb → propositional_logic/true.rb} +11 -2
- data/lib/satre/formula/term.rb +3 -0
- data/lib/satre/formula/term/function.rb +33 -0
- data/lib/satre/formula/term/term.rb +6 -0
- data/lib/satre/formula/term/variable.rb +24 -0
- data/lib/satre/parser.rb +4 -15
- data/lib/satre/parser/formula_parser.rb +74 -0
- data/lib/satre/parser/lexer.rb +55 -0
- data/lib/satre/parser/parser.rb +80 -0
- data/lib/satre/parser/term_parser.rb +85 -0
- data/lib/satre/version.rb +1 -1
- data/satre.gemspec +1 -0
- metadata +43 -20
- data/lib/satre/expression.rb +0 -6
- data/lib/satre/expression/add.rb +0 -26
- data/lib/satre/expression/const.rb +0 -23
- data/lib/satre/expression/expression.rb +0 -22
- data/lib/satre/expression/expression_parser.rb +0 -56
- data/lib/satre/expression/mul.rb +0 -30
- data/lib/satre/expression/var.rb +0 -14
- data/lib/satre/formula/and.rb +0 -26
- data/lib/satre/formula/atom.rb +0 -28
- data/lib/satre/formula/entails.rb +0 -29
- data/lib/satre/formula/iff.rb +0 -27
- data/lib/satre/formula/imp.rb +0 -25
- data/lib/satre/formula/not.rb +0 -22
- data/lib/satre/formula/or.rb +0 -25
- data/lib/satre/formula_parser.rb +0 -92
- data/lib/satre/lexer.rb +0 -47
@@ -0,0 +1,44 @@
|
|
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 :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = value.dup.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def holds?(domain, func, predicate, valudation)
|
16
|
+
fail(OperationExceprion, 'Atomic value must be a relation') unless value.is_a?(Relation)
|
17
|
+
value.holds?(domain, func, predicate, valudation)
|
18
|
+
end
|
19
|
+
|
20
|
+
# A predicate p(x_1,...,x_n) is well-formed if
|
21
|
+
# (a) each term x_1,...,x_n is well-formed
|
22
|
+
# (b) there is a pair (q, m) in signature sig where q = p and n = m
|
23
|
+
def wellformed?(sig)
|
24
|
+
@value.wellformed?(sig)
|
25
|
+
end
|
26
|
+
|
27
|
+
def eval(valudation)
|
28
|
+
fail(Error, 'valudation must be a hash') unless valudation.is_a?(Hash)
|
29
|
+
fail(Error, 'all validations must be booleans') unless valudation.values.all?{|b|!!b == b}
|
30
|
+
valudation = valudation.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
31
|
+
fail(Error, "valudation for Atom #{value} not given") unless valudation.keys.include?(to_s.to_sym)
|
32
|
+
valudation[to_s.to_sym]
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
value.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def atoms
|
40
|
+
[value.to_s.to_sym]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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 :knowledge_base
|
9
|
+
attr_reader :logical_consequence
|
10
|
+
|
11
|
+
def initialize(knowledge_base, logical_consequence)
|
12
|
+
fail(ArgumentError, 'Argument must be a Formula') unless knowledge_base.is_a?(Formula)
|
13
|
+
fail(ArgumentError, 'Argument must be a Formula') unless logical_consequence.is_a?(Formula)
|
14
|
+
@knowledge_base = knowledge_base.dup.freeze
|
15
|
+
@logical_consequence = logical_consequence.dup.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"(#{knowledge_base} ⊨ #{logical_consequence})"
|
20
|
+
end
|
21
|
+
|
22
|
+
# p |= q is wellformed if p and q are well-formed
|
23
|
+
def wellformed?(sig)
|
24
|
+
knowledge_base.wellformed?(sig) && logical_consequence.wellformed?(sig)
|
25
|
+
end
|
26
|
+
|
27
|
+
def hold?(domain, func, predicate, valudation)
|
28
|
+
fail 'not implemented'
|
29
|
+
#And.new(knowledge_base, Not.new(logical_consequence)).unsatifiable?
|
30
|
+
end
|
31
|
+
|
32
|
+
def eval(*)
|
33
|
+
And.new(knowledge_base, Not.new(logical_consequence)).unsatifiable?
|
34
|
+
end
|
35
|
+
|
36
|
+
def atoms
|
37
|
+
atoms = knowledge_base.atoms + logical_consequence.atoms
|
38
|
+
atoms.uniq || []
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Iff < Formula
|
5
|
+
attr_reader :left_conditional
|
6
|
+
attr_reader :right_conditional
|
7
|
+
|
8
|
+
def initialize(left_conditional, right_conditional)
|
9
|
+
fail(ArgumentError, 'Argument must be a Formula') unless left_conditional.is_a?(Formula)
|
10
|
+
fail(ArgumentError, 'Argument must be a Formula') unless right_conditional.is_a?(Formula)
|
11
|
+
@left_conditional = left_conditional.dup.freeze
|
12
|
+
@right_conditional = right_conditional.dup.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def holds?(domain, func, pred, valudation)
|
16
|
+
left_conditional.holds?(domain, func, pred, valudation) == right_conditional.holds?(domain, func, pred, valudation)
|
17
|
+
end
|
18
|
+
|
19
|
+
# p <=> q is well-formed if p and q are well-formed
|
20
|
+
def wellformed?(sig)
|
21
|
+
left_conjunct.wellformed?(sig) && right_conjunct.wellformed(sig)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"(#{left_conditional} ⇔ #{right_conditional})"
|
26
|
+
end
|
27
|
+
|
28
|
+
def eval(valudation)
|
29
|
+
left_conditional.eval(valudation) == right_conditional.eval(valudation)
|
30
|
+
end
|
31
|
+
|
32
|
+
def atoms
|
33
|
+
atoms = left_conditional.atoms + right_conditional.atoms
|
34
|
+
atoms.uniq || []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Imp < Formula
|
5
|
+
attr_reader :antendence
|
6
|
+
attr_reader :consequence
|
7
|
+
|
8
|
+
def initialize(antendence, consequence)
|
9
|
+
fail(ArgumentError, "Argument must be a Formula p:#{p}") unless antendence.is_a?(Formula)
|
10
|
+
fail(ArgumentError, "Argument must be a Formula q:#{q}") unless consequence.is_a?(Formula)
|
11
|
+
@antendence = antendence.dup.freeze
|
12
|
+
@consequence = consequence.dup.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"(#{antendence} → #{consequence})"
|
17
|
+
end
|
18
|
+
|
19
|
+
# p ==> q is well-formed if p and q are well-formed
|
20
|
+
def wellformed?(sig)
|
21
|
+
antendence.wellformed?(sig) && consequence.wellformed?(sig)
|
22
|
+
end
|
23
|
+
|
24
|
+
# | Imp(p,q) -> not(holds m v p) or (holds m v q)
|
25
|
+
def holds?(domain, func, pred, valudation)
|
26
|
+
(! antendence.holds?(domain, func, pred, valudation)) || (consequence.holds?(domain, func, pred, valudation))
|
27
|
+
end
|
28
|
+
|
29
|
+
def eval(valudation)
|
30
|
+
(! antendence.eval(valudation)) || (consequence.eval(valudation))
|
31
|
+
end
|
32
|
+
|
33
|
+
def atoms
|
34
|
+
atoms = antendence.atoms + consequence.atoms
|
35
|
+
atoms.uniq || []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Not < Formula
|
5
|
+
attr_reader :literal
|
6
|
+
|
7
|
+
def initialize(literal)
|
8
|
+
fail(ArgumentError, 'Argument must be a Formula') unless literal.is_a?(Formula)
|
9
|
+
@literal = literal.dup.freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"(¬#{literal})"
|
14
|
+
end
|
15
|
+
|
16
|
+
def holds?(domain, func, pred, valudation)
|
17
|
+
! literal.holds?(domain, func, pred, valudation)
|
18
|
+
end
|
19
|
+
|
20
|
+
# ~p is well-formed if p is well-formed
|
21
|
+
def wellformed?(sig)
|
22
|
+
literal.wellformed?(sig)
|
23
|
+
end
|
24
|
+
|
25
|
+
def eval(valudation)
|
26
|
+
! literal.eval(valudation)
|
27
|
+
end
|
28
|
+
|
29
|
+
def atoms
|
30
|
+
literal.atoms || []
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'satre/formula'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Or < Formula
|
5
|
+
attr_reader :left_disjunct
|
6
|
+
attr_reader :right_disjunct
|
7
|
+
|
8
|
+
def initialize(left_disjunct, right_disjunct)
|
9
|
+
fail(ArgumentError, 'Argument must be a Formula') unless left_disjunct.is_a?(Formula)
|
10
|
+
fail(ArgumentError, 'Argument must be a Formula') unless right_disjunct.is_a?(Formula)
|
11
|
+
@left_disjunct = left_disjunct.dup.freeze
|
12
|
+
@right_disjunct = right_disjunct.dup.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"(#{left_disjunct} ∨ #{right_disjunct})"
|
17
|
+
end
|
18
|
+
|
19
|
+
def holds?(domain, func, pred, valudation)
|
20
|
+
left_disjunct.holds?(domain, func, pred, valudation) or right_disjunct.holds?(domain, func, pred, valudation)
|
21
|
+
end
|
22
|
+
|
23
|
+
# p \// q is well-formed if p and q are well-formed
|
24
|
+
def wellformed?(sig)
|
25
|
+
left_disjunct.wellformed?(sig) && right_disjunct.wellformed?(sig)
|
26
|
+
end
|
27
|
+
|
28
|
+
def eval(valudation)
|
29
|
+
left_disjunct.eval(valudation) or right_disjunct.eval(valudation)
|
30
|
+
end
|
31
|
+
|
32
|
+
def atoms
|
33
|
+
atoms = left_disjunct.atoms + right_disjunct.atoms
|
34
|
+
atoms.uniq || []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'satre/formula/term/term'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Function < Term
|
5
|
+
attr_reader :function
|
6
|
+
attr_reader :term_list
|
7
|
+
|
8
|
+
def initialize(function, term_list=[])
|
9
|
+
#fail(ArgumentError, '...') unless function.is_a?(String)
|
10
|
+
#fail(ArgumentError, '...') unless term_list.is_a?(Array)
|
11
|
+
@function = function.dup.freeze
|
12
|
+
@term_list = term_list.dup.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
# A term $f(x_1,...,x_n)$ is well-formed if
|
16
|
+
# (a) Each term $x_1,...,x_n$ is well formed
|
17
|
+
# (b) There is a pair (a, m) in signature sig where s = f and n = m
|
18
|
+
#
|
19
|
+
# sig is a hash containing the signature domain
|
20
|
+
def wellformed?(sig)
|
21
|
+
term_list.all? { |x| x.wellformed?(sig) } && sig[function.to_sym] == term_list.length
|
22
|
+
end
|
23
|
+
|
24
|
+
# | Fn(f,args) -> func f (map (termval m v), args);;
|
25
|
+
def validate(func, pred, valudation)
|
26
|
+
func.call(f, term_list.map { |t| t.validate(func, pred, valudation) })
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{function}(#{term_list.map(&:to_s).join(',')})"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'satre/formula/term/term'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class Variable < Term
|
5
|
+
attr_reader :variable
|
6
|
+
|
7
|
+
def initialize(variable)
|
8
|
+
@variable = variable.dup.freeze
|
9
|
+
end
|
10
|
+
|
11
|
+
# A variable is well-formed
|
12
|
+
def wellformed?(_)
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate(_, _, valudation)
|
17
|
+
valudation[variable.to_sym]
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
variable.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/satre/parser.rb
CHANGED
@@ -1,15 +1,4 @@
|
|
1
|
-
require 'satre/lexer'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
class << self
|
6
|
-
def make_parser
|
7
|
-
lambda do |pfn, inp|
|
8
|
-
expr, rest = pfn.call(Lexer.lex(inp))
|
9
|
-
return expr if rest == []
|
10
|
-
fail(ParserError, 'Unparsed input')
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
1
|
+
require 'satre/parser/lexer'
|
2
|
+
require 'satre/parser/parser'
|
3
|
+
require 'satre/parser/formula_parser'
|
4
|
+
require 'satre/parser/term_parser'
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'satre/parser/parser'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class FormulaParser < Parser
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def parse_formula(ifn, afn, vs, inp)
|
8
|
+
parse_Entails = parse_right_infix("|=", ->(p,q) { Entails.new(p,q)})
|
9
|
+
parse_Iff = parse_right_infix("<=>", ->(p,q) { Iff.new(p,q)})
|
10
|
+
parse_Imp = parse_right_infix("==>", ->(p,q) { Imp.new(p,q)})
|
11
|
+
parse_Or = parse_right_infix("\\/", ->(p,q) { Or.new(p,q)})
|
12
|
+
parse_And = parse_right_infix("/\\", ->(p,q) { And.new(p,q)})
|
13
|
+
parse_Atom = method(:parse_atomic_formula).curry.call(ifn, afn, vs)
|
14
|
+
parse_Entails.call(parse_Imp.call(parse_Or.call(parse_And.call(parse_Iff.call(parse_Atom)))),inp)
|
15
|
+
end
|
16
|
+
|
17
|
+
# absorbs the list of variables allowing the convention of
|
18
|
+
# repeated quantifiers
|
19
|
+
def parse_quant(ifn, afn, vs, qcon, x, inp)
|
20
|
+
fail(ArgumentError, 'Body of quantified term expected') if inp == []
|
21
|
+
head = inp.first
|
22
|
+
rest = inp[1..-1]
|
23
|
+
ast, rest = if head == '.' then parse_formula(ifn, afn, vs, rest) else parse_quant(ifn, afn, vs.push(head), qcon, head, rest) end
|
24
|
+
papply( ->(fm) { qcon.call(x,fm) }, ast, rest )
|
25
|
+
end
|
26
|
+
|
27
|
+
# ifn: restricted parser for infix atoms
|
28
|
+
# afn: more general parser for arbitary atoms
|
29
|
+
def parse_atomic_formula(ifn, afn, vs, inp)
|
30
|
+
fail(ArgumentError, 'Expected an formula at end of input') if inp == []
|
31
|
+
head = inp.first
|
32
|
+
rest = inp[1..-1]
|
33
|
+
case head
|
34
|
+
when "false" then return False.new, rest
|
35
|
+
when "true" then return True.new, rest
|
36
|
+
when "("
|
37
|
+
begin
|
38
|
+
ifn.call(vs, inp)
|
39
|
+
rescue
|
40
|
+
parse_bracketed(method(:parse_formula).curry.call(ifn, afn, vs), ")", rest)
|
41
|
+
end
|
42
|
+
when "~"
|
43
|
+
ast, rest = parse_atomic_formula(ifn, afn, vs, rest)
|
44
|
+
papply(->(p) { Not.new(p) }, ast, rest)
|
45
|
+
when "forall" then
|
46
|
+
parse_quant(ifn, afn, vs.push(rest[0]), ->(x,p) { Forall.new(x,p) }, rest[0], rest[1..-1])
|
47
|
+
when "exists" then parse_quant(ifn, afn, vs.push(rest[0]), ->(x,p) { Exists.new(x,p) }, rest[0], rest[1..-1])
|
48
|
+
else afn.call(vs, inp)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_infix_atom(vs, inp)
|
53
|
+
tm, rest = TermParser.parse_term(vs, inp)
|
54
|
+
if rest != [] && ["=", '<', '<=', '>', ">="].include?(rest.first)
|
55
|
+
ast, ost = TermParser.parse_term(vs, rest[1..-1])
|
56
|
+
papply( ->(tm_) {Atom.new(Relation.new(rest[0], [tm, tm_])) }, ast, ost )
|
57
|
+
else fail ParserError, 'calculated' # Excepion erfinden
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse(inp)
|
62
|
+
make_parser(method(:parse_formula).curry.call(method(:parse_infix_atom),
|
63
|
+
TermParser.method(:parse_atom), []), inp)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class String
|
71
|
+
def to_formula
|
72
|
+
Satre::FormulaParser.parse(self)
|
73
|
+
end
|
74
|
+
end
|