├── .gitignore ├── Cargo.toml ├── Cargo.lock ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eq" 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 | chumsky = {version = "1.0.0-alpha.4", features = ["label"]} 10 | ariadne = "0.3.0" 11 | fxhash = "0.2.1" 12 | -------------------------------------------------------------------------------- /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 = "ahash" 7 | version = "0.8.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "ariadne" 18 | version = "0.3.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" 21 | dependencies = [ 22 | "unicode-width", 23 | "yansi", 24 | ] 25 | 26 | [[package]] 27 | name = "byteorder" 28 | version = "1.4.3" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 31 | 32 | [[package]] 33 | name = "cc" 34 | version = "1.0.81" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" 37 | dependencies = [ 38 | "libc", 39 | ] 40 | 41 | [[package]] 42 | name = "cfg-if" 43 | version = "1.0.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 46 | 47 | [[package]] 48 | name = "chumsky" 49 | version = "1.0.0-alpha.4" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "cc3172a80699de358070dd99f80ea8badc6cdf8ac2417cb5a96e6d81bf5fe06d" 52 | dependencies = [ 53 | "hashbrown", 54 | "stacker", 55 | ] 56 | 57 | [[package]] 58 | name = "eq" 59 | version = "0.1.0" 60 | dependencies = [ 61 | "ariadne", 62 | "chumsky", 63 | "fxhash", 64 | ] 65 | 66 | [[package]] 67 | name = "fxhash" 68 | version = "0.2.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 71 | dependencies = [ 72 | "byteorder", 73 | ] 74 | 75 | [[package]] 76 | name = "hashbrown" 77 | version = "0.13.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" 80 | dependencies = [ 81 | "ahash", 82 | ] 83 | 84 | [[package]] 85 | name = "libc" 86 | version = "0.2.147" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 89 | 90 | [[package]] 91 | name = "once_cell" 92 | version = "1.18.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 95 | 96 | [[package]] 97 | name = "psm" 98 | version = "0.1.21" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" 101 | dependencies = [ 102 | "cc", 103 | ] 104 | 105 | [[package]] 106 | name = "stacker" 107 | version = "0.1.15" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" 110 | dependencies = [ 111 | "cc", 112 | "cfg-if", 113 | "libc", 114 | "psm", 115 | "winapi", 116 | ] 117 | 118 | [[package]] 119 | name = "unicode-width" 120 | version = "0.1.10" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 123 | 124 | [[package]] 125 | name = "version_check" 126 | version = "0.9.4" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 129 | 130 | [[package]] 131 | name = "winapi" 132 | version = "0.3.9" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 135 | dependencies = [ 136 | "winapi-i686-pc-windows-gnu", 137 | "winapi-x86_64-pc-windows-gnu", 138 | ] 139 | 140 | [[package]] 141 | name = "winapi-i686-pc-windows-gnu" 142 | version = "0.4.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 145 | 146 | [[package]] 147 | name = "winapi-x86_64-pc-windows-gnu" 148 | version = "0.4.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 151 | 152 | [[package]] 153 | name = "yansi" 154 | version = "0.5.1" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Writing an Equation Solver 2 | 3 | Writing an Equation Solver is a process that is made of: parsing, 4 | equating/unifying and rewriting. 5 | 6 | - Equating: it's the step that two terms are equalized and 7 | tries to make equity between them 2, just like an equation 8 | 9 | - Rewriting: to write equations in real life we need to rewrite 10 | the equation until the variables are discovered, right? For an example: 11 | 12 | ``` 13 | 10 = x + 7 14 | 10 - 7 = x + 7 - 7 15 | 3 = x 16 | x = 3 17 | ``` 18 | 19 | So, there was 3 rewrites before the final result. The same process is 20 | applied in a computer, we need to apply some mathematical properties and 21 | rewrite it, and normalise it. 22 | 23 | - Parsing: we need to get the string of the equation and translate into 24 | objects like the following enum: 25 | 26 | ```rs 27 | /// A term in the calculator. This is the lowest level of the calculator. 28 | #[derive(Default, Debug, Clone, Hash, PartialEq, Eq)] 29 | pub enum Expr { 30 | #[default] 31 | Error, 32 | Number(usize), 33 | Group(Term), 34 | Variable(Variable), 35 | BinOp(BinOp), 36 | } 37 | ``` 38 | 39 | ## Sections 40 | 41 | - [Mathematical Properties](#mathematical-properties) 42 | - [Parser](#parser) 43 | - [Parser combinators](#parser-combinators) 44 | - [Lexer](#lexer) 45 | - [Tree](#tree) 46 | - [Parser Implementation](#parser-implementation) 47 | - [Reducing terms](#reducing-terms) 48 | - [Symmetry](#symmetry) 49 | - [Distributivity](#distributivity) 50 | - [Associativity](#associativity) 51 | - [Rewriting](#rewriting) 52 | - [Unifiying/Equating](#unifiyingequating) 53 | - [Final](#final) 54 | 55 | ## Mathematical Properties 56 | 57 | To write a equation solver, we need to clarify what are the mathematical properties 58 | we are working with. The main ones that we are going to use are the 59 | `Associativity`, `Identity`, `Commutativity`, `Symmetry`, `Distribitivity`. We can 60 | define the following rules: 61 | 62 | - `Associativity`: (a + b) + c = a + (b + c) 63 | - `Identity`: a = a 64 | - `Commutativity`: a + b = b + a 65 | - `Symmetry`: a + b = c; a = c - b 66 | 67 | Ok, right, these mathematical properties are hard to read if we don't have any 68 | code to parallel with it, so let's start writing the parser. 69 | 70 | ## Parser 71 | 72 | We need first to tokenize the inputs into a bunch of tokens, which are a kind 73 | of letter with spaces, and some characters ignored: 74 | 75 | ```rs 76 | /// The token type used by the lexer. 77 | #[derive(Debug, PartialEq, Clone)] 78 | pub enum Token<'src> { 79 | Number(usize), 80 | Decimal(usize, usize), 81 | Identifier(&'src str), 82 | Ctrl(char), 83 | } 84 | ``` 85 | 86 | The token tooks the lifetime `src`, because it's referring directly the part 87 | of the source code. 88 | 89 | ### Parser Combinators 90 | 91 | We are using technique called [parser combinator](https://en.wikipedia.org/wiki/Parser_combinator). And we are using a library [chumsky](https://github.com/zesterer/chumsky) to write parser combinators. 92 | 93 | ### Lexer 94 | 95 | The code of the lexer is simply, removing the junk and returning the letters and 96 | numbers as `Token`. Read the snippet 97 | 98 | ```rs 99 | /// Parse a string into a set of tokens. 100 | /// 101 | /// This function is a wrapper around the lexer and parser, and is the main entry point 102 | /// for the calculator. 103 | fn lexer<'src>() -> impl Parser<'src, &'src str, Vec<(Token<'src>, Span)>, LexerError<'src>> { 104 | let num = text::int(10) 105 | .then(just('.').then(text::int(10)).or_not()) 106 | .try_map(|(int, decimal), span| { 107 | // int: &str, decimal: Option<(char, &str)> 108 | // define the types of the variables, because the chumsky 109 | // parser tries to infer it 110 | let int: &str = int; 111 | let decimal: Option<(char, &str)> = decimal; 112 | 113 | let Ok(int) = int.parse::() else { 114 | return Err(Rich::custom(span, "invalid integer")); 115 | }; 116 | let Some((_, decimal)) = decimal else { 117 | return Ok(Token::Number(int)); 118 | }; 119 | 120 | let Ok(decimal) = decimal.parse::() else { 121 | return Err(Rich::custom(span, "invalid decimal")); 122 | }; 123 | 124 | Ok(Token::Decimal(int, decimal)) 125 | }) 126 | .labelled("number"); 127 | 128 | // Maps the common mathematical operations like addition, multiplication 129 | // subtraction, division, and go on.. 130 | let op = one_of("+*-/!^|&<>=") 131 | .repeated() 132 | .at_least(1) 133 | .map_slice(Token::Identifier) 134 | .labelled("operator"); 135 | 136 | // Maps simple incognito variables into identifiers, these are the variables 137 | // we are trying to discover :) 138 | let ident = text::ident().map(Token::Identifier).labelled("icognito"); 139 | 140 | // The groups that change the precedence. 141 | let ctrl = one_of("()[]{}").map(Token::Ctrl).labelled("ctrl"); 142 | 143 | // Now this finishes the lexer 144 | num.or(op) 145 | .or(ctrl) 146 | .or(ident) 147 | .map_with_span(|token, span| (token, span)) 148 | .padded() 149 | .repeated() 150 | .collect() 151 | } 152 | ``` 153 | 154 | > The full source code is wrote in [main.rs](https://github.com/aripiprazole/eq/blob/main/src/main.rs#L274). 155 | 156 | ### Tree 157 | 158 | We need to define an abstract syntax tree, to translate the mathematical terms 159 | into it: 160 | 161 | ```rs 162 | /// A term in the calculator. This is the lowest level of the calculator. 163 | #[derive(Default, Debug, Clone, Hash, PartialEq, Eq)] 164 | pub enum Expr { 165 | #[default] 166 | Error, 167 | Number(usize), 168 | Group(Term), 169 | Variable(Variable), 170 | BinOp(BinOp), 171 | } 172 | ``` 173 | 174 | Define the variables and incognitos: 175 | 176 | ```rs 177 | /// A variable. This is a variable that can be assigned to. 178 | #[derive(Default, Debug, Clone)] 179 | pub struct Variable { 180 | pub name: String, 181 | 182 | /// We use Rc of RefCell here so we can clone and use internal 183 | /// mutability to change it's value 184 | pub data: Rc>>, 185 | } 186 | 187 | impl Variable { 188 | /// Gets the data of the variable. 189 | pub fn data(&self) -> Option { 190 | *self.data.borrow().deref() 191 | } 192 | } 193 | 194 | impl PartialEq for Variable { 195 | fn eq(&self, other: &Self) -> bool { 196 | self.name == other.name 197 | } 198 | } 199 | 200 | impl Eq for Variable {} 201 | 202 | impl Hash for Variable { 203 | fn hash(&self, state: &mut H) { 204 | self.name.hash(state); 205 | } 206 | } 207 | ``` 208 | 209 | And finally, the binary operations: 210 | 211 | ```rs 212 | /// A binary operation. This is a binary operation that takes two arguments. 213 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 214 | pub struct BinOp { 215 | pub op: Op, 216 | pub lhs: Term, 217 | pub rhs: Term, 218 | } 219 | ``` 220 | 221 | The full source code can be found in the [main.rs](https://github.com/aripiprazole/eq/blob/main/src/main.rs#L117). 222 | 223 | ### Parser implementation 224 | 225 | Now we need to write an expression parser, which will take translate tokens into 226 | mathematical terms. 227 | 228 | ```rs 229 | recursive(|expr| { 230 | // Defines the parser for the value. It is the base of the 231 | // expression parser. 232 | let value = select! { 233 | Token::Number(number) => Expr::Number(number), 234 | Token::Identifier(identifier) => Expr::Variable(Variable { name: identifier.into(), data: Rc::default() }), 235 | } 236 | .map_with_span(|kind, span| (kind, span)) 237 | .map_with_state(|term, _, state: &mut TermArena| state.intern(term)) 238 | .labelled("value"); 239 | }); 240 | ``` 241 | 242 | Note thate we used state in the parser, and this is the "arena". The arena will 243 | store the real expressions, and will return an ID to the expression. We will have 244 | to define the `Term`: 245 | 246 | ```rs 247 | #[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq)] 248 | pub struct Term(usize); 249 | ``` 250 | 251 | And define the "arena": 252 | 253 | ```rs 254 | #[derive(Default)] 255 | pub struct TermArena { 256 | pub id_to_slot: HashMap>>, 257 | pub slot_to_id: HashMap, 258 | } 259 | 260 | impl TermArena { 261 | /// Creates a new term in the arena 262 | pub fn intern(&mut self, term: Spanned) -> Term { 263 | let id = Term(fxhash::hash(&term.0)); 264 | self.id_to_slot.insert(id, Rc::new(term.clone())); 265 | self.slot_to_id.insert(term.0, id); 266 | id 267 | } 268 | 269 | /// Checks if the term exists in the arena. 270 | pub fn exists(&self, term: &Expr) -> Option { 271 | self.slot_to_id.get(term).cloned() 272 | } 273 | 274 | /// Gets the term from the arena. 275 | pub fn get(&self, id: Term) -> Rc> { 276 | self.id_to_slot 277 | .get(&id) 278 | .cloned() 279 | 280 | // It will return a default expression so the program doesn't crash 281 | // when the id is missing, it's called sentinel values 282 | .unwrap_or_else(|| Rc::new((Expr::Error, Range::::default().into()))) 283 | } 284 | } 285 | ``` 286 | 287 | > Here it's used the library [fxhash](https://crates.io/crates/fxhash) to fast 288 | > hash the values 289 | 290 | The arena stuff makes interning of expressions, which is returning the same ID 291 | to same-hash expressions, if the expressions have the same hash, they will return 292 | the same hash, so they will return the same ID. This is useful to improve 293 | performance, and later doing the `Commutativvity` rule, since `1 + 2` is the 294 | same as `2 + 1`, and the hash of it is the same. 295 | 296 | So now, we need to write the group terms, like [], {} or even (), so we are 297 | writing the following code in the `recursive` function. 298 | 299 | > This is why we need to use recursive, the code will refer to expression 300 | 301 | ```rs 302 | let brackets = expr 303 | .clone() 304 | .delimited_by(just(Token::Ctrl('[')), just(Token::Ctrl(']'))); 305 | 306 | let parenthesis = expr 307 | .clone() 308 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))); 309 | 310 | let braces = expr 311 | .clone() 312 | .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))); 313 | ``` 314 | 315 | And we define the "primary" expression, which will catch all values, and 316 | group expressions. 317 | 318 | ```rs 319 | // Defines the parser for the primary expression. It is the 320 | // base of the expression parser. 321 | let primary = value 322 | .or(braces) 323 | .or(parenthesis) 324 | .or(brackets) 325 | .labelled("primary"); 326 | ``` 327 | 328 | Now we need to define mathematical operations like addition, subtraction, 329 | multiplication and division. 330 | 331 | They are splitted in two declarations, so we can have precedence. 332 | 333 | ```rs 334 | let factor = primary 335 | .foldl_with_state( 336 | just(Token::Identifier("+")) 337 | .to(Op::Add) 338 | .or(just(Token::Identifier("-")).to(Op::Sub)) 339 | .then(expr.clone()) 340 | .repeated(), 341 | |lhs: Term, (op, rhs), state: &mut TermArena| { 342 | let (_, fst) = &*state.get(lhs); 343 | let (_, snd) = &*state.get(rhs); 344 | 345 | let span = SimpleSpan::new(fst.start, snd.end); 346 | let expr = Expr::BinOp(BinOp { op, lhs, rhs }); 347 | 348 | // RULE: Commutativity 349 | // 350 | // The commutativity rule states that the order of the operands 351 | // does not matter. This means that `1 + 2` is the same as `2 + 1`. 352 | // 353 | // This rule is implemented by checking if the expression already 354 | // exists in the state. If it does, then we return the existing 355 | // expression, otherwise we create a new one. 356 | match state.exists(&Expr::BinOp(BinOp { op, lhs: rhs, rhs: lhs })) { 357 | Some(term) if op == Op::Add => term, 358 | None => state.intern((expr, span)), 359 | _ => state.intern((expr, span)), 360 | } 361 | }, 362 | ) 363 | .labelled("factor"); 364 | 365 | let term = factor 366 | .foldl_with_state( 367 | just(Token::Identifier("*")) 368 | .to(Op::Mul) 369 | .or(just(Token::Identifier("/")).to(Op::Div)) 370 | .then(expr.clone()) 371 | .repeated(), 372 | |lhs: Term, (op, rhs), state: &mut TermArena| { 373 | let (_, fst) = &*state.get(lhs); 374 | let (_, snd) = &*state.get(rhs); 375 | 376 | let span = SimpleSpan::new(fst.start, snd.end); 377 | let expr = Expr::BinOp(BinOp { op, lhs, rhs }); 378 | 379 | // RULE: Commutativity 380 | // 381 | // The commutativity rule states that the order of the operands 382 | // does not matter. This means that `1 + 2` is the same as `2 + 1`. 383 | // 384 | // This rule is implemented by checking if the expression already 385 | // exists in the state. If it does, then we return the existing 386 | // expression, otherwise we create a new one. 387 | match state.exists(&Expr::BinOp(BinOp { op, lhs: rhs, rhs: lhs })) { 388 | Some(term) if op == Op::Mul => term, 389 | None => state.intern((expr, span)), 390 | _ => state.intern((expr, span)), 391 | } 392 | }, 393 | ) 394 | .labelled("term"); 395 | ``` 396 | 397 | > The commutativity part starts here, the parser will check if the expression 398 | > already exists in the context, and will return the same id, if it exists 399 | > 400 | > Of course, this isn't an automatic process, so we need to reverse the operators 401 | > and try to find it as reversed, and if it does exist, we take it from the 402 | > context. 403 | 404 | Now, we have finished the `expression` parser, and you can have a look in the 405 | full source code [here](https://github.com/aripiprazole/eq/blob/main/src/main.rs#L336). 406 | We must write the equation parser, and it's quite simple to do it. 407 | 408 | ```rs 409 | // Defines the parser for the equation. It is the base of the 410 | // parser of equations and inequations. 411 | expr_parser 412 | .clone() 413 | .then( 414 | // Parses an operation 415 | just(Token::Identifier("=")) 416 | .to(EqKind::Eq) 417 | .or(just(Token::Identifier("!=")).to(EqKind::Neq)), 418 | ) 419 | .then(expr_parser.clone()) 420 | .map(|((lhs, op), rhs)| Equation { kind: op, lhs, rhs }) 421 | .map_with_span(|equation, span| (equation, span)) 422 | .labelled("equation") 423 | ``` 424 | 425 | Its the combination of `expr (== | !=) expr`. 426 | 427 | ## Reducing terms 428 | 429 | We need to reduce the terms to it's normal form, to be compared with another 430 | terms, like: `1 + 2` needs to be reduced to `3` to be compared with `3` properly. 431 | 432 | It's the first rewrite rule we need to write! So let's create a function like 433 | `rewrite` in 434 | 435 | ```rs 436 | impl Term { 437 | /// Rewrites a term to its normal form. 438 | pub fn rewrite(self, state: &mut TermArena) -> Term { 439 | self.normalize(state) 440 | } 441 | } 442 | ``` 443 | 444 | And start writing the [`normalize function`](https://wiki.haskell.org/Weak_head_normal_form). 445 | which is the reduced/or evaluated form. 446 | 447 | The link is appointing to the Haskell documentation. 448 | 449 | ```rs 450 | impl Term { 451 | /// Reduces a term to its normal form. 452 | pub fn normalize(self, state: &mut TermArena) -> Term { 453 | let (kind, span) = &*state.get(self); 454 | let new_kind = match kind { 455 | Expr::Group(group) => Expr::Group(group.rewrite(state)), 456 | Expr::BinOp(bin_op) => { 457 | let lhs = bin_op.lhs.rewrite(state); 458 | let rhs = bin_op.rhs.rewrite(state); 459 | 460 | // If the term is a number, we try to reduce it evaluating 461 | // the operation. 462 | match &state.get(lhs).0 { 463 | // If the term is a number 464 | // 465 | // We assume that the term is a number and we try to 466 | // reduce it. 467 | Expr::Number(lhs) => match &state.get(rhs).0 { 468 | Expr::Number(rhs) => { 469 | let number = match bin_op.op { 470 | Op::Add => lhs + rhs, 471 | Op::Sub => lhs - rhs, 472 | Op::Mul => lhs * rhs, 473 | Op::Div => lhs / rhs, 474 | }; 475 | 476 | Expr::Number(number) 477 | } 478 | Expr::Group(group) => return group.rewrite(state), 479 | _ => return self, 480 | }, 481 | Expr::Group(group) => return group.rewrite(state), 482 | _ => return self, 483 | } 484 | } 485 | // If the term is a variable, we try to reduce it. 486 | Expr::Variable(hole) => match hole.data() { 487 | Some(value) => return value.rewrite(state), 488 | None => kind.clone(), 489 | }, 490 | _ => kind.clone(), 491 | }; 492 | state.intern((new_kind, *span)) 493 | } 494 | } 495 | ``` 496 | 497 | It basically evaluates the term to it's normal form 498 | 499 | ## Symmetry 500 | 501 | We need to write the symmetry rule, which will be used in a further step called 502 | `unifying/equating`. 503 | 504 | ```rs 505 | impl BinOp { 506 | /// RULE: Symmetry 507 | /// 508 | /// Examples: 509 | /// `a + b = c` 510 | /// `a = c - b` 511 | pub fn symmetry(&self, value: Term, state: &mut TermArena) -> (Term, Term) { 512 | let reverse_op = match self.op { 513 | Op::Add => Op::Sub, 514 | Op::Sub => Op::Add, 515 | Op::Mul => Op::Div, 516 | Op::Div => Op::Mul, 517 | }; 518 | 519 | let span: Span = (state.get(self.lhs).1.start..state.get(self.rhs).1.end).into(); 520 | 521 | let lhs = state.intern(( 522 | Expr::BinOp(BinOp { 523 | op: reverse_op, 524 | lhs: value, 525 | rhs: self.rhs, 526 | }), 527 | span, 528 | )); 529 | 530 | (lhs.rewrite(state), self.lhs.rewrite(state)) 531 | } 532 | } 533 | ``` 534 | 535 | The symmetry is basically, the following steps with the given [BinOp]: 536 | 537 | ``` 538 | x + 7 = 10 539 | x = 10 - 7 540 | ``` 541 | 542 | It's a fundamental step for solving equations! 543 | 544 | ## Distributivity 545 | 546 | The distributivity property is when we apply an operation to it's left side, like 547 | 548 | ``` 549 | (x + 2) * 2 550 | ``` 551 | 552 | Which will be rewrote into: 553 | 554 | ``` 555 | 2 * x + 4 556 | ``` 557 | 558 | This is fundamental to write some equations, the source code for this step is: 559 | 560 | ```rs 561 | impl Term { 562 | /// Distributes a term over another term. 563 | pub fn distribute(self, op: Op, another: Term, state: &mut TermArena) -> Term { 564 | let (kind, span) = &*state.get(self); 565 | let new_kind = match kind { 566 | Expr::Group(_) => return self, 567 | Expr::BinOp(bin_op) => { 568 | let lhs = bin_op.lhs.apply_distributive_property(state); 569 | let rhs = bin_op.rhs.apply_distributive_property(state); 570 | 571 | Expr::BinOp(BinOp { 572 | op: bin_op.op, 573 | lhs, 574 | rhs, 575 | }) 576 | } 577 | _ => Expr::BinOp(BinOp { 578 | op, 579 | lhs: self, 580 | rhs: another, 581 | }), 582 | }; 583 | 584 | state.intern((new_kind, *span)) 585 | } 586 | 587 | /// Applies the distributive property to a term. 588 | pub fn apply_distributive_property(self, state: &mut TermArena) -> Term { 589 | let Expr::BinOp(bin_op) = &state.get(self).0 else { 590 | return self; 591 | }; 592 | 593 | match (&state.get(bin_op.lhs).0, &state.get(bin_op.rhs).0) { 594 | (Expr::Group(group), _) => group.distribute(bin_op.op, bin_op.rhs, state), 595 | (_, Expr::Group(group)) => group.distribute(bin_op.op, bin_op.lhs, state), 596 | (_, _) => self, 597 | } 598 | } 599 | } 600 | ``` 601 | 602 | ## Associativity 603 | 604 | The associativity rules represents an equation that's like: (1 + 2) + 3 = 1 + (2 + 3). It does represents an reorder based in the precedence, to normalize the operation. 605 | 606 | We can write a code to associate 3 terms based on it's operator precedence: 607 | 608 | ```rs 609 | /// Applies the associativity rule to a binary operation. 610 | fn associate(lhs: Term, fop: Op, mhs: Term, sop: Op, rhs: Term, state: &mut TermArena) -> BinOp { 611 | // RULE: Associativity 612 | // 613 | // If the term is a binary operation, we try to reduce it 614 | // to its weak head normal form using the precedence of 615 | // the operators. 616 | // 617 | // This step is called precedence climbing. 618 | // 619 | // Evaluate the operation if the precedence of the 620 | // operator is higher than the precedence of the 621 | // operator of the right hand side. 622 | let lhs = lhs.apply_associativity(state).rewrite(state); 623 | let mhs = mhs.apply_associativity(state).rewrite(state); 624 | let rhs = rhs.apply_associativity(state).rewrite(state); 625 | 626 | // If the precedence of the operator of the left hand side 627 | // is higher than the precedence of the operator of the 628 | // right hand side, we change the order. 629 | if op_power(sop) >= op_power(fop) { 630 | BinOp { 631 | op: fop, 632 | lhs, 633 | rhs: state.intern(( 634 | Expr::BinOp(BinOp { 635 | op: sop, 636 | lhs: mhs, 637 | rhs, 638 | }), 639 | (0..0).into(), 640 | )), 641 | } 642 | } else { 643 | BinOp { 644 | op: fop, 645 | lhs: state.intern(( 646 | Expr::BinOp(BinOp { 647 | op: sop, 648 | lhs, 649 | rhs: mhs, 650 | }), 651 | (0..0).into(), 652 | )), 653 | rhs, 654 | } 655 | } 656 | } 657 | ``` 658 | 659 | And write a wrapper in [Term] to call it with [BinOp] operations: 660 | 661 | ```rs 662 | impl Term { 663 | /// Applies the associativity rule to a term. 664 | pub fn apply_associativity(self, state: &mut TermArena) -> Term { 665 | let (kind, span) = &*state.get(self); 666 | 667 | // If the term is not a binary operation, we return it. 668 | let Expr::BinOp(mut bin_op) = kind.clone() else { 669 | return self; 670 | }; 671 | 672 | // Apply associativy to the leftmost side of the expression. 673 | // 674 | // This is done by recursively applying the associativity 675 | if let Expr::BinOp(lhs_bin) = &state.get(bin_op.lhs).0 { 676 | bin_op = associate( 677 | lhs_bin.lhs, 678 | lhs_bin.op, 679 | lhs_bin.rhs, 680 | bin_op.op, 681 | bin_op.rhs, 682 | state, 683 | ); 684 | } 685 | 686 | // Apply associativy to the rightmost side of the expression. 687 | // 688 | // This is done by recursively applying the associativity 689 | if let Expr::BinOp(rhs_bin) = &state.get(bin_op.rhs).0 { 690 | bin_op = associate( 691 | bin_op.lhs, 692 | bin_op.op, 693 | rhs_bin.lhs, 694 | rhs_bin.op, 695 | rhs_bin.rhs, 696 | state, 697 | ); 698 | } 699 | 700 | // Reintern the term. 701 | state.intern((Expr::BinOp(bin_op), *span)) 702 | } 703 | } 704 | ``` 705 | 706 | ## Rewriting 707 | 708 | We need to change the `rewrite` function to compute all rewrite rules we have 709 | made: 710 | 711 | ```rs 712 | /// Rewrites a term to its normal form. 713 | pub fn rewrite(self, state: &mut TermArena) -> Term { 714 | self.apply_distributive_property(state) 715 | .apply_associativity(state) 716 | .normalize(state) 717 | } 718 | ``` 719 | 720 | ## Unifiying/Equating 721 | 722 | Now we need to write the logical part, which will compare the operations. We need 723 | to first start a pattern matching: 724 | 725 | ```rs 726 | impl Term { 727 | /// Unifies two terms. It's the main point of the equation. 728 | /// 729 | /// The logic relies here. 730 | pub fn unify(self, another: Term, state: &mut TermArena) -> Result<(), TypeError> { 731 | match (&state.get(self).0, &state.get(another).0) { 732 | // Errors are sentinel values, so they are threated like holes 733 | // and they are ignored, anything unifies with them. 734 | (Expr::Error, _) => {} 735 | (_, Expr::Error) => {} 736 | 737 | (a, b) => { 738 | return Err(TypeError::NotUnifiable(a.clone(), b.clone())); 739 | } 740 | // ... 741 | } 742 | 743 | Ok(()) 744 | } 745 | } 746 | ``` 747 | 748 | > The terms successfully unified will fallback ino the `Ok(())` expressions, 749 | > and if it's not unified, it will return an error just like in the `TypeError::NotUnifiable` 750 | > part 751 | 752 | And wrap the group terms, unifying its values: 753 | 754 | ```rs 755 | // Reduce groups to the normal form 756 | (_, Expr::Group(another)) => { 757 | let term = self.rewrite(state); 758 | let another = another.rewrite(state); 759 | 760 | term.unify(another, state)?; 761 | } 762 | (Expr::Group(term), _) => { 763 | let term = term.rewrite(state); 764 | let another = another.rewrite(state); 765 | 766 | term.unify(another, state)?; 767 | } 768 | ``` 769 | 770 | And unify numbers, if they are the same number, it will unify properly 771 | 772 | ```rs 773 | // If they are the same, they unify. 774 | (Expr::Number(a), Expr::Number(b)) if a == b => {} 775 | ``` 776 | 777 | Now, the variable stuff, which is the same as the number part, if the 778 | name is the same, it will unify. 779 | 780 | ```rs 781 | 782 | // If they are variables, they unify if they are the same. 783 | // 784 | // This check isn't inehenterly necessary, but it's a good catcher 785 | // to avoid panics, because if the variables are the same, they will 786 | // try to borrow the same data, and it will panic with ref cells. 787 | (Expr::Variable(variable_a), Expr::Variable(variable_b)) 788 | if variable_a.name == variable_b.name => {} 789 | ``` 790 | 791 | Now we need to unify attributions, like `x = 5`, or something like this, this 792 | will basically give the variable, a meaning. 793 | 794 | ```rs 795 | // Unifies the variable with the term, if the variable is not bound. If 796 | // it's bound, it will try to unify, if it's not unifiable, it will 797 | // return an error. 798 | (_, Expr::Variable(variable)) => { 799 | match variable.data() { 800 | // If the variable is already bound, we unify the bound 801 | Some(bound) => { 802 | self.unify(bound, state)?; 803 | } 804 | // Empty hole 805 | None => { 806 | variable.data.replace(Some(self)); 807 | } 808 | } 809 | } 810 | (Expr::Variable(variable), _) => { 811 | match variable.data() { 812 | // If the variable is already bound, we unify the bound 813 | Some(bound) => { 814 | bound.unify(another, state)?; 815 | } 816 | // Empty hole 817 | None => { 818 | variable.data.replace(Some(another)); 819 | } 820 | } 821 | } 822 | 823 | ``` 824 | 825 | And now, unify the operations, like `1 + 1` = `1 + 1`, we need to reduce the 826 | terms, like: `1 + 1` is equivalent to `2`, so we will compare `2` with `2`, 827 | and it will successfully unify: 828 | 829 | ```rs 830 | // Unifies the bin ops if they are the same, and unifies the 831 | // operands. 832 | (Expr::BinOp(bin_op_a), Expr::BinOp(bin_op_b)) => { 833 | if bin_op_a.op == bin_op_b.op { 834 | let lhs_a = bin_op_a.lhs.rewrite(state); 835 | let rhs_a = bin_op_a.rhs.rewrite(state); 836 | 837 | let lhs_b = bin_op_b.lhs.rewrite(state); 838 | let rhs_b = bin_op_b.rhs.rewrite(state); 839 | 840 | lhs_a.unify(lhs_b, state)?; 841 | rhs_a.unify(rhs_b, state)?; 842 | } else { 843 | return Err(TypeError::IncompatibleOp(bin_op_a.op, bin_op_b.op)); 844 | } 845 | } 846 | ``` 847 | 848 | Now, the last part, we need to unify the symmetry, if we have an example like 849 | the comments, it will use the `symmetry` function. 850 | 851 | ```rs 852 | // If the term is a number, we try the following steps given the example: 853 | // 9 = x + 6 854 | // 855 | // 1. We get the reverse of `+`, which is `-`. 856 | // 2. We subtract `6` from both sides, to equate it, 857 | // and since the `9` is a constant, we can reduce 858 | // it to the normal form. 859 | // 3. Got the equation solved, we can unify the `x` with 860 | // the result of the subtraction. 861 | // 862 | // 3 = x and then x = 3 863 | (Expr::Number(_), Expr::BinOp(bin_op)) => { 864 | let (term, another) = bin_op.symmetry(self, state); 865 | 866 | term.unify(another, state)?; 867 | } 868 | (Expr::BinOp(bin_op), Expr::Number(_)) => { 869 | let (term, another) = bin_op.symmetry(another, state); 870 | 871 | term.unify(another, state)?; 872 | } 873 | ``` 874 | 875 | # Final 876 | 877 | Thanks for your read :) Have a nice day! 878 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::HashMap, 4 | fmt::{Debug, Display}, 5 | hash::Hash, 6 | ops::{Deref, Range}, 7 | rc::Rc, 8 | }; 9 | 10 | use ariadne::{Color, Label, Report, ReportKind, Source}; 11 | use chumsky::{ 12 | extra::{self, Err}, 13 | prelude::{Input, Rich}, 14 | primitive::{just, one_of}, 15 | recursive::recursive, 16 | select, 17 | span::SimpleSpan, 18 | text, IterParser, Parser, 19 | }; 20 | 21 | /// The token type used by the lexer. 22 | #[derive(Debug, PartialEq, Clone)] 23 | pub enum Token<'src> { 24 | Number(usize), 25 | Decimal(usize, usize), 26 | Identifier(&'src str), 27 | Ctrl(char), 28 | } 29 | 30 | impl<'src> Display for Token<'src> { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | match self { 33 | Token::Number(value) => write!(f, "{value}"), 34 | Token::Decimal(int, decimal) => write!(f, "{int}.{decimal}"), 35 | Token::Identifier(id) => write!(f, "{id}"), 36 | Token::Ctrl(ctrl) => write!(f, "{ctrl}"), 37 | } 38 | } 39 | } 40 | 41 | /// An operator. This is a binary operation that takes two arguments. 42 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 43 | pub enum Op { 44 | Add, 45 | Sub, 46 | Mul, 47 | Div, 48 | } 49 | 50 | /// A binary operation. This is a binary operation that takes two arguments. 51 | #[derive(Debug, Clone, Hash, PartialEq, Eq)] 52 | pub struct BinOp { 53 | pub op: Op, 54 | pub lhs: Term, 55 | pub rhs: Term, 56 | } 57 | 58 | impl BinOp { 59 | /// RULE: Symmetry 60 | /// 61 | /// Examples: 62 | /// `a + b = c` 63 | /// `a = c - b` 64 | pub fn symmetry(&self, value: Term, state: &mut TermArena) -> (Term, Term) { 65 | let reverse_op = match self.op { 66 | Op::Add => Op::Sub, 67 | Op::Sub => Op::Add, 68 | Op::Mul => Op::Div, 69 | Op::Div => Op::Mul, 70 | }; 71 | 72 | let span: Span = (state.get(self.lhs).1.start..state.get(self.rhs).1.end).into(); 73 | 74 | let lhs = state.intern(( 75 | Expr::BinOp(BinOp { 76 | op: reverse_op, 77 | lhs: value, 78 | rhs: self.rhs, 79 | }), 80 | span, 81 | )); 82 | 83 | (lhs.rewrite(state), self.lhs.rewrite(state)) 84 | } 85 | } 86 | 87 | /// A variable. This is a variable that can be assigned to. 88 | #[derive(Default, Debug, Clone)] 89 | pub struct Variable { 90 | pub name: String, 91 | pub data: Rc>>, 92 | } 93 | 94 | impl Variable { 95 | /// Gets the data of the variable. 96 | pub fn data(&self) -> Option { 97 | *self.data.borrow().deref() 98 | } 99 | } 100 | 101 | impl PartialEq for Variable { 102 | fn eq(&self, other: &Self) -> bool { 103 | self.name == other.name 104 | } 105 | } 106 | 107 | impl Eq for Variable {} 108 | 109 | impl Hash for Variable { 110 | fn hash(&self, state: &mut H) { 111 | self.name.hash(state); 112 | } 113 | } 114 | 115 | /// A term in the calculator. This is the lowest level of the calculator. 116 | #[derive(Default, Debug, Clone, Hash, PartialEq, Eq)] 117 | pub enum Expr { 118 | #[default] 119 | Error, 120 | Number(usize), 121 | Group(Term), 122 | Variable(Variable), 123 | BinOp(BinOp), 124 | } 125 | 126 | impl Term { 127 | /// Creates a debug wrapper for the term, which will print 128 | /// the term in a human-readable format. 129 | pub fn debug(self, arena: &TermArena) -> ExprDebug { 130 | self.debug_with_fuel(128, arena) 131 | } 132 | 133 | /// Creates a debug wrapper for the term, which will print 134 | /// the term in a human-readable format. 135 | /// 136 | /// With specified fuel. 137 | pub fn debug_with_fuel(self, fuel: usize, arena: &TermArena) -> ExprDebug { 138 | ExprDebug { 139 | arena, 140 | term: self, 141 | fuel, 142 | } 143 | } 144 | } 145 | 146 | /// A debug wrapper for a term, which will print the term in a human-readable format. 147 | #[derive(Copy, Clone)] 148 | pub struct ExprDebug<'a> { 149 | arena: &'a TermArena, 150 | term: Term, 151 | 152 | /// The amount of fuel to use when printing the term. 153 | /// 154 | /// The fuel is used to prevent infinite recursion when printing 155 | /// the term. If the fuel runs out, the term will be printed as `…`. 156 | fuel: usize, 157 | } 158 | 159 | impl Debug for ExprDebug<'_> { 160 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 161 | write!(f, "{}", show(self.term, self.fuel, self.arena)) 162 | } 163 | } 164 | 165 | /// Shows a term in a human-readable format. 166 | pub fn show(term: Term, fuel: usize, state: &TermArena) -> String { 167 | if fuel == 0 { 168 | return "…".into(); 169 | } 170 | 171 | match &state.get(term).0 { 172 | Expr::Error => "error".into(), 173 | Expr::Number(n) => format!("{n}"), 174 | Expr::Group(group) => format!("({})", show(*group, fuel - 1, state)), 175 | Expr::Variable(variable) => match variable.data() { 176 | Some(value) => format!("{}", show(value, fuel - 1, state)), 177 | None => format!("?{}", variable.name), 178 | }, 179 | Expr::BinOp(bin_op) => { 180 | let lhs = show(bin_op.lhs, fuel - 1, state); 181 | let rhs = show(bin_op.rhs, fuel - 1, state); 182 | let op_str = match bin_op.op { 183 | Op::Add => "+", 184 | Op::Sub => "-", 185 | Op::Mul => "*", 186 | Op::Div => "/", 187 | }; 188 | 189 | format!("({lhs} {op_str} {rhs})") 190 | } 191 | } 192 | } 193 | 194 | #[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq)] 195 | pub struct Term(usize); 196 | 197 | #[derive(Default)] 198 | pub struct TermArena { 199 | pub id_to_slot: HashMap>>, 200 | pub slot_to_id: HashMap, 201 | } 202 | 203 | impl TermArena { 204 | /// Creates a new term in the arena 205 | pub fn intern(&mut self, term: Spanned) -> Term { 206 | let id = Term(fxhash::hash(&term.0)); 207 | self.id_to_slot.insert(id, Rc::new(term.clone())); 208 | self.slot_to_id.insert(term.0, id); 209 | id 210 | } 211 | 212 | /// Checks if the term exists in the arena. 213 | pub fn exists(&self, term: &Expr) -> Option { 214 | self.slot_to_id.get(term).cloned() 215 | } 216 | 217 | /// Gets the term from the arena. 218 | pub fn get(&self, id: Term) -> Rc> { 219 | self.id_to_slot 220 | .get(&id) 221 | .cloned() 222 | .unwrap_or_else(|| Rc::new((Expr::Error, Range::::default().into()))) 223 | } 224 | 225 | /// Gets the term from the arena. 226 | pub fn resolutions(&self) -> Vec<(String, Term)> { 227 | self.id_to_slot 228 | .iter() 229 | .filter_map(|(_, slot)| { 230 | if let Expr::Variable(variable) = &slot.0 { 231 | variable.data().map(|value| (variable.name.clone(), value)) 232 | } else { 233 | None 234 | } 235 | }) 236 | .collect::>() 237 | } 238 | } 239 | 240 | /// The kind of equation. This is used to determine the kind of comparison to perform. 241 | /// 242 | /// It can be an equality or inequality. 243 | #[derive(Debug, Clone, Copy)] 244 | pub enum EqKind { 245 | Neq, 246 | Eq, 247 | } 248 | 249 | /// An equation. This is the top-level type of the calculator. 250 | #[derive(Debug, Clone)] 251 | pub struct Equation { 252 | pub kind: EqKind, 253 | pub lhs: Term, 254 | pub rhs: Term, 255 | } 256 | 257 | //// The span type used by the parser. 258 | type Span = SimpleSpan; 259 | 260 | /// The input type for the parser. 261 | type ParserInput<'tokens, 'src> = 262 | chumsky::input::SpannedInput, Span, &'tokens [(Token<'src>, Span)]>; 263 | 264 | /// A lexer error. 265 | type LexerError<'src> = Err>; 266 | 267 | /// A spanned value. A value that has a source code span attached to it. 268 | type Spanned = (T, Span); 269 | 270 | /// Parse a string into a set of tokens. 271 | /// 272 | /// This function is a wrapper around the lexer and parser, and is the main entry point 273 | /// for the calculator. 274 | fn lexer<'src>() -> impl Parser<'src, &'src str, Vec<(Token<'src>, Span)>, LexerError<'src>> { 275 | let num = text::int(10) 276 | .then(just('.').then(text::int(10)).or_not()) 277 | .try_map(|(int, decimal), span| { 278 | // int: &str, decimal: Option<(char, &str)> 279 | // define the types of the variables, because the chumsky 280 | // parser tries to infer it 281 | let int: &str = int; 282 | let decimal: Option<(char, &str)> = decimal; 283 | 284 | let Ok(int) = int.parse::() else { 285 | return Err(Rich::custom(span, "invalid integer")); 286 | }; 287 | let Some((_, decimal)) = decimal else { 288 | return Ok(Token::Number(int)); 289 | }; 290 | 291 | let Ok(decimal) = decimal.parse::() else { 292 | return Err(Rich::custom(span, "invalid decimal")); 293 | }; 294 | 295 | Ok(Token::Decimal(int, decimal)) 296 | }) 297 | .labelled("number"); 298 | 299 | let op = one_of("+*-/!^|&<>=") 300 | .repeated() 301 | .at_least(1) 302 | .map_slice(Token::Identifier) 303 | .labelled("operator"); 304 | 305 | let ident = text::ident().map(Token::Identifier).labelled("icognito"); 306 | 307 | let ctrl = one_of("()[]{}").map(Token::Ctrl).labelled("ctrl"); 308 | 309 | num.or(op) 310 | .or(ctrl) 311 | .or(ident) 312 | .map_with_span(|token, span| (token, span)) 313 | .padded() 314 | .repeated() 315 | .collect() 316 | } 317 | 318 | /// Defines the base parser for the simple math language. It 319 | /// does parse a set of tokens, into a equation. 320 | /// 321 | /// The parser is defined as a function, because it is recursive. 322 | /// 323 | /// [`recursive`]: https://docs.rs/chumsky/0.1.0/chumsky/recursive/index.html 324 | /// [`Parser`]: https://docs.rs/chumsky/0.1.0/chumsky/prelude/trait.Parser.html 325 | /// [`Expr`]: [`Expr`] 326 | /// [`Equation`]: [`Equation`] 327 | fn parser<'tokens, 'src: 'tokens>() -> impl Parser< 328 | // Input Types 329 | 'tokens, 330 | ParserInput<'tokens, 'src>, 331 | Spanned, 332 | extra::Full, Span>, TermArena, ()>, 333 | > { 334 | // Defines the parser for the expression. It is recursive, because 335 | // it can be nested. 336 | let expr_parser = recursive(|expr| { 337 | // Defines the parser for the value. It is the base of the 338 | // expression parser. 339 | let value = select! { 340 | Token::Number(number) => Expr::Number(number), 341 | Token::Identifier(identifier) => Expr::Variable(Variable { name: identifier.into(), data: Rc::default() }), 342 | } 343 | .map_with_span(|kind, span| (kind, span)) 344 | .map_with_state(|term, _, state: &mut TermArena| state.intern(term)) 345 | .labelled("value"); 346 | 347 | let brackets = expr 348 | .clone() 349 | .delimited_by(just(Token::Ctrl('[')), just(Token::Ctrl(']'))); 350 | 351 | let parenthesis = expr 352 | .clone() 353 | .delimited_by(just(Token::Ctrl('(')), just(Token::Ctrl(')'))); 354 | 355 | let braces = expr 356 | .clone() 357 | .delimited_by(just(Token::Ctrl('{')), just(Token::Ctrl('}'))); 358 | 359 | // Defines the parser for the primary expression. It is the 360 | // base of the expression parser. 361 | let primary = value 362 | .or(braces) 363 | .or(parenthesis) 364 | .or(brackets) 365 | .labelled("primary"); 366 | 367 | let factor = primary 368 | .foldl_with_state( 369 | just(Token::Identifier("+")) 370 | .to(Op::Add) 371 | .or(just(Token::Identifier("-")).to(Op::Sub)) 372 | .then(expr.clone()) 373 | .repeated(), 374 | |lhs: Term, (op, rhs), state: &mut TermArena| { 375 | let (_, fst) = &*state.get(lhs); 376 | let (_, snd) = &*state.get(rhs); 377 | 378 | let span = SimpleSpan::new(fst.start, snd.end); 379 | let expr = Expr::BinOp(BinOp { op, lhs, rhs }); 380 | 381 | // RULE: Commutativity 382 | // 383 | // The commutativity rule states that the order of the operands 384 | // does not matter. This means that `1 + 2` is the same as `2 + 1`. 385 | // 386 | // This rule is implemented by checking if the expression already 387 | // exists in the state. If it does, then we return the existing 388 | // expression, otherwise we create a new one. 389 | match state.exists(&Expr::BinOp(BinOp { 390 | op, 391 | lhs: rhs, 392 | rhs: lhs, 393 | })) { 394 | Some(term) if op == Op::Add => term, 395 | None => state.intern((expr, span)), 396 | _ => state.intern((expr, span)), 397 | } 398 | }, 399 | ) 400 | .labelled("factor"); 401 | 402 | let term = factor 403 | .foldl_with_state( 404 | just(Token::Identifier("*")) 405 | .to(Op::Mul) 406 | .or(just(Token::Identifier("/")).to(Op::Div)) 407 | .then(expr.clone()) 408 | .repeated(), 409 | |lhs: Term, (op, rhs), state: &mut TermArena| { 410 | let (_, fst) = &*state.get(lhs); 411 | let (_, snd) = &*state.get(rhs); 412 | 413 | let span = SimpleSpan::new(fst.start, snd.end); 414 | let expr = Expr::BinOp(BinOp { op, lhs, rhs }); 415 | 416 | // RULE: Commutativity 417 | // 418 | // The commutativity rule states that the order of the operands 419 | // does not matter. This means that `1 + 2` is the same as `2 + 1`. 420 | // 421 | // This rule is implemented by checking if the expression already 422 | // exists in the state. If it does, then we return the existing 423 | // expression, otherwise we create a new one. 424 | match state.exists(&Expr::BinOp(BinOp { 425 | op, 426 | lhs: rhs, 427 | rhs: lhs, 428 | })) { 429 | Some(term) if op == Op::Mul => term, 430 | None => state.intern((expr, span)), 431 | _ => state.intern((expr, span)), 432 | } 433 | }, 434 | ) 435 | .labelled("term"); 436 | 437 | term 438 | }); 439 | 440 | // Defines the parser for the equation. It is the base of the 441 | // parser of equations and inequations. 442 | expr_parser 443 | .clone() 444 | .then( 445 | just(Token::Identifier("=")) 446 | .to(EqKind::Eq) 447 | .or(just(Token::Identifier("!=")).to(EqKind::Neq)), 448 | ) 449 | .then(expr_parser.clone()) 450 | .map(|((lhs, op), rhs)| Equation { kind: op, lhs, rhs }) 451 | .map_with_span(|equation, span| (equation, span)) 452 | .labelled("equation") 453 | } 454 | 455 | /// Parses a string into an [`Equation`]. 456 | /// 457 | /// [`Equation`]: [`Equation`] 458 | fn parse(s: &str, state: &mut TermArena) -> Spanned { 459 | type AriadneSpan = (String, std::ops::Range); 460 | 461 | // Defines the filename of the source. And it is used to 462 | // create the report. 463 | let filename = "terminal".to_string(); 464 | 465 | let (tokens, lex_errors) = lexer().parse(s).into_output_errors(); 466 | let tokens = tokens.unwrap_or_default(); 467 | let tokens = tokens.as_slice().spanned((s.len()..s.len()).into()); 468 | let (expr, errors) = parser() 469 | .parse_with_state(tokens, state) 470 | .into_output_errors(); 471 | 472 | // If there are no errors, return the parsed expression. 473 | if !errors.is_empty() || !lex_errors.is_empty() { 474 | errors 475 | .into_iter() 476 | .map(|error| error.map_token(|c| c.to_string())) 477 | .chain( 478 | lex_errors 479 | .into_iter() 480 | .map(|error| error.map_token(|token| token.to_string())), 481 | ) 482 | .for_each(|error| { 483 | Report::::build(ReportKind::Error, filename.clone(), 0) 484 | .with_code(1) 485 | .with_message(error.to_string()) 486 | .with_label( 487 | Label::new((filename.clone(), error.span().into_range())) 488 | .with_message(error.reason().to_string()) 489 | .with_color(Color::Red), 490 | ) 491 | .with_labels(error.contexts().map(|(label, span)| { 492 | Label::new((filename.clone(), span.into_range())) 493 | .with_message(format!("while parsing this {}", label)) 494 | .with_color(Color::Yellow) 495 | })) 496 | .finish() 497 | .eprint((filename.to_string(), Source::from(s.to_string()))) 498 | .unwrap(); 499 | }); 500 | } 501 | 502 | // If the expression is not present, we return an error sentinel 503 | // value to avoid crashing. 504 | expr.unwrap_or_else(|| { 505 | let span: Span = (s.len()..s.len()).into(); 506 | let equation = Equation { 507 | kind: EqKind::Eq, 508 | lhs: state.intern((Expr::Error, span)), 509 | rhs: state.intern((Expr::Error, span)), 510 | }; 511 | (equation, span) 512 | }) 513 | } 514 | 515 | /// The type of the error that can be returned by the unification 516 | #[derive(Debug, Clone)] 517 | pub enum TypeError { 518 | /// The two terms are not unifiable. 519 | NotUnifiable(Expr, Expr), 520 | 521 | /// The two terms are not compatible. 522 | IncompatibleOp(Op, Op), 523 | } 524 | 525 | /// Gets the precedence of an operator. 526 | pub fn op_power(op: Op) -> usize { 527 | match op { 528 | Op::Add | Op::Sub => 1, 529 | Op::Mul | Op::Div => 2, 530 | } 531 | } 532 | 533 | /// Applies the associativity rule to a binary operation. 534 | fn associate(lhs: Term, fop: Op, mhs: Term, sop: Op, rhs: Term, state: &mut TermArena) -> BinOp { 535 | // RULE: Associativity 536 | // 537 | // If the term is a binary operation, we try to reduce it 538 | // to its weak head normal form using the precedence of 539 | // the operators. 540 | // 541 | // This step is called precedence climbing. 542 | // 543 | // Evaluate the operation if the precedence of the 544 | // operator is higher than the precedence of the 545 | // operator of the right hand side. 546 | let lhs = lhs.apply_associativity(state).rewrite(state); 547 | let mhs = mhs.apply_associativity(state).rewrite(state); 548 | let rhs = rhs.apply_associativity(state).rewrite(state); 549 | 550 | // If the precedence of the operator of the left hand side 551 | // is higher than the precedence of the operator of the 552 | // right hand side, we change the order. 553 | if op_power(sop) >= op_power(fop) { 554 | BinOp { 555 | op: fop, 556 | lhs, 557 | rhs: state.intern(( 558 | Expr::BinOp(BinOp { 559 | op: sop, 560 | lhs: mhs, 561 | rhs, 562 | }), 563 | (0..0).into(), 564 | )), 565 | } 566 | } else { 567 | BinOp { 568 | op: fop, 569 | lhs: state.intern(( 570 | Expr::BinOp(BinOp { 571 | op: sop, 572 | lhs, 573 | rhs: mhs, 574 | }), 575 | (0..0).into(), 576 | )), 577 | rhs, 578 | } 579 | } 580 | } 581 | 582 | impl Term { 583 | /// Distributes a term over another term. 584 | pub fn distribute(self, op: Op, another: Term, state: &mut TermArena) -> Term { 585 | let (kind, span) = &*state.get(self); 586 | let new_kind = match kind { 587 | Expr::Group(_) => return self, 588 | Expr::BinOp(bin_op) => { 589 | let lhs = bin_op.lhs.apply_distributive_property(state); 590 | let rhs = bin_op.rhs.apply_distributive_property(state); 591 | 592 | Expr::BinOp(BinOp { 593 | op: bin_op.op, 594 | lhs, 595 | rhs, 596 | }) 597 | } 598 | _ => Expr::BinOp(BinOp { 599 | op, 600 | lhs: self, 601 | rhs: another, 602 | }), 603 | }; 604 | 605 | state.intern((new_kind, *span)) 606 | } 607 | 608 | /// Applies the distributive property to a term. 609 | pub fn apply_distributive_property(self, state: &mut TermArena) -> Term { 610 | let Expr::BinOp(bin_op) = &state.get(self).0 else { 611 | return self; 612 | }; 613 | 614 | match (&state.get(bin_op.lhs).0, &state.get(bin_op.rhs).0) { 615 | (Expr::Group(group), _) => group.distribute(bin_op.op, bin_op.rhs, state), 616 | (_, Expr::Group(group)) => group.distribute(bin_op.op, bin_op.lhs, state), 617 | (_, _) => self, 618 | } 619 | } 620 | 621 | /// Applies the associativity rule to a term. 622 | pub fn apply_associativity(self, state: &mut TermArena) -> Term { 623 | let (kind, span) = &*state.get(self); 624 | 625 | // If the term is not a binary operation, we return it. 626 | let Expr::BinOp(mut bin_op) = kind.clone() else { 627 | return self; 628 | }; 629 | 630 | // Apply associativy to the leftmost side of the expression. 631 | // 632 | // This is done by recursively applying the associativity 633 | if let Expr::BinOp(lhs_bin) = &state.get(bin_op.lhs).0 { 634 | bin_op = associate( 635 | lhs_bin.lhs, 636 | lhs_bin.op, 637 | lhs_bin.rhs, 638 | bin_op.op, 639 | bin_op.rhs, 640 | state, 641 | ); 642 | } 643 | 644 | // Apply associativy to the rightmost side of the expression. 645 | // 646 | // This is done by recursively applying the associativity 647 | if let Expr::BinOp(rhs_bin) = &state.get(bin_op.rhs).0 { 648 | bin_op = associate( 649 | bin_op.lhs, 650 | bin_op.op, 651 | rhs_bin.lhs, 652 | rhs_bin.op, 653 | rhs_bin.rhs, 654 | state, 655 | ); 656 | } 657 | 658 | // Reintern the term. 659 | state.intern((Expr::BinOp(bin_op), *span)) 660 | } 661 | 662 | /// Rewrites a term to its normal form. 663 | pub fn rewrite(self, state: &mut TermArena) -> Term { 664 | self.apply_distributive_property(state) 665 | .apply_associativity(state) 666 | .normalize(state) 667 | } 668 | 669 | /// Reduces a term to its normal form. 670 | pub fn normalize(self, state: &mut TermArena) -> Term { 671 | let (kind, span) = &*state.get(self); 672 | let new_kind = match kind { 673 | Expr::Group(group) => Expr::Group(group.rewrite(state)), 674 | Expr::BinOp(bin_op) => { 675 | let lhs = bin_op.lhs.rewrite(state); 676 | let rhs = bin_op.rhs.rewrite(state); 677 | 678 | // If the term is a number, we try to reduce it evaluating 679 | // the operation. 680 | match &state.get(lhs).0 { 681 | // If the term is a number 682 | // 683 | // We assume that the term is a number and we try to 684 | // reduce it. 685 | Expr::Number(lhs) => match &state.get(rhs).0 { 686 | Expr::Number(rhs) => { 687 | let number = match bin_op.op { 688 | Op::Add => lhs + rhs, 689 | Op::Sub => lhs - rhs, 690 | Op::Mul => lhs * rhs, 691 | Op::Div => lhs / rhs, 692 | }; 693 | 694 | Expr::Number(number) 695 | } 696 | Expr::Group(group) => return group.rewrite(state), 697 | _ => return self, 698 | }, 699 | Expr::Group(group) => return group.rewrite(state), 700 | _ => return self, 701 | } 702 | } 703 | // If the term is a variable, we try to reduce it. 704 | Expr::Variable(hole) => match hole.data() { 705 | Some(value) => return value.rewrite(state), 706 | None => kind.clone(), 707 | }, 708 | _ => kind.clone(), 709 | }; 710 | state.intern((new_kind, *span)) 711 | } 712 | 713 | /// Unifies two terms. It's the main point of the equation. 714 | /// 715 | /// The logic relies here. 716 | pub fn unify(self, another: Term, state: &mut TermArena) -> Result<(), TypeError> { 717 | match (&state.get(self).0, &state.get(another).0) { 718 | // Errors are sentinel values, so they are threated like holes 719 | // and they are ignored, anything unifies with them. 720 | (Expr::Error, _) => {} 721 | (_, Expr::Error) => {} 722 | 723 | // If they are the same, they unify. 724 | (Expr::Number(a), Expr::Number(b)) if a == b => {} 725 | 726 | // If the term is a number, we try the following steps given the example: 727 | // 9 = x + 6 728 | // 729 | // 1. We get the reverse of `+`, which is `-`. 730 | // 2. We subtract `6` from both sides, to equate it, 731 | // and since the `9` is a constant, we can reduce 732 | // it to the WHNF. 733 | // 3. Got the equation solved, we can unify the `x` with 734 | // the result of the subtraction. 735 | // 736 | // 3 = x and then x = 3 737 | (Expr::Number(_), Expr::BinOp(bin_op)) => { 738 | let (term, another) = bin_op.symmetry(self, state); 739 | 740 | term.unify(another, state)?; 741 | } 742 | (Expr::BinOp(bin_op), Expr::Number(_)) => { 743 | let (term, another) = bin_op.symmetry(another, state); 744 | 745 | term.unify(another, state)?; 746 | } 747 | 748 | // If they are variables, they unify if they are the same. 749 | // 750 | // This check isn't inehenterly necessary, but it's a good catcher 751 | // to avoid panics, because if the variables are the same, they will 752 | // try to borrow the same data, and it will panic with ref cells. 753 | (Expr::Variable(variable_a), Expr::Variable(variable_b)) 754 | if variable_a.name == variable_b.name => {} 755 | 756 | // Unifies the variable with the term, if the variable is not bound. If 757 | // it's bound, it will try to unify, if it's not unifiable, it will 758 | // return an error. 759 | (_, Expr::Variable(variable)) => { 760 | match variable.data() { 761 | // If the variable is already bound, we unify the bound 762 | Some(bound) => { 763 | self.unify(bound, state)?; 764 | } 765 | // Empty hole 766 | None => { 767 | variable.data.replace(Some(self)); 768 | } 769 | } 770 | } 771 | (Expr::Variable(variable), _) => { 772 | match variable.data() { 773 | // If the variable is already bound, we unify the bound 774 | Some(bound) => { 775 | bound.unify(another, state)?; 776 | } 777 | // Empty hole 778 | None => { 779 | variable.data.replace(Some(another)); 780 | } 781 | } 782 | } 783 | 784 | // Unifies the bin ops if they are the same, and unifies the 785 | // operands. 786 | (Expr::BinOp(bin_op_a), Expr::BinOp(bin_op_b)) => { 787 | if bin_op_a.op == bin_op_b.op { 788 | let lhs_a = bin_op_a.lhs.rewrite(state); 789 | let rhs_a = bin_op_a.rhs.rewrite(state); 790 | 791 | let lhs_b = bin_op_b.lhs.rewrite(state); 792 | let rhs_b = bin_op_b.rhs.rewrite(state); 793 | 794 | lhs_a.unify(lhs_b, state)?; 795 | rhs_a.unify(rhs_b, state)?; 796 | } else { 797 | return Err(TypeError::IncompatibleOp(bin_op_a.op, bin_op_b.op)); 798 | } 799 | } 800 | 801 | // Reduce groups to the normal form 802 | (_, Expr::Group(another)) => { 803 | let term = self.rewrite(state); 804 | let another = another.rewrite(state); 805 | 806 | term.unify(another, state)?; 807 | } 808 | (Expr::Group(term), _) => { 809 | let term = term.rewrite(state); 810 | let another = another.rewrite(state); 811 | 812 | term.unify(another, state)?; 813 | } 814 | 815 | (a, b) => { 816 | return Err(TypeError::NotUnifiable(a.clone(), b.clone())); 817 | } 818 | } 819 | 820 | Ok(()) 821 | } 822 | } 823 | 824 | fn main() { 825 | let mut state = TermArena::default(); 826 | let (equation, _) = parse("10 = x + 7", &mut state); 827 | print!("Input: {:?}", equation.lhs.debug(&state)); 828 | print!(" = "); 829 | println!("{:?}", equation.rhs.debug(&state)); 830 | 831 | let lhs = equation.lhs.rewrite(&mut state); 832 | let rhs = equation.rhs.rewrite(&mut state); 833 | lhs.unify(rhs, &mut state).unwrap(); 834 | 835 | let resolutions = state 836 | .resolutions() 837 | .into_iter() 838 | .map(|(incognito, term)| format!("{incognito} = {:?}", term.debug(&state))) 839 | .collect::>() 840 | .join(", "); 841 | 842 | println!("Output: {resolutions}"); 843 | } 844 | --------------------------------------------------------------------------------