From c7c70242f78c01001b66bb6ca1003c302ecc7835 Mon Sep 17 00:00:00 2001 From: nat Date: Sun, 1 Dec 2024 21:33:05 -0800 Subject: [PATCH] init --- .gitignore | 5 ++ Cargo.lock | 7 ++ Cargo.toml | 8 ++ README.md | 11 +++ SPEC.md | 133 ++++++++++++++++++++++++++++++ src/control.rs | 17 ++++ src/forestry.rs | 139 +++++++++++++++++++++++++++++++ src/main.rs | 215 ++++++++++++++++++++++++++++++++++++++++++++++++ src/math.rs | 101 +++++++++++++++++++++++ src/op_utils.rs | 24 ++++++ src/stack.rs | 103 +++++++++++++++++++++++ src/state.rs | 94 +++++++++++++++++++++ test.rom | Bin 0 -> 28 bytes 13 files changed, 857 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 SPEC.md create mode 100644 src/control.rs create mode 100644 src/forestry.rs create mode 100644 src/main.rs create mode 100644 src/math.rs create mode 100644 src/op_utils.rs create mode 100644 src/stack.rs create mode 100644 src/state.rs create mode 100644 test.rom diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e4f154 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target/ + +# Vim +.swp +.swo diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ea722fd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ponderosa" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..abf1c61 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ponderosa" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..93512b8 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +## ponderosa + +ponderosa is a 16-bit virtual computing platform inspired by stack machines and cellular automata, or an attempt to build a computer that's also an extended metaphor for a forest. Use at your own peril. See [SPEC.md](https://git.nats.solutions/nat/ponderosa/src/branch/main/SPEC.md) for a "complete" description of the project. + +## license + +This software is a gift from me to you. By accepting this gift, we're forming a relationship, and with that comes certain expectations. Namely: + +- When you share this gift with others, you will share it in the same spirit as I share it with you. +- You will not use this gift to hurt people, any living creatures, or the planet + diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..24346fe --- /dev/null +++ b/SPEC.md @@ -0,0 +1,133 @@ +## Overview + +A forest is a 16-bit virtual computing platform inspired by stack machines and cellular automata with some unique characteristics: +- Every program (forest) is broken into limited-sized, parallel, looping units of execution (trees) +- Trees act on two stacks (the root and trunk) and cells relative to its position (base) in an N-dimensional array (land) + +Forests are ideological machines. They have a highly opinionated design to encourage developers to think about memory as something to be conserved and shared. Lots of forestry jargon ahead. + +## Forests +A forest is the entire universe of operation + +Every forest grows on land. Internally, land is a matrix. It can be any number of dimensions, though using more than two or three would rapidly become difficult to visualize. It's also limited in size. Both the size and dimensionality of a forest's land is predetermined when the virtual machine starts + +If an access is made to a cell outside of the "boundaries" of a forest, then it loops around to the other side. For example, in a one dimensional forest read left-to-right, if a cell one cell beyond the rightmost boundary is accessed, that should be treated as an access to the cell at the leftmost bounary. + +Preconfigured details of a forest are specified in a configuration file ROM. This ROM specifies: +- Initial state of the land as a sparse matrix +- Start of execution + +## Trees + +A tree is a single, spatially rooted context of execution in a forest. It's analagous to a process or a thread on most operating systems, but different in a few key ways. + +Like processes, trees run in parallel. Unlike modern processes, trees don't get time slices, they get slices of execution. All trees in the forest get to execute a limited number of instructions each cycle. The capped number of instructions run per cycle is called the tree's "rule," and a "generation" is the time it takes to execute every tree's rule once. The terms "rule" and "generation" are borrowed from cellular automata. + +Rules, like in celullar automata, are looped every generation. + +Trees use their rules to operate on two stacks: the root and the trunk. By convention, we imagine the tree pulling values from it's root, operating on them, and then pushing them to the trunk. In other words, the trunk is used for results, and the roots are used for operands. However, trees may push to the root and pull from the trunk if applicable. + +Every tree has a base, that is, a particular spot on the land, or a cell in the matrix. Land-access is made relative to the position of the base cell, and with respect to the tree, the base cell has coordinates of the zero vector of the forest (i.e. [0] for a 1-dimensional forest, [0, 0] for two dimensions, and so on). + +In a given direction, trees can access cells -4 through 3 (mapping to the possible integers in four-bit two's compliment representation). + +### Planting trees +Planting a tree means scheduling its rule and building its dynamic execution context. + +At least one rule is specified in the forest ROM. Each rule is assigned a unique, incrementing integer starting from zero that can be used elsewhere for identification. + +Later trees can be planted by other trees, referenced by numeric ID. + +How a tree's dynamic execution context is organized is implementation-dependent, but it must include the: +- coordinates of the base +- root stack +- trunk stack +- rule + +### Rules + +Rules may be defined in the forest ROM. During execution, rules should be stored in a data structure such that they can be referenced by numeric ID. By using particular instructions, trees can load new rules into this structure. + +#### Instructions +##### Stack code +A stack code is at least three bits. A 0 bit represents the root, and a 1 bit represents the trunk. + +A unary operation needs only a one bit stack code. If the operation has a stack code with a bit-width of two, the second bit is used and the first is ignored. + +A binary operation needs at least two stack bits, the first being for the "left" side and the second being for the "right" side. If both bits are the same, then the arguments are the results of calling "pop" on the stack twice (as opposed two the same value twice). + +A third stack bit signifies where any relevant result is stored. + +##### Arithmetic +| op code | stack | signed? | unused | shift amount | +----------------------------------------------------- +| 00000 | 000 | 0 | 000 | 0000 | + +00110 001 1 000 0000 +0011 0001 1000 0000 +3180 - add two from root and put them in trunk, signed + +- `and`; 0000 1 +- `nand` +- `or` +- `nor` +- `xor` +- `not` +- `add` +- `multiply` +- `divide` +- `slt` - "set if less than" +- `left` - left shift by `n` bits +- `right` - right shift by `n` bits + +##### Stack +###### Stack management +| op code | stack code | signed? | immediate | +----------------------------------------------- +| 00000 | 0 | 0 | 0 0000 0000 | + +- `push` + +###### Stack operations +| op code | stack | unused | +---------------------------------------- +| 00000 | 00 | 0 0000 0000 | +- `pull` +- `swap` +- `rotate` +- `flip` - Move from one stack to the other +- `clear` + +##### Control +| op code | stack code | immediate | +------------------------------------------ +| 00000 | 00 | 0 0000 0000 | + +You can only jump forward. You "jump back" by letting the rule repeat + +- `skip` - Skip "immediate" instructions +- `skim` - Skip the number of instructions given in the first stack + + +##### Forestry +| op code | stack | relative x | relative y | +--------------------------------------------- +| 00000 | 0 | 00000 | 00000 | + +The coordinates specified here are signed, two's compliment integers and are computed relative to the base of the tree. Therefore, a tree can sow and reap within the 32x32 cube surrounding it + +- `sow` - store value in cell +- `reap` - retrieve value from cell +- `plant`- plants tree. The first value signifies the rule ID, and the second the stack size (must be equal to or less than the current rule's stack size) +- `replant` - Move tree to specified location +- `yeild` - (prematurely) end execution of the rule +- `compost` - (prematurely) end execution of rule, remove the tree from the scheduler, and deallocate the tree's stacks +- `define`- Creates a new rule based on values from the first stack. The first popped value defines the number of instructions, and subsequently that many more values are popped from the stack for inclusion in the rule. The ID of the rule is stored in the second stack. +- `wait` - execute `wait` on a cell's semaphore +- `signal` - execute `signal` on a cell's semaphore + +| literally just a zero word | +------------------------------ +| 0000 0000 0000 0000 | + +Deliniates between different rules, parts of the rom, etc. Every rule must terminate with a null word. As such, rules stored across separate roms may be combined by `cat`ing the files together, lead by some base rom describing the world. diff --git a/src/control.rs b/src/control.rs new file mode 100644 index 0000000..f63b103 --- /dev/null +++ b/src/control.rs @@ -0,0 +1,17 @@ +use crate::state::Tree; + +pub fn op_skip(instruction: &u16, instruction_pointer: &mut u16) { + *instruction_pointer += instruction & 0b0000000_111111111; +} + +pub fn op_skim(instruction: &u16, tree: &mut Tree, instruction_pointer: &mut u16) { + let stack_code = (instruction & 0b00000_1_0000000000) >> 10; + + let skip_count = if stack_code == 0 { + tree.pull_root() + } else { + tree.pull_trunk() + }; + + *instruction_pointer += skip_count; +} diff --git a/src/forestry.rs b/src/forestry.rs new file mode 100644 index 0000000..f00d359 --- /dev/null +++ b/src/forestry.rs @@ -0,0 +1,139 @@ +use std::collections::{VecDeque, HashMap}; +use crate::state::{Land, Rule, Tree}; + +fn extract_forestry_format(instruction: &u16) -> (u16, u16, u16) { + let stack_code = (instruction & 0b00000_1_0000000000) >> 10; + let relative_x = (instruction & 0b000000_11111_00000) >> 5; + let relative_y = instruction & 0b000000_00000_11111; + + let relative_x = if relative_x >> 9 == 0 { + relative_x + } else { + relative_x | 0b1111111111100000 + }; + + let relative_y = if relative_y >> 4 == 0 { + relative_y + } else { + relative_y | 0b1111111111100000 + }; + + return ( + stack_code, + relative_x, + relative_y + ); +} + +pub fn op_sow(instruction: &u16, tree: &mut Tree, land: &mut Land) { + let (stack_code, relative_x, relative_y) = extract_forestry_format(instruction); + + let value = tree.pull_by_stack_code(stack_code); + + land.sow(tree.position.0 + relative_x, tree.position.1 + relative_y, value); +} + +pub fn op_reap(instruction: &u16, tree: &mut Tree, land: &mut Land) { + let (stack_code, relative_x, relative_y) = extract_forestry_format(instruction); + + let value = land.reap(tree.position.0 + relative_x, tree.position.1 + relative_y); + + if stack_code == 0 { + tree.push_root(value); + } else { + tree.push_trunk(value); + } +} + +pub fn op_plant(instruction: &u16, tree: &mut Tree, next_tree_id: &mut u16, tree_vector: &mut Vec, land: &Land) { + let (stack_code, relative_x, relative_y) = extract_forestry_format(instruction); + + let rule_id = tree.pull_by_stack_code(stack_code); + let new_stack_size = tree.pull_by_stack_code(stack_code) as usize; + + if tree.root.len() + tree.trunk.len() > new_stack_size { + panic!("Tree of id {} attempted to plant a tree with a stack exceeding its own capacity", tree.rule_id); + } + + tree.stack_size -= new_stack_size; + + tree_vector.push(Tree { + id: *next_tree_id, + parent_id: tree.id, + rule_id, + stack_size: new_stack_size, + root: Vec::with_capacity((new_stack_size / 2) as usize), + trunk: Vec::with_capacity((new_stack_size / 2) as usize), + asleep: false, + resume_from: 0, + position: ((tree.position.0 + relative_x) % land.width, (tree.position.1 + relative_y) % land.height) + }); + + *next_tree_id += 1; +} + +pub fn op_replant(instruction: &u16, tree: &mut Tree, land: &Land) { + let (_, relative_x, relative_y) = extract_forestry_format(instruction); + + tree.position = land.derelativize(tree, relative_x, relative_y); +} + +pub fn op_define(instruction: &u16, tree: &mut Tree, rules: &Vec, rules_to_define: &mut Vec) { + let (stack_code, _, _) = extract_forestry_format(instruction); + + let instruction_count = tree.pull_by_stack_code(stack_code); + let mut new_rule = VecDeque::with_capacity(instruction_count as usize); + for i in 0..instruction_count { + new_rule.push_back(tree.pull_by_stack_code(stack_code)); + } + + rules_to_define.push(new_rule); + + // NOTE: We can confidently say that the new rule's ID is rules.len() + + // rules_to_define.len() - 2 because that's just the way the vectors will + // shake out. If we were running this in parallel, we'd probably have to use + // a more complex globally unique ID. + tree.push_by_stack_code(stack_code, (rules.len() + rules_to_define.len() - 2) as u16) +} + +pub fn op_wait(instruction: &u16, tree: &mut Tree, land: &mut Land, instruction_pointer: &u16) { + let (_, relative_x, relative_y) = extract_forestry_format(instruction); + let (abs_x, abs_y) = land.derelativize(tree, relative_x, relative_y); + + // Decrement the counting semaphore + let current_value = land.map.get(&(abs_x, abs_y)).unwrap(); + land.sow(abs_x, abs_y, current_value - 1); + + if current_value - 1 < 0 { + tree.asleep = true; + tree.resume_from = instruction_pointer + 1; + if land.semaphores.contains_key(&(abs_x, abs_y)) { + land.semaphores.get(&(abs_x, abs_y)).unwrap().push_back(tree.id); + } else { + // We've got to create if it doesn't already exist + let semaphores_for_cell: VecDeque = VecDeque::from([tree.id]); + land.semaphores.insert((abs_x, abs_y), semaphores_for_cell); + } + } +} + +pub fn op_signal(instruction: &u16, tree: &mut Tree, trees: &mut HashMap, land: &mut Land) { + let (_, relative_x, relative_y) = extract_forestry_format(instruction); + let (abs_x, abs_y) = land.derelativize(tree, relative_x, relative_y); + + // Increment the counting semaphore + let current_value = land.map.get(&(abs_x, abs_y)).unwrap(); + land.sow(abs_x, abs_y, current_value + 1); + + // Wake the oldest sleeping tree if needed + if *current_value >= 0 { + // If this call returns None, there's a logic error somewhere, either in the + // VM, or someone called signal on an uninitialized semaphore + let semaphores_for_cell = land.semaphores.get(&(abs_x, abs_y)).unwrap(); + let tree_id_to_wake = semaphores_for_cell.pop_front().unwrap(); + + let tree_to_wake = trees.get(&tree_id_to_wake).unwrap(); + tree_to_wake.asleep = false; + tree.resume_from = 0; + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..776b7a4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,215 @@ +use std::io; +use std::env; +use std::fs::File; +use std::io::Read; +use std::collections::{VecDeque, HashMap, BTreeSet}; + +mod op_utils; +mod math; +mod stack; +mod state; +mod control; +mod forestry; + +fn parse_rom(path: &String) -> io::Result<(state::Land, Vec, HashMap)> { + let mut rom_file = File::open(path)?; + let mut rom_buffer = Vec::new(); + + rom_file.read_to_end(&mut rom_buffer)?; + + let mut current_word = 0u16; + let mut read_word = false; + + let mut rom_words = VecDeque::new(); + + // Combine pairs of bytes into words + for byte in rom_buffer { + if !read_word { + current_word += (byte as u16) << 8; + read_word = true; + } else { + current_word = current_word | (byte as u16); + rom_words.push_back(current_word); + current_word = 0u16; + read_word = false; + } + } + + // First word + let original_stack_size: usize = rom_words.pop_front().unwrap().into(); + println!("Original stack size: {}", original_stack_size); + + // Second and third words -> land dimensions + let land_width = rom_words.pop_front().unwrap(); + let land_height = rom_words.pop_front().unwrap(); + println!("Land dimensions: {}x{}", land_width, land_height); + + // Fourth and fifth words-> the absolute coordinates of the seed tree + let seed_cell_x = rom_words.pop_front().unwrap(); + let seed_cell_y = rom_words.pop_front().unwrap(); + println!("Seed tree position: ({}, {})", seed_cell_x, seed_cell_y); + + let mut land = state::Land { + width: land_width, + height: land_height, + map: HashMap::new(), + semaphores: HashMap::new(), + }; + + let mut rules = Vec::::new(); + + // Sixth word + let predefined_cell_count = rom_words.pop_front().unwrap(); + + // Land definition + for _ in 0..predefined_cell_count { + let value = rom_words.pop_front().unwrap(); + let x_pos = rom_words.pop_front().unwrap(); + let y_pos = rom_words.pop_front().unwrap(); + + land.sow(x_pos, y_pos, value); + } + + println!("World\n====="); + + for col_idx in 0..land_width { + for row_idx in 0..land_height { + print!("{}\t", land.map.get(&(col_idx, row_idx)).unwrap_or(&0u16)); + } + println!(); + } + + rom_words.pop_front().unwrap(); // null-word delimiter + + let mut latest_rule = VecDeque::new(); + + for word in rom_words { + if word == 0u16 { + rules.push(latest_rule); + latest_rule = VecDeque::new(); + } else { + latest_rule.push_back(word); + } + } + + let mut trees: HashMap = HashMap::with_capacity(rules.len()); + + trees.insert(0, state::Tree { + id: 0, + rule_id: 0, + parent_id: 0, + root: Vec::with_capacity(original_stack_size / 2), + trunk: Vec::with_capacity(original_stack_size / 2), + asleep: false, + resume_from: 0, + stack_size: original_stack_size, + position: (seed_cell_x % land.width, seed_cell_y % land.height) + }); + + Ok((land, rules, trees)) +} + +fn main() -> io::Result<()> { + let args: Vec = env::args().collect(); + let rom_path = &args[1]; + + let (mut land, mut rules, mut trees) = parse_rom(rom_path).unwrap(); + let mut scheduled_trees = vec![trees.get(&0).unwrap()]; + + let mut next_tree_id = 1u16; + + let generation_count = 1; + + for _ in 0..generation_count { + let mut trees_to_plant: Vec = Vec::new(); + let mut rules_to_define: Vec = Vec::new(); + + // We shouldn't be able to compost the same tree twice in a generation, + // so we use a BTreeSet here. BTreeSets have the advantage of not + // visiting empty buckets when iterating, as opposed to HashSet + let mut trees_to_compost: BTreeSet = BTreeSet::new(); + + for tree_index in 0..scheduled_trees.len() { + let mut tree = &mut scheduled_trees[tree_index]; + + if tree.asleep { + continue; + } + + let instructions = &rules[tree.rule_id as usize]; + + // tree.resume_from will be 0, unless it's awoken after having to + // `wait`, in which case it'll be the index of the instruction after + // the `wait` call. + let mut instruction_pointer: u16 = tree.resume_from; + + while (instruction_pointer as usize) < instructions.len() { + let word = &instructions[instruction_pointer as usize]; + let opcode = word >> 11; + match opcode { + 0 => break, + 1 => math::op_and(word, tree), + 2 => math::op_nand(word, tree), + 3 => math::op_or(word, tree), + 4 => math::op_nor(word, tree), + 5 => math::op_xor(word, tree), + 6 => math::op_add(word, tree), + 7 => math::op_multiply(word, tree), + 8 => math::op_divide(word, tree), + 9 => math::op_slt(word, tree), + 10 => math::op_not(word, tree), + 11 => math::op_right(word, tree), + 12 => math::op_left(word, tree), + 13 => stack::op_push(word, &mut tree), + 14 => stack::op_pull(word, &mut tree), + 15 => stack::op_swap(word, &mut tree), + 16 => stack::op_rotate(word, &mut tree), + 17 => stack::op_flip(word, &mut tree), + 18 => stack::op_clear(word, &mut tree), + 19 => control::op_skip(word, &mut instruction_pointer), + 20 => control::op_skim(word, &mut tree, &mut instruction_pointer), + 21 => forestry::op_sow(word, tree, &mut land), + 22 => forestry::op_reap(word, tree, &mut land), + 23 => forestry::op_plant(word, tree, &mut next_tree_id, &mut trees_to_plant, &mut land), + 24 => forestry::op_replant(word, tree, &land), + 25 => break, // yeild + 26 => { + trees_to_compost.insert(tree_index); + break; + }, + 27 => forestry::op_define(word, tree, &rules, &mut rules_to_define), + 28 => forestry::op_wait(word, tree, &mut land, &instruction_pointer), + 29 => forestry::op_signal(word, tree, &mut trees, &mut land), + _ => panic!("Unknown opcode: {}", opcode) + }; + + instruction_pointer += 1; + } + } + + /* TODO: strange delimeter error + for tree_index in trees_to_compost.iter() { + let mut tree = &mut scheduled_trees.swap_remove(tree_index); + let parent_id = tree.parent_id; + let id = tree.id; + let parent = trees.get_mut(&parent_id).unwrap()); + parent.stack_size += tree.stack_size; + trees.remove(&id); + } + */ + + for _ in 0..trees_to_plant.len() { + let tree_to_plant = trees_to_plant.pop().unwrap(); + trees.insert(tree_to_plant.id, tree_to_plant); + } + + rules.append(&mut rules_to_define); + } + + for (_, tree) in trees { + dbg!(tree.root); + dbg!(tree.trunk); + } + + Ok(()) +} diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..efeff25 --- /dev/null +++ b/src/math.rs @@ -0,0 +1,101 @@ +use crate::match_binary_stack_code; +use crate::state::Tree; + +fn extract_math_format(instruction: &u16) -> (u16, bool, u16) { + let stack_code = (0b00000111_00000000 & instruction) >> 8; + let is_signed = ((0b00000000_10000000 & instruction) >> 7) == 1u16; + let literal = 0b00000000_00001111 & instruction; + + return (stack_code , is_signed, literal); +} + +macro_rules! match_ternary_stack_code { + ($stack_code:expr, $root:expr, $trunk:expr, $macro: ident) => { + match $stack_code { + 0b000 => $macro!($root, $root, $root), + 0b001 => $macro!($root, $root, $trunk), + 0b010 => $macro!($root, $trunk, $root), + 0b011 => $macro!($root, $trunk, $trunk), + 0b100 => $macro!($trunk, $root, $root), + 0b101 => $macro!($trunk, $root, $trunk), + 0b110 => $macro!($trunk, $trunk, $root), + 0b111 => $macro!($trunk, $trunk, $trunk), + _ => panic!("Invalid stack code: {}", $stack_code) + } + }; +} + +pub fn pull_left_and_right(stack_code: u16, tree: &mut Tree) -> (u16, u16) { + let left = tree.pull_by_stack_code(stack_code & 0b100); + let right = tree.pull_by_stack_code(stack_code & 0b010); + + (left, right) +} + +macro_rules! define_binary_op { + ($name:ident, |$left:ident, $right: ident| $body: expr) => { + pub fn $name(instruction: &u16, tree: &mut Tree) { + let (stack_code, is_signed, _) = extract_math_format(instruction); + let ($left, $right) = pull_left_and_right(stack_code, tree); + + if is_signed { + let $left = $left as i16; + let $right = $right as i16; + } + + tree.push_by_stack_code(stack_code & 0b001, $body); + } + }; +} + +define_binary_op!(op_and, |left, right| left & right); +define_binary_op!(op_nand, |left, right| !(left & right)); +define_binary_op!(op_or, |left, right| left | right); +define_binary_op!(op_nor, |left, right| !(left | right)); +define_binary_op!(op_xor, |left, right| left ^ right); +define_binary_op!(op_add, |left, right| left + right); +define_binary_op!(op_multiply, |left, right| left * right); +define_binary_op!(op_divide, |left, right| left / right); +define_binary_op!(op_slt, |left, right| if left < right { 1u16 } else { 0u16 }); + +pub fn op_not(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! op_not { + ($src_stack: expr, $dest_stack: expr, $immediate: expr) => { + { + let arg = $src_stack.pop().unwrap(); + $dest_stack.push(!arg); + } + }; + } + + let (stack_code, _, _immediate) = extract_math_format(instruction); + match_binary_stack_code!(op_not, stack_code >> 1, root, trunk); +} + +pub fn op_left(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! op_macro { + ($src_stack: expr, $dest_stack: expr, $immediate: expr) => { + { + let arg = $src_stack.pop().unwrap(); + $dest_stack.push(arg << $immediate); + } + }; + } + + let (stack_code, _, immediate) = extract_math_format(instruction); + match_binary_stack_code!(op_macro, stack_code >> 1, root, trunk, immediate); +} + +pub fn op_right(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! op_macro { + ($src_stack: expr, $dest_stack: expr, $immediate: expr) => { + { + let arg = $src_stack.pop().unwrap(); + $dest_stack.push(arg >> $immediate); + } + }; + } + + let (stack_code, _, immediate) = extract_math_format(instruction); + match_binary_stack_code!(op_macro, stack_code >> 1, root, trunk, immediate); +} diff --git a/src/op_utils.rs b/src/op_utils.rs new file mode 100644 index 0000000..2fee367 --- /dev/null +++ b/src/op_utils.rs @@ -0,0 +1,24 @@ +/** + * Runs the given macro on a combination of two stacks depending on the value + * of the stack code. The highest-order bit represents the source stack and the + * lowest represents the destination. + * + * Unlike `match_ternary_stack_code`, `match_binary_stack_code` takes a third, + * immediate argument, for a literal number that may be used in the operation + */ +#[macro_export] +macro_rules! match_binary_stack_code { + ($macro: ident, $stack_code:expr, $root:expr, $trunk:expr) => { + match_binary_stack_code!($macro, $stack_code, $root, $trunk, 0); + }; + + ($macro: ident, $stack_code:expr, $root:expr, $trunk:expr, $immediate:expr) => { + match $stack_code { + 0b00 => $macro!($root, $root, $immediate), + 0b01 => $macro!($root, $trunk, $immediate), + 0b10 => $macro!($trunk, $root, $immediate), + 0b11 => $macro!($trunk, $trunk, $immediate), + _ => panic!("Invalid stack code: {}", $stack_code) + } + }; +} diff --git a/src/stack.rs b/src/stack.rs new file mode 100644 index 0000000..79f3def --- /dev/null +++ b/src/stack.rs @@ -0,0 +1,103 @@ +use crate::match_binary_stack_code; + +pub fn extract_stack_format(instruction: &u16) -> (u16, u16) { + let stack_code = (0b00000_11_000000000 & instruction) >> 9; + let literal = 0b00000_0_1111111111 & instruction; + + return (stack_code, literal); +} + +pub fn op_push(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + let stack_code = (0b00000_1_0_000000000 & instruction) >> 10; + let is_signed = (0b00000_0_1_000000000 & instruction) >> 9; + let immediate = 0b00000_0_0_111111111 & instruction; + + let number_to_push = if (is_signed == 1u16) && (immediate >> 8 == 1u16) { + // Replicate the highest bit + immediate | 0b1111111000000000 + } else { + immediate + }; + + if stack_code >> 1 == 0u16 { + root.push(number_to_push); + } else { + trunk.push(number_to_push); + } +} + +pub fn op_pull(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! pull_macro { + ($src: expr, $dest: expr, $immediate: expr) => { + { + $src.pop().unwrap(); + } + }; + } + + let (stack_code, _) = extract_stack_format(instruction); + match_binary_stack_code!(pull_macro, stack_code, root, trunk); +} + +pub fn op_swap(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! swap_macro { + ($src: expr, $dest: expr, $immediate: expr) => { + { + let first = $src.pop().unwrap(); + let second = $src.pop().unwrap(); + + $dest.push(first); + $dest.push(second); + } + }; + } + let (stack_code, _) = extract_stack_format(instruction); + + match_binary_stack_code!(swap_macro, stack_code, root, trunk); +} + +pub fn op_rotate(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! rotate_macro { + ($src: expr, $dest: expr, $immediate: expr) => { + { + let first = $src.pop().unwrap(); + let second = $src.pop().unwrap(); + let third = $src.pop().unwrap(); + + $dest.push(first); + $dest.push(second); + $dest.push(third); + } + }; + } + + let (stack_code, _) = extract_stack_format(instruction); + match_binary_stack_code!(rotate_macro, stack_code, root, trunk); +} + +pub fn op_flip(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! flip_macro { + ($src: expr, $dest: expr, $immediate: expr) => { + { + let item = $src.pop().unwrap(); + $dest.push(item); + } + }; + } + + let (stack_code, _) = extract_stack_format(instruction); + match_binary_stack_code!(flip_macro, stack_code, root, trunk); +} + +pub fn op_clear(instruction: &u16, root: &mut Vec, trunk: &mut Vec) { + macro_rules! clear_macro { + ($src: expr, $dest: expr, $immediate: expr) => { + { + $src.clear(); + } + }; + } + + let (stack_code, _) = extract_stack_format(instruction); + match_binary_stack_code!(clear_macro, stack_code, root, trunk); +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..5330042 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; +use std::collections::VecDeque; + +pub struct Land { + pub map: HashMap<(u16, u16), u16>, + pub width: u16, + pub height: u16, + + // Maps positions to a queue of rule IDs + pub semaphores: HashMap<(u16, u16), VecDeque>, +} + +impl Land { + #[deprecated(since="0.0.0", note="please use `derelativize` instead")] + pub fn wrap_absolute_coordinates(&self, x: u16, y: u16) -> (u16, u16) { + (x % self.width, y % self.height) + } + + pub fn derelativize(&self, tree: &Tree, relative_x: u16, relative_y: u16) -> (u16, u16) { + ((tree.position.0 + relative_x) % self.width, (tree.position.1 + relative_y) % self.height) + } + + pub fn sow(&mut self, x: u16, y: u16, value: u16) { + // We use modulo arithmetic here to account for values that wrap around + // the world. + self.map.insert((x % self.width, y % self.height), value); + } + + pub fn reap(&mut self, x: u16, y: u16) -> u16 { + self.map.remove(&(x % self.width, y % self.height)).unwrap_or(0u16) + } +} + +pub type Rule = VecDeque; + +pub struct Tree { + pub id: u16, + pub rule_id: u16, + pub parent_id: u16, + pub root: Vec, + pub trunk: Vec, + pub asleep: bool, + pub resume_from: u16, + pub stack_size: usize, + pub position: (u16, u16) +} + +impl Tree { + fn assert_capacity_constraint(&self) { + if self.root.len() + self.trunk.len() > self.stack_size { + panic!("Stack capacity of rule {} exceeded", self.rule_id); + } + } + + pub fn push_root(&mut self, value: u16) { + self.root.push(value); + self.assert_capacity_constraint(); + } + + pub fn push_trunk(&mut self, value: u16) { + self.trunk.push(value); + self.assert_capacity_constraint(); + } + + pub fn pull_root(&mut self) -> u16 { + match self.root.pop() { + Some(v) => v, + None => panic!("Cannot pop from empty root stack in {}", self.rule_id) + } + } + + pub fn pull_trunk(&mut self) -> u16 { + match self.root.pop() { + Some(v) => v, + None => panic!("Cannot pop from empty trunk stack in {}", self.rule_id) + } + } + + pub fn push_by_stack_code(&mut self, stack_code: u16, value: u16) { + if stack_code == 0u16 { + self.push_root(value); + } else { + self.push_trunk(value); + } + } + + pub fn pull_by_stack_code(&mut self, stack_code: u16) -> u16 { + if stack_code == 0u16 { + self.pull_root() + } else { + self.pull_trunk() + } + } +} diff --git a/test.rom b/test.rom new file mode 100644 index 0000000000000000000000000000000000000000..aa13b2c78baab1655130343e9ea9a8ce5dc600f0 GIT binary patch literal 28 YcmZP&U| literal 0 HcmV?d00001