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,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
@@ -3,8 +3,15 @@ require 'satre/formula'
3
3
  module Satre
4
4
  class False < Formula
5
5
 
6
- def initialize
7
- super '⊥'
6
+ def to_s
7
+ '⊥'
8
+ end
9
+
10
+ alias :holds? :eval
11
+
12
+ # False is well-formed
13
+ def wellformed?(sig)
14
+ true
8
15
  end
9
16
 
10
17
  def eval(*)
@@ -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
@@ -2,8 +2,17 @@ require 'satre/formula'
2
2
 
3
3
  module Satre
4
4
  class True < Formula
5
- def initialize
6
- super '⊤'
5
+
6
+ def to_s
7
+ '⊤'
8
+ end
9
+
10
+ # true holds
11
+ alias :holds? :eval
12
+
13
+ # True is wellformed
14
+ def wellformed?(_)
15
+ true
7
16
  end
8
17
 
9
18
  def eval(*)
@@ -0,0 +1,3 @@
1
+ require 'satre/formula/term/term.rb'
2
+ require 'satre/formula/term/function.rb'
3
+ require 'satre/formula/term/variable.rb'
@@ -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,6 @@
1
+ require 'satre/formula/formula'
2
+
3
+ module Satre
4
+ class Term < Formula
5
+ end
6
+ 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
@@ -1,15 +1,4 @@
1
- require 'satre/lexer'
2
-
3
- module Satre
4
- class Parser
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