propose 0.0.1

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 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: []