this is literal garbage

This commit is contained in:
Crispy 2023-12-12 22:25:56 +01:00
commit 355811be3a
6 changed files with 349 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

16
Cargo.lock generated Normal file
View file

@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "worm"
version = "0.1.0"
dependencies = [
"owo-colors",
]

9
Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "worm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
owo-colors = "3.5.0"

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# sandworm
program space is an arbitrary sized grid
the worm starts where you place the worm head @
as it passes over commands, they get moved to the back of the worm (also depending on the worm length)
commands to change direction <>^v
values get pushed to stack (eaten) when passed over, worm body length increases
autosarkophagy/self-cannibalism is possible, cutting off the back of the worm (the lower part of the stack) and pushing the passed value to the stack (front of worm)
conditional: pop then side/forward if top of stack is zero
## commands
```
+- pop 2 values *, push sum/difference (uses the order they are popped, so `0 -` negates the top of the stack)
><^v change direction
0123456789ABCDEF push number to stack
/\ pop stack, reflect to the side if zero
? reads one byte of input
= duplicate top of stack
! pop and write output as ascii char
" pop and write output as number
```
* pop when stack is empty yields zero

1
rustfmt.toml Normal file
View file

@ -0,0 +1 @@
hard_tabs = true

301
src/main.rs Normal file
View file

@ -0,0 +1,301 @@
use std::{env, fs, io::stdin, process::exit};
use owo_colors::OwoColorize;
#[derive(Debug)]
struct SandWormInterpreter {
program: Vec<Vec<u8>>,
width: usize,
height: usize,
/// worm body locations
worm: Vec<(usize, usize)>,
worm_head: (usize, usize),
/// queue for outputting commands at the back of the worm
worm_out: Vec<u8>,
worm_in: Vec<u8>,
direction: Direction,
input: Vec<u8>,
input_index: usize,
output: Vec<u8>,
state: State,
}
#[derive(Debug, Default)]
enum Direction {
Up,
Down,
Left,
#[default]
Right,
}
#[derive(Debug, Default, PartialEq)]
enum State {
#[default]
Running,
EndOfProgram,
}
fn main() {
let args: Vec<_> = env::args().collect();
if args.len() <= 1 {
println!("usage: sandworm source_file input_file");
exit(0);
}
let filename = &args[1];
let source = fs::read_to_string(filename).unwrap_or_else(|err| {
println!("Error reading file: {err}");
exit(1);
});
let input_data = args
.get(2)
.map(|path| {
fs::read(path).unwrap_or_else(|err| {
println!("Error reading file: {err}");
exit(1);
})
})
.unwrap_or_default();
let mut interpreter = SandWormInterpreter::new(&source, input_data);
loop {
interpreter.show();
let mut input_text = String::new();
stdin().read_line(&mut input_text).unwrap();
let action: Vec<_> = input_text.trim().split_ascii_whitespace().collect();
if input_text.starts_with("input ") {
interpreter.input.extend(&input_text.as_bytes()[6..]);
continue;
}
match action.as_slice() {
[] | ["step"] => interpreter.step_once(),
["step", num] => _ = num.parse().map(|n| interpreter.step(n)),
// ["run"] => interpreter.run(),
["q" | "exit" | "quit"] => break,
_ => println!("{}", "unrecognised command".red()),
}
}
}
impl SandWormInterpreter {
fn new(source: &str, input: Vec<u8>) -> Self {
let (program, start_pos) = parse(source);
Self {
width: program[0].len(),
height: program.len(),
program,
worm: Vec::new(),
worm_head: start_pos,
worm_in: Vec::new(),
worm_out: Vec::new(),
input,
output: Vec::new(),
state: State::default(),
direction: Direction::default(),
input_index: 0,
}
}
fn step(&mut self, n: usize) {
for _ in 0..n {
if self.state != State::Running {
break;
}
self.step_once();
}
}
fn show(&self) {
dbg!(&self);
println!(
"{:?}",
self.worm.iter().map(|p| self.get(*p)).collect::<Vec<_>>()
);
for (row, line) in self.program.iter().enumerate() {
for (col, &byte) in line.iter().enumerate() {
if self.worm.contains(&(col, row)) {
if byte < 10 {
print!("{:x}", byte.on_green());
} else {
print!("{}", "*".green().on_red());
}
} else if self.worm_head == (col, row) {
if byte == b'@' {
print!("{}", "@".on_yellow());
} else {
panic!("worm head corrupted");
}
} else if byte == 0 || byte == b' ' {
print!(" ");
} else if byte.is_ascii_alphanumeric() || byte.is_ascii_punctuation() {
print!("{}", byte as char);
} else {
print!("{}", "*".green());
}
}
println!();
}
println!("output: {}", String::from_utf8_lossy(&self.output));
println!("input: {}", String::from_utf8_lossy(&self.input));
}
fn step_once(&mut self) {
if self.state != State::Running {
return;
}
let front = self.front();
if front.0 >= self.width || front.1 >= self.height {
self.state = State::EndOfProgram;
return;
}
let instruction = self.get(front);
let mut dont_push_instruction = false;
match instruction {
b'0'..=b'9' => {
self.worm_in.push(instruction - 48);
dont_push_instruction = true;
}
// b'0'..=b'9' => self.grow(instruction - 48),
b'+' => {
let a = self.shrink();
self.worm_out.push(instruction);
let b = self.shrink();
dont_push_instruction = true;
self.worm_in.push(a + b);
}
b'-' => {
let a = self.shrink();
self.worm_out.push(instruction);
dont_push_instruction = true;
let b = self.shrink();
self.worm_in.push(a + b);
}
b'v' => self.direction = Direction::Down,
b'^' => self.direction = Direction::Up,
b'<' => self.direction = Direction::Left,
b'>' => self.direction = Direction::Right,
b'"' => {
let n = self.shrink();
self.output.extend(n.to_string().as_bytes());
}
b'!' => {
let n = self.shrink();
self.output.push(n);
}
b'?' => {
let val = self
.input
.get(self.input_index)
.copied()
.unwrap_or_default();
self.worm_in.push(val);
}
b'=' => {
let last_val = self.worm.last().map(|&p| self.get(p)).unwrap_or_default();
self.worm_in.push(last_val);
}
b'\\' => {
let val = self.shrink();
if val != 0 {
self.direction = match self.direction {
Direction::Up => Direction::Left,
Direction::Down => Direction::Right,
Direction::Left => Direction::Up,
Direction::Right => Direction::Down,
}
}
}
b'/' => {
let val = self.shrink();
if val != 0 {
self.direction = match self.direction {
Direction::Up => Direction::Right,
Direction::Down => Direction::Left,
Direction::Left => Direction::Down,
Direction::Right => Direction::Up,
}
}
}
b' ' | 0 => (),
other => panic!("{} not implemented", other as char),
}
if !dont_push_instruction {
self.worm_out.push(instruction);
}
self.move_to(front);
}
fn move_to(&mut self, front: (usize, usize)) {
if let Some(input) = self.worm_in.pop() {
*self.get_mut(self.worm_head) = input;
self.worm.push(self.worm_head);
} else {
let mut next = self.worm_head;
for body_segment in self.worm.iter_mut().rev() {
self.program[next.1][next.0] = self.program[body_segment.1][body_segment.0];
(*body_segment, next) = (next, *body_segment);
}
*self.get_mut(next) = self.worm_out.pop().unwrap_or(b' ');
}
self.worm_head = front;
*self.get_mut(front) = b'@';
}
/// get the front number and move the body forward (leaves the head where it was).
/// also shits out any queued instruction
fn shrink(&mut self) -> u8 {
if let Some(neck) = self.worm.pop() {
let ret = self.get(neck);
let mut next = neck;
for body_segment in self.worm.iter_mut().rev() {
self.program[next.1][next.0] = self.program[body_segment.1][body_segment.0];
(*body_segment, next) = (next, *body_segment);
}
*self.get_mut(next) = self.worm_out.pop().unwrap_or(b' ');
ret
} else {
0
}
}
fn get(&self, pos: (usize, usize)) -> u8 {
self.program[pos.1][pos.0]
}
fn get_mut(&mut self, pos: (usize, usize)) -> &mut u8 {
&mut self.program[pos.1][pos.0]
}
fn front(&self) -> (usize, usize) {
let mut front = self.worm_head;
match self.direction {
Direction::Up => front.1 = front.1.wrapping_sub(1),
Direction::Down => front.1 = front.1.saturating_add(1),
Direction::Left => front.0 = front.0.wrapping_sub(1),
Direction::Right => front.0 = front.0.saturating_add(1),
}
front
}
}
fn parse(source: &str) -> (Vec<Vec<u8>>, (usize, usize)) {
let mut program = Vec::new();
let mut width = 0;
let mut start_pos = (0, 0);
for (row, line) in source.lines().enumerate() {
width = width.max(line.len());
if let Some(col) = line.find('@') {
start_pos = (row, col);
}
program.push(line.as_bytes().to_vec());
}
for line in &mut program {
line.resize(width, 0);
}
(program, start_pos)
}