├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── favicon.ico ├── iosevka-regular.ttf └── iosevka-regular.woff2 ├── index.html └── src ├── editor.rs ├── editor ├── actions.rs ├── deep_clone.rs ├── help.rs ├── impls.rs ├── key_listener.rs ├── types.rs └── views.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | dist/ 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yew-app" 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 | yew = "0.19.3" 10 | yew-hooks = "0.1.56" 11 | js-sys = "0.3" 12 | gloo = "0.7" 13 | wasm-bindgen = "0.2" 14 | strum_macros = "0.24" 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Joomy Korkut 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dilim 2 | 3 | A structure editor for a simple functional programming language, with Vim-like shortcuts and commands. 4 | 5 | Written in Rust, using [the Yew framework](https://yew.rs/), compiled to WebAssembly. I wrote it as my first Rust project, to learn Rust, so the code probably isn't all idiomatic Rust. 6 | 7 | [Live here for now.](http://joomy.korkutblech.com/dilim/) 8 | 9 | Press `h` to get help, press `Tab` to get suggestions on how to complete commands. 10 | 11 | ## Installation and Running 12 | 13 | If you don't have the Rust WASM bundle tool [trunk](https://github.com/thedodd/trunk), you should install it: 14 | ``` 15 | cargo install trunk 16 | ``` 17 | 18 | Then you can build the app and run a static file server with this command: 19 | ``` 20 | trunk serve 21 | ``` 22 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joom/dilim/50d9753fb8a044a4de3a62c7a4ab1e5067e9fbd2/assets/favicon.ico -------------------------------------------------------------------------------- /assets/iosevka-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joom/dilim/50d9753fb8a044a4de3a62c7a4ab1e5067e9fbd2/assets/iosevka-regular.ttf -------------------------------------------------------------------------------- /assets/iosevka-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joom/dilim/50d9753fb8a044a4de3a62c7a4ab1e5067e9fbd2/assets/iosevka-regular.woff2 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dilim 6 | 7 | 8 | 9 | 330 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /src/editor.rs: -------------------------------------------------------------------------------- 1 | pub mod actions; 2 | pub mod deep_clone; 3 | pub mod help; 4 | pub mod impls; 5 | pub mod key_listener; 6 | pub mod types; 7 | pub mod views; 8 | -------------------------------------------------------------------------------- /src/editor/actions.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use crate::editor::deep_clone::DeepClone; 3 | use crate::*; 4 | 5 | impl App { 6 | pub fn go_outwards(&mut self) -> Result { 7 | match &self.selected { 8 | Some(s) => { 9 | let n = self.find_parent(s.clone()); 10 | match n { 11 | Some(s) => { 12 | self.selected = Some(s.clone()); 13 | Ok(s) 14 | } 15 | None => Err(ErrorMsg::NowhereToGo), 16 | } 17 | } 18 | None => Err(ErrorMsg::NothingSelected), 19 | } 20 | } 21 | 22 | pub fn go_inwards(&mut self) -> Result { 23 | match &self.selected { 24 | Some(s) => { 25 | let n = App::find_child(s.clone()); 26 | match n { 27 | Some(s) => { 28 | self.selected = Some(s.clone()); 29 | Ok(s) 30 | } 31 | None => Err(ErrorMsg::NowhereToGo), 32 | } 33 | } 34 | None => Err(ErrorMsg::NothingSelected), 35 | } 36 | } 37 | 38 | pub fn go_next_sibling(&mut self) -> Result { 39 | match &self.selected { 40 | Some(s) => { 41 | let n = self.find_sibling(s.clone(), true); 42 | match n { 43 | Some(s) => { 44 | self.selected = Some(s.clone()); 45 | Ok(s) 46 | } 47 | None => Err(ErrorMsg::NowhereToGo), 48 | } 49 | } 50 | None => Err(ErrorMsg::NothingSelected), 51 | } 52 | } 53 | 54 | pub fn go_prev_sibling(&mut self) -> Result { 55 | match &self.selected { 56 | Some(s) => { 57 | let n = self.find_sibling(s.clone(), false); 58 | match n { 59 | Some(s) => { 60 | self.selected = Some(s.clone()); 61 | Ok(s) 62 | } 63 | None => Err(ErrorMsg::NowhereToGo), 64 | } 65 | } 66 | None => Err(ErrorMsg::NothingSelected), 67 | } 68 | } 69 | 70 | pub fn go_prev_hole(&mut self) -> Result { 71 | match &self.selected { 72 | Some(u) => { 73 | let next = self.find_adjacent_hole(u.clone(), false); 74 | match next { 75 | Some(s) => { 76 | self.selected = Some(s.clone()); 77 | Ok(s) 78 | } 79 | _ => Err(ErrorMsg::NowhereToGo), 80 | } 81 | } 82 | None => Err(ErrorMsg::NothingSelected), 83 | } 84 | } 85 | 86 | pub fn go_next_hole(&mut self) -> Result { 87 | match &self.selected { 88 | Some(u) => { 89 | let next = self.find_adjacent_hole(u.clone(), true); 90 | match next { 91 | Some(s) => { 92 | self.selected = Some(s.clone()); 93 | Ok(s) 94 | } 95 | _ => Err(ErrorMsg::NowhereToGo), 96 | } 97 | } 98 | None => Err(ErrorMsg::NothingSelected), 99 | } 100 | } 101 | 102 | pub fn go_specific_hole(&mut self, i: u8) -> Result { 103 | let holes = self.holes(); 104 | match holes.get(i as usize) { 105 | None => Err(ErrorMsg::NowhereToGo), 106 | Some(h) => { 107 | self.selected = Some(h.clone()); 108 | Ok(h.clone()) 109 | } 110 | } 111 | } 112 | 113 | pub fn delete(&mut self) -> Result { 114 | self.clipboard = self.selected.clone().deep_clone(); 115 | match &self.selected { 116 | Some(Selection::SName(r)) => { 117 | *(r.borrow_mut()) = Name::Hole; 118 | Ok(Selection::SName(r.clone())) 119 | } 120 | Some(Selection::STerm(r)) => { 121 | *(r.borrow_mut()) = Term::Hole; 122 | Ok(Selection::STerm(r.clone())) 123 | } 124 | Some(Selection::SStmt(r1)) => { 125 | let r1c = r1.borrow().clone(); 126 | match r1c { 127 | Stmt::Hole => match self.find_parent(Selection::SStmt(r1.clone())) { 128 | Some(Selection::SProgram(r2)) => { 129 | let sel = Selection::SStmt(r1.clone()); 130 | match *(r2.borrow_mut()) { 131 | Program(ref mut ps) => { 132 | let i = ps 133 | .iter() 134 | .position(move |p| { 135 | Selection::SStmt(p.clone()).eq(&sel.clone()) 136 | }) 137 | .unwrap(); 138 | ps.remove(i); 139 | match ps.get(i) { 140 | Some(s) => { 141 | self.selected = Some(Selection::SStmt(s.clone())); 142 | Ok(Selection::SStmt(s.clone())) 143 | } 144 | None => { 145 | self.selected = None; 146 | Ok(Selection::SProgram(r2.clone())) 147 | } 148 | } 149 | } 150 | } 151 | } 152 | _ => Err(ErrorMsg::Impossible), // parent is not a program 153 | }, 154 | _ => { 155 | *(r1.borrow_mut()) = Stmt::Hole; 156 | Ok(Selection::SStmt(r1.clone())) 157 | } 158 | } 159 | } 160 | Some(Selection::SProgram(r)) => { 161 | let t = Stmt::Hole; 162 | *(r.borrow_mut()) = Program(vec![Rnew(t)]); 163 | Ok(Selection::SProgram(r.clone())) 164 | } 165 | None => Err(ErrorMsg::NothingSelected), 166 | } 167 | } 168 | 169 | pub fn yank(&mut self) -> Result { 170 | match &self.selected { 171 | Some(s) => { 172 | let t = s.clone().deep_clone(); 173 | self.clipboard = Some(t.clone()); 174 | Ok(t) 175 | } 176 | None => Err(ErrorMsg::NothingSelected), 177 | } 178 | } 179 | 180 | pub fn paste(&mut self) -> Result { 181 | match &self.selected { 182 | Some(Selection::SName(r1)) => match &self.clipboard { 183 | Some(Selection::SName(r2)) => { 184 | *(r1.borrow_mut()) = r2.borrow().clone().deep_clone(); 185 | Ok(Selection::SName((*r1).clone())) 186 | } 187 | Some(_) => Err(ErrorMsg::NotNameInClipboard), 188 | None => Err(ErrorMsg::NothingInClipboard), 189 | }, 190 | Some(Selection::STerm(r1)) => match &self.clipboard { 191 | Some(Selection::STerm(r2)) => { 192 | *(r1.borrow_mut()) = r2.borrow().clone().deep_clone(); 193 | Ok(Selection::STerm((*r1).clone())) 194 | } 195 | Some(_) => Err(ErrorMsg::NotTermInClipboard), 196 | None => Err(ErrorMsg::NothingInClipboard), 197 | }, 198 | Some(Selection::SStmt(r1)) => match &self.clipboard { 199 | Some(Selection::SStmt(r2)) => { 200 | *(r1.borrow_mut()) = r2.borrow().clone().deep_clone(); 201 | Ok(Selection::SStmt((*r1).clone())) 202 | } 203 | Some(_) => Err(ErrorMsg::NotStmtInClipboard), 204 | None => Err(ErrorMsg::NothingInClipboard), 205 | }, 206 | Some(Selection::SProgram(r1)) => match &self.clipboard { 207 | Some(Selection::SProgram(r2)) => { 208 | *(r1.borrow_mut()) = r2.borrow().clone().deep_clone(); 209 | Ok(Selection::SProgram((*r1).clone())) 210 | } 211 | Some(_) => Err(ErrorMsg::NotProgramInClipboard), 212 | None => Err(ErrorMsg::NothingInClipboard), 213 | }, 214 | None => Err(ErrorMsg::NothingSelected), 215 | } 216 | } 217 | 218 | pub fn add_fresh(&mut self) -> Result { 219 | let name = self.gensym(); 220 | match &self.selected { 221 | Some(Selection::SName(r)) => { 222 | let t = Name::Named { name }; 223 | *(r.borrow_mut()) = t.clone(); 224 | Ok(Selection::SName(r.clone())) 225 | } 226 | Some(_) => Err(ErrorMsg::NotANameHole), 227 | None => Err(ErrorMsg::NothingSelected), 228 | } 229 | } 230 | 231 | pub fn add_stmt_to_program(&mut self) -> Result { 232 | let h = Rnew(Stmt::Hole); 233 | match &self.selected { 234 | Some(Selection::SProgram(r)) => match *(r.borrow_mut()) { 235 | Program(ref mut ps) => { 236 | ps.push(h.clone()); 237 | Ok(Selection::SStmt(h)) 238 | } 239 | }, 240 | Some(_) => Err(ErrorMsg::NotAProgram), 241 | None => Err(ErrorMsg::NothingSelected), 242 | } 243 | } 244 | 245 | pub fn add_stmt_after_stmt(&mut self) -> Result { 246 | let h = Rnew(Stmt::Hole); 247 | match &self.selected { 248 | Some(Selection::SStmt(r1)) => match self.find_parent(Selection::SStmt(r1.clone())) { 249 | Some(Selection::SProgram(r2)) => { 250 | let sel = Selection::SStmt(r1.clone()); 251 | match *(r2.borrow_mut()) { 252 | Program(ref mut ps) => { 253 | let i = ps 254 | .iter() 255 | .position(move |p| Selection::SStmt(p.clone()).eq(&sel.clone())) 256 | .unwrap(); 257 | ps.insert(i + 1, h.clone()); 258 | self.selected = Some(Selection::SStmt(h.clone())); 259 | Ok(Selection::SStmt(h)) 260 | } 261 | } 262 | } 263 | _ => Err(ErrorMsg::NotAStmtHole), 264 | }, 265 | Some(_) => Err(ErrorMsg::NotAStmt), 266 | None => Err(ErrorMsg::NothingSelected), 267 | } 268 | } 269 | 270 | pub fn add_term_stmt_in_stmt_hole(&mut self) -> Result { 271 | match self.selected.clone() { 272 | Some(Selection::SStmt(r)) => match *r.borrow_mut() { 273 | ref mut s @ Stmt::Hole => { 274 | let h = Rnew(Term::Hole); 275 | *s = Stmt::Term { term: h.clone() }; 276 | self.selected = Some(Selection::STerm(h.clone())); 277 | Ok(Selection::STerm(h)) 278 | } 279 | _ => Err(ErrorMsg::NotAStmtHole), 280 | }, 281 | Some(_) => Err(ErrorMsg::NotAStmtHole), 282 | None => Err(ErrorMsg::NothingSelected), 283 | } 284 | } 285 | 286 | pub fn add_defn_stmt_in_stmt_hole(&mut self) -> Result { 287 | match self.selected.clone() { 288 | Some(Selection::SStmt(r)) => match *r.borrow_mut() { 289 | ref mut s @ Stmt::Hole => { 290 | let h1 = Rnew(Name::Hole); 291 | let h2 = Rnew(Term::Hole); 292 | *s = Stmt::Defn { 293 | v: h1.clone(), 294 | body: h2.clone(), 295 | }; 296 | self.selected = Some(Selection::STerm(h2.clone())); 297 | Ok(Selection::STerm(h2)) 298 | } 299 | _ => Err(ErrorMsg::NotAStmtHole), 300 | }, 301 | Some(_) => Err(ErrorMsg::NotAStmtHole), 302 | None => Err(ErrorMsg::NothingSelected), 303 | } 304 | } 305 | 306 | pub fn add_while_stmt_in_stmt_hole(&mut self) -> Result { 307 | match self.selected.clone() { 308 | Some(Selection::SStmt(r)) => match *r.borrow_mut() { 309 | ref mut s @ Stmt::Hole => { 310 | let h1 = Rnew(Term::Hole); 311 | let h2 = Rnew(Stmt::Hole); 312 | let h3 = Rnew(Program(vec![h2])); 313 | *s = Stmt::While { 314 | c: h1.clone(), 315 | b1: h3.clone(), 316 | }; 317 | self.selected = Some(Selection::SStmt(r.clone())); 318 | Ok(Selection::SStmt(r.clone())) 319 | } 320 | _ => Err(ErrorMsg::NotAStmtHole), 321 | }, 322 | Some(_) => Err(ErrorMsg::NotAStmtHole), 323 | None => Err(ErrorMsg::NothingSelected), 324 | } 325 | } 326 | 327 | pub fn add_if_stmt_in_stmt_hole(&mut self) -> Result { 328 | match self.selected.clone() { 329 | Some(Selection::SStmt(r)) => match *r.borrow_mut() { 330 | ref mut s @ Stmt::Hole => { 331 | let h1 = Rnew(Term::Hole); 332 | let h2 = Rnew(Stmt::Hole); 333 | let h3 = Rnew(Program(vec![h2])); 334 | *s = Stmt::If { 335 | c: h1.clone(), 336 | b1: h3.clone(), 337 | }; 338 | self.selected = Some(Selection::SStmt(r.clone())); 339 | Ok(Selection::SStmt(r.clone())) 340 | } 341 | _ => Err(ErrorMsg::NotAStmtHole), 342 | }, 343 | Some(_) => Err(ErrorMsg::NotAStmtHole), 344 | None => Err(ErrorMsg::NothingSelected), 345 | } 346 | } 347 | 348 | pub fn add_else_to_if_stmt(&mut self) -> Result { 349 | match self.selected.clone() { 350 | Some(Selection::SStmt(r)) => match &*r.deep_clone().borrow() { 351 | Stmt::If {c, b1} => { 352 | let h1 = Rnew(Stmt::Hole); 353 | let h2 = Rnew(Program(vec![h1.clone()])); 354 | *r.borrow_mut() = Stmt::IfElse { 355 | c: c.clone(), 356 | b1: b1.clone(), 357 | b2: h2.clone() 358 | }; 359 | self.selected = Some(Selection::SStmt(h1.clone())); 360 | Ok(Selection::SStmt(h1.clone())) 361 | } 362 | _ => Err(ErrorMsg::NotAStmtHole), 363 | }, 364 | Some(_) => Err(ErrorMsg::NotAStmtHole), 365 | None => Err(ErrorMsg::NothingSelected), 366 | } 367 | } 368 | 369 | pub fn add_var_in_term_hole(&mut self) -> Result { 370 | match self.selected.clone() { 371 | Some(Selection::STerm(r)) => match *r.borrow_mut() { 372 | ref mut s @ Term::Hole => { 373 | let v = Rnew(Name::Hole); 374 | *s = Term::Var { v: v.clone() }; 375 | self.selected = Some(Selection::SName(v.clone())); 376 | Ok(Selection::SName(v)) 377 | } 378 | _ => Err(ErrorMsg::NotATermHole), 379 | }, 380 | Some(_) => Err(ErrorMsg::NotATermHole), 381 | None => Err(ErrorMsg::NothingSelected), 382 | } 383 | } 384 | 385 | pub fn add_const_in_term_hole(&mut self, c: Const) -> Result { 386 | match self.selected.clone() { 387 | Some(Selection::STerm(r)) => match *r.borrow_mut() { 388 | ref mut s @ Term::Hole => { 389 | *s = Term::Const { c }; 390 | self.selected = Some(Selection::STerm(r.clone())); 391 | Ok(Selection::STerm(r.clone())) 392 | } 393 | _ => Err(ErrorMsg::NotATermHole), 394 | }, 395 | Some(_) => Err(ErrorMsg::NotATermHole), 396 | None => Err(ErrorMsg::NothingSelected), 397 | } 398 | } 399 | 400 | pub fn add_app_in_term_hole(&mut self) -> Result { 401 | match self.selected.clone() { 402 | Some(Selection::STerm(r)) => match *r.borrow_mut() { 403 | ref mut s @ Term::Hole => { 404 | let h1 = Rnew(Term::Hole); 405 | let h2 = Rnew(Term::Hole); 406 | *s = Term::App { 407 | t1: h1.clone(), 408 | t2: h2, 409 | }; 410 | self.selected = Some(Selection::STerm(h1.clone())); 411 | Ok(Selection::STerm(h1)) 412 | } 413 | _ => Err(ErrorMsg::NotATermHole), 414 | }, 415 | Some(_) => Err(ErrorMsg::NotATermHole), 416 | None => Err(ErrorMsg::NothingSelected), 417 | } 418 | } 419 | 420 | pub fn add_lam_in_term_hole(&mut self) -> Result { 421 | match self.selected.clone() { 422 | Some(Selection::STerm(r)) => match *r.borrow_mut() { 423 | ref mut s @ Term::Hole => { 424 | let h1 = Rnew(Name::Hole); 425 | let h2 = Rnew(Term::Hole); 426 | *s = Term::Lam { 427 | v: h1.clone(), 428 | body: h2.clone(), 429 | }; 430 | self.selected = Some(Selection::STerm(h2.clone())); 431 | Ok(Selection::STerm(h2)) 432 | } 433 | _ => Err(ErrorMsg::NotATermHole), 434 | }, 435 | Some(_) => Err(ErrorMsg::NotATermHole), 436 | None => Err(ErrorMsg::NothingSelected), 437 | } 438 | } 439 | 440 | pub fn fill_name_hole(&mut self, name: String) -> Result { 441 | match self.selected.clone() { 442 | Some(Selection::SName(r)) => match *r.borrow_mut() { 443 | ref mut s @ Name::Hole => { 444 | *s = Name::Named { name }; 445 | self.selected = Some(Selection::SName(r.clone())); 446 | Ok(Selection::SName(r.clone())) 447 | } 448 | _ => Err(ErrorMsg::NotANameHole), 449 | }, 450 | _ => Err(ErrorMsg::NotANameHole), 451 | } 452 | } 453 | 454 | pub fn wrap_in_app(&mut self) -> Result { 455 | match &self.selected.clone() { 456 | Some(Selection::STerm(r)) => match *r.borrow_mut() { 457 | ref mut term => { 458 | let h1 = Rnew(term.clone()); 459 | let h2 = Rnew(Term::Hole); 460 | let t = Term::App { 461 | t1: h1.clone(), 462 | t2: h2.clone(), 463 | }; 464 | *term = t; 465 | self.selected = Some(Selection::STerm(h1.clone())); 466 | Ok(Selection::STerm(h1)) 467 | } 468 | }, 469 | Some(_) => Err(ErrorMsg::NotATermHole), 470 | None => Err(ErrorMsg::NothingSelected), 471 | } 472 | } 473 | 474 | pub fn wrap_in_lam(&mut self) -> Result { 475 | match &self.selected.clone() { 476 | Some(Selection::STerm(r)) => match *r.borrow_mut() { 477 | ref mut term => { 478 | let h1 = Rnew(Name::Hole); 479 | let h2 = Rnew(term.clone()); 480 | let t = Term::Lam { 481 | v: h1.clone(), 482 | body: h2.clone(), 483 | }; 484 | *term = t; 485 | self.selected = Some(Selection::STerm(h2.clone())); 486 | Ok(Selection::STerm(h2)) 487 | } 488 | }, 489 | Some(_) => Err(ErrorMsg::NotATermHole), 490 | None => Err(ErrorMsg::NothingSelected), 491 | } 492 | } 493 | 494 | pub fn wrap_in_defn(&mut self) -> Result { 495 | match &self.selected.clone() { 496 | Some(Selection::SStmt(r)) => match *r.borrow_mut() { 497 | ref mut stmt => match stmt.clone() { 498 | Stmt::Term { term } => { 499 | let h1 = Rnew(Name::Hole); 500 | let t = Stmt::Defn { 501 | v: h1.clone(), 502 | body: term.clone(), 503 | }; 504 | *stmt = t; 505 | self.selected = Some(Selection::STerm(term.clone())); 506 | Ok(Selection::STerm(term.clone())) 507 | } 508 | _ => Err(ErrorMsg::NotATermStmt), 509 | }, 510 | }, 511 | Some(_) => Err(ErrorMsg::NotATermStmt), 512 | None => Err(ErrorMsg::NothingSelected), 513 | } 514 | } 515 | } 516 | -------------------------------------------------------------------------------- /src/editor/deep_clone.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | pub trait DeepClone { 4 | fn deep_clone(&self) -> Self; 5 | } 6 | 7 | impl DeepClone for R { 8 | fn deep_clone(&self) -> R { 9 | Rnew(self.borrow().deep_clone()) 10 | } 11 | } 12 | 13 | impl DeepClone for Option { 14 | fn deep_clone(&self) -> Option { 15 | match self { 16 | Some(s) => Some(s.deep_clone()), 17 | None => None, 18 | } 19 | } 20 | } 21 | 22 | impl DeepClone for Vec { 23 | fn deep_clone(&self) -> Vec { 24 | self.iter().map(|x| x.deep_clone()).collect() 25 | } 26 | } 27 | 28 | impl DeepClone for Name { 29 | fn deep_clone(&self) -> Self { 30 | self.clone() 31 | } 32 | } 33 | 34 | impl DeepClone for Term { 35 | fn deep_clone(&self) -> Self { 36 | match self { 37 | Term::Hole => Term::Hole, 38 | Term::Const { c } => Term::Const { c: c.clone() }, 39 | Term::Var { v } => Term::Var { v: v.deep_clone() }, 40 | Term::App { t1, t2 } => Term::App { 41 | t1: t1.deep_clone(), 42 | t2: t2.deep_clone(), 43 | }, 44 | Term::Lam { v, body } => Term::Lam { 45 | v: v.deep_clone(), 46 | body: body.deep_clone(), 47 | }, 48 | } 49 | } 50 | } 51 | 52 | impl DeepClone for Stmt { 53 | fn deep_clone(&self) -> Self { 54 | match self { 55 | Stmt::Hole => Stmt::Hole, 56 | Stmt::Term { term } => Stmt::Term { 57 | term: term.deep_clone(), 58 | }, 59 | Stmt::Defn { v, body } => Stmt::Defn { 60 | v: v.deep_clone(), 61 | body: body.deep_clone(), 62 | }, 63 | Stmt::While { c, b1 } => Stmt::While { 64 | c: c.deep_clone(), 65 | b1: b1.deep_clone(), 66 | }, 67 | Stmt::If { c, b1 } => Stmt::If { 68 | c: c.deep_clone(), 69 | b1: b1.deep_clone(), 70 | }, 71 | Stmt::IfElse { c, b1, b2 } => Stmt::IfElse { 72 | c: c.deep_clone(), 73 | b1: b1.deep_clone(), 74 | b2: b2.deep_clone(), 75 | }, 76 | } 77 | } 78 | } 79 | 80 | impl DeepClone for Program { 81 | fn deep_clone(&self) -> Self { 82 | match self { 83 | Program(ps) => Program(ps.deep_clone()), 84 | } 85 | } 86 | } 87 | 88 | impl DeepClone for Selection { 89 | fn deep_clone(&self) -> Self { 90 | match self { 91 | Selection::SName(r) => Selection::SName(r.deep_clone()), 92 | Selection::STerm(r) => Selection::STerm(r.deep_clone()), 93 | Selection::SStmt(r) => Selection::SStmt(r.deep_clone()), 94 | Selection::SProgram(r) => Selection::SProgram(r.deep_clone()), 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/editor/help.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use yew::{html, Html}; 3 | 4 | fn dilim() -> Html { 5 | html! { 6 | 7 | {"dilim"} 8 | 9 | } 10 | } 11 | 12 | 13 | macro_rules! letter { 14 | ($l:expr) => { Cmd::Text(String::from($l)) } 15 | } 16 | 17 | pub fn help() -> Html { 18 | html! { 19 |
20 |

21 | {"This is "} 22 | { dilim() } 23 | {", a structural editor for a simple functional programming language, with Vim-like shortcuts and commands."} 24 |

25 |

26 | {"Our programs consist of a sequence of statements, where each statement is a definition or a term statement. A term can be a lambda, an application or a variable."} 27 |

28 |

29 | {"Incomplete programs are ones that have holes in them. There are different actions you can perform on holes and existing programs, statements, terms, and names."} 30 |

31 |

32 | {"Here is an example development process of a simple program:"} 33 |

34 |

35 |

    36 |
  1. 37 | {"Select the statement hole in the empty program either by clicking or pressing "} 38 | {Cmds(vec![letter!("g"), letter!("0"), letter!("h")]).view()} 39 | {" in a sequence, which will run the command "} 40 | {Cmds(vec![Cmd::Go, Cmd::Number(0), Cmd::Hole]).view()} 41 | {"."} 42 |
    43 |
    44 | {"The first letters of the keys you press correspond to the commands you give. For future directions, you will only see the command, not the keys to press. (Although the keys to press are also bolded and underlined.)"} 45 |
  2. 46 | 47 |
  3. 48 | {"Let's add a definition statement first. Run "} 49 | {Cmds(vec![Cmd::Add, Cmd::Defn]).view()} 50 | {"."} 51 |
  4. 52 |
  5. 53 | {"Now hole #1 should be selected. This is where you add a term. Run "} 54 | {Cmds(vec![Cmd::Number(2), Cmd::Add, Cmd::Lam]).view()} 55 | {". This will add 2 nested lambda terms."} 56 |
  6. 57 |
  7. 58 | {"Now hole #3, the body of nested lambdas, should be selected. This is where you add the function body. Run "} 59 | {Cmds(vec![Cmd::Add, Cmd::App]).view()} 60 | {". This will add a function application."} 61 |
  8. 62 |
  9. 63 | {"Now hole #3 should still be selected. You can add a variable term by running "} 64 | {Cmds(vec![Cmd::Add, Cmd::Var]).view()} 65 | {", which will create a variable term and a name hole."} 66 |
  10. 67 |
  11. 68 | {"Now the name hole should be selected. You can put in a variable name by running "} 69 | {Cmds(vec![Cmd::Insert, letter!("f"), Cmd::Enter]).view()} 70 | {". By now you must have gotten the hang of creating terms in the editor."} 71 |
  12. 72 |
  13. 73 | {"You can select the entire statement now by running "} 74 | {Cmds(vec![Cmd::Number(5), Cmd::Go, Cmd::Outwards]).view()} 75 | {". This command selects the immediate outer command 5 times. Feel free to experiment by changing the number, or omitting it, or trying the command "} 76 | {Cmds(vec![Cmd::Go, Cmd::Inwards]).view()} 77 | {"."} 78 |

    79 | {"There are also other commands to move between entities, such as "} 80 | {Cmds(vec![Cmd::Go, Cmd::Prev, Cmd::Sibling]).view()} 81 | {", "} 82 | {Cmds(vec![Cmd::Go, Cmd::Next, Cmd::Sibling]).view()} 83 | {", "} 84 | {Cmds(vec![Cmd::Go, Cmd::Prev, Cmd::Hole]).view()} 85 | {", and "} 86 | {Cmds(vec![Cmd::Go, Cmd::Next, Cmd::Hole]).view()} 87 | {"."} 88 |
  14. 89 |
  15. 90 | {"You can select a name hole, either by clicking or with the movement commands, and then run "} 91 | {Cmds(vec![Cmd::Add, Cmd::Fresh]).view()} 92 | {", which will add a fresh variable name, i.e. a name that hasn't appeared anywhere else in the program."} 93 |
  16. 94 |
  17. 95 | {"You can cut a part of the program by running "} 96 | {Cmds(vec![Cmd::Delete]).view()} 97 | {", copy by running "} 98 | {Cmds(vec![Cmd::Yank]).view()} 99 | {", or paste by running "} 100 | {Cmds(vec![Cmd::Paste]).view()} 101 | {". This is all similar to Vim."} 102 |
  18. 103 |
  19. 104 | {"If you want to add an entity surrounding another entity, you can use the "} 105 | {"Wrap"} 106 | {" command. For example, you can select the application term we constructed above, and then run "} 107 | 108 | {"Wrap"} 109 | {"Lam"} 110 | 111 | {", which would add one more nested lambda just outside the application term."} 112 |
  20. 113 |
114 |

115 |

116 | {"Following the Vim tradition, we intend "} 117 | { dilim() } 118 | {" commands to compose as much as they can, as long as it makes sense. Try compositions that make sense to you. You can use "} 119 | {Cmds(vec![letter!("Tab")]).view()} 120 | {" what commands are available, even at the middle of a command! If the command works, great! If it doesn't work, let us know and we'll fix it or add it to the editor! Happy hacking!"} 121 |

122 |
123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/editor/impls.rs: -------------------------------------------------------------------------------- 1 | use crate::editor::deep_clone::DeepClone; 2 | use crate::*; 3 | 4 | impl Name { 5 | fn new(x: String) -> Self { 6 | Name::Named { name: x } 7 | } 8 | fn new_rc(x: String) -> R { 9 | Rnew(Name::Named { name: x }) 10 | } 11 | fn names(t: R) -> Vec { 12 | let mut v = Vec::new(); 13 | Name::names_acc(t, &mut v); 14 | v 15 | } 16 | fn names_acc(t: R, vec: &mut Vec) -> () { 17 | match &*t.borrow_mut() { 18 | Name::Hole => {} 19 | Name::Named { name } => { 20 | vec.push(name.clone()); 21 | } 22 | } 23 | } 24 | fn holes_acc(t: R, vec: &mut Vec) -> () { 25 | match &*t.borrow_mut() { 26 | Name::Hole => { 27 | vec.push(Selection::SName(t.clone())); 28 | } 29 | Name::Named { .. } => {} 30 | } 31 | } 32 | } 33 | 34 | impl Term { 35 | fn names(t: R) -> Vec { 36 | let mut v = Vec::new(); 37 | Term::names_acc(t, &mut v); 38 | v 39 | } 40 | 41 | fn names_acc(t: R, vec: &mut Vec) -> () { 42 | match &*t.borrow_mut() { 43 | Term::Hole => {} 44 | Term::Const { .. } => {} 45 | Term::Var { v } => match &*v.borrow() { 46 | Name::Named { name } => { 47 | vec.push(name.clone()); 48 | } 49 | _ => {} 50 | }, 51 | Term::App { t1, t2 } => { 52 | Term::names_acc(t1.clone(), vec); 53 | Term::names_acc(t2.clone(), vec); 54 | } 55 | Term::Lam { v, body } => { 56 | match &*v.borrow() { 57 | Name::Named { name } => { 58 | vec.push(name.clone()); 59 | } 60 | _ => {} 61 | } 62 | Term::names_acc(body.clone(), vec); 63 | } 64 | } 65 | } 66 | 67 | pub fn holes_acc(t: R, vec: &mut Vec) -> () { 68 | match &*t.borrow_mut() { 69 | Term::Hole => { 70 | vec.push(Selection::STerm(t.clone())); 71 | } 72 | Term::Const { .. } => {} 73 | Term::Var { v } => match &*v.borrow_mut() { 74 | Name::Named { .. } => {} 75 | Name::Hole => { 76 | vec.push(Selection::SName(v.clone())); 77 | } 78 | }, 79 | Term::App { t1, t2 } => { 80 | Term::holes_acc(t1.clone(), vec); 81 | Term::holes_acc(t2.clone(), vec); 82 | } 83 | Term::Lam { v, body } => { 84 | match &*v.borrow_mut() { 85 | Name::Named { .. } => {} 86 | Name::Hole => { 87 | vec.push(Selection::SName(v.clone())); 88 | } 89 | } 90 | Term::holes_acc(body.clone(), vec); 91 | } 92 | } 93 | } 94 | 95 | 96 | fn simplify(t : R) -> Term { 97 | match &*t.borrow_mut() { 98 | e => e.clone() 99 | } 100 | } 101 | } 102 | 103 | impl Stmt { 104 | fn names(t: R) -> Vec { 105 | let mut v = Vec::new(); 106 | Stmt::names_acc(t, &mut v); 107 | v 108 | } 109 | 110 | fn names_acc(t: R, vec: &mut Vec) -> () { 111 | match &*t.borrow_mut() { 112 | Stmt::Hole => {} 113 | Stmt::Term { term } => { 114 | Term::names_acc(term.clone(), vec); 115 | } 116 | Stmt::Defn { v, body } => { 117 | Name::names_acc(v.clone(), vec); 118 | Term::names_acc(body.clone(), vec); 119 | } 120 | Stmt::While { c, b1 } => { 121 | Term::names_acc(c.clone(), vec); 122 | Program::names_acc(b1.clone(), vec); 123 | } 124 | Stmt::If { c, b1 } => { 125 | Term::names_acc(c.clone(), vec); 126 | Program::names_acc(b1.clone(), vec); 127 | } 128 | Stmt::IfElse { c, b1, b2 } => { 129 | Term::names_acc(c.clone(), vec); 130 | Program::names_acc(b1.clone(), vec); 131 | Program::names_acc(b2.clone(), vec); 132 | } 133 | } 134 | } 135 | 136 | pub fn holes_acc(t: R, vec: &mut Vec) -> () { 137 | match &*t.borrow_mut() { 138 | Stmt::Hole => vec.push(Selection::SStmt(t.clone())), 139 | Stmt::Term { term } => { 140 | Term::holes_acc(term.clone(), vec); 141 | } 142 | Stmt::Defn { v, body } => { 143 | Name::holes_acc(v.clone(), vec); 144 | Term::holes_acc(body.clone(), vec); 145 | } 146 | Stmt::While { c, b1 } => { 147 | Term::holes_acc(c.clone(), vec); 148 | Program::holes_acc(b1.clone(), vec); 149 | } 150 | Stmt::If { c, b1 } => { 151 | Term::holes_acc(c.clone(), vec); 152 | Program::holes_acc(b1.clone(), vec); 153 | } 154 | Stmt::IfElse { c, b1, b2 } => { 155 | Term::holes_acc(c.clone(), vec); 156 | Program::holes_acc(b1.clone(), vec); 157 | Program::holes_acc(b2.clone(), vec); 158 | } 159 | } 160 | } 161 | } 162 | 163 | impl Program { 164 | fn names(t: R) -> Vec { 165 | let mut v = Vec::new(); 166 | Program::names_acc(t, &mut v); 167 | v 168 | } 169 | 170 | fn names_acc(t: R, vec: &mut Vec) -> () { 171 | match &*t.borrow_mut() { 172 | Program(ps) => { 173 | ps.iter().map(|p| Stmt::names_acc(p.clone(), vec)).count(); 174 | } 175 | } 176 | () 177 | } 178 | 179 | pub fn holes_acc(t: R, vec: &mut Vec) -> () { 180 | match &*t.borrow_mut() { 181 | Program(ps) => { 182 | ps.iter().map(|p| Stmt::holes_acc(p.clone(), vec)).count(); 183 | } 184 | } 185 | () 186 | } 187 | } 188 | 189 | impl Cmd { 190 | pub fn cmd_ended(v: &Vec) -> bool { 191 | match v.last() { 192 | Some(Cmd::Done) | Some(Cmd::Failed) => true, 193 | _ => false, 194 | } 195 | } 196 | } 197 | 198 | impl std::ops::Deref for Cmds { 199 | type Target = Vec; 200 | 201 | fn deref(&self) -> &Self::Target { 202 | &self.0 203 | } 204 | } 205 | 206 | impl App { 207 | pub fn complete_command(&self, keys: Vec) -> Vec { 208 | match &keys[..] { 209 | [] => vec![ 210 | Cmd::Help, 211 | Cmd::Add, 212 | Cmd::Delete, 213 | Cmd::Go, 214 | Cmd::Insert, 215 | Cmd::Wrap, 216 | Cmd::Yank, 217 | Cmd::Paste, 218 | ], 219 | [Cmd::Wrap] => vec![Cmd::App, Cmd::Lam, Cmd::Defn], 220 | [Cmd::Add] => 221 | match &self.selected { 222 | None => vec![], 223 | Some(Selection::SName(_)) => 224 | vec![Cmd::Fresh], 225 | Some(Selection::STerm(_)) => 226 | vec![ 227 | Cmd::App, 228 | Cmd::Lam, 229 | Cmd::Var, 230 | Cmd::Constant 231 | ], 232 | Some(Selection::SStmt(_)) => 233 | vec![ 234 | Cmd::Defn, 235 | Cmd::Term, 236 | Cmd::Statement, 237 | Cmd::While, 238 | Cmd::If, 239 | Cmd::Else 240 | ], 241 | Some(Selection::SProgram(_)) => 242 | vec![Cmd::Statement] 243 | } 244 | [Cmd::Add, Cmd::Constant] => vec![Cmd::True, Cmd::False, Cmd::Number(0)], 245 | [Cmd::Delete] => vec![], 246 | [Cmd::Go] => vec![ 247 | Cmd::Number(0), 248 | Cmd::Prev, 249 | Cmd::Next, 250 | Cmd::Outwards, 251 | Cmd::Inwards, 252 | ], 253 | [Cmd::Go, Cmd::Number(_)] => vec![Cmd::Hole], 254 | [Cmd::Go, Cmd::Number(_), Cmd::Hole] => vec![], 255 | [Cmd::Go, Cmd::Outwards | Cmd::Inwards] => vec![], 256 | [Cmd::Go, Cmd::Prev | Cmd::Next] => vec![Cmd::Hole, Cmd::Sibling], 257 | [Cmd::Go, Cmd::Prev | Cmd::Next, Cmd::Hole | Cmd::Sibling] => vec![], 258 | [Cmd::Insert] => vec![Cmd::Text(String::from("name"))], 259 | [Cmd::Insert, Cmd::Text(_)] => vec![Cmd::Text(String::from("more")), Cmd::Enter], 260 | [Cmd::Insert, Cmd::Text(_), Cmd::Enter] => vec![], 261 | _ => vec![], 262 | } 263 | } 264 | 265 | pub fn run_command(&mut self) -> () { 266 | let keys = self.keys.clone(); 267 | match self.run_command_res(keys) { 268 | Ok(true) => { 269 | self.keys.push(Cmd::Done); 270 | } 271 | Ok(false) => {} 272 | Err(e) => { 273 | self.error = Some(e); 274 | self.keys.push(Cmd::Failed); 275 | } 276 | } 277 | } 278 | pub fn run_command_res(&mut self, keys: Vec) -> Result { 279 | match &keys.clone()[..] { 280 | [Cmd::Number(_)] => Ok(false), 281 | [Cmd::Number(n), ..] => { 282 | let mut rest = keys.clone(); 283 | rest.remove(0); 284 | let mut temp = Ok(false); 285 | for _i in 0..*n { 286 | temp = self.run_command_res(rest.clone()); 287 | match temp { 288 | Ok(false) => return Ok(false), 289 | Ok(true) => {} 290 | Err(e) => return Err(e), 291 | } 292 | } 293 | temp 294 | } 295 | [Cmd::Help] => { 296 | self.page = Page::Help; 297 | Ok(true) 298 | } 299 | [Cmd::Wrap] => Ok(false), 300 | [Cmd::Wrap, Cmd::Lam] => self.wrap_in_lam().map(|_| true), 301 | [Cmd::Wrap, Cmd::App] => self.wrap_in_app().map(|_| true), 302 | [Cmd::Wrap, Cmd::Defn] => self.wrap_in_defn().map(|_| true), 303 | // .or_else(|_| self.go_outwards().and_then(|_| self.wrap_in_defn().map(|_| true))), 304 | [Cmd::Add] => Ok(false), 305 | [Cmd::Add, Cmd::Statement] => self.add_stmt_to_program().map(|_| true).or_else(|_| { 306 | self.add_stmt_after_stmt() 307 | .map(|_| true) 308 | .or_else(|_| Err(ErrorMsg::NotAProgramOrStmtHole)) 309 | }), 310 | [Cmd::Add, Cmd::Term] => self.add_term_stmt_in_stmt_hole().map(|_| true), 311 | [Cmd::Add, Cmd::Defn] => self.add_defn_stmt_in_stmt_hole().map(|_| true), 312 | [Cmd::Add, Cmd::While] => self.add_while_stmt_in_stmt_hole().map(|_| true), 313 | [Cmd::Add, Cmd::If] => self.add_if_stmt_in_stmt_hole().map(|_| true), 314 | [Cmd::Add, Cmd::Else] => self.add_else_to_if_stmt().map(|_| true), 315 | [Cmd::Add, Cmd::Var] => (self 316 | .add_term_stmt_in_stmt_hole() 317 | .and_then(|_| self.add_var_in_term_hole().map(|_| true))) 318 | .or_else(|_| self.add_var_in_term_hole().map(|_| true)), 319 | [Cmd::Add, Cmd::App] => (self 320 | .add_term_stmt_in_stmt_hole() 321 | .and_then(|_| self.add_app_in_term_hole().map(|_| true))) 322 | .or_else(|_| self.add_app_in_term_hole().map(|_| true)), 323 | [Cmd::Add, Cmd::Lam] => (self 324 | .add_term_stmt_in_stmt_hole() 325 | .and_then(|_| self.add_lam_in_term_hole().map(|_| true))) 326 | .or_else(|_| self.add_lam_in_term_hole().map(|_| true)), 327 | [Cmd::Add, Cmd::Fresh] => self.add_fresh().map(|_| true), 328 | [Cmd::Add, Cmd::Constant] => Ok(false), 329 | [Cmd::Add, Cmd::Constant, Cmd::True] => { 330 | self.add_const_in_term_hole(Const::Bool(true)).map(|_| true) 331 | } 332 | [Cmd::Add, Cmd::Constant, Cmd::False] => self 333 | .add_const_in_term_hole(Const::Bool(false)) 334 | .map(|_| true), 335 | [Cmd::Delete] => self.delete().map(|_| true), 336 | [Cmd::Yank] => self.yank().map(|_| true), 337 | [Cmd::Paste] => self.paste().map(|_| true), 338 | [Cmd::Insert, Cmd::Enter] => Err(ErrorMsg::NothingToInsert), 339 | [Cmd::Insert, Cmd::Text(s), Cmd::Enter] => { 340 | let name = s.to_string(); 341 | (self.add_term_stmt_in_stmt_hole().and_then(|_| { 342 | self.add_var_in_term_hole() 343 | .and_then(|_| self.fill_name_hole(name.clone()).map(|_| true)) 344 | })) 345 | .or_else(|_| { 346 | self.add_var_in_term_hole() 347 | .and_then(|_| self.fill_name_hole(name.clone()).map(|_| true)) 348 | }) 349 | .or_else(|_| self.fill_name_hole(name).map(|_| true)) 350 | } 351 | [Cmd::Insert] | [Cmd::Insert, Cmd::Text(_)] => Ok(false), 352 | [Cmd::Go, Cmd::Outwards] => self.go_outwards().map(|_| true), 353 | [Cmd::Go, Cmd::Inwards] => self.go_inwards().map(|_| true), 354 | [Cmd::Go, Cmd::Prev, Cmd::Sibling] => self.go_prev_sibling().map(|_| true), 355 | [Cmd::Go, Cmd::Next, Cmd::Sibling] => self.go_next_sibling().map(|_| true), 356 | [Cmd::Go, Cmd::Prev, Cmd::Hole] => self.go_prev_hole().map(|_| true), 357 | [Cmd::Go, Cmd::Next, Cmd::Hole] => self.go_next_hole().map(|_| true), 358 | [Cmd::Go, Cmd::Number(i), Cmd::Hole] => self.go_specific_hole(*i).map(|_| true), 359 | [Cmd::Go] => Ok(false), 360 | [Cmd::Add, _] | [_] => Err(ErrorMsg::NoSuchCommand), 361 | _ => Ok(false), 362 | } 363 | } 364 | 365 | fn gensym_aux(chars: &Vec, start: &Vec) -> Vec { 366 | start 367 | .iter() 368 | .map(|c| chars.iter().map(move |d| format!("{}{}", c, d))) 369 | .flatten() 370 | .collect() 371 | } 372 | 373 | // generates a,b,c,...y,z,aa,ab,ac,ad...,aaa,aab,aac... 374 | pub fn gensym(&self) -> String { 375 | let names = Program::names(self.program.clone()); 376 | let chars: Vec = vec![ 377 | "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", 378 | "r", "s", "t", "u", "v", "w", "x", "y", "z", 379 | ] 380 | .iter() 381 | .map(|x| x.to_string()) 382 | .collect(); 383 | let mut v: Vec = vec![String::new()]; 384 | loop { 385 | v = App::gensym_aux(&chars, &v); 386 | for n in v.iter() { 387 | if !names.contains(&n) { 388 | return n.to_string(); 389 | } 390 | } 391 | } 392 | } 393 | 394 | pub fn holes(&self) -> Vec { 395 | let mut v = Vec::new(); 396 | Program::holes_acc(self.program.clone(), &mut v); 397 | v 398 | } 399 | 400 | pub fn prev_hole(&self, t: Selection) -> Option { 401 | let v = self.holes(); 402 | let oi = v.iter().position(move |u| t.eq(u)); 403 | match oi { 404 | None => None, 405 | Some(i) => { 406 | if !(0 < i) { 407 | None 408 | } else { 409 | v.get(i - 1).map(|x| (*x).clone()) 410 | } 411 | } 412 | } 413 | } 414 | 415 | pub fn next_hole(&self, t: Selection) -> Option { 416 | let v = self.holes(); 417 | let oi = v.iter().position(move |u| t.eq(u)); 418 | match oi { 419 | None => None, 420 | Some(i) => { 421 | let len = v.len(); 422 | if !(i + 1 < len) { 423 | None 424 | } else { 425 | v.get(i + 1).map(|x| (*x).clone()) 426 | } 427 | } 428 | } 429 | } 430 | 431 | pub fn find_adjacent_hole(&self, t: Selection, is_next: bool) -> Option { 432 | let v = self.holes(); 433 | let oi = v.iter().position(move |u| t.eq(u)); 434 | match oi { 435 | None => None, 436 | Some(i) => { 437 | let j = if i == 0 { 438 | if is_next { 439 | 1 440 | } else { 441 | v.len() - 1 442 | } 443 | } else { 444 | (if is_next { i + 1 } else { i - 1 }) % v.len() 445 | }; 446 | v.get(j).map(|x| (*x).clone()) 447 | } 448 | } 449 | } 450 | 451 | pub fn find_parent_aux( 452 | &self, 453 | goal: Selection, 454 | curr: Selection, 455 | prev: Option, 456 | ) -> Option { 457 | if goal == curr { 458 | prev 459 | } else { 460 | let currc = curr.clone(); 461 | match curr { 462 | Selection::SName(_) => None, 463 | Selection::STerm(r) => match &*(r.borrow()) { 464 | Term::Hole => None, 465 | Term::Const { .. } => None, 466 | Term::Var { v } => { 467 | self.find_parent_aux(goal, Selection::SName((*v).clone()), Some(currc)) 468 | } 469 | Term::App { t1, t2 } => self 470 | .find_parent_aux( 471 | goal.clone(), 472 | Selection::STerm((*t1).clone()), 473 | Some(currc.clone()), 474 | ) 475 | .or(self.find_parent_aux( 476 | goal, 477 | Selection::STerm((*t2).clone()), 478 | Some(currc), 479 | )), 480 | Term::Lam { v, body } => self 481 | .find_parent_aux( 482 | goal.clone(), 483 | Selection::SName((*v).clone()), 484 | Some(currc.clone()), 485 | ) 486 | .or(self.find_parent_aux( 487 | goal, 488 | Selection::STerm((*body).clone()), 489 | Some(currc), 490 | )), 491 | }, 492 | Selection::SStmt(r) => match &*(r.borrow()) { 493 | Stmt::Hole => None, 494 | Stmt::Term { term } => { 495 | self.find_parent_aux(goal, Selection::STerm((*term).clone()), Some(currc)) 496 | } 497 | Stmt::Defn { v, body } => self 498 | .find_parent_aux( 499 | goal.clone(), 500 | Selection::SName((*v).clone()), 501 | Some(currc.clone()), 502 | ) 503 | .or(self.find_parent_aux( 504 | goal, 505 | Selection::STerm((*body).clone()), 506 | Some(currc), 507 | )), 508 | Stmt::While { c, b1 } => self 509 | .find_parent_aux( 510 | goal.clone(), 511 | Selection::STerm((*c).clone()), 512 | Some(currc.clone()), 513 | ) 514 | .or(self.find_parent_aux( 515 | goal, 516 | Selection::SProgram((*b1).clone()), 517 | Some(currc), 518 | )), 519 | Stmt::If { c, b1 } => self 520 | .find_parent_aux( 521 | goal.clone(), 522 | Selection::STerm((*c).clone()), 523 | Some(currc.clone()), 524 | ) 525 | .or(self.find_parent_aux( 526 | goal, 527 | Selection::SProgram((*b1).clone()), 528 | Some(currc), 529 | )), 530 | Stmt::IfElse { c, b1, b2 } => self 531 | .find_parent_aux( 532 | goal.clone(), 533 | Selection::STerm((*c).clone()), 534 | Some(currc.clone()), 535 | ) 536 | .or(self.find_parent_aux( 537 | goal.clone(), 538 | Selection::SProgram((*b1).clone()), 539 | Some(currc.clone()), 540 | )) 541 | .or(self.find_parent_aux( 542 | goal, 543 | Selection::SProgram((*b2).clone()), 544 | Some(currc), 545 | )), 546 | }, 547 | Selection::SProgram(r) => match &*(r.borrow()) { 548 | Program(vec) => { 549 | for s in vec { 550 | match self.find_parent_aux( 551 | goal.clone(), 552 | Selection::SStmt(s.clone()), 553 | Some(currc.clone()), 554 | ) { 555 | None => {} 556 | Some(s) => { 557 | return Some(s); 558 | } 559 | } 560 | } 561 | return None; 562 | } 563 | }, 564 | } 565 | } 566 | } 567 | 568 | pub fn find_parent(&self, goal: Selection) -> Option { 569 | let t = Selection::SProgram(self.program.clone()); 570 | self.find_parent_aux(goal, t, None) 571 | } 572 | pub fn find_child(goal: Selection) -> Option { 573 | match goal { 574 | Selection::SName(_) => None, 575 | Selection::STerm(r) => match &*(r.borrow()) { 576 | Term::Hole => None, 577 | Term::Const { .. } => None, 578 | Term::Var { v } => Some(Selection::SName(v.clone())), 579 | Term::App { t1, t2: _ } => Some(Selection::STerm(t1.clone())), 580 | Term::Lam { v, body: _ } => Some(Selection::SName(v.clone())), 581 | }, 582 | Selection::SStmt(r) => match &*(r.borrow()) { 583 | Stmt::Hole => None, 584 | Stmt::Term { term } => Some(Selection::STerm(term.clone())), 585 | Stmt::Defn { v, body: _ } => Some(Selection::SName(v.clone())), 586 | Stmt::While{ c, b1: _ } => Some(Selection::STerm(c.clone())), 587 | Stmt::If { c, b1: _ } => Some(Selection::STerm(c.clone())), 588 | Stmt::IfElse { c, b1: _, b2: _ } => Some(Selection::STerm(c.clone())), 589 | }, 590 | Selection::SProgram(r) => match &*(r.borrow()) { 591 | Program(ps) => ps.get(0).map(|s| Selection::SStmt(s.clone())), 592 | }, 593 | } 594 | } 595 | 596 | pub fn find_sibling(&self, goal: Selection, is_next: bool) -> Option { 597 | let goalc = goal.clone(); 598 | let p = self.find_parent(goal); 599 | match p { 600 | Some(Selection::STerm(r)) => match &*(r.borrow()) { 601 | Term::App { t1, t2 } => { 602 | let v = vec![Selection::STerm(t1.clone()), Selection::STerm(t2.clone())]; 603 | let oi = v.iter().position(move |u| goalc.eq(u)); 604 | match oi { 605 | None => None, 606 | Some(i) => { 607 | let j = if i == 0 { 608 | if is_next { 609 | 1 610 | } else { 611 | v.len() - 1 612 | } 613 | } else { 614 | (if is_next { i + 1 } else { i - 1 }) % v.len() 615 | }; 616 | v.get(j).map(|x| (*x).clone()) 617 | } 618 | } 619 | } 620 | Term::Lam { v, body } => { 621 | let v = vec![Selection::SName(v.clone()), Selection::STerm(body.clone())]; 622 | let oi = v.iter().position(move |u| goalc.eq(u)); 623 | match oi { 624 | None => None, 625 | Some(i) => { 626 | let j = if i == 0 { 627 | if is_next { 628 | 1 629 | } else { 630 | v.len() - 1 631 | } 632 | } else { 633 | (if is_next { i + 1 } else { i - 1 }) % v.len() 634 | }; 635 | v.get(j).map(|x| (*x).clone()) 636 | } 637 | } 638 | } 639 | _ => None, 640 | }, 641 | Some(Selection::SStmt(r)) => match &*(r.borrow()) { 642 | Stmt::Defn { v, body } => { 643 | let v = vec![Selection::SName(v.clone()), Selection::STerm(body.clone())]; 644 | let oi = v.iter().position(move |u| goalc.eq(u)); 645 | match oi { 646 | None => None, 647 | Some(i) => { 648 | let j = if i == 0 { 649 | if is_next { 650 | 1 651 | } else { 652 | v.len() - 1 653 | } 654 | } else { 655 | (if is_next { i + 1 } else { i - 1 }) % v.len() 656 | }; 657 | v.get(j).map(|x| (*x).clone()) 658 | } 659 | } 660 | } 661 | _ => None, 662 | }, 663 | Some(Selection::SProgram(r)) => match &*(r.borrow()) { 664 | Program(ps) => { 665 | let oi = ps 666 | .iter() 667 | .map(|u| Selection::SStmt(u.clone())) 668 | .position(move |u| goalc.eq(&u)); 669 | match oi { 670 | None => None, 671 | Some(i) => { 672 | let j = if i == 0 { 673 | if is_next { 674 | 1 675 | } else { 676 | ps.len() - 1 677 | } 678 | } else { 679 | (if is_next { i + 1 } else { i - 1 }) % ps.len() 680 | }; 681 | ps.get(j).map(|x| Selection::SStmt((*x).clone())) 682 | } 683 | } 684 | } 685 | }, 686 | _ => None, 687 | } 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /src/editor/key_listener.rs: -------------------------------------------------------------------------------- 1 | use yew::html; 2 | use yew::events::KeyboardEvent; 3 | use yew_hooks::prelude::*; 4 | use yew::{function_component, Properties}; 5 | 6 | use crate::editor::types::*; 7 | 8 | #[derive(Properties, Clone)] 9 | pub struct ScopeProps { 10 | pub link: yew::html::Scope 11 | } 12 | 13 | impl PartialEq for ScopeProps { 14 | fn eq(&self, _: &Self) -> bool { true } 15 | } 16 | 17 | #[function_component(KeyListenerComponent)] 18 | pub fn key_listener_component(props: &ScopeProps) -> Html { 19 | let propsc = props.clone(); 20 | use_event_with_window("keypress", move |e: KeyboardEvent| { 21 | propsc.link.send_message(Msg::KeyboardMsg(e)); 22 | }); 23 | let propscc = props.clone(); 24 | use_event_with_window("keydown", move |e: KeyboardEvent| { 25 | match e.key_code() { 26 | 9 /* tab */ | 27 | 8 /* backspace */ | 46 /* delete */ | 28 | 37..=40 /* arrows */ | 27 /* escape */ => { 29 | e.prevent_default(); 30 | propscc.link.send_message(Msg::KeyboardMsg(e)); 31 | } 32 | _ => {} 33 | } 34 | }); 35 | html! { <> } 36 | } 37 | -------------------------------------------------------------------------------- /src/editor/types.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use strum_macros::Display; 4 | use yew::events::KeyboardEvent; 5 | 6 | pub type R = Rc>; 7 | 8 | #[inline(always)] 9 | pub fn Rnew(x: T) -> R { 10 | Rc::new(RefCell::new(x)) 11 | } 12 | 13 | #[derive(Clone)] 14 | pub enum Name { 15 | Hole, 16 | Named { name: String }, 17 | } 18 | 19 | #[derive(Clone)] 20 | pub enum Const { 21 | Bool(bool), 22 | Int(u32), 23 | } 24 | 25 | #[derive(Clone)] 26 | pub enum Term { 27 | Hole, 28 | Var { v: R }, 29 | App { t1: R, t2: R }, 30 | Lam { v: R, body: R }, 31 | Const { c: Const }, 32 | } 33 | 34 | #[derive(Clone)] 35 | pub enum Stmt { 36 | Hole, 37 | Term { term: R }, 38 | Defn { v: R, body: R }, 39 | While { c: R, b1: R }, 40 | If { c: R, b1: R }, 41 | IfElse { c: R, b1: R, b2: R } 42 | } 43 | 44 | #[derive(Clone)] 45 | pub struct Program(pub Vec>); 46 | 47 | #[derive(Clone)] 48 | pub enum Selection { 49 | STerm(R), 50 | SName(R), 51 | SStmt(R), 52 | SProgram(R), 53 | } 54 | 55 | pub enum Msg { 56 | KeyboardMsg(KeyboardEvent), 57 | Unselect, 58 | Select(Selection), 59 | Unhover, 60 | Hover(Selection), 61 | } 62 | 63 | #[derive(Debug, Display, Clone)] 64 | pub enum Cmd { 65 | // Main command 66 | Help, // h 67 | Add, // a 68 | Delete, // d 69 | Wrap, // w 70 | Go, // g 71 | Insert, // i 72 | Yank, // y 73 | Paste, // p 74 | // Input 75 | Term, // t 76 | Defn, // d 77 | If, // i 78 | Else, // e 79 | While, // w 80 | Var, // v 81 | Constant, // c 82 | Lam, // l 83 | App, // a 84 | Hole, // h 85 | Fresh, // f 86 | Sibling, // s 87 | Statement, // s 88 | Text(String), 89 | Number(u8), 90 | True, // t 91 | False, // f 92 | // Location or direction 93 | Prev, // p 94 | Next, // n 95 | Inwards, // i 96 | Outwards, // o 97 | // Final: special command added once the command is run 98 | Enter, 99 | Done, 100 | Failed, 101 | } 102 | 103 | #[derive(Debug, Clone)] 104 | pub struct Cmds(pub Vec); 105 | 106 | #[derive(Debug, Clone)] 107 | pub enum ErrorMsg { 108 | Intro, 109 | Impossible, 110 | NumberTooBig, 111 | NotANameHole, 112 | NotATermHole, 113 | NotAStmtHole, 114 | NotAHole, 115 | NotAProgramOrStmtHole, 116 | NotAStmt, 117 | NotAProgram, 118 | NotATermStmt, 119 | NothingSelected, 120 | NothingInClipboard, 121 | NotNameInClipboard, 122 | NotTermInClipboard, 123 | NotStmtInClipboard, 124 | NotProgramInClipboard, 125 | NothingToInsert, 126 | NowhereToGo, 127 | NoSuchCommand, 128 | } 129 | 130 | #[derive(Debug, Clone, PartialEq)] 131 | pub enum Page { 132 | Main, 133 | Help, 134 | } 135 | 136 | pub struct App { 137 | pub page: Page, 138 | pub program: R, 139 | pub selected: Option, 140 | pub hovered: Option, 141 | pub clipboard: Option, 142 | pub keys: Vec, 143 | pub suggestions: Vec, 144 | pub error: Option, 145 | } 146 | 147 | // Minor trait implementations 148 | 149 | impl PartialEq for Selection { 150 | fn eq(&self, other: &Self) -> bool { 151 | match self { 152 | Selection::SName(t) => match other { 153 | Selection::SName(u) => { 154 | return Rc::ptr_eq(&t, &u); 155 | } 156 | _ => { 157 | return false; 158 | } 159 | }, 160 | Selection::STerm(t) => match other { 161 | Selection::STerm(u) => { 162 | return Rc::ptr_eq(&t, &u); 163 | } 164 | _ => { 165 | return false; 166 | } 167 | }, 168 | Selection::SStmt(t) => match other { 169 | Selection::SStmt(u) => { 170 | return Rc::ptr_eq(&t, &u); 171 | } 172 | _ => { 173 | return false; 174 | } 175 | }, 176 | Selection::SProgram(t) => match other { 177 | Selection::SProgram(u) => { 178 | return Rc::ptr_eq(&t, &u); 179 | } 180 | _ => { 181 | return false; 182 | } 183 | }, 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/editor/views.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use yew::{html, Html}; 3 | 4 | impl Const { 5 | pub fn view(self) -> Html { 6 | match self { 7 | Const::Bool(b) => { 8 | if b { html! { {"True"} } } else { html! { {"False"} } } 9 | } 10 | Const::Int(i) => { 11 | html! { {i} } 12 | } 13 | } 14 | } 15 | } 16 | 17 | impl Stmt { 18 | pub fn view(t: R, holes: &Vec, app: &App, ctx: &Context) -> Html { 19 | match &*(t.borrow()) { 20 | Stmt::Hole => { 21 | let mut cls = vec!["hole", "stmt"]; 22 | let is_selected = match &app.selected { 23 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 24 | _ => false, 25 | }; 26 | if is_selected { 27 | cls.push("selected"); 28 | } 29 | let is_hovered = match &app.hovered { 30 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 31 | _ => false, 32 | }; 33 | if is_hovered { 34 | cls.push("hovered"); 35 | } 36 | let t = t.clone(); 37 | let tc = t.clone(); 38 | let sel = Selection::SStmt(t.clone()); 39 | let i = holes.iter().position(move |u| sel.eq(u)).unwrap(); 40 | html! { 41 |
44 | {i} 45 | { " " } 46 |
47 | } 48 | } 49 | Stmt::Term { term } => { 50 | let mut cls = vec!["term", "stmt"]; 51 | let is_selected = match &app.selected { 52 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 53 | _ => false, 54 | }; 55 | if is_selected { 56 | cls.push("selected"); 57 | } 58 | let is_hovered = match &app.hovered { 59 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 60 | _ => false, 61 | }; 62 | if is_hovered { 63 | cls.push("hovered"); 64 | } 65 | let t = t.clone(); 66 | let tc = t.clone(); 67 | let cl = ctx 68 | .link() 69 | .callback_once(move |_| Msg::Select(Selection::SStmt(t))); 70 | let cl2 = ctx 71 | .link() 72 | .callback_once(move |_| Msg::Hover(Selection::SStmt(tc))); 73 | html! { 74 |
76 | { Term::view(term.clone(), holes, app, ctx) } 77 |
78 | } 79 | } 80 | Stmt::Defn { v, body } => { 81 | let mut cls = vec!["defn", "stmt"]; 82 | let is_selected = match &app.selected { 83 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 84 | _ => false, 85 | }; 86 | if is_selected { 87 | cls.push("selected"); 88 | } 89 | let is_hovered = match &app.hovered { 90 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 91 | _ => false, 92 | }; 93 | if is_hovered { 94 | cls.push("hovered"); 95 | } 96 | let body_outer = vec!["defnBodyOuter"]; 97 | let t = t.clone(); 98 | let tc = t.clone(); 99 | let cl = ctx 100 | .link() 101 | .callback_once(move |_| Msg::Select(Selection::SStmt(t))); 102 | let cl2 = ctx 103 | .link() 104 | .callback_once(move |_| Msg::Hover(Selection::SStmt(tc))); 105 | html! { 106 |
108 |
109 | { Name::view(v.clone(), holes, app, ctx) } 110 |
111 |
112 | { Term::view(body.clone(), holes, app, ctx) } 113 |
114 |
115 | } 116 | } 117 | Stmt::While { c, b1 } => { 118 | let mut cls = vec!["while", "stmt"]; 119 | let is_selected = match &app.selected { 120 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 121 | _ => false, 122 | }; 123 | if is_selected { 124 | cls.push("selected"); 125 | } 126 | let is_hovered = match &app.hovered { 127 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 128 | _ => false, 129 | }; 130 | if is_hovered { 131 | cls.push("hovered"); 132 | } 133 | let body_outer = vec!["whileBodyOuter"]; 134 | let t = t.clone(); 135 | let tc = t.clone(); 136 | let cl = ctx 137 | .link() 138 | .callback_once(move |_| Msg::Select(Selection::SStmt(t))); 139 | let cl2 = ctx 140 | .link() 141 | .callback_once(move |_| Msg::Hover(Selection::SStmt(tc))); 142 | html! { 143 |
145 |
146 | { Term::view(c.clone(), holes, app, ctx) } 147 |
148 |
149 | { Program::view(b1.clone(), holes, app, ctx) } 150 |
151 |
152 | } 153 | } 154 | Stmt::If { c, b1 } => { 155 | let mut cls = vec!["if", "stmt"]; 156 | let is_selected = match &app.selected { 157 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 158 | _ => false, 159 | }; 160 | if is_selected { 161 | cls.push("selected"); 162 | } 163 | let is_hovered = match &app.hovered { 164 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 165 | _ => false, 166 | }; 167 | if is_hovered { 168 | cls.push("hovered"); 169 | } 170 | let body_outer = vec!["ifBodyOuter"]; 171 | let t = t.clone(); 172 | let tc = t.clone(); 173 | let cl = ctx 174 | .link() 175 | .callback_once(move |_| Msg::Select(Selection::SStmt(t))); 176 | let cl2 = ctx 177 | .link() 178 | .callback_once(move |_| Msg::Hover(Selection::SStmt(tc))); 179 | html! { 180 |
182 |
183 | { Term::view(c.clone(), holes, app, ctx) } 184 |
185 |
186 | { Program::view(b1.clone(), holes, app, ctx) } 187 |
188 |
189 | } 190 | } 191 | Stmt::IfElse { c, b1, b2 } => { 192 | let mut cls = vec!["ifelse", "stmt"]; 193 | let is_selected = match &app.selected { 194 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 195 | _ => false, 196 | }; 197 | if is_selected { 198 | cls.push("selected"); 199 | } 200 | let is_hovered = match &app.hovered { 201 | Some(Selection::SStmt(u)) => Rc::ptr_eq(&t, &u), 202 | _ => false, 203 | }; 204 | if is_hovered { 205 | cls.push("hovered"); 206 | } 207 | let body_outer = vec!["ifBodyOuter"]; 208 | let t = t.clone(); 209 | let tc = t.clone(); 210 | let cl = ctx 211 | .link() 212 | .callback_once(move |_| Msg::Select(Selection::SStmt(t))); 213 | let cl2 = ctx 214 | .link() 215 | .callback_once(move |_| Msg::Hover(Selection::SStmt(tc))); 216 | html! { 217 |
219 |
220 | { Term::view(c.clone(), holes, app, ctx) } 221 |
222 |
223 | { Program::view(b1.clone(), holes, app, ctx) } 224 |
225 |
226 | { Program::view(b2.clone(), holes, app, ctx) } 227 |
228 |
229 | } 230 | } 231 | } 232 | } 233 | } 234 | 235 | impl Program { 236 | pub fn view(t: R, holes: &Vec, app: &App, ctx: &Context) -> Html { 237 | match &*(t.borrow()) { 238 | Program(ps) => { 239 | let mut cls = vec!["program"]; 240 | let is_selected = match &app.selected { 241 | Some(Selection::SProgram(u)) => Rc::ptr_eq(&t, &u), 242 | _ => false, 243 | }; 244 | if is_selected { 245 | cls.push("selected"); 246 | } 247 | let is_hovered = match &app.hovered { 248 | Some(Selection::SProgram(u)) => Rc::ptr_eq(&t, &u), 249 | _ => false, 250 | }; 251 | if is_hovered { 252 | cls.push("hovered"); 253 | } 254 | let t = t.clone(); 255 | let tc = t.clone(); 256 | html! { 257 |
260 | { for ps.iter().map(|k| Stmt::view(k.clone(), holes, app, ctx)) } 261 |
262 | } 263 | } 264 | } 265 | } 266 | } 267 | 268 | impl Name { 269 | pub fn view(t: R, holes: &Vec, app: &App, ctx: &Context) -> Html { 270 | match &*t.borrow_mut() { 271 | Name::Hole => { 272 | let mut cls = vec!["namehole"]; 273 | let is_selected = match &app.selected { 274 | Some(Selection::SName(u)) => Rc::ptr_eq(&t, &u), 275 | _ => false, 276 | }; 277 | if is_selected { 278 | cls.push("selected"); 279 | } 280 | let is_hovered = match &app.hovered { 281 | Some(Selection::SName(u)) => Rc::ptr_eq(&t, &u), 282 | _ => false, 283 | }; 284 | if is_hovered { 285 | cls.push("hovered"); 286 | } 287 | let t = t.clone(); 288 | let tc = t.clone(); 289 | let sel = Selection::SName(t.clone()); 290 | let i = holes.iter().position(move |u| sel.eq(u)).unwrap(); 291 | html! { 292 |
295 | {i} 296 | { " " } 297 |
298 | } 299 | } 300 | Name::Named { name: x } => { 301 | let mut cls = vec!["name"]; 302 | let is_selected = match &app.selected { 303 | Some(Selection::SName(u)) => Rc::ptr_eq(&t, &u), 304 | _ => false, 305 | }; 306 | if is_selected { 307 | cls.push("selected"); 308 | } 309 | let is_hovered = match &app.hovered { 310 | Some(Selection::SName(u)) => Rc::ptr_eq(&t, &u), 311 | _ => false, 312 | }; 313 | if is_hovered { 314 | cls.push("hovered"); 315 | } 316 | let t = t.clone(); 317 | let tc = t.clone(); 318 | html! { 319 |
322 | { x } 323 |
324 | } 325 | } 326 | } 327 | } 328 | } 329 | 330 | impl Term { 331 | pub fn view(t: R, holes: &Vec, app: &App, ctx: &Context) -> Html { 332 | match &*t.borrow_mut() { 333 | Term::Hole => { 334 | let mut cls = vec!["hole"]; 335 | let is_selected = match &app.selected { 336 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 337 | _ => false, 338 | }; 339 | if is_selected { 340 | cls.push("selected"); 341 | } 342 | let is_hovered = match &app.hovered { 343 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 344 | _ => false, 345 | }; 346 | if is_hovered { 347 | cls.push("hovered"); 348 | } 349 | let t = t.clone(); 350 | let tc = t.clone(); 351 | let sel = Selection::STerm(t.clone()); 352 | let i = holes.iter().position(move |u| sel.eq(u)).unwrap(); 353 | html! { 354 |
357 | {i} 358 | { " " } 359 |
360 | } 361 | } 362 | 363 | Term::Const { c } => { 364 | let mut cls = vec!["const"]; 365 | let is_selected = match &app.selected { 366 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 367 | _ => false, 368 | }; 369 | if is_selected { 370 | cls.push("selected"); 371 | } 372 | let is_hovered = match &app.hovered { 373 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 374 | _ => false, 375 | }; 376 | if is_hovered { 377 | cls.push("hovered"); 378 | } 379 | let t = t.clone(); 380 | let tc = t.clone(); 381 | html! { 382 |
385 | { c.clone().view() } 386 |
387 | } 388 | } 389 | 390 | Term::Var { v } => { 391 | let mut cls = vec!["var"]; 392 | let is_selected = match &app.selected { 393 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 394 | _ => false, 395 | }; 396 | if is_selected { 397 | cls.push("selected"); 398 | } 399 | let is_hovered = match &app.hovered { 400 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 401 | _ => false, 402 | }; 403 | if is_hovered { 404 | cls.push("hovered"); 405 | } 406 | let t = t.clone(); 407 | let tc = t.clone(); 408 | html! { 409 |
412 | { Name::view(v.clone(), holes, app, ctx) } 413 |
414 | } 415 | } 416 | 417 | Term::App { t1, t2 } => { 418 | let mut cls = vec!["app"]; 419 | let is_selected = match &app.selected { 420 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 421 | _ => false, 422 | }; 423 | if is_selected { 424 | cls.push("selected"); 425 | } 426 | let is_hovered = match &app.hovered { 427 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 428 | _ => false, 429 | }; 430 | if is_hovered { 431 | cls.push("hovered"); 432 | } 433 | let t = t.clone(); 434 | let tc = t.clone(); 435 | html! { 436 |
439 | { Term::view(t1.clone(), holes, app, ctx) } 440 | { Term::view(t2.clone(), holes, app, ctx) } 441 |
442 | } 443 | } 444 | 445 | Term::Lam { v, body } => { 446 | let mut cls = vec!["lam"]; 447 | let is_selected = match &app.selected { 448 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 449 | _ => false, 450 | }; 451 | if is_selected { 452 | cls.push("selected"); 453 | } 454 | let is_hovered = match &app.hovered { 455 | Some(Selection::STerm(u)) => Rc::ptr_eq(&t, &u), 456 | _ => false, 457 | }; 458 | if is_hovered { 459 | cls.push("hovered"); 460 | } 461 | let mut body_outer = vec!["bodyOuter"]; 462 | let has_lam_inside = match *(body.borrow()) { 463 | Term::Lam { v: _, body: _ } => true, 464 | _ => false, 465 | }; 466 | if !has_lam_inside { 467 | body_outer.push("bodyOuterLast"); 468 | } 469 | let t = t.clone(); 470 | let tc = t.clone(); 471 | let cl = ctx 472 | .link() 473 | .callback_once(move |_| Msg::Select(Selection::STerm(t))); 474 | let cl2 = ctx 475 | .link() 476 | .callback_once(move |_| Msg::Hover(Selection::STerm(tc))); 477 | html! { 478 |
480 |
481 | { Name::view(v.clone(), holes, app, ctx) } 482 |
483 |
484 | { Term::view(body.clone(), holes, app, ctx) } 485 |
486 |
487 | } 488 | } 489 | } 490 | } 491 | } 492 | 493 | impl Cmd { 494 | pub fn view(self) -> Html { 495 | match self { 496 | Cmd::Done => { 497 | html! { } 498 | } 499 | Cmd::Failed => { 500 | html! { } 501 | } 502 | Cmd::Text(c) => { 503 | html! { {c} } 504 | } 505 | Cmd::Number(c) => { 506 | html! { {c} } 507 | } 508 | _ => { 509 | html! { { format!("{:?}", self) } } 510 | } 511 | } 512 | } 513 | } 514 | 515 | impl Cmds { 516 | pub fn view(self) -> Html { 517 | html! { 518 | 519 | { for self.iter().map(|k| k.clone().view()) } 520 | 521 | } 522 | } 523 | } 524 | 525 | impl ErrorMsg { 526 | pub fn view(&self) -> Html { 527 | match self { 528 | ErrorMsg::Intro => { 529 | html! { 530 | 531 | {"This is "} 532 | 533 | {"dilim"} 534 | 535 | {", a structural editor. Press "} 536 | {"h"} 537 | {" for help. Press "} 538 | {"Esc"} 539 | {" to dismiss this message."} 540 | 541 | } 542 | } 543 | ErrorMsg::Impossible => { 544 | html! { {"Impossible."} } 545 | } 546 | ErrorMsg::NumberTooBig => { 547 | html! { {"The number is too big."} } 548 | } 549 | ErrorMsg::NotANameHole => { 550 | html! { {"Not a name hole!"} } 551 | } 552 | ErrorMsg::NotATermHole => { 553 | html! { {"Not a term hole!"} } 554 | } 555 | ErrorMsg::NotAStmtHole => { 556 | html! { {"Not a statement hole!"} } 557 | } 558 | ErrorMsg::NotAHole => { 559 | html! { {"Not a hole!"} } 560 | } 561 | ErrorMsg::NotAProgramOrStmtHole => { 562 | html! { {"Not a program or statement hole!"} } 563 | } 564 | ErrorMsg::NotAStmt => { 565 | html! { {"Not a statement!"} } 566 | } 567 | ErrorMsg::NotAProgram => { 568 | html! { {"Not a program!"} } 569 | } 570 | ErrorMsg::NotATermStmt => { 571 | html! { {"Not a term statement!"} } 572 | } 573 | ErrorMsg::NothingSelected => { 574 | html! { {"Nothing is selected!"} } 575 | } 576 | ErrorMsg::NothingInClipboard => { 577 | html! { {"There is nothing in the clipboard!"} } 578 | } 579 | ErrorMsg::NotNameInClipboard => { 580 | html! { {"Not a name in the clipboard!"} } 581 | } 582 | ErrorMsg::NotTermInClipboard => { 583 | html! { {"Not a term in the clipboard!"} } 584 | } 585 | ErrorMsg::NotStmtInClipboard => { 586 | html! { {"Not a statement in the clipboard!"} } 587 | } 588 | ErrorMsg::NotProgramInClipboard => { 589 | html! { {"Not a program in the clipboard!"} } 590 | } 591 | ErrorMsg::NothingToInsert => { 592 | html! { {"There is nothing to insert!"} } 593 | } 594 | ErrorMsg::NowhereToGo => { 595 | html! { {"Nowhere to go!"} } 596 | } 597 | ErrorMsg::NoSuchCommand => { 598 | html! { {"No such command!"} } 599 | } 600 | } 601 | } 602 | } 603 | 604 | impl App { 605 | pub fn status(&self) -> Html { 606 | let len = self.holes().len(); 607 | html! { 608 | format!("{} hole{}", len, if len == 1 {""} else {"s"}) 609 | } 610 | } 611 | } 612 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Component, Context, Html}; 2 | use gloo::console::{self}; 3 | 4 | use std::rc::Rc; 5 | 6 | mod editor; 7 | use editor::types::*; 8 | use editor::help as H; 9 | use editor::key_listener; 10 | 11 | use wasm_bindgen::prelude::*; 12 | 13 | #[wasm_bindgen] 14 | extern "C" { 15 | pub fn save(content: &str, file_name: &str); 16 | } 17 | 18 | impl Component for App { 19 | type Message = Msg; 20 | type Properties = (); 21 | 22 | fn create(_ctx: &Context) -> Self { 23 | let h = Stmt::Hole; 24 | let t = Program(vec![Rnew(h)]); 25 | Self { 26 | page: Page::Main, 27 | program: Rnew(t), 28 | selected: None, 29 | hovered: None, 30 | clipboard: None, 31 | keys: Vec::new(), 32 | suggestions: Vec::new(), 33 | error: Some(ErrorMsg::Intro) 34 | } 35 | } 36 | 37 | fn update(&mut self, _: &Context, msg: Self::Message) -> bool { 38 | match msg { 39 | Msg::Unselect => { 40 | self.selected = None; 41 | true 42 | } 43 | Msg::Select(s) => { 44 | self.selected = Some(s); 45 | true 46 | } 47 | Msg::Unhover => { 48 | self.hovered = None; 49 | true 50 | } 51 | Msg::Hover(s) => { 52 | self.hovered = Some(s); 53 | true 54 | } 55 | Msg::KeyboardMsg(e) => { 56 | let key_code = e.key_code(); 57 | console::info!(key_code); 58 | console::info!(e.key()); 59 | 60 | self.error = None; 61 | self.suggestions = Vec::new(); 62 | let len = self.keys.len(); 63 | if Cmd::cmd_ended(&self.keys) { 64 | self.keys = Vec::new(); 65 | } 66 | 67 | match self.keys.first() { 68 | Some(Cmd::Insert) 69 | if (97..=122).contains(&key_code) /* a-z */ || key_code == 8 /* backspace */ => { 70 | if let Some(Cmd::Text(ref mut s)) = self.keys.last_mut() { 71 | if key_code == 8 { 72 | if s.len() > 1 { 73 | s.pop(); 74 | } else { 75 | self.keys = vec![Cmd::Insert]; 76 | } 77 | } else { 78 | s.push_str(&e.key()); 79 | } 80 | } else { 81 | if key_code != 8 { 82 | self.keys.push(Cmd::Text(e.key())); 83 | } 84 | } 85 | } 86 | _ => { 87 | let mut keys = self.keys.clone(); 88 | match keys.first() { 89 | Some(Cmd::Number(_)) => { keys.remove(0); } 90 | _ => {} 91 | } 92 | match key_code { 93 | 27 /* escape */ => { 94 | self.keys = Vec::new(); 95 | self.suggestions = Vec::new(); 96 | self.page = Page::Main; 97 | } 98 | 9 /* tab */ => { 99 | self.suggestions = self.complete_command(keys); 100 | } 101 | 13 /* enter */ => { 102 | self.keys.push(Cmd::Enter); 103 | } 104 | d if (48..=57).contains(&d) /* 0-9 */ => { 105 | let m = (d - 48) as u8; 106 | if let Some(Cmd::Number(n)) = self.keys.last_mut() { 107 | if *n < 20 { 108 | *n *= 10; 109 | *n += m; 110 | } else { 111 | self.error = Some(ErrorMsg::NumberTooBig); 112 | } 113 | } else { 114 | self.keys.push(Cmd::Number(m)); 115 | } 116 | return true; 117 | } 118 | _ => { 119 | let options = self.complete_command(keys); 120 | for cmd in options { 121 | match cmd { 122 | Cmd::Number(_) => { continue; } 123 | Cmd::Text(_) => { continue; } 124 | _ => {} 125 | } 126 | let c = cmd.to_string().to_lowercase().chars().next().unwrap() as u32; 127 | if key_code == c { 128 | self.keys.push(cmd); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | match &self.selected { 136 | Some(u) if (37..=40).contains(&key_code) => { 137 | match key_code { 138 | 37 /* left arrow */ => { 139 | let next = self.prev_hole(u.clone()); 140 | match next { 141 | Some(s) => { self.selected = Some(s); } 142 | _ => {} 143 | } 144 | } 145 | 38 /* up arrow */ => {} 146 | 39 /* right arrow */ => { 147 | let next = self.next_hole(u.clone()); 148 | match next { 149 | Some(s) => { self.selected = Some(s); } 150 | _ => {} 151 | } 152 | } 153 | 40 /* down arrow */ => {} 154 | _ => {} 155 | } 156 | } 157 | _ => {} 158 | } 159 | if self.keys.len() != len { 160 | self.run_command(); 161 | } 162 | true 163 | } 164 | } 165 | } 166 | 167 | fn view(&self, ctx: &Context) -> Html { 168 | let link = ctx.link().clone(); 169 | let holes = self.holes(); 170 | html! { 171 | <> 172 | 173 |
176 | { Program::view(self.program.clone(), &holes, self, ctx) } 177 |
178 | if self.page == Page::Help { 179 | { H::help() } 180 | } 181 | 192 |
193 | if let Some(e) = &self.error { { e.view() } } 194 |
195 | 196 | } 197 | } 198 | } 199 | 200 | fn main() { 201 | yew::set_event_bubbling(false); 202 | yew::start_app::(); 203 | } 204 | --------------------------------------------------------------------------------