├── .gitignore ├── Cargo.toml ├── README.md ├── UNLICENSE └── src ├── devices.rs ├── lib.rs └── uxn.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ruxn" 3 | version = "0.1.0" 4 | authors = ["Morgan Arnold "] 5 | description = "An implementation of the Uxn stack-machine written in Rust." 6 | categories = ["emulators"] 7 | keywords = ["uxn", "varvara"] 8 | repository = "https://git.sr.ht/~mra/ruxn" 9 | license = "Unlicense" 10 | edition = "2021" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruxn 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/ruxn?style=flat-square&logo=rust)](https://crates.io/crates/ruxn) 4 | [![Crates.io](https://img.shields.io/crates/l/ruxn?style=flat-square)](./UNLICENSE) 5 | 6 | ruxn is an emulator for [the Uxn stack-machine](https://100r.co/site/uxn.html), designed to make the creation and emulation of Uxn-based computers simple and fun. 7 | 8 | # Installation 9 | 10 | Either run 11 | ``` 12 | cargo add ruxn 13 | ``` 14 | or modify your `Cargo.toml` 15 | ``` 16 | ruxn = "0.1.0" 17 | ``` 18 | 19 | # Usage 20 | 21 | 22 | 23 | # Contributing 24 | 25 | Contributrions are welcome! Please open an issue or submit a pull request. 26 | 27 | # License 28 | 29 | This is free and unencumbered software released into the public domain. See the [UNLICENSE](./UNLICENSE) file or [unlicense.org](https://unlicense.org/) for details. 30 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/devices.rs: -------------------------------------------------------------------------------- 1 | pub trait Device { 2 | fn read_byte(&mut self, address: u8) -> u8; 3 | fn write_byte(&mut self, address: u8, byte: u8); 4 | fn read_short(&mut self, address: u8) -> u16; 5 | fn write_short(&mut self, address: u8, short: u16); 6 | } 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod devices; 2 | pub mod uxn; 3 | -------------------------------------------------------------------------------- /src/uxn.rs: -------------------------------------------------------------------------------- 1 | use std::ops::IndexMut; 2 | use std::sync::mpsc::{channel, Receiver, Sender}; 3 | 4 | use crate::devices::Device; 5 | 6 | type Result = std::result::Result; 7 | 8 | /// All possible errors which may occur while a Uxntal program is being run, as specified 9 | /// [here](https://wiki.xxiivv.com/site/uxntal_errors.html). 10 | /// 11 | /// As detailed in the specification, [`UxnError::Underflow`](UxnError#variant.Underflow) and 12 | /// [`UxnError::Overflow`](`UxnError#variant.Overflow`) occur only when an underflow or overflow 13 | /// occurs in one of the stacks of the Uxn stack-machine. In particular, it is not detailed whether 14 | /// or not the *program counter* is allowed to underflow or overflow. This does not currently raise 15 | /// an error, but this is subject to change if the specification is clarified. 16 | #[derive(Debug, PartialEq, Eq)] 17 | pub enum UxnError { 18 | Underflow, 19 | Overflow, 20 | ZeroDiv, 21 | } 22 | 23 | /// A stack able to hold exactly 0xff bytes, in accordance with the capacity of the working and 24 | /// return stacks of the Uxn stack-machine, as specified 25 | /// [here](https://wiki.xxiivv.com/site/uxntal_stacks.html). 26 | #[derive(Debug)] 27 | pub struct UxnStack { 28 | // HACK: Giving s a capacity of 256 rather than 255 is a bit of a silly hack which gives quite 29 | // a few benefits. Since sp points to the location on the stack where the next byte will be 30 | // placed, we can tell that the stack is at capacity simply by checking whether or not 31 | // incrementing sp would cause an overflow. It also means that indexing sp with a u8 is always 32 | // valid, so we can dispense with bounds-checking and use get_unchecked. 33 | s: [u8; 0x100], 34 | sp: u8, 35 | } 36 | 37 | impl UxnStack { 38 | /// Constructs a new, empty [`UxnStack`]. 39 | pub fn new() -> Self { 40 | UxnStack { 41 | s: [0x00; 0x100], 42 | sp: 0, 43 | } 44 | } 45 | 46 | // TODO: Write proper documentation. 47 | fn update_stack_pointer( 48 | &mut self, 49 | operand_bytes: u8, 50 | result_bytes: u8, 51 | keep_mode: bool, 52 | ) -> Result<()> { 53 | // Check that there are enough bytes on the stack to perform the operation. 54 | if operand_bytes > self.sp { 55 | return Err(UxnError::Underflow); 56 | } 57 | 58 | // Compute the new stack pointer. If keep_mode is true, then we expect result_bytes to be 59 | // pushed to the stack. If keep_mode is false, then we expect operand_bytes to be popped 60 | // from the stack, and then for result_bytes to be pushed to the stack. 61 | let new_sp = if keep_mode { 62 | self.sp 63 | } else { 64 | // The subtraction of operand_bytes does not need to be checked, as we have already 65 | // checked that operand_bytes <= sp. 66 | self.sp.wrapping_sub(operand_bytes) 67 | } 68 | .checked_add(result_bytes) 69 | .ok_or(UxnError::Overflow)?; 70 | 71 | self.sp = new_sp; 72 | 73 | Ok(()) 74 | } 75 | 76 | // TODO: Write proper documentation. 77 | fn get_byte(&self, offset: u8) -> u8 { 78 | unsafe { 79 | // This never fails, because the index is guaranteed to be in the range 0..=255. 80 | *self.s.get_unchecked(self.sp.wrapping_sub(offset) as usize) 81 | } 82 | } 83 | 84 | // TODO: Write proper documentation. 85 | fn get_short(&self, offset: u8) -> u16 { 86 | let msb = self.get_byte(offset.wrapping_add(1)); 87 | let lsb = self.get_byte(offset); 88 | u16::from_be_bytes([msb, lsb]) 89 | } 90 | 91 | // TODO: Write proper documentation. 92 | fn set_byte(&mut self, offset: u8, value: u8) { 93 | unsafe { 94 | *self 95 | .s 96 | .get_unchecked_mut(self.sp.wrapping_sub(offset) as usize) = value; 97 | } 98 | } 99 | 100 | // TODO: Write proper documentation. 101 | fn set_short(&mut self, offset: u8, value: u16) { 102 | let value = value.to_be_bytes(); 103 | let msb = value[0]; 104 | let lsb = value[1]; 105 | self.set_byte(offset.wrapping_add(1), msb); 106 | self.set_byte(offset, lsb); 107 | } 108 | 109 | // TODO: UxnStack should probably have a saner set of methods. Someone using the Uxn struct may 110 | // conceivably want to interact with the CPU's internal stacks, so they should have a more 111 | // extensive and well-thought-out interfact. 112 | } 113 | 114 | pub struct Uxn { 115 | pub ram: T, 116 | pub pc: u16, 117 | pub ws: UxnStack, 118 | pub rs: UxnStack, 119 | 120 | vrx: Receiver, 121 | vtx: Sender, 122 | 123 | pub devices: [Option>; 16], 124 | } 125 | 126 | impl Uxn 127 | where 128 | T: IndexMut, 129 | { 130 | pub fn new(ram: T) -> Self { 131 | let (vtx, vrx) = channel(); 132 | Uxn { 133 | ram, 134 | pc: 0x0100, 135 | ws: UxnStack::new(), 136 | rs: UxnStack::new(), 137 | 138 | vrx, 139 | vtx, 140 | 141 | devices: [ 142 | None, None, None, None, None, None, None, None, None, None, None, None, None, None, 143 | None, None, 144 | ], 145 | } 146 | } 147 | 148 | pub fn get_vector_queue_sender(&self) -> Sender { 149 | self.vtx.clone() 150 | } 151 | 152 | fn step(&mut self) -> Result { 153 | // Fetch instruction and increment program counter. 154 | let instruction = self.ram[self.pc]; 155 | self.pc = self.pc.wrapping_add(1); 156 | 157 | // Some useful boolean flags. 158 | let keep_mode = (instruction & 0b10000000) != 0; 159 | let return_mode = (instruction & 0b01000000) != 0; 160 | let immediate = (instruction & 0b00011111) == 0; 161 | 162 | // In almost all cases (with the exception of JSR, STH, and JSI), the stack upon which we 163 | // operate depends only on whether or not the opcode specifies return mode. 164 | let stack = if return_mode { 165 | &mut self.rs 166 | } else { 167 | &mut self.ws 168 | }; 169 | 170 | // Mask off the keep and return mode bits of the instruction, leaving only the short mode 171 | // and opcode bits. We only want to apply this mask if the instruction is not an immediate 172 | // instruction, as if it is immediate then all of the bits are necessary to identify the 173 | // instruction. 174 | let masked_instruction = if immediate { 175 | instruction 176 | } else { 177 | instruction & 0b00111111 178 | }; 179 | 180 | // For the sake of avoiding repetition in the match statement, it is worth defining the 181 | // stack variables upon which we will be operating here. The names for these variables are 182 | // taken from the reference Uxn implementation at 183 | // https://git.sr.ht/~rabbits/uxn11/blob/main/src/uxn.c. 184 | // FIXME: These variable names should be changed at some point. They are not good. 185 | let t = stack.get_byte(1); 186 | let n = stack.get_byte(2); 187 | let l = stack.get_byte(3); 188 | let h2 = stack.get_short(2); 189 | let t2 = stack.get_short(1); 190 | let n2 = stack.get_short(3); 191 | let l2 = stack.get_short(5); 192 | 193 | // HACK: There are definitely some things here that could be tidier. 194 | match masked_instruction { 195 | // Immediate instructions. 196 | 197 | // BRK 198 | 0x00 => { 199 | return Ok(true); 200 | } 201 | 202 | // JCI 203 | 0x20 => { 204 | let msb = self.ram[self.pc]; 205 | let lsb = self.ram[self.pc.wrapping_add(1)]; 206 | self.pc = self.pc.wrapping_add(2); 207 | stack.update_stack_pointer(1, 0, false)?; 208 | if t != 0 { 209 | self.pc = self.pc.wrapping_add(u16::from_be_bytes([msb, lsb])); 210 | } 211 | } 212 | 213 | // JMI 214 | 0x40 => { 215 | let msb = self.ram[self.pc]; 216 | let lsb = self.ram[self.pc.wrapping_add(1)]; 217 | self.pc = self.pc.wrapping_add(2); 218 | self.pc = self.pc.wrapping_add(u16::from_be_bytes([msb, lsb])); 219 | } 220 | 221 | // JSI 222 | 0x60 => { 223 | let msb = self.ram[self.pc]; 224 | let lsb = self.ram[self.pc.wrapping_add(1)]; 225 | self.pc = self.pc.wrapping_add(2); 226 | self.rs.update_stack_pointer(0, 2, false)?; 227 | self.rs.set_short(1, self.pc); 228 | self.pc = self.pc.wrapping_add(u16::from_be_bytes([msb, lsb])); 229 | } 230 | 231 | // LIT 232 | 0x80 => { 233 | stack.update_stack_pointer(0, 1, true)?; 234 | stack.set_byte(1, self.ram[self.pc]); 235 | self.pc = self.pc.wrapping_add(1); 236 | } 237 | 238 | // LIT2 239 | 0xa0 => { 240 | stack.update_stack_pointer(0, 2, true)?; 241 | let msb = self.ram[self.pc]; 242 | let lsb = self.ram[self.pc.wrapping_add(1)]; 243 | stack.set_short(1, u16::from_be_bytes([msb, lsb])); 244 | self.pc = self.pc.wrapping_add(2); 245 | } 246 | 247 | // LITr 248 | 0xc0 => { 249 | stack.update_stack_pointer(0, 1, true)?; 250 | stack.set_byte(1, self.ram[self.pc]); 251 | self.pc = self.pc.wrapping_add(1); 252 | } 253 | 254 | // LIT2r 255 | 0xe0 => { 256 | stack.update_stack_pointer(0, 2, true)?; 257 | let msb = self.ram[self.pc]; 258 | let lsb = self.ram[self.pc.wrapping_add(1)]; 259 | stack.set_short(1, u16::from_be_bytes([msb, lsb])); 260 | self.pc = self.pc.wrapping_add(2); 261 | } 262 | 263 | // Non-immediate instructions. 264 | 265 | // INC(2) 266 | 0x01 => { 267 | stack.update_stack_pointer(1, 1, keep_mode)?; 268 | stack.set_byte(1, t + 1); 269 | } 270 | 0x21 => { 271 | stack.update_stack_pointer(2, 2, keep_mode)?; 272 | stack.set_short(1, t2 + 1); 273 | } 274 | 275 | // POP(2) 276 | 0x02 => { 277 | stack.update_stack_pointer(1, 0, keep_mode)?; 278 | } 279 | 0x22 => { 280 | stack.update_stack_pointer(2, 0, keep_mode)?; 281 | } 282 | 283 | // NIP(2) 284 | 0x03 => { 285 | stack.update_stack_pointer(2, 1, keep_mode)?; 286 | stack.set_byte(1, t); 287 | } 288 | 0x23 => { 289 | stack.update_stack_pointer(4, 2, keep_mode)?; 290 | stack.set_short(1, t2); 291 | } 292 | 293 | // SWP(2) 294 | 0x04 => { 295 | stack.update_stack_pointer(2, 2, keep_mode)?; 296 | stack.set_byte(1, n); 297 | stack.set_byte(2, t); 298 | } 299 | 0x24 => { 300 | stack.update_stack_pointer(4, 4, keep_mode)?; 301 | stack.set_short(1, n2); 302 | stack.set_short(3, t2); 303 | } 304 | 305 | // ROT(2) 306 | 0x05 => { 307 | stack.update_stack_pointer(3, 3, keep_mode)?; 308 | stack.set_byte(1, l); 309 | stack.set_byte(2, t); 310 | stack.set_byte(3, n); 311 | } 312 | 0x25 => { 313 | stack.update_stack_pointer(6, 6, keep_mode)?; 314 | stack.set_short(1, l2); 315 | stack.set_short(3, t2); 316 | stack.set_short(5, n2); 317 | } 318 | 319 | // DUP(2) 320 | 0x06 => { 321 | stack.update_stack_pointer(1, 2, keep_mode)?; 322 | stack.set_byte(1, t); 323 | stack.set_byte(2, t); 324 | } 325 | 0x26 => { 326 | stack.update_stack_pointer(2, 4, keep_mode)?; 327 | stack.set_short(1, t2); 328 | stack.set_short(3, t2); 329 | } 330 | 331 | // OVR(2) 332 | 0x07 => { 333 | stack.update_stack_pointer(2, 3, keep_mode)?; 334 | stack.set_byte(1, n); 335 | stack.set_byte(2, t); 336 | stack.set_byte(3, n); 337 | } 338 | 0x27 => { 339 | stack.update_stack_pointer(4, 6, keep_mode)?; 340 | stack.set_short(1, n2); 341 | stack.set_short(3, t2); 342 | stack.set_short(5, n2); 343 | } 344 | 345 | // EQU(2) 346 | 0x08 => { 347 | stack.update_stack_pointer(2, 1, keep_mode)?; 348 | stack.set_byte(1, (n == t).into()); 349 | } 350 | 0x28 => { 351 | stack.update_stack_pointer(4, 1, keep_mode)?; 352 | stack.set_byte(1, (n2 == t2).into()); 353 | } 354 | 355 | // NEQ(2) 356 | 0x09 => { 357 | stack.update_stack_pointer(2, 1, keep_mode)?; 358 | stack.set_byte(1, (n != t).into()); 359 | } 360 | 0x29 => { 361 | stack.update_stack_pointer(4, 1, keep_mode)?; 362 | stack.set_byte(1, (n2 != t2).into()); 363 | } 364 | 365 | // GTH(2) 366 | 0x0a => { 367 | stack.update_stack_pointer(2, 1, keep_mode)?; 368 | stack.set_byte(1, (n > t).into()); 369 | } 370 | 0x2a => { 371 | stack.update_stack_pointer(4, 1, keep_mode)?; 372 | stack.set_byte(1, (n2 > t2).into()); 373 | } 374 | 375 | // LTH(2) 376 | 0x0b => { 377 | stack.update_stack_pointer(2, 1, keep_mode)?; 378 | stack.set_byte(1, (n < t).into()); 379 | } 380 | 0x2b => { 381 | stack.update_stack_pointer(4, 1, keep_mode)?; 382 | stack.set_byte(1, (n2 < t2).into()); 383 | } 384 | 385 | // JMP(2) 386 | 0x0c => { 387 | stack.update_stack_pointer(1, 0, keep_mode)?; 388 | self.pc = self.pc.wrapping_add_signed(i8::from_be_bytes([t]).into()); 389 | } 390 | 0x2c => { 391 | stack.update_stack_pointer(2, 0, keep_mode)?; 392 | self.pc = t2; 393 | } 394 | 395 | // JCN(2) 396 | 0x0d => { 397 | stack.update_stack_pointer(2, 0, keep_mode)?; 398 | if n != 0 { 399 | self.pc = self.pc.wrapping_add_signed(i8::from_be_bytes([t]).into()); 400 | } 401 | } 402 | 0x2d => { 403 | stack.update_stack_pointer(3, 0, keep_mode)?; 404 | if l != 0 { 405 | self.pc = t2; 406 | } 407 | } 408 | 409 | // JSR(2) 410 | 0x0e => { 411 | stack.update_stack_pointer(1, 0, keep_mode)?; 412 | self.rs.update_stack_pointer(0, 2, false)?; 413 | self.rs.set_short(1, self.pc); 414 | self.pc = self.pc.wrapping_add_signed(i8::from_be_bytes([t]).into()); 415 | } 416 | 0x2e => { 417 | stack.update_stack_pointer(2, 0, keep_mode)?; 418 | self.rs.update_stack_pointer(0, 2, false)?; 419 | self.rs.set_short(1, self.pc); 420 | self.pc = t2 421 | } 422 | 423 | // STH(2) 424 | 0x0f => { 425 | stack.update_stack_pointer(1, 0, keep_mode)?; 426 | let other_stack = if return_mode { 427 | &mut self.ws 428 | } else { 429 | &mut self.rs 430 | }; 431 | other_stack.update_stack_pointer(0, 1, false)?; 432 | other_stack.set_byte(1, t); 433 | } 434 | 0x2f => { 435 | stack.update_stack_pointer(2, 0, keep_mode)?; 436 | let other_stack = if return_mode { 437 | &mut self.ws 438 | } else { 439 | &mut self.rs 440 | }; 441 | other_stack.update_stack_pointer(0, 2, false)?; 442 | other_stack.set_short(1, t2); 443 | } 444 | 445 | // LDZ(2) 446 | 0x10 => { 447 | stack.update_stack_pointer(1, 1, keep_mode)?; 448 | stack.set_byte(1, self.ram[t.into()]); 449 | } 450 | 0x30 => { 451 | stack.update_stack_pointer(1, 2, keep_mode)?; 452 | stack.set_byte(1, self.ram[t.wrapping_add(1).into()]); 453 | stack.set_byte(2, self.ram[t.into()]); 454 | } 455 | 456 | // STZ(2) 457 | 0x11 => { 458 | stack.update_stack_pointer(2, 0, keep_mode)?; 459 | self.ram[t.into()] = n; 460 | } 461 | 0x31 => { 462 | stack.update_stack_pointer(3, 0, keep_mode)?; 463 | self.ram[t.wrapping_add(1).into()] = n; 464 | self.ram[t.into()] = l; 465 | } 466 | 467 | // LDR(2) 468 | 0x12 => { 469 | stack.update_stack_pointer(1, 1, keep_mode)?; 470 | stack.set_byte( 471 | 1, 472 | self.ram[self.pc.wrapping_add_signed(i8::from_be_bytes([t]).into())], 473 | ); 474 | } 475 | 0x32 => { 476 | stack.update_stack_pointer(1, 2, keep_mode)?; 477 | stack.set_byte( 478 | 1, 479 | self.ram[self 480 | .pc 481 | .wrapping_add_signed(i8::from_be_bytes([t]).into()) 482 | .wrapping_add(1)], 483 | ); 484 | stack.set_byte( 485 | 2, 486 | self.ram[self.pc.wrapping_add_signed(i8::from_be_bytes([t]).into())], 487 | ); 488 | } 489 | 490 | // STR(2) 491 | 0x13 => { 492 | stack.update_stack_pointer(2, 0, keep_mode)?; 493 | self.ram[self.pc.wrapping_add_signed(i8::from_be_bytes([t]).into())] = n; 494 | } 495 | 0x33 => { 496 | stack.update_stack_pointer(3, 0, keep_mode)?; 497 | self.ram[self.pc.wrapping_add_signed(i8::from_be_bytes([t]).into())] = l; 498 | self.ram[self 499 | .pc 500 | .wrapping_add_signed(i8::from_be_bytes([t]).into()) 501 | .wrapping_add(1)] = n; 502 | } 503 | 504 | // LDA(2) 505 | 0x14 => { 506 | stack.update_stack_pointer(2, 1, keep_mode)?; 507 | stack.set_byte(1, self.ram[t2]); 508 | } 509 | 0x34 => { 510 | stack.update_stack_pointer(2, 2, keep_mode)?; 511 | stack.set_byte(1, self.ram[t2.wrapping_add(1)]); 512 | stack.set_byte(2, self.ram[t2]); 513 | } 514 | 515 | // STA(2) 516 | 0x15 => { 517 | stack.update_stack_pointer(3, 0, keep_mode)?; 518 | self.ram[t2] = l; 519 | } 520 | 0x35 => { 521 | stack.update_stack_pointer(4, 0, keep_mode)?; 522 | let value = n2.to_be_bytes(); 523 | self.ram[t2] = value[0]; 524 | self.ram[t2.wrapping_add(1)] = value[1]; 525 | } 526 | 527 | // DEI(2) 528 | 0x16 => { 529 | stack.update_stack_pointer(1, 1, keep_mode)?; 530 | if let Some(device) = &mut self.devices[((t & 0xf0) >> 4) as usize] { 531 | stack.set_byte(1, device.read_byte(t & 0x0f)); 532 | } else { 533 | // FIXME: This is kind of a lazy placeholder. I'm not totally sure how I want 534 | // this to work yet. 535 | stack.set_byte(1, 0x00); 536 | } 537 | } 538 | 0x36 => { 539 | stack.update_stack_pointer(1, 2, keep_mode)?; 540 | if let Some(device) = &mut self.devices[((t & 0xf0) >> 4) as usize] { 541 | stack.set_short(1, device.read_short(t & 0x0f)); 542 | } else { 543 | stack.set_short(1, 0x0000); 544 | } 545 | } 546 | 547 | // DEO(2) 548 | 0x17 => { 549 | stack.update_stack_pointer(2, 0, keep_mode)?; 550 | stack.update_stack_pointer(1, 1, keep_mode)?; 551 | if let Some(device) = &mut self.devices[((t & 0xf0) >> 4) as usize] { 552 | device.write_byte(t & 0x0f, n); 553 | } 554 | } 555 | 0x37 => { 556 | stack.update_stack_pointer(3, 0, keep_mode)?; 557 | if let Some(device) = &mut self.devices[((t & 0xf0) >> 4) as usize] { 558 | device.write_short(t & 0x0f, h2); 559 | } 560 | } 561 | 562 | // ADD(2) 563 | 0x18 => { 564 | stack.update_stack_pointer(2, 1, keep_mode)?; 565 | stack.set_byte(1, n.wrapping_add(t)); 566 | } 567 | 0x38 => { 568 | stack.update_stack_pointer(4, 2, keep_mode)?; 569 | stack.set_short(1, n2.wrapping_add(t2)); 570 | } 571 | 572 | // SUB(2) 573 | 0x19 => { 574 | stack.update_stack_pointer(2, 1, keep_mode)?; 575 | stack.set_byte(1, n.wrapping_sub(t)); 576 | } 577 | 0x39 => { 578 | stack.update_stack_pointer(4, 2, keep_mode)?; 579 | stack.set_short(1, n2.wrapping_sub(t2)); 580 | } 581 | 582 | // MUL(2) 583 | 0x1a => { 584 | stack.update_stack_pointer(2, 1, keep_mode)?; 585 | stack.set_byte(1, n.wrapping_mul(t)); 586 | } 587 | 0x3a => { 588 | stack.update_stack_pointer(4, 2, keep_mode)?; 589 | stack.set_short(1, n2.wrapping_mul(t2)); 590 | } 591 | 592 | // DIV(2) 593 | 0x1b => { 594 | let quotient = n.checked_div(t).ok_or(UxnError::ZeroDiv)?; 595 | stack.update_stack_pointer(2, 1, keep_mode)?; 596 | stack.set_byte(1, quotient); 597 | } 598 | 0x3b => { 599 | let quotient = n2.checked_div(t2).ok_or(UxnError::ZeroDiv)?; 600 | stack.update_stack_pointer(4, 2, keep_mode)?; 601 | stack.set_short(1, quotient); 602 | } 603 | 604 | // AND(2) 605 | 0x1c => { 606 | stack.update_stack_pointer(2, 1, keep_mode)?; 607 | stack.set_byte(1, n & t); 608 | } 609 | 0x3c => { 610 | stack.update_stack_pointer(4, 2, keep_mode)?; 611 | stack.set_short(1, n2 & t2); 612 | } 613 | 614 | // ORA(2) 615 | 0x1d => { 616 | stack.update_stack_pointer(2, 1, keep_mode)?; 617 | stack.set_byte(1, n | t); 618 | } 619 | 0x3d => { 620 | stack.update_stack_pointer(4, 2, keep_mode)?; 621 | stack.set_short(1, n2 | t2); 622 | } 623 | 624 | // EOR(2) 625 | 0x1e => { 626 | stack.update_stack_pointer(2, 1, keep_mode)?; 627 | stack.set_byte(1, n ^ t); 628 | } 629 | 0x3e => { 630 | stack.update_stack_pointer(4, 2, keep_mode)?; 631 | stack.set_short(1, n2 ^ t2); 632 | } 633 | 634 | // SFT(2) 635 | 0x1f => { 636 | stack.update_stack_pointer(2, 1, keep_mode)?; 637 | stack.set_byte(1, (n >> (t & 0x0f)) << ((t & 0xf0) >> 4)); 638 | } 639 | 0x3f => { 640 | stack.update_stack_pointer(3, 2, keep_mode)?; 641 | stack.set_short(1, (h2 >> (t & 0x0f)) << ((t & 0xf0) >> 4)); 642 | } 643 | 644 | // Impossible. 645 | _ => { 646 | unreachable!(); 647 | } 648 | } 649 | 650 | Ok(false) 651 | } 652 | 653 | pub fn run_vector(&mut self) -> Result<()> { 654 | loop { 655 | if self.step()? { 656 | break; 657 | } 658 | } 659 | 660 | Ok(()) 661 | } 662 | } 663 | 664 | #[cfg(test)] 665 | mod tests { 666 | use super::*; 667 | 668 | // A simple RAM implementation for testing purposes. The reason for using a HashMap for 669 | // addresses outside of the range of some vector is to allow the construction of TestRam 670 | // directly from the output of uxnasm. In testing, reads to and writes from RAM are fairly 671 | // uncommon, so this shouldn't substantially impact test performance. 672 | struct TestRam { 673 | program: Vec, 674 | variables: std::collections::HashMap, 675 | } 676 | 677 | impl std::ops::Index for TestRam { 678 | type Output = u8; 679 | 680 | fn index(&self, index: u16) -> &Self::Output { 681 | self.program 682 | .get(index.wrapping_sub(0x0100) as usize) 683 | .or_else(|| self.variables.get(&index)) 684 | .unwrap_or(&0x00) 685 | } 686 | } 687 | 688 | impl std::ops::IndexMut for TestRam { 689 | fn index_mut(&mut self, index: u16) -> &mut Self::Output { 690 | self.program 691 | .get_mut(index.wrapping_sub(0x0100) as usize) 692 | .unwrap_or_else(|| self.variables.entry(index).or_insert(0x00)) 693 | } 694 | } 695 | 696 | impl TestRam { 697 | fn from_tal(tal: &str) -> Self { 698 | let mut assembler = std::process::Command::new("uxnasm") 699 | .args(["/dev/stdin", "/dev/stdout"]) 700 | .stdin(std::process::Stdio::piped()) 701 | .stdout(std::process::Stdio::piped()) 702 | .stderr(std::process::Stdio::null()) 703 | .spawn() 704 | .expect("uxnasm is not installed"); 705 | std::io::Write::write(&mut assembler.stdin.take().unwrap(), tal.as_bytes()).unwrap(); 706 | let program = assembler.wait_with_output().unwrap().stdout; 707 | 708 | TestRam { 709 | program, 710 | variables: std::collections::HashMap::new(), 711 | } 712 | } 713 | } 714 | 715 | impl std::cmp::PartialEq> for UxnStack { 716 | fn eq(&self, other: &Vec) -> bool { 717 | self.s[..self.sp as usize].to_vec() == *other 718 | } 719 | } 720 | 721 | impl Uxn { 722 | fn from_tal(tal: &str) -> Self { 723 | Uxn::new(TestRam::from_tal(tal)) 724 | } 725 | } 726 | 727 | #[test] 728 | fn brk() {} 729 | 730 | #[test] 731 | fn jci() {} 732 | 733 | #[test] 734 | fn jmi() {} 735 | 736 | #[test] 737 | fn jsi() {} 738 | 739 | #[test] 740 | fn lit() { 741 | let mut cpu = Uxn::from_tal("LIT 12"); 742 | cpu.run_vector().unwrap(); 743 | assert_eq!(cpu.ws, vec![0x12]); 744 | 745 | let mut cpu = Uxn::from_tal("LIT2 abcd"); 746 | cpu.run_vector().unwrap(); 747 | assert_eq!(cpu.ws, vec![0xab, 0xcd]); 748 | } 749 | 750 | #[test] 751 | fn inc() { 752 | let mut cpu = Uxn::from_tal("#01 INC"); 753 | cpu.run_vector().unwrap(); 754 | assert_eq!(cpu.ws, vec![0x02]); 755 | 756 | let mut cpu = Uxn::from_tal("#0001 INC2"); 757 | cpu.run_vector().unwrap(); 758 | assert_eq!(cpu.ws, vec![0x00, 0x02]); 759 | 760 | let mut cpu = Uxn::from_tal("#0001 INC2k"); 761 | cpu.run_vector().unwrap(); 762 | assert_eq!(cpu.ws, vec![0x00, 0x01, 0x00, 0x02]); 763 | } 764 | 765 | #[test] 766 | fn pop() { 767 | let mut cpu = Uxn::from_tal("#1234 POP"); 768 | cpu.run_vector().unwrap(); 769 | assert_eq!(cpu.ws, vec![0x12]); 770 | 771 | let mut cpu = Uxn::from_tal("#1234 POP2"); 772 | cpu.run_vector().unwrap(); 773 | assert_eq!(cpu.ws, vec![]); 774 | 775 | let mut cpu = Uxn::from_tal("#1234 POP2k"); 776 | cpu.run_vector().unwrap(); 777 | assert_eq!(cpu.ws, vec![0x12, 0x34]); 778 | } 779 | 780 | #[test] 781 | fn nip() { 782 | let mut cpu = Uxn::from_tal("#1234 NIP"); 783 | cpu.run_vector().unwrap(); 784 | assert_eq!(cpu.ws, vec![0x34]); 785 | 786 | let mut cpu = Uxn::from_tal("#1234 #5678 NIP2"); 787 | cpu.run_vector().unwrap(); 788 | assert_eq!(cpu.ws, vec![0x56, 0x78]); 789 | 790 | let mut cpu = Uxn::from_tal("#1234 #5678 NIP2k"); 791 | cpu.run_vector().unwrap(); 792 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x56, 0x78, 0x56, 0x78]); 793 | } 794 | 795 | #[test] 796 | fn swp() { 797 | let mut cpu = Uxn::from_tal("#1234 SWP"); 798 | cpu.run_vector().unwrap(); 799 | assert_eq!(cpu.ws, vec![0x34, 0x12]); 800 | 801 | let mut cpu = Uxn::from_tal("#1234 SWPk"); 802 | cpu.run_vector().unwrap(); 803 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x34, 0x12]); 804 | 805 | let mut cpu = Uxn::from_tal("#1234 #5678 SWP2"); 806 | cpu.run_vector().unwrap(); 807 | assert_eq!(cpu.ws, vec![0x56, 0x78, 0x12, 0x34]); 808 | 809 | let mut cpu = Uxn::from_tal("#1234 #5678 SWP2k"); 810 | cpu.run_vector().unwrap(); 811 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x56, 0x78, 0x56, 0x78, 0x12, 0x34]); 812 | } 813 | 814 | #[test] 815 | fn rot() { 816 | let mut cpu = Uxn::from_tal("#1234 #56 ROT"); 817 | cpu.run_vector().unwrap(); 818 | assert_eq!(cpu.ws, vec![0x34, 0x56, 0x12]); 819 | 820 | let mut cpu = Uxn::from_tal("#1234 #56 ROTk"); 821 | cpu.run_vector().unwrap(); 822 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x56, 0x34, 0x56, 0x12]); 823 | 824 | let mut cpu = Uxn::from_tal("#1234 #5678 #9abc ROT2"); 825 | cpu.run_vector().unwrap(); 826 | assert_eq!(cpu.ws, vec![0x56, 0x78, 0x9a, 0xbc, 0x12, 0x34]); 827 | 828 | let mut cpu = Uxn::from_tal("#1234 #5678 #9abc ROT2k"); 829 | cpu.run_vector().unwrap(); 830 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0x56, 0x78, 0x9a, 0xbc, 0x12, 0x34]); 831 | } 832 | 833 | #[test] 834 | fn dup() { 835 | let mut cpu = Uxn::from_tal("#1234 DUP"); 836 | cpu.run_vector().unwrap(); 837 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x34]); 838 | 839 | let mut cpu = Uxn::from_tal("#12 DUPk"); 840 | cpu.run_vector().unwrap(); 841 | assert_eq!(cpu.ws, vec![0x12, 0x12, 0x12]); 842 | 843 | let mut cpu = Uxn::from_tal("#1234 DUP2"); 844 | cpu.run_vector().unwrap(); 845 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x12, 0x34]); 846 | } 847 | 848 | #[test] 849 | fn ovr() { 850 | let mut cpu = Uxn::from_tal("#1234 OVR"); 851 | cpu.run_vector().unwrap(); 852 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x12]); 853 | 854 | let mut cpu = Uxn::from_tal("#1234 OVRk"); 855 | cpu.run_vector().unwrap(); 856 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x12, 0x34, 0x12]); 857 | 858 | let mut cpu = Uxn::from_tal("#1234 #5678 OVR2"); 859 | cpu.run_vector().unwrap(); 860 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x56, 0x78, 0x12, 0x34]); 861 | 862 | let mut cpu = Uxn::from_tal("#1234 #5678 OVR2k"); 863 | cpu.run_vector().unwrap(); 864 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34]); 865 | } 866 | 867 | #[test] 868 | fn equ() { 869 | let mut cpu = Uxn::from_tal("#1212 EQU"); 870 | cpu.run_vector().unwrap(); 871 | assert_eq!(cpu.ws, vec![0x01]); 872 | 873 | let mut cpu = Uxn::from_tal("#1234 EQUk"); 874 | cpu.run_vector().unwrap(); 875 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x00]); 876 | 877 | let mut cpu = Uxn::from_tal("#abcd #ef01 EQU2"); 878 | cpu.run_vector().unwrap(); 879 | assert_eq!(cpu.ws, vec![0x00]); 880 | 881 | let mut cpu = Uxn::from_tal("#abcd #abcd EQU2k"); 882 | cpu.run_vector().unwrap(); 883 | assert_eq!(cpu.ws, vec![0xab, 0xcd, 0xab, 0xcd, 0x01]); 884 | } 885 | 886 | #[test] 887 | fn neq() { 888 | let mut cpu = Uxn::from_tal("#1212 NEQ"); 889 | cpu.run_vector().unwrap(); 890 | assert_eq!(cpu.ws, vec![0x00]); 891 | 892 | let mut cpu = Uxn::from_tal("#1234 NEQk"); 893 | cpu.run_vector().unwrap(); 894 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x01]); 895 | 896 | let mut cpu = Uxn::from_tal("#abcd #ef01 NEQ2"); 897 | cpu.run_vector().unwrap(); 898 | assert_eq!(cpu.ws, vec![0x01]); 899 | 900 | let mut cpu = Uxn::from_tal("#abcd #abcd NEQ2k"); 901 | cpu.run_vector().unwrap(); 902 | assert_eq!(cpu.ws, vec![0xab, 0xcd, 0xab, 0xcd, 0x00]); 903 | } 904 | 905 | #[test] 906 | fn gth() { 907 | let mut cpu = Uxn::from_tal("#1234 GTH"); 908 | cpu.run_vector().unwrap(); 909 | assert_eq!(cpu.ws, vec![0x00]); 910 | 911 | let mut cpu = Uxn::from_tal("#3412 GTHk"); 912 | cpu.run_vector().unwrap(); 913 | assert_eq!(cpu.ws, vec![0x34, 0x12, 0x01]); 914 | 915 | let mut cpu = Uxn::from_tal("#3456 #1234 GTH2"); 916 | cpu.run_vector().unwrap(); 917 | assert_eq!(cpu.ws, vec![0x01]); 918 | 919 | let mut cpu = Uxn::from_tal("#1234 #3456 GTH2k"); 920 | cpu.run_vector().unwrap(); 921 | assert_eq!(cpu.ws, vec![0x12, 0x34, 0x34, 0x56, 0x00]); 922 | } 923 | 924 | #[test] 925 | fn lth() { 926 | let mut cpu = Uxn::from_tal("#0101 LTH"); 927 | cpu.run_vector().unwrap(); 928 | assert_eq!(cpu.ws, vec![0x00]); 929 | 930 | let mut cpu = Uxn::from_tal("#0100 LTHk"); 931 | cpu.run_vector().unwrap(); 932 | assert_eq!(cpu.ws, vec![0x01, 0x00, 0x00]); 933 | 934 | let mut cpu = Uxn::from_tal("#0001 #0000 LTH2"); 935 | cpu.run_vector().unwrap(); 936 | assert_eq!(cpu.ws, vec![0x00]); 937 | 938 | let mut cpu = Uxn::from_tal("#0001 #0000 LTH2k"); 939 | cpu.run_vector().unwrap(); 940 | assert_eq!(cpu.ws, vec![0x00, 0x01, 0x00, 0x00, 0x00]); 941 | } 942 | 943 | #[test] 944 | fn jmp() { 945 | let mut cpu = Uxn::from_tal(",&skip-rel JMP BRK &skip-rel #01"); 946 | cpu.run_vector().unwrap(); 947 | assert_eq!(cpu.ws, vec![0x01]); 948 | } 949 | 950 | #[test] 951 | fn jcn() { 952 | let mut cpu = Uxn::from_tal("#abcd #01 ,&pass JCN SWP &pass POP"); 953 | cpu.run_vector().unwrap(); 954 | assert_eq!(cpu.ws, vec![0xab]); 955 | 956 | let mut cpu = Uxn::from_tal("#abcd #00 ,&fail JCN SWP &fail POP"); 957 | cpu.run_vector().unwrap(); 958 | assert_eq!(cpu.ws, vec![0xcd]); 959 | } 960 | 961 | #[test] 962 | fn jsr() { 963 | let mut cpu = Uxn::from_tal(",&get JSR #01 BRK &get #02 JMP2r"); 964 | cpu.run_vector().unwrap(); 965 | assert_eq!(cpu.ws, vec![0x02, 0x01]); 966 | } 967 | 968 | #[test] 969 | fn sth() { 970 | let mut cpu = Uxn::from_tal("#01 STH LITr 02 ADDr STHr"); 971 | cpu.run_vector().unwrap(); 972 | assert_eq!(cpu.ws, vec![0x03]); 973 | } 974 | 975 | #[test] 976 | fn ldz() { 977 | let mut cpu = Uxn::from_tal("|00 @cell $2 |0100 .cell LDZ"); 978 | cpu.run_vector().unwrap(); 979 | assert_eq!(cpu.ws, vec![0x00]); 980 | } 981 | 982 | #[test] 983 | fn stz() { 984 | let mut cpu = Uxn::from_tal("|00 @cell $2 |0100 #abcd .cell STZ2"); 985 | cpu.run_vector().unwrap(); 986 | assert_eq!(cpu.ram[0x0000], 0xab); 987 | assert_eq!(cpu.ram[0x0001], 0xcd); 988 | } 989 | 990 | #[test] 991 | fn ldr() { 992 | let mut cpu = Uxn::from_tal(",cell LDR2 BRK @cell abcd"); 993 | cpu.run_vector().unwrap(); 994 | assert_eq!(cpu.ws, vec![0xab, 0xcd]); 995 | } 996 | 997 | #[test] 998 | fn str() { 999 | let mut cpu = Uxn::from_tal("#1234 ,cell STR2 BRK @cell $2"); 1000 | cpu.run_vector().unwrap(); 1001 | assert_eq!(cpu.ws, vec![]); 1002 | } 1003 | 1004 | #[test] 1005 | fn lda() { 1006 | let mut cpu = Uxn::from_tal(";cell LDA BRK @cell abcd"); 1007 | cpu.run_vector().unwrap(); 1008 | assert_eq!(cpu.ws, vec![0xab]); 1009 | } 1010 | 1011 | #[test] 1012 | fn sta() { 1013 | let mut cpu = Uxn::from_tal("#abcd ;cell STA BRK @cell $1"); 1014 | cpu.run_vector().unwrap(); 1015 | assert_eq!(cpu.ws, vec![0xab]); 1016 | } 1017 | 1018 | #[test] 1019 | fn dei() {} 1020 | 1021 | #[test] 1022 | fn deo() {} 1023 | 1024 | #[test] 1025 | fn add() { 1026 | let mut cpu = Uxn::from_tal("#1a #2e ADD"); 1027 | cpu.run_vector().unwrap(); 1028 | assert_eq!(cpu.ws, vec![0x48]); 1029 | 1030 | let mut cpu = Uxn::from_tal("#02 #5d ADDk"); 1031 | cpu.run_vector().unwrap(); 1032 | assert_eq!(cpu.ws, vec![0x02, 0x5d, 0x5f]); 1033 | 1034 | let mut cpu = Uxn::from_tal("#0001 #0002 ADD2"); 1035 | cpu.run_vector().unwrap(); 1036 | assert_eq!(cpu.ws, vec![0x00, 0x03]); 1037 | } 1038 | 1039 | #[test] 1040 | fn sub() {} 1041 | 1042 | #[test] 1043 | fn mul() {} 1044 | 1045 | #[test] 1046 | fn div() {} 1047 | 1048 | #[test] 1049 | fn and() {} 1050 | 1051 | #[test] 1052 | fn ora() {} 1053 | 1054 | #[test] 1055 | fn eor() {} 1056 | 1057 | #[test] 1058 | fn sft() { 1059 | let mut cpu = Uxn::from_tal("#34 #10 SFT"); 1060 | cpu.run_vector().unwrap(); 1061 | assert_eq!(cpu.ws, vec![0x68]); 1062 | 1063 | let mut cpu = Uxn::from_tal("#34 #01 SFT"); 1064 | cpu.run_vector().unwrap(); 1065 | assert_eq!(cpu.ws, vec![0x1a]); 1066 | 1067 | let mut cpu = Uxn::from_tal("#34 #33 SFTk"); 1068 | cpu.run_vector().unwrap(); 1069 | assert_eq!(cpu.ws, vec![0x34, 0x33, 0x30]); 1070 | 1071 | let mut cpu = Uxn::from_tal("#1248 #34 SFTk2"); 1072 | cpu.run_vector().unwrap(); 1073 | assert_eq!(cpu.ws, vec![0x12, 0x48, 0x34, 0x09, 0x20]); 1074 | } 1075 | } 1076 | --------------------------------------------------------------------------------