propose 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5e51a21663914889a221fc855ab81d9b864e4f7c
4
+ data.tar.gz: c100032842cfb108b8181ca745767d428921aac4
5
+ SHA512:
6
+ metadata.gz: 0fa8554ec8b5b662c683cf8b8dafd9beecbd90a92ace372f3dd18308d9e21ce05013343288c0394ca32841bdbe0007400f7c0b1e1124e1895cc263207b4697fb
7
+ data.tar.gz: fd45c6fd818f0255ebae73f43c56feb70ea892b7a9e12fc12739d5604a7a3e7529b6abd5b8c506366b803a7cc3f4e681841fc2e3530c572d9d5689990c4cc576
data/bin/propose ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'propose'
4
+
5
+ Propose::REPL.new.run
data/lib/propose.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'propose/parser'
2
+ require 'propose/repl'
3
+ require 'propose/truth_table'
4
+ require 'propose/version'
5
+
6
+ require 'propose/visitor'
7
+ require 'propose/atom_finder'
8
+
9
+ require 'propose/tree/node'
10
+ require 'propose/tree/binary_operation'
11
+ require 'propose/tree/conjunction'
12
+ require 'propose/tree/disjunction'
13
+ require 'propose/tree/falsum'
14
+ require 'propose/tree/implication'
15
+ require 'propose/tree/atom'
16
+ require 'propose/tree/unary_operation'
17
+ require 'propose/tree/negation'
18
+ require 'propose/tree/sequent'
19
+ require 'propose/tree/verum'
@@ -0,0 +1,19 @@
1
+ require 'set'
2
+
3
+ module Propose
4
+ # Finds all atoms in a parse tree.
5
+ class AtomFinder
6
+ include Propose::Visitor
7
+
8
+ attr_reader :atoms
9
+
10
+ def initialize(formula)
11
+ @atoms = Set.new
12
+ visit(formula)
13
+ end
14
+
15
+ def visit_atom(atom)
16
+ @atoms << atom
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ require 'treetop'
2
+
3
+ module Propose
4
+ # Raised when the parser is unable to parse the input.
5
+ class ParseError < StandardError; end
6
+
7
+ # Parses a propositional logic statement into an abstract syntax tree.
8
+ class Parser
9
+ GRAMMAR_FILE = File.expand_path(File.join(File.dirname(__FILE__),
10
+ '..', '..', 'grammar',
11
+ 'propositional_logic.treetop'))
12
+
13
+ class << self
14
+ def parse(string)
15
+ tree = parser.parse(string)
16
+ if tree
17
+ tree.to_ast
18
+ else
19
+ raise ParseError, parser.failure_reason
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def parser
26
+ @parser ||= begin
27
+ Treetop.load(GRAMMAR_FILE)
28
+ PropositionalLogicParser.new
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,67 @@
1
+ require 'colorize'
2
+ require 'readline'
3
+ require 'terminal-table'
4
+
5
+ module Propose
6
+ # Read-Eval-Print-Loop engine, allowing a user to interactively create
7
+ # and explore the properties of propositional logic statements.
8
+ class REPL
9
+ def run
10
+ while input = Readline.readline('> '.green, true)
11
+ handle_input(input)
12
+ end
13
+ rescue SignalException # rubocop:disable HandleExceptions
14
+ # User hit Ctrl-C; close gracefully
15
+ end
16
+
17
+ private
18
+
19
+ def handle_input(input)
20
+ input = input.strip
21
+ interpret(input) unless input.empty?
22
+ rescue ParseError => ex
23
+ puts "Invalid expression: #{ex.message}".red
24
+ end
25
+
26
+ def interpret(input)
27
+ statement = Parser.parse(input)
28
+
29
+ case statement
30
+ when Tree::Sequent
31
+ puts statement.to_s.cyan
32
+ # TODO: Find and display proof of sequent
33
+ else
34
+ print_truth_table(statement)
35
+ end
36
+ end
37
+
38
+ def print_truth_table(statement)
39
+ tt = Propose::TruthTable.new(statement)
40
+
41
+ atoms = tt.formula_atoms.to_a
42
+
43
+ table = Terminal::Table.new do |t|
44
+ t << atoms + ['Result']
45
+ t.add_separator
46
+ tt.evaluations.each do |assignment, result|
47
+ t << assignment.values.map { |v| shorthand_value(v) } + [shorthand_value(result)]
48
+ end
49
+ end
50
+
51
+ table.title = statement.to_s
52
+
53
+ (atoms.count + 1).times do |i|
54
+ table.align_column(i, :center)
55
+ end
56
+
57
+ puts table.to_s
58
+ .gsub('T', 'T'.green)
59
+ .gsub('F', 'F'.red)
60
+ .gsub(table.title, table.title.cyan)
61
+ end
62
+
63
+ def shorthand_value(value)
64
+ value ? 'T' : 'F'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ module Propose::Tree
2
+ # Declarative statement that can be either true or false.
3
+ #
4
+ # For example: "The sun is shining."
5
+ class Atom < Node
6
+ attr_reader :name
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ end
11
+
12
+ def ==(other)
13
+ super || @name == other.name
14
+ end
15
+
16
+ def evaluate(assignment)
17
+ assignment[self]
18
+ end
19
+
20
+ def literal?
21
+ true
22
+ end
23
+
24
+ def inspect
25
+ "#<Atom #{name}>"
26
+ end
27
+
28
+ def to_s
29
+ name.to_s
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ module Propose::Tree
2
+ # Represents two propositional formula joined by a logical connective.
3
+ class BinaryOperation < Node
4
+ attr_reader :operator, :left, :right
5
+
6
+ def initialize(operator, left, right)
7
+ @operator = operator
8
+ @left = left
9
+ @right = right
10
+ end
11
+
12
+ def ==(other)
13
+ super || @operator == other.operator &&
14
+ @left == other.left &&
15
+ @right == other.right
16
+ end
17
+
18
+ def children
19
+ [left, right]
20
+ end
21
+
22
+ def inspect
23
+ "#<#{self.class.name.split('::').last} #{left.inspect} #{right.inspect}>"
24
+ end
25
+
26
+ def to_s
27
+ output = []
28
+ output << (left.literal? ? left.to_s : "(#{left})")
29
+ output << " #{operator} "
30
+ output << (right.literal? ? right.to_s : "(#{right})")
31
+ output.join
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ module Propose::Tree
2
+ # A conjunction of two expressions, indicating both are true.
3
+ class Conjunction < BinaryOperation
4
+ def initialize(left, right)
5
+ super('∧', left, right)
6
+ end
7
+
8
+ def evaluate(assignment)
9
+ left.evaluate(assignment) && right.evaluate(assignment)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Propose::Tree
2
+ # A disjunction of two expressions, indicating at least one of them is true.
3
+ class Disjunction < BinaryOperation
4
+ def initialize(left, right)
5
+ super('∨', left, right)
6
+ end
7
+
8
+ def evaluate(assignment)
9
+ left.evaluate(assignment) || right.evaluate(assignment)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ require 'singleton'
2
+
3
+ module Propose::Tree
4
+ # Represents a contradiction, or falseness.
5
+ class Falsum < Node
6
+ include Singleton
7
+
8
+ def evaluate(_assignment)
9
+ false
10
+ end
11
+
12
+ def literal?
13
+ true
14
+ end
15
+
16
+ def inspect
17
+ '#<Falsum>'
18
+ end
19
+
20
+ def to_s
21
+ '⊥'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module Propose::Tree
2
+ # Expresses the concept that the right expression is a logical consequence of
3
+ # the left expression.
4
+ class Implication < BinaryOperation
5
+ def initialize(left, right)
6
+ super('→', left, right)
7
+ end
8
+
9
+ def evaluate(assignment)
10
+ !left.evaluate(assignment) || right.evaluate(assignment)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Propose::Tree
2
+ # A negated expression.
3
+ class Negation < UnaryOperation
4
+ def initialize(sentence)
5
+ super('¬', sentence)
6
+ end
7
+
8
+ def evaluate(assignment)
9
+ !@formula.evaluate(assignment)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ module Propose::Tree
2
+ # Generic definition of a parse tree node for propositional logic.
3
+ class Node
4
+ # Nodes which can be considered the children of this node.
5
+ def children
6
+ []
7
+ end
8
+
9
+ # Whether this node can be considered an unambiguous single lexical unit.
10
+ #
11
+ # This is used to omit unnecessary parentheses when rendering.
12
+ def literal?
13
+ false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module Propose::Tree
2
+ # Statement indicating that a set of premises (propositional formulae) can be
3
+ # used to prove to a conclusion (another propositional formula).
4
+ #
5
+ # Sequents which the premises do not prove the conclusion are called invalid.
6
+ class Sequent < Node
7
+ attr_reader :premises, :conclusion
8
+
9
+ def initialize(premises, conclusion)
10
+ @premises = premises
11
+ @conclusion = conclusion
12
+ end
13
+
14
+ def ==(other)
15
+ super || @premises == other.premises && @conclusion == other.conclusion
16
+ end
17
+
18
+ def children
19
+ premises + [conclusion]
20
+ end
21
+
22
+ def inspect
23
+ "#<Sequent [#{premises.map(&:inspect).join(', ')}] ⊢ #{conclusion.inspect}"
24
+ end
25
+
26
+ def to_s
27
+ "#{premises.map(&:to_s).join(', ')} ⊢ #{conclusion}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Propose::Tree
2
+ # Represents an operation on a single propositional formula.
3
+ class UnaryOperation < Node
4
+ attr_reader :operator, :formula
5
+
6
+ def initialize(operator, formula)
7
+ @operator = operator
8
+ @formula = formula
9
+ end
10
+
11
+ def ==(other)
12
+ super || @operator == other.operator && @formula == other.formula
13
+ end
14
+
15
+ def children
16
+ [formula]
17
+ end
18
+
19
+ def literal?
20
+ @formula.literal?
21
+ end
22
+
23
+ def inspect
24
+ "#<#{self.class.name.split('::').last} #{formula.inspect}>"
25
+ end
26
+
27
+ def to_s
28
+ output = [operator.to_s]
29
+ output << (formula.literal? ? formula.to_s : "(#{formula})")
30
+ output.join
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ require 'singleton'
2
+
3
+ module Propose::Tree
4
+ # Represents absolute tautological truth.
5
+ class Verum < Node
6
+ include Singleton
7
+
8
+ def evaluate(_assignment)
9
+ true
10
+ end
11
+
12
+ def literal?
13
+ true
14
+ end
15
+
16
+ def inspect
17
+ '#<Verum>'
18
+ end
19
+
20
+ def to_s
21
+ '⊤'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ module Propose
2
+ # Generates a truth table for a given propositional logic formula.
3
+ class TruthTable
4
+ attr_reader :formula, :evaluations
5
+
6
+ def initialize(formula)
7
+ @formula = formula
8
+ @evaluations = generate(formula)
9
+ end
10
+
11
+ def formula_atoms
12
+ @formula_atoms ||= AtomFinder.new(formula).atoms
13
+ end
14
+
15
+ private
16
+
17
+ def generate(formula)
18
+ assignments = generate_assignments(formula_atoms.to_a)
19
+
20
+ assignments.map do |assignment|
21
+ result = formula.evaluate(assignment)
22
+ [assignment, result]
23
+ end
24
+ end
25
+
26
+ def generate_assignments(atoms)
27
+ return [{}] unless atoms && atoms.count > 0
28
+ atom = atoms[0]
29
+ subset = atoms[1..-1]
30
+
31
+ sub_assignments = generate_assignments(subset)
32
+
33
+ sub_assignments.flat_map do |sub_assign|
34
+ [{ atom => true }.merge(sub_assign), { atom => false }.merge(sub_assign)]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ # Specifies the gem version.
2
+ module Propose
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,39 @@
1
+ module Propose
2
+ # Provides basic functionality for traversing a parse tree.
3
+ #
4
+ # Classes which include this module can define methods such as
5
+ # `visit_conjunction` to have the visitor visit all conjunctions in the parse
6
+ # tree.
7
+ module Visitor
8
+ def visit(node)
9
+ # Keep track of whether this block was consumed by the visitor. This
10
+ # allows us to visit all nodes by default, but can override the behavior
11
+ # by specifying `yield false` in a visit method, indicating that no
12
+ # further visiting should occur for the current node's children.
13
+ block_called = false
14
+
15
+ block = ->(descend = :children) do
16
+ block_called = true
17
+ visit_children(node) if descend == :children
18
+ end
19
+
20
+ method = "visit_#{node_name(node)}"
21
+
22
+ send(method, node, &block) if respond_to?(method, true)
23
+
24
+ visit_children(node) unless block_called
25
+ end
26
+
27
+ private
28
+
29
+ def visit_children(parent)
30
+ parent.children.each { |node| visit(node) }
31
+ end
32
+
33
+ def node_name(node)
34
+ klass = node.class
35
+ @node_names ||= {}
36
+ @node_names[klass] ||= klass.name.gsub(/.*::(.*?)$/, '\\1').downcase
37
+ end
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: propose
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Shane da Silva
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: terminal-table
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: treetop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-its
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.25.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.25.0
97
+ description: Create, manipulate, and verify propositional logic sentences
98
+ email: shane@dasilva.io
99
+ executables:
100
+ - propose
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - bin/propose
105
+ - lib/propose.rb
106
+ - lib/propose/atom_finder.rb
107
+ - lib/propose/parser.rb
108
+ - lib/propose/repl.rb
109
+ - lib/propose/tree/atom.rb
110
+ - lib/propose/tree/binary_operation.rb
111
+ - lib/propose/tree/conjunction.rb
112
+ - lib/propose/tree/disjunction.rb
113
+ - lib/propose/tree/falsum.rb
114
+ - lib/propose/tree/implication.rb
115
+ - lib/propose/tree/negation.rb
116
+ - lib/propose/tree/node.rb
117
+ - lib/propose/tree/sequent.rb
118
+ - lib/propose/tree/unary_operation.rb
119
+ - lib/propose/tree/verum.rb
120
+ - lib/propose/truth_table.rb
121
+ - lib/propose/version.rb
122
+ - lib/propose/visitor.rb
123
+ homepage: https://github.com/sds/propose
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: '2.0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.2.2
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Propositional logic sentence playground
147
+ test_files: []