├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── src ├── utils.rs ├── main.rs ├── lexer.rs ├── parser.rs └── runtime.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "plogic" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plogic" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::{Expr, Rule}; 2 | 3 | pub fn usage(){ 4 | println!("Usage:"); 5 | println!(" ----------------------------------------"); 6 | println!(" | And oprator | '&' | 'and' |"); 7 | println!(" | Or oprator | '|' | 'or' |"); 8 | println!(" | Not oprator | '~' | 'not' |"); 9 | println!(" | Cond oprator | '->' | 'implies' |"); 10 | println!(" | Bi-Cond oprator | '<->' | 'equiv' |"); 11 | println!(" ----------------------------------------"); 12 | println!(" -----------------------------------------------------------------------"); 13 | println!(" | Rule | ':=' | identifier-name := lhs-pattern = rhs-pattern |"); 14 | println!(" | binding | 'rule' | example: commutative := p & q = q & p |"); 15 | println!(" -----------------------------------------------------------------------"); 16 | println!(" | Rule | '=>' | inline pattern: A & B => p & q = q & p |"); 17 | println!(" | Derivation | | bound pattern : A & B => commutative |"); 18 | println!(" -----------------------------------------------------------------------"); 19 | println!(" - help: usage info"); 20 | println!(" - ans: previous answer"); 21 | println!(" - toggle: toggle between (T/F) and (1/0) in truth tables"); 22 | println!(" - quit: exit repl"); 23 | } 24 | 25 | pub fn rule_to_string(rule: &Rule, interned: &Vec) -> String { 26 | match rule { 27 | Rule::Equivalence(lhs, rhs) => format!("{} = {}", expr_to_string(lhs, interned), expr_to_string(rhs, interned)), 28 | Rule::RuleId(n) => format!("{}", interned[*n]) 29 | } 30 | } 31 | 32 | pub fn expr_to_string(expr: &Expr, interned: &Vec) -> String { 33 | match expr { 34 | Expr::Pattern(e, rule) => 35 | format!("{} => {}", expr_to_string(e, interned), rule_to_string(rule, interned)), 36 | Expr::Binding(id, rule) => 37 | format!("{} := {}", expr_to_string(id, interned), rule_to_string(rule, interned)), 38 | Expr::Binary(l, op, r) => format!("{} {} {}", expr_to_string(l, interned), op, expr_to_string(r, interned)), 39 | Expr::Not(e) => format!("~{}", expr_to_string(e, interned)), 40 | Expr::Group(e) => format!("({})", expr_to_string(e, interned)), 41 | Expr::Primary(n) => format!("{}", interned[*n]), 42 | } 43 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{io::{Write, self}, collections::HashMap}; 2 | 3 | use crate::{parser::{Expr, Rule}, lexer::Token}; 4 | 5 | mod lexer; 6 | mod parser; 7 | mod utils; 8 | mod runtime; 9 | 10 | fn main() { 11 | let mut input = String::new(); 12 | let mut tokens: Vec = Vec::new(); 13 | let mut prev_input: String = String::new(); 14 | let mut rule_bindings: HashMap = HashMap::new(); 15 | let mut is_num_symbol: bool = true; 16 | 17 | println!("Welcome to the REPL of Plogic."); 18 | utils::usage(); 19 | loop { 20 | input.clear(); 21 | tokens.clear(); 22 | 23 | print!("> "); 24 | io::stdout().flush().expect("Failed to flush stdout"); 25 | io::stdin().read_line(&mut input).expect("Failed to read line from stdin"); 26 | 27 | let mut input = input.trim().to_string(); 28 | match input.as_str() { 29 | "help" => { 30 | utils::usage(); 31 | continue; 32 | }, 33 | "toggle" => { 34 | is_num_symbol = !is_num_symbol; 35 | if is_num_symbol { 36 | println!("Changed truthtable symbols from 'T'/'F' to '1'/'0'"); 37 | continue; 38 | } else { 39 | println!("Changed truthtable symbols from '1'/'0' to 'T'/'F'"); 40 | continue; 41 | } 42 | }, 43 | "\n" | "" => continue, 44 | "quit" => break, 45 | _ => {}, 46 | } 47 | 48 | lexer::tokenize(&mut tokens, &mut input, &mut prev_input); 49 | 50 | let mut interned: Vec = Vec::new(); 51 | let expr = parser::parse(&mut tokens, &mut interned); 52 | 53 | match expr { 54 | Ok(parser::Expr::Pattern(e, rule)) => { 55 | prev_input = match runtime::match_patterns(&*e, *rule, &interned, &rule_bindings) { 56 | Ok(s) => { 57 | println!("{}", s); 58 | s 59 | }, 60 | Err(msg) => { 61 | println!("{}", msg); 62 | "".to_string() 63 | } 64 | } 65 | }, 66 | Ok(parser::Expr::Binding(id, rule)) => { 67 | if let Expr::Primary(n) = *id { 68 | rule_bindings.insert(interned[n].clone(), *rule); 69 | } else { 70 | println!("Unreachable"); 71 | } 72 | } 73 | Ok(e) => { 74 | let mut table = runtime::Table::new(&interned, &is_num_symbol); 75 | prev_input = utils::expr_to_string(&e, &interned); 76 | table.generate_truthtable(e); 77 | table.print(); 78 | }, 79 | Err(msg) => println!("{}", msg), 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::{iter::Peekable, str::Chars}; 2 | 3 | 4 | #[derive(Debug, PartialEq, Eq, Hash)] 5 | pub enum Token { 6 | Identifier(String), // [a-z/A-Z] 7 | Not, // ~ 8 | And, // & 9 | Or, // | 10 | Arrow, // -> 11 | TwinArrow, // <-> 12 | Rule, // => 13 | Binding, // := 14 | OpenParen, // ( 15 | CloseParen, // ) 16 | Equal, // = 17 | } 18 | 19 | fn add_token(list: &mut Vec, input: &mut Peekable, token: Token) { 20 | list.push(token); 21 | input.next(); 22 | } 23 | 24 | fn add_arrow(list: &mut Vec, input: &mut Peekable){ 25 | input.next(); 26 | match input.peek() { 27 | Some('>') => { 28 | input.next(); 29 | list.push(Token::Arrow); 30 | }, 31 | Some(other) => { 32 | println!("Unexpected character: {}", other); 33 | input.next(); 34 | }, 35 | None => { 36 | println!("Unexpected character: None"); 37 | input.next(); 38 | } 39 | } 40 | } 41 | 42 | fn add_twin_arrow(list: &mut Vec, input: &mut Peekable){ 43 | 44 | for c in "<->".chars(){ 45 | if c != *input.peek().unwrap() { 46 | println!("Unexpected character: {}", c); 47 | input.next(); 48 | return; 49 | } 50 | input.next(); 51 | } 52 | list.push(Token::TwinArrow); 53 | } 54 | 55 | fn identifier(list: &mut Vec, input: &mut Peekable, prev_input: &mut String){ 56 | let mut lexeme = String::new(); 57 | while let Some(c @ 'a'..='z') | Some(c @ 'A'..='Z') = input.peek() { 58 | lexeme.push(*c); 59 | input.next(); 60 | } 61 | 62 | match lexeme.as_str() { 63 | "ans" => { 64 | if prev_input.len() > 0 { 65 | tokenize(list, prev_input, &mut "".to_string()); 66 | return; 67 | } 68 | }, 69 | "and" => list.push(Token::And), 70 | "or" => list.push(Token::Or), 71 | "not" => list.push(Token::Not), 72 | "implies" => list.push(Token::Arrow), 73 | "equiv" => list.push(Token::TwinArrow), 74 | "rule" => list.push(Token::Rule), 75 | _ => list.push(Token::Identifier(lexeme)), 76 | } 77 | } 78 | 79 | pub fn tokenize(list: &mut Vec, input: &mut String, prev_input: &mut String) { 80 | let mut input = input.chars().peekable(); 81 | loop { 82 | match input.peek() { 83 | Some('&') => add_token(list, &mut input, Token::And), 84 | Some('|') => add_token(list, &mut input, Token::Or), 85 | Some('~') => add_token(list, &mut input, Token::Not), 86 | Some('(') => add_token(list, &mut input, Token::OpenParen), 87 | Some(')') => add_token(list, &mut input, Token::CloseParen), 88 | Some('=') => { 89 | input.next(); 90 | if let Some('>') = input.peek() { 91 | list.push(Token::Rule); 92 | input.next(); 93 | } else { 94 | list.push(Token::Equal); 95 | } 96 | }, 97 | Some(':') => { 98 | input.next(); 99 | match input.next() { 100 | Some('=') => list.push(Token::Binding), 101 | other => println!("Unexpected character: expected '=', but found {:?}", other) 102 | } 103 | }, 104 | Some('<') => add_twin_arrow(list, &mut input), 105 | Some('-') => add_arrow(list, &mut input), 106 | Some('a'..='z') | Some('A'..='Z') => identifier(list, &mut input, prev_input), 107 | 108 | Some(' ') | Some('\t') | Some('\n') => { 109 | input.next(); 110 | }, 111 | 112 | Some(other) => { 113 | println!("Unexpected character: {}", other); 114 | input.next(); 115 | }, 116 | None => break, 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, iter::Peekable, slice::Iter}; 2 | use crate::lexer::Token; 3 | 4 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)] 5 | pub enum BinOperator { 6 | And, // & 7 | Or, // | 8 | Arrow, // -> 9 | TwinArrow, // <-> 10 | } 11 | 12 | impl Display for BinOperator { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | match self { 15 | BinOperator::And => write!(f, "&"), 16 | BinOperator::Or => write!(f, "|"), 17 | BinOperator::Arrow => write!(f, "->"), 18 | BinOperator::TwinArrow => write!(f, "<->"), 19 | } 20 | } 21 | } 22 | 23 | #[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] 24 | pub enum Rule { 25 | Equivalence(Expr, Expr), 26 | RuleId(usize) 27 | } 28 | 29 | #[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] 30 | pub enum Expr { 31 | Pattern(Box, Box), // Binary => Equivalence // r & s => p & q = q & p 32 | Binding(Box, Box), // switch := p & q = q & p // x & y => switch 33 | Binary(Box, BinOperator, Box), 34 | Not(Box), 35 | Group(Box), 36 | Primary(usize), 37 | } 38 | 39 | pub fn parse(list: &mut Vec, interned: &mut Vec) -> Result { 40 | let mut tokens = list.iter().peekable(); 41 | let res = pattern_match(&mut tokens, interned)?; 42 | if let Some(t) = tokens.peek() { 43 | Err(format!("Unexpected token, expected end of input: {:?}", t)) 44 | } else { 45 | Ok(res) 46 | } 47 | } 48 | 49 | fn pattern_match(tokens: &mut Peekable>, interned: &mut Vec) -> Result { 50 | 51 | let mut left = logic_twin_arrow(tokens, interned); 52 | 53 | if let Some(Token::Binding) = tokens.peek() { 54 | 55 | if let Ok(Expr::Primary(_)) = left { 56 | tokens.next(); 57 | let eq_lhs = logic_twin_arrow(tokens, interned)?; 58 | if let Some(Token::Equal) = tokens.peek() { 59 | tokens.next(); 60 | let eq_rhs = logic_twin_arrow(tokens, interned)?; 61 | return Ok(Expr::Binding(Box::new(left?), Box::new(Rule::Equivalence(eq_lhs, eq_rhs)))); 62 | } else { 63 | return Err("Expected '=' in pattern expression".to_string()); 64 | } 65 | } else { 66 | return Err(format!("Can only bind rule to identifier, found {:?}", left)); 67 | } 68 | } 69 | 70 | while let Some(Token::Rule) = tokens.peek() { 71 | tokens.next(); 72 | let eq_lhs = logic_twin_arrow(tokens, interned)?; 73 | match tokens.peek() { 74 | Some(Token::Equal) => { 75 | tokens.next(); 76 | let eq_rhs = logic_twin_arrow(tokens, interned)?; 77 | left = Ok(Expr::Pattern(Box::new(left?), Box::new(Rule::Equivalence(eq_lhs, eq_rhs)))); 78 | }, 79 | Some(other) => return Err(format!("Expected '=' in pattern expression or rule identifier, found {:?}", other)), 80 | None => { 81 | if let Expr::Primary(n) = eq_lhs { 82 | return Ok(Expr::Pattern(Box::new(left?), Box::new(Rule::RuleId(n)))); 83 | } else { 84 | return Err(format!("Expected rule identifier name, but got {:?}", eq_lhs)); 85 | } 86 | } 87 | } 88 | } 89 | left 90 | } 91 | 92 | fn logic_twin_arrow(tokens: &mut Peekable>, interned: &mut Vec) -> Result { 93 | let mut left = logic_arrow(tokens, interned); 94 | 95 | while let Some(Token::TwinArrow) = tokens.peek() { 96 | tokens.next(); 97 | let right = logic_arrow(tokens, interned)?; 98 | left = Ok(Expr::Binary(Box::new(left?), BinOperator::TwinArrow, Box::new(right))); 99 | } 100 | left 101 | } 102 | 103 | fn logic_arrow(tokens: &mut Peekable>, interned: &mut Vec) -> Result { 104 | let mut left = logic_or(tokens, interned); 105 | 106 | while let Some(Token::Arrow) = tokens.peek() { 107 | tokens.next(); 108 | let right = logic_or(tokens, interned)?; 109 | left = Ok(Expr::Binary(Box::new(left?), BinOperator::Arrow, Box::new(right))); 110 | } 111 | left 112 | } 113 | 114 | fn logic_or(tokens: &mut Peekable>, interned: &mut Vec) -> Result { 115 | let mut left = logic_and(tokens, interned); 116 | 117 | while let Some(Token::Or) = tokens.peek() { 118 | tokens.next(); 119 | let right = logic_and(tokens, interned)?; 120 | left = Ok(Expr::Binary(Box::new(left?), BinOperator::Or, Box::new(right))); 121 | } 122 | left 123 | } 124 | 125 | fn logic_and(tokens: &mut Peekable>, interned: &mut Vec) -> Result { 126 | let mut left = logic_not(tokens, interned); 127 | 128 | while let Some(Token::And) = tokens.peek() { 129 | tokens.next(); 130 | let right = logic_not(tokens, interned)?; 131 | left = Ok(Expr::Binary(Box::new(left?), BinOperator::And, Box::new(right))); 132 | } 133 | left 134 | } 135 | 136 | fn logic_not(tokens: &mut Peekable>, interned: &mut Vec) -> Result { 137 | 138 | if let Some(Token::Not) = tokens.peek() { 139 | tokens.next(); 140 | return Ok(Expr::Not(Box::new(logic_not(tokens, interned)?))); 141 | } 142 | primary(tokens, interned) 143 | } 144 | 145 | fn primary(tokens: &mut Peekable>, interned: &mut Vec) -> Result { 146 | match tokens.next() { 147 | Some(Token::Identifier(s)) => { 148 | let index = interned.iter().position(|i| i == s); 149 | match index { 150 | Some(n) => Ok(Expr::Primary(n)), 151 | None => { 152 | interned.push(s.to_string()); 153 | Ok(Expr::Primary(interned.len()-1)) 154 | } 155 | } 156 | }, 157 | Some(Token::OpenParen) => { 158 | let expr = logic_twin_arrow(tokens, interned)?; 159 | if let Some(Token::CloseParen) = tokens.next() { 160 | Ok(Expr::Group(Box::new(expr))) 161 | } else { 162 | Err("Missing closing parenthesis".to_string()) 163 | } 164 | }, 165 | Some(other) => { 166 | Err(format!("Unexpected token: {:?}", other)) 167 | }, 168 | None => Err("Unexpected token: None".to_string()), 169 | } 170 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plogic 2 | Propositional logic evaluator and pattern transformer written in Rust. Plogic evaluates logic expressions in a REPL (Read, Execute, Print, Loop) environment and generates every truth table necessary to derive the final truth table result. Another use-case of Plogic is to define and apply pattern rules to transform a expression into another expression. This is a common operation in proof systems. Plogic is a very basic version of a proof system. 3 | 4 | This project is build purely out of recreational and educational purposes. 5 | 6 | ## Getting started 7 | To build the binary executable, `cargo` is required alongside the [Rust](https://www.rust-lang.org/tools/install) installation. 8 | ``` 9 | $ git clone https://github.com/Janko-dev/plogic.git 10 | $ cd plogic 11 | $ cargo build --release 12 | ``` 13 | Go to `target/release/` to find the `plogic.exe` (on windows). Run the executable to enter the REPL environment. 14 | 15 | ``` 16 | $ cargo run --release 17 | Welcome to the REPL of Plogic. 18 | Usage: 19 | ---------------------------------------- 20 | | And oprator | '&' | 'and' | 21 | | Or oprator | '|' | 'or' | 22 | | Not oprator | '~' | 'not' | 23 | | Cond oprator | '->' | 'implies' | 24 | | Bi-Cond oprator | '<->' | 'equiv' | 25 | ---------------------------------------- 26 | ----------------------------------------------------------------------- 27 | | Rule | ':=' | identifier-name := lhs-pattern = rhs-pattern | 28 | | binding | 'rule' | example: commutative := p & q = q & p | 29 | ----------------------------------------------------------------------- 30 | | Rule | '=>' | inline pattern: A & B => p & q = q & p | 31 | | Derivation | | bound pattern : A & B => commutative | 32 | ----------------------------------------------------------------------- 33 | - help: usage info 34 | - ans: previous answer 35 | - toggle: toggle between (T/F) and (1/0) in truth tables 36 | - quit: exit repl 37 | > p & q 38 | --------------------- 39 | [ q ] [ p ] [ p & q ] 40 | |---| |---| |-------| 41 | | 0 | | 0 | | 0 | 42 | | 1 | | 0 | | 0 | 43 | | 0 | | 1 | | 0 | 44 | | 1 | | 1 | | 1 | 45 | --------------------- 46 | > 47 | ``` 48 | 49 | ## Grammar 50 | The following grammar describes the parsing strategy to build the abstract syntax tree. It is noteworthy to mention that the usual mathematical symbols for the operators are not used. Instead, the operators come from the bitwise operators found in various programming languages and optional keywords which may be used for the sake of convenience. The table below shows what each operator means. 51 | | Operator | Meaning | 52 | | -------- | ------- | 53 | | `&` or `and` | The and-operator which says that both left-hand side and right-hand side should evaluate to true, for the result to equal true as well. | 54 | | `\|` or `or` | The or-operator which says that either or both left-hand side and right-hand side should evaluate to true, for the result to equal true as well.| 55 | | `~` or `not` | The negation-operator flips true to false and false to true. | 56 | | `->` or `implies` | The conditional implication-operator only evaluates to false when the left-hand side is true and the right-hand side is false, otherwise the result is true. | 57 | | `<->` or `equiv` | The biconditional implication-operator only evaluates to true when both left and right hand sides are equal to eachother.| 58 | 59 | ### Rule-based pattern matching 60 | Furthermore, to pattern match expressions and transform them into other expressions, the `=>` or `rule` keyword is used after a valid propositional expression. Thereafter must follow a valid left hand-side expression, then a `=`, and then a valid right hand-side expression. Example: 61 | 62 | `A & (B | C) => p & (q | r) = (p & q) | (p & r)` 63 | 64 | This expression describes the following. The expression `A & (B | C)` will be matched against the left hand-side after `=>`, i.e. `p & (q | r)`. Since both have the same pattern, it is a valid match. Thereafter, the right hand-side will be substituted according to the matched symbols in the left hand-side. Therefore, producing the following result: 65 | 66 | `(A & B) | (A & C)` 67 | 68 | In the case that the left hand-side did not match the expression, an attempt will be made to match the right hand side instead. The same procedure for pattern matching applies, only in reverse. Now, the right hand-side is matched, then substituted in the left hand-side of the given rule. An example will make this more clear. Consider the 'almost' same expression: 69 | 70 | `(A & B) | (A & C) => p & (q | r) = (p & q) | (p & r)` 71 | 72 | Notice that the left hand-side of the rule portion `p & (q | r)` does not match the sub-expression `(A & B) | (A & C)`. Therefore, the right hand-side will be attempted to match, which produces the result: 73 | 74 | `A & (B | C)` 75 | 76 | Aside from using inline rule patterns as demonstrated above, which can get convoluted to type if the given rule pattern is used multiple times, there is also the possibility to bind a rule to an identifier name. Consider the following: 77 | 78 | `distributive := p & (q | r) = (p & q) | (p & r)` 79 | 80 | the identifier name `distributive` is now bound to the pattern given after `:=`. The pattern identifier can now be used instead. i.e., 81 | 82 | `A & (B | C) => distributive` 83 | 84 | which produces the same result as before, i.e., `(A & B) | (A & C)`. 85 | 86 | ``` ebnf 87 | Expression = Rule_binding | Rule_apply ; 88 | Rule_binding = Atom ":=" Bi_conditional "=" Bi_conditional ; 89 | Rule_apply = Bi_conditional ("=>" Bi_conditional "=" Bi_conditional)? ; 90 | Bi_conditional = Conditional (("<->") Conditional)* ; 91 | Conditional = Or (("->") Or)* ; 92 | Or = And (("|") And)* ; 93 | And = Negation (("&") Negation)* ; 94 | Negation = "~" Negation | Primary ; 95 | Primary = Atom | "(" Bi_conditional ")" ; 96 | Atom = ["a"-"z" | "A"-"Z"]* ; 97 | ``` 98 | 99 | ## More Examples 100 | 101 | ``` 102 | > A & ~A 103 | ----------------------- 104 | [ A ] [ ~A ] [ A & ~A ] 105 | |---| |----| |--------| 106 | | 0 | | 1 | | 0 | 107 | | 1 | | 0 | | 0 | 108 | ----------------------- 109 | ``` 110 | ``` 111 | > (p implies q) and (q implies p) 112 | ----------------------------------------------------------------------------------- 113 | [ q ] [ p ] [ (q -> p) ] [ (p -> q) ] [ q -> p ] [ p -> q ] [ (p -> q) & (q -> p) ] 114 | |---| |---| |----------| |----------| |--------| |--------| |---------------------| 115 | | 0 | | 0 | | 1 | | 1 | | 1 | | 1 | | 1 | 116 | | 1 | | 0 | | 0 | | 1 | | 0 | | 1 | | 0 | 117 | | 0 | | 1 | | 1 | | 0 | | 1 | | 0 | | 0 | 118 | | 1 | | 1 | | 1 | | 1 | | 1 | | 1 | | 1 | 119 | ----------------------------------------------------------------------------------- 120 | ``` 121 | ``` 122 | > A & (B | C) => p & (q | r) = (p & q) | (p & r) 123 | (A & B) | (A & C) 124 | > ans 125 | ----------------------------------------------------------- 126 | [ C ] [ B ] [ A ] [ A & C ] [ A & B ] [ (A & B) | (A & C) ] 127 | |---| |---| |---| |-------| |-------| |-------------------| 128 | | 0 | | 0 | | 0 | | 0 | | 0 | | 0 | 129 | | 0 | | 0 | | 1 | | 0 | | 0 | | 0 | 130 | | 0 | | 1 | | 0 | | 0 | | 0 | | 0 | 131 | | 0 | | 1 | | 1 | | 0 | | 1 | | 1 | 132 | | 1 | | 0 | | 0 | | 0 | | 0 | | 0 | 133 | | 1 | | 0 | | 1 | | 1 | | 0 | | 1 | 134 | | 1 | | 1 | | 0 | | 0 | | 0 | | 0 | 135 | | 1 | | 1 | | 1 | | 1 | | 1 | | 1 | 136 | ----------------------------------------------------------- 137 | > 138 | ``` 139 | ``` 140 | > DeMorgan := ~(p & q) = ~p | ~q 141 | > ~((a -> b) & ~(b -> c)) => DeMorgan 142 | ~(a -> b) | ~~(b -> c) 143 | > toggle 144 | Changed truthtable symbols from '1'/'0' to 'T'/'F' 145 | > ans 146 | ------------------------------------------------------------------------------------------------------------- 147 | [ c ] [ b ] [ a ] [ ~(b -> c) ] [ ~(a -> b) ] [ ~~(b -> c) ] [ b -> c ] [ a -> b ] [ ~(a -> b) | ~~(b -> c) ] 148 | |---| |---| |---| |-----------| |-----------| |------------| |--------| |--------| |------------------------| 149 | | F | | F | | F | | F | | F | | T | | T | | T | | T | 150 | | F | | F | | T | | F | | T | | T | | T | | F | | T | 151 | | F | | T | | F | | T | | F | | F | | F | | T | | F | 152 | | F | | T | | T | | T | | F | | F | | F | | T | | F | 153 | | T | | F | | F | | F | | F | | T | | T | | T | | T | 154 | | T | | F | | T | | F | | T | | T | | T | | F | | T | 155 | | T | | T | | F | | F | | F | | T | | T | | T | | T | 156 | | T | | T | | T | | F | | F | | T | | T | | T | | T | 157 | ------------------------------------------------------------------------------------------------------------- 158 | ``` 159 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap}; 2 | use crate::{parser::{Expr, Rule, BinOperator}, utils}; 3 | 4 | #[derive(Debug)] 5 | pub struct Table { 6 | map: HashMap>, 7 | atoms: Vec, 8 | pub interned: Vec, 9 | rows: usize, 10 | is_num_symbols: bool, 11 | } 12 | 13 | impl Table { 14 | pub fn new(interned: &Vec, is_num_symbols: &bool) -> Self { 15 | Self { 16 | map: HashMap::new(), 17 | atoms: Vec::new(), 18 | interned: interned.clone(), 19 | rows: 0, 20 | is_num_symbols: is_num_symbols.clone(), 21 | } 22 | } 23 | 24 | fn fill_symbols(&mut self, expr: &Expr){ 25 | match expr { 26 | Expr::Binary(left, _, right) => { 27 | self.fill_symbols(&*left); 28 | self.fill_symbols(&*right); 29 | }, 30 | Expr::Not(e) => { 31 | self.fill_symbols(&*e); 32 | }, 33 | Expr::Group(e) => { 34 | self.fill_symbols(&*e); 35 | }, 36 | e @ Expr::Primary(_) => { 37 | if let None = self.map.get(&e) { 38 | self.map.insert(e.clone(), Vec::new()); 39 | self.atoms.push(e.clone()); 40 | } 41 | }, 42 | other => println!("Unreachable: found {}", utils::expr_to_string(other, &self.interned)) 43 | } 44 | } 45 | 46 | fn zipped_operation usize>(left: Vec, right: Vec, f: F) -> Vec { 47 | left 48 | .iter() 49 | .zip(right.iter()) 50 | .map(f) 51 | .collect::>() 52 | } 53 | 54 | fn eval(&mut self, expr: &Expr) -> Vec { 55 | match expr { 56 | e @ Expr::Binary(l, op, r) => { 57 | let left = self.eval(&*l); 58 | let right = self.eval(&*r); 59 | 60 | let res = match op { 61 | BinOperator::And => Table::zipped_operation(left, right, |(a, b)| a & b), 62 | BinOperator::Or => Table::zipped_operation(left, right, |(a, b)| a | b), 63 | BinOperator::Arrow => Table::zipped_operation(left, right, |(a, b)| if a.eq(&1) && b.eq(&0) { 0 } else { 1 }), 64 | BinOperator::TwinArrow => Table::zipped_operation(left, right, |(a, b)| a.eq(b) as usize), 65 | }; 66 | 67 | if let None = self.map.get(&e) { 68 | self.map.insert(e.clone(), res.clone()); 69 | } 70 | res 71 | }, 72 | e @ Expr::Not(inner) => { 73 | let res = self.eval(&*inner) 74 | .iter() 75 | .map(|x| if x.eq(&0) { 1 } else { 0 }) 76 | .collect::>(); 77 | if let None = self.map.get(&e) { 78 | self.map.insert(e.clone(), res.clone()); 79 | } 80 | res 81 | }, 82 | e @ Expr::Group(inner) => { 83 | let res = self.eval(&*inner); 84 | if let None = self.map.get(&e) { 85 | self.map.insert(e.clone(), res.clone()); 86 | } 87 | res 88 | }, 89 | e @ Expr::Primary(_) => { 90 | self.map.get(e).unwrap().to_vec() 91 | }, 92 | _ => panic!("Unreachable eval"), 93 | } 94 | } 95 | 96 | pub fn generate_truthtable(&mut self, expr: Expr){ 97 | self.fill_symbols(&expr); 98 | 99 | let count = self.map.len(); 100 | self.rows = (2 as usize).pow(count as u32); 101 | 102 | self.atoms.sort_by(|a, b| a.cmp(&b)); 103 | for (j, e) in self.atoms.iter().enumerate() { 104 | let mut entry: Vec = Vec::new(); 105 | for i in 0..self.rows{ 106 | let k = (2 as usize).pow((j+1) as u32); 107 | if i % k + 1 > k / 2 { 108 | entry.push(1); 109 | } else { 110 | entry.push(0); 111 | } 112 | } 113 | self.map.insert(e.clone(), entry); 114 | } 115 | 116 | self.eval(&expr); 117 | self.map.retain(|k, _| { 118 | if let Expr::Group(_) = k { 119 | false 120 | } else { 121 | true 122 | } 123 | }); 124 | 125 | } 126 | 127 | fn sort(&self) -> Vec<(&Expr, &Vec)> { 128 | let mut res = self.map 129 | .iter() 130 | .collect::>(); 131 | res.sort_by(|x, y| y.0.cmp(&x.0)); 132 | res 133 | } 134 | 135 | pub fn print(&self) { 136 | let mut total = 0; 137 | let mut headers: Vec = Vec::new(); 138 | let list = self.sort(); 139 | for (expr, _) in &list { 140 | let head = format!("[ {} ] ", utils::expr_to_string(expr, &self.interned)); 141 | total += head.len(); 142 | headers.push(head); 143 | } 144 | 145 | println!("{:-<1$}", "", total-1); 146 | for head in &headers { 147 | print!("{}", head); 148 | } 149 | println!(); 150 | 151 | for head in &headers { 152 | print!("|{:-<1$}| ", "", head.len()-3); 153 | } 154 | println!(); 155 | 156 | for i in 0..self.rows { 157 | for (j, head) in headers.iter().enumerate() { 158 | let len = head.len(); 159 | let sym = if self.is_num_symbols { 160 | char::from_digit(list[j].1[i] as u32, 10) 161 | } else if list[j].1[i] == 1 { 162 | Some('T') 163 | } else { 164 | Some('F') 165 | }; 166 | 167 | if len % 2 == 0 { 168 | print!("|{: <1$}", "", len/2-2); 169 | print!("{}", sym.unwrap_or_default()); 170 | print!("{: <1$}| ", "", len/2-2); 171 | } else { 172 | print!("|{: <1$}", "", len/2-2); 173 | print!("{}", sym.unwrap_or_default()); 174 | print!("{: <1$}| ", "", len/2-1); 175 | } 176 | } 177 | println!(); 178 | } 179 | println!("{:-<1$}", "", total-1); 180 | } 181 | } 182 | 183 | pub fn match_patterns(expr: &Expr, rule: Rule, interned: &Vec, rule_bindings: &HashMap) -> Result { 184 | let result = match rule { 185 | Rule::Equivalence(lhs, rhs) => { 186 | let mut patterns: HashMap = HashMap::new(); 187 | match traverse_and_match(expr, &lhs, &mut patterns) { 188 | Ok(()) => subsitute_in(&rhs, &mut patterns), 189 | Err(_) => { 190 | let mut patterns: HashMap = HashMap::new(); 191 | match traverse_and_match(expr, &rhs, &mut patterns) { 192 | Ok(()) => subsitute_in(&lhs, &mut patterns), 193 | Err(s) => Err(s), 194 | } 195 | } 196 | } 197 | }, 198 | Rule::RuleId(n) => { 199 | let rule_maybe = rule_bindings.get(&interned[n]); 200 | if let Some(Rule::Equivalence(lhs, rhs)) = rule_maybe { 201 | let mut patterns: HashMap = HashMap::new(); 202 | match traverse_and_match(expr, &lhs, &mut patterns) { 203 | Ok(()) => subsitute_in(&rhs, &mut patterns), 204 | Err(_) => { 205 | let mut patterns: HashMap = HashMap::new(); 206 | match traverse_and_match(expr, &rhs, &mut patterns) { 207 | Ok(()) => subsitute_in(&lhs, &mut patterns), 208 | Err(s) => Err(s), 209 | } 210 | } 211 | } 212 | } else { 213 | return Err("Undefined rule used".to_string()); 214 | } 215 | } 216 | }; 217 | 218 | match result { 219 | Ok(e) => Ok(utils::expr_to_string(&e, interned)), 220 | Err(s) => Err(s), 221 | } 222 | } 223 | 224 | fn traverse_and_match(expr: &Expr, lhs: &Expr, patterns: &mut HashMap) -> Result<(), String>{ 225 | match lhs { 226 | Expr::Binary(pat_left, pat_op, pat_right) => { 227 | if let Expr::Binary(e_left, e_op, e_right) = expr { 228 | if pat_op == e_op { 229 | if let Ok(()) = traverse_and_match(&*e_left, &*pat_left, patterns) { 230 | traverse_and_match(&*e_right, &*pat_right, patterns) 231 | } else { 232 | Err(format!("Expression does not match binary pattern")) 233 | } 234 | } else { 235 | Err("Expression does not match pattern".to_string()) 236 | } 237 | } else { 238 | Err(format!("Expression does not match pattern: {:?}", expr)) 239 | } 240 | }, 241 | Expr::Not(pat_e) => { 242 | if let Expr::Not(e) = expr { 243 | traverse_and_match(&*e, &*pat_e, patterns) 244 | } else { 245 | Err(format!("Expression does not match pattern: {:?}", expr)) 246 | } 247 | }, 248 | Expr::Group(pat_e) => { 249 | if let Expr::Group(e) = expr { 250 | traverse_and_match(&*e, &*pat_e, patterns) 251 | } else { 252 | Err(format!("Expression does not match pattern: {:?}", expr)) 253 | } 254 | }, 255 | Expr::Primary(_) => { 256 | if let None = patterns.get(lhs) { 257 | patterns.insert(lhs.clone(), expr.clone()); 258 | } 259 | Ok(()) 260 | }, 261 | _ => { 262 | Err(format!("Expression does not match pattern: {:?}", expr)) 263 | } 264 | } 265 | } 266 | 267 | fn subsitute_in(expr: &Expr, patterns: &mut HashMap) -> Result { 268 | match expr { 269 | Expr::Binary(l, op, r) => { 270 | let left = subsitute_in(&*l, patterns)?; 271 | let right = subsitute_in(&*r, patterns)?; 272 | Ok(Expr::Binary(Box::new(left), *op, Box::new(right))) 273 | }, 274 | Expr::Not(e) => { 275 | let res = subsitute_in(&*e, patterns)?; 276 | Ok(Expr::Not(Box::new(res))) 277 | }, 278 | Expr::Group(e) => { 279 | let res = subsitute_in(&*e, patterns)?; 280 | Ok(Expr::Group(Box::new(res))) 281 | }, 282 | Expr::Primary(_) => { 283 | if let Some(v) = patterns.get(&expr) { 284 | Ok(v.clone()) 285 | } else { 286 | Err("Pattern could not be found in expression or rule".to_string()) 287 | } 288 | }, 289 | _ => Err("Unreachable".to_string()) 290 | } 291 | } --------------------------------------------------------------------------------