this is literal garbage
This commit is contained in:
commit
6286136e21
6 changed files with 347 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
16
Cargo.lock
generated
Normal file
16
Cargo.lock
generated
Normal 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
9
Cargo.toml
Normal 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"
|
19
README.md
Normal file
19
README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Stupid Worm Languag
|
||||
- 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
|
||||
- values get pushed to stack (eaten) when passed over, worm body length increases
|
||||
- the program gets rearranged every time the worm executes it
|
||||
|
||||
## commands
|
||||
```
|
||||
+- pop 2 values *, push sum/difference (uses the order they are popped, so `0 -` negates the top of the stack)
|
||||
><^v change direction
|
||||
0..9 push number to stack
|
||||
/\ pop stack, reflect to the side if not 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
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
|||
hard_tabs = true
|
301
src/main.rs
Normal file
301
src/main.rs
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue