satre 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|