From 5c5fd81b38572b282cae9c02895729cecec6c645 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Tue, 12 Jul 2022 13:05:18 +0200 Subject: [PATCH] :sparkles: stackline-cli --- Cargo.toml | 3 +- stackline-cli/Cargo.toml | 11 + stackline-cli/src/main.rs | 505 ++++++++++++++++++++++++++++++++++++ stackline/build.rs | 11 + stackline/src/context.rs | 10 +- stackline/src/tile/full.rs | 2 +- stackline/src/world.rs | 61 +++++ stackline/tiles/transmit.rs | 4 +- stackline/tiles/wire.rs | 14 +- 9 files changed, 605 insertions(+), 16 deletions(-) create mode 100644 stackline-cli/Cargo.toml create mode 100644 stackline-cli/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 03c8554..032292e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ members = [ "stackline-wasm", - "stackline" + "stackline-cli", + "stackline", ] [profile.release] diff --git a/stackline-cli/Cargo.toml b/stackline-cli/Cargo.toml new file mode 100644 index 0000000..19ba7f1 --- /dev/null +++ b/stackline-cli/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "stackline-cli" +version = "0.1.0" +edition = "2021" +authors = ["Shad Amethyst"] +description = "A simple runner and editor for Stackline 2" + +[dependencies] +clap = { version = "3.2", features = ["derive"] } +stackline = { path = "../stackline" } +serde_json = "1.0" diff --git a/stackline-cli/src/main.rs b/stackline-cli/src/main.rs new file mode 100644 index 0000000..7f1b1b7 --- /dev/null +++ b/stackline-cli/src/main.rs @@ -0,0 +1,505 @@ +#![feature(iter_intersperse)] + +use stackline::prelude::*; +use stackline::tile::*; +use clap::Parser; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use std::io::Write; + +fn main() { + let args = Args::parse(); + + // let mut world = World::new(); + // let mut pane = Pane::empty(4, 4).unwrap(); + // pane.set_tile((0, 0), FullTile::from(Wire::new(Orientation::Any))); + // world.set_pane(String::from("main"), pane); + // let raw = serde_json::to_string(&world).unwrap(); + // std::fs::write(&args.file, &raw).unwrap(); + + let raw = std::fs::read_to_string(&args.file).expect(&format!("Couldn't open {}!", args.file.display())); + + let mut world: World = serde_json::from_str(&raw).unwrap(); + + loop { + let mut line = String::new(); + std::io::stdin().read_line(&mut line).unwrap(); + + line = line.trim().to_string(); + let mut tokens = line.split(' '); + match tokens.next() { + None => continue, + Some("run") => { + if let Some(Ok(steps)) = tokens.next().map(|s| s.parse::()) { + run(&mut world, steps).unwrap(); + } else { + eprintln!("Syntax error: invalid number of steps"); + } + } + Some("step") => { + step(&mut world); + } + + Some("print") => { + print!("{}", world); + } + Some("get") => { + if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + get(&world, x, y); + } + } else { + eprintln!("Expected two arguments"); + } + } + + Some("set") => { + if let (Some(x), Some(y), Some(name)) = (tokens.next(), tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + set(&mut world, x, y, name); + } + } else { + eprintln!("Expected three arguments"); + } + } + Some("remove") => { + if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + remove(&mut world, x, y); + } + } else { + eprintln!("Expected two arguments"); + } + } + Some("prop") => { + if let (Some(x), Some(y), Some(prop_name)) = (tokens.next(), tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + prop(&mut world, x, y, prop_name, tokens.intersperse(" ").collect()); + } + } else { + eprintln!("Expected four arguments"); + } + } + Some("state") => { + if let (Some(x), Some(y), Some(new_state)) = (tokens.next(), tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + state(&mut world, x, y, new_state); + } + } else { + eprintln!("Expected three arguments"); + } + } + + Some("signal") => { + if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + signal(&mut world, x, y); + } + } else { + eprintln!("Expected two arguments"); + } + } + Some("clear") => { + if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + clear(&mut world, x, y); + } + } else { + eprintln!("Expected two arguments"); + } + } + Some("push") => { + if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + push(&mut world, x, y, tokens.intersperse(" ").collect()); + } + } else { + eprintln!("Expected three arguments"); + } + } + Some("pop") => { + if let (Some(x), Some(y)) = (tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + pop(&mut world, x, y); + } + } else { + eprintln!("Expected two arguments"); + } + } + Some("dir") => { + if let (Some(x), Some(y), Some(direction)) = (tokens.next(), tokens.next(), tokens.next()) { + if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) { + dir(&mut world, x, y, direction); + } + } else { + eprintln!("Expected three arguments"); + } + } + + Some("load") => { + if let Some(path) = tokens.next() { + load(&mut world, path); + } else { + load(&mut world, &args.file); + } + } + Some("save") => { + if let Some(path) = tokens.next() { + save(&world, path); + } else { + save(&world, &args.file); + } + } + + Some("help") => { + println!("- `print`: prints the current world"); + println!("- `get `: prints the JSON-serialized data of the tile at (x, y)"); + println!("- `set `: sets the tile at (x, y) to a default tilename"); + println!("- `remove `: removes the tile at (x, y)"); + + println!("- `prop [data]`: sets the property of the tile at (x, y)"); + println!(" if the tile is a single tuple struct, then prop_name is ignored."); + println!(" if the tile is a tuple struct, then prop_name should be the index of the property"); + + println!("- `state `: sets the state at (x, y) to `state`"); + + println!("- `signal `: adds an empty signal to the tile at (x, y)"); + println!("- `push `: pushes `value` to the signal at (x, y)"); + println!("- `pop `: pops a value from the signal at (x, y)"); + println!("- `clear `: clears the signal of the tile at (x, y)"); + println!("- `dir `: sets the direction of the signal at (x, y)"); + + println!("- `run `: runs a number of steps"); + println!("- `step`: runs a single step"); + println!("- `load [file]`: saves the current state to `file` (defaults to the path in the parameters)"); + println!("- `save [file]`: saves the current state to `file` (defaults to the path in the parameters)"); + } + Some(cmd) => { + eprintln!("Syntax error: unknown command {}", cmd); + } + } + } +} + +#[derive(Parser, Debug)] +#[clap(author, version, about)] +struct Args { + #[clap(short, long, value_parser)] + file: PathBuf, +} + +fn run(world: &mut World, steps: usize) -> std::io::Result<()> { + let mut stdout = std::io::stdout(); + let mut first = true; + for _ in 0..steps { + if !first { + world.step(); + write!(stdout, "\x1b[4;A")?; + } + first = false; + write!(stdout, "{}", world)?; + stdout.flush()?; + std::thread::sleep(Duration::new(0, 100_000_000)); + } + Ok(()) +} + +fn step(world: &mut World) { + world.step(); + print!("{}", world); +} + +fn get(world: &World, x: i32, y: i32) { + match world.get((x, y)) { + Some(tile) => { + match serde_json::to_string_pretty(&*tile) { + Ok(serialized) => println!("{}", serialized), + Err(err) => eprintln!("Error while serializing tile at {}:{}; {}", x, y, err), + } + } + None => { + eprintln!("No tile at {}:{}!", x, y); + } + } +} + +fn prop(world: &mut World, x: i32, y: i32, prop_name: &str, value: String) { + use serde_json::Value; + + let tile = match world.get_mut((x, y)) { + Some(tile) => { + if let Some(tile) = tile.get_mut() { + tile + } else { + eprintln!("Tile at {}:{} is empty!", x, y); + return + } + } + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + }; + + let mut tile_value = match serde_json::to_value(&tile) { + Ok(serialized) => serialized, + Err(err) => { + eprintln!("Error while serializing tile at {}:{}; {}", x, y, err); + return + } + }; + + let parsed: Value = match serde_json::from_str(&value) { + Ok(parsed) => parsed, + Err(err) => { + eprintln!("Error while parsing value: {}", err); + return + } + }; + + + if let Value::Object(ref mut enum_map) = &mut tile_value { + if let Some(enum_value) = enum_map.values_mut().next() { + match enum_value { + Value::Object(map) => { + map.insert(prop_name.to_string(), parsed); + } + Value::Array(vec) => { + if let Ok(num) = prop_name.parse::() { + if num >= vec.len() { + eprintln!("Index out of bound: len is {} but index is {}", vec.len(), num); + return + } + vec[num] = parsed; + } + } + _ => { + *enum_value = parsed; + } + } + } else { + eprintln!("Format error: expected enum to be encoded as a single-element map."); + return + } + } else { + eprintln!("Format error: expected enum to be encoded as a single-element map."); + return + } + + *tile = match serde_json::from_value(tile_value) { + Ok(tile) => tile, + Err(err) => { + eprintln!("Error while inserting value: {}", err); + return + } + }; +} + +fn set(world: &mut World, x: i32, y: i32, name: &str) { + let tile = match world.get_mut((x, y)) { + Some(tile) => tile, + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + }; + + *tile = match AnyTile::new(name) { + Some(tile) => FullTile::from(tile), + None => { + eprintln!("No tile named {}", name); + return; + } + }; +} + +fn remove(world: &mut World, x: i32, y: i32) { + match world.get_mut((x, y)) { + Some(tile) => *tile = FullTile::new(None), + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + } +} + +fn signal(world: &mut World, x: i32, y: i32) { + match world.get_mut_with_pos((x, y)) { + Some((tile, x, y)) => { + tile.set_signal(Some(Signal::empty((x, y), Direction::Right))); + tile.set_state(State::Active); + } + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + } +} + +fn clear(world: &mut World, x: i32, y: i32) { + match world.get_mut((x, y)) { + Some(tile) => { + tile.set_signal(None); + tile.set_state(State::Idle); + } + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + } +} + +fn push(world: &mut World, x: i32, y: i32, value: String) { + use serde_json::Value as JValue; + + let value: JValue = match serde_json::from_str(&value) { + Ok(value) => value, + Err(err) => { + eprintln!("Error while parsing value: {}", err); + return + } + }; + + let value: Value = match value { + JValue::Number(num) => { + if let Some(f) = num.as_f64() { + Value::Number(f) + } else { + eprintln!("Unsupported value: {:?}", num); + return + } + } + JValue::String(s) => Value::String(s), + x => { + eprintln!("Unsupported value: {:?}", x); + return + } + }; + + match world.get_mut((x, y)) { + Some(tile) => { + match tile.take_signal() { + Some(mut signal) => { + signal.push(value); + tile.set_signal(Some(signal)); + } + None => { + eprintln!("No signal at {}:{}!", x, y); + } + } + } + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + } +} + +fn pop(world: &mut World, x: i32, y: i32) { + match world.get_mut((x, y)) { + Some(tile) => { + match tile.take_signal() { + Some(mut signal) => { + let popped = signal.pop(); + tile.set_signal(Some(signal)); + + if let Some(popped) = popped { + match serde_json::to_string_pretty(&popped) { + Ok(pretty) => println!("{}", pretty), + Err(err) => { + eprintln!("Error while printing popped value: {}", err); + } + } + } else { + eprintln!("Nothing to pop at {}:{}!", x, y); + } + } + None => { + eprintln!("No signal at {}:{}!", x, y); + } + } + } + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + } +} + +fn dir(world: &mut World, x: i32, y: i32, direction: &str) { + let direction: Direction = match serde_json::from_str(direction) { + Ok(direction) => direction, + Err(err) => { + eprintln!("Error while parsing direction: {}", err); + return + } + }; + + match world.get_mut((x, y)) { + Some(tile) => { + match tile.take_signal() { + Some(signal) => { + tile.set_signal(Some(signal.moved(direction))); + } + None => { + eprintln!("No signal at {}:{}!", x, y); + } + } + } + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + } +} + +fn state(world: &mut World, x: i32, y: i32, state: &str) { + let state: State = match serde_json::from_str(state) { + Ok(state) => state, + Err(err) => { + eprintln!("Error while parsing state: {}", err); + return + } + }; + + match world.get_mut((x, y)) { + Some(tile) => { + tile.set_state(state); + } + None => { + eprintln!("No tile at {}:{}!", x, y); + return; + } + } +} + +fn save(world: &World, path: impl AsRef) { + match serde_json::to_string(world) { + Ok(raw) => { + std::fs::write(path.as_ref(), &raw).unwrap_or_else(|err| { + eprintln!("Error while saving: {}", err); + }); + } + Err(err) => { + eprintln!("Error while converting world to JSON: {}", err); + } + } +} + +fn load(world: &mut World, path: impl AsRef) { + match std::fs::read_to_string(path.as_ref()) { + Ok(string) => { + match serde_json::from_str(&string) { + Ok(parsed) => { + *world = parsed; + } + Err(err) => { + eprintln!("Error while parsing file: {}", err); + } + } + } + Err(err) => { + eprintln!("Error while reading file: {}", err); + } + } +} diff --git a/stackline/build.rs b/stackline/build.rs index e43df79..b85c3f4 100644 --- a/stackline/build.rs +++ b/stackline/build.rs @@ -129,6 +129,17 @@ fn generate_code(files: Vec<(PathBuf, Vec)>, names: Vec) -> Stri res += "\n"; + res += "impl AnyTile {\n"; + res += " pub fn new(name: &str) -> Option {\n"; + res += " match name {\n"; + + for name in names.iter() { + res += &format!(" \"{0}\" => Some(Self::{0}(<{0} as Default>::default())),\n", name); + } + + res += " _ => None\n"; + res += " }\n }\n}\n"; + for name in names { // impl TryInto<&T> for &AnyTile res += &format!( diff --git a/stackline/src/context.rs b/stackline/src/context.rs index 8c23346..b4df71c 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -27,7 +27,7 @@ use veccell::{VecRef, VecRefMut}; ``` # use stackline::tile::prelude::*; - #[derive(Clone, Debug, Serialize, Deserialize)] + #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct CounterTile(usize); impl CounterTile { @@ -113,7 +113,7 @@ impl<'a> UpdateContext<'a> { /// /// ``` /// # use stackline::tile::prelude::*; - /// # #[derive(Clone, Debug, Serialize, Deserialize)] + /// # #[derive(Clone, Debug, Serialize, Deserialize, Default)] /// # pub struct MyTile; /// # impl Tile for MyTile { /// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) { @@ -166,7 +166,7 @@ impl<'a> UpdateContext<'a> { /// /// ``` /// # use stackline::tile::prelude::*; - /// #[derive(Clone, Debug, Serialize, Deserialize)] + /// #[derive(Clone, Debug, Serialize, Deserialize, Default)] /// pub struct PrintTile; /// /// impl Tile for PrintTile { @@ -278,7 +278,7 @@ impl<'a> UpdateContext<'a> { /// /// ``` /// # use stackline::tile::prelude::*; - /// # #[derive(Clone, Debug, Serialize, Deserialize)] + /// # #[derive(Clone, Debug, Serialize, Deserialize, Default)] /// # pub struct MyTile; /// # impl Tile for MyTile { /// fn update<'b>(&'b mut self, mut ctx: UpdateContext<'b>) { @@ -382,7 +382,7 @@ impl<'a> UpdateContext<'a> { /// /// ``` /// # use stackline::tile::prelude::*; - /// #[derive(Clone, Debug, Serialize, Deserialize)] + /// #[derive(Clone, Debug, Serialize, Deserialize, Default)] /// pub struct StorageTile {}; /// /// impl Tile for StorageTile { diff --git a/stackline/src/tile/full.rs b/stackline/src/tile/full.rs index f52dfc2..5997909 100644 --- a/stackline/src/tile/full.rs +++ b/stackline/src/tile/full.rs @@ -37,7 +37,7 @@ impl FullTile { } /// Returns `Some` iff self.cell.is_some() - pub(crate) fn set_signal(&mut self, signal: Option) -> Option<()> { + pub fn set_signal(&mut self, signal: Option) -> Option<()> { if self.cell.is_some() { self.signal = signal; Some(()) diff --git a/stackline/src/world.rs b/stackline/src/world.rs index 8e694cd..2e980bc 100644 --- a/stackline/src/world.rs +++ b/stackline/src/world.rs @@ -1,6 +1,7 @@ use super::*; use std::collections::HashMap; use serde::{Serialize, Deserialize}; +use veccell::{VecRef, VecRefMut}; #[derive(Debug, Serialize, Deserialize)] pub struct World { @@ -33,6 +34,66 @@ impl World { self.panes.insert(name, pane); } + pub fn get<'b>(&'b self, (x, y): (i32, i32)) -> Option> { + for pane in self.panes.values() { + let x2 = x - pane.position().0; + let y2 = y - pane.position().1; + if x2 >= 0 && x2 < pane.width().get() as i32 && y2 >= 0 && y2 < pane.height().get() as i32 { + let x2 = x2 as usize; + let y2 = y2 as usize; + if let Some(tile) = pane.get((x2, y2)) { + return Some(tile); + } + } + } + None + } + + pub fn get_with_pos<'b>(&'b self, (x, y): (i32, i32)) -> Option<(VecRef<'b, FullTile>, usize, usize)> { + for pane in self.panes.values() { + let x2 = x - pane.position().0; + let y2 = y - pane.position().1; + if x2 >= 0 && x2 < pane.width().get() as i32 && y2 >= 0 && y2 < pane.height().get() as i32 { + let x2 = x2 as usize; + let y2 = y2 as usize; + if let Some(tile) = pane.get((x2, y2)) { + return Some((tile, x2, y2)); + } + } + } + None + } + + pub fn get_mut<'b>(&'b mut self, (x, y): (i32, i32)) -> Option<&'b mut FullTile> { + for pane in self.panes.values_mut() { + let x2 = x - pane.position().0; + let y2 = y - pane.position().1; + if x2 >= 0 && x2 < pane.width().get() as i32 && y2 >= 0 && y2 < pane.height().get() as i32 { + let x2 = x2 as usize; + let y2 = y2 as usize; + if let Some(tile) = pane.get_mut((x2, y2)) { + return Some(tile); + } + } + } + None + } + + pub fn get_mut_with_pos<'b>(&'b mut self, (x, y): (i32, i32)) -> Option<(&'b mut FullTile, usize, usize)> { + for pane in self.panes.values_mut() { + let x2 = x - pane.position().0; + let y2 = y - pane.position().1; + if x2 >= 0 && x2 < pane.width().get() as i32 && y2 >= 0 && y2 < pane.height().get() as i32 { + let x2 = x2 as usize; + let y2 = y2 as usize; + if let Some(tile) = pane.get_mut((x2, y2)) { + return Some((tile, x2, y2)); + } + } + } + None + } + pub fn get_pane<'b>(&'b self, name: &str) -> Option<&'b Pane> { self.panes.get(name) } diff --git a/stackline/tiles/transmit.rs b/stackline/tiles/transmit.rs index f3e91ed..1bd886e 100644 --- a/stackline/tiles/transmit.rs +++ b/stackline/tiles/transmit.rs @@ -4,7 +4,7 @@ use crate::prelude::*; use crate::tile::prelude::*; /// Instantly sends any incomming signals to `coordinates` -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Teleporter { pub coordinates: (String, usize, usize), } @@ -34,7 +34,7 @@ impl Tile for Teleporter { } /// Sends a signal through a virtual wire towards `coordinates`. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Sender { pub coordinates: (String, usize, usize), pub path: Vec<(i32, i32)>, // x, y diff --git a/stackline/tiles/wire.rs b/stackline/tiles/wire.rs index d7175e3..bc993c9 100644 --- a/stackline/tiles/wire.rs +++ b/stackline/tiles/wire.rs @@ -2,7 +2,7 @@ use crate::tile::prelude::*; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Wire(Orientation); impl Wire { @@ -45,7 +45,7 @@ impl Tile for Wire { } } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Diode(Direction); impl Diode { @@ -84,7 +84,7 @@ impl Tile for Diode { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Resistor { direction: Direction, signal: Option, @@ -119,10 +119,10 @@ impl Tile for Resistor { fn draw(&self, x: usize, y: usize, state: State, surface: &mut TextSurface) { let ch = match self.direction { - Direction::Up => '\u{219f}', // Upwards Two Headed Arrow - Direction::Down => '\u{21a1}', // Downwards Two Headed Arrow - Direction::Left => '\u{219e}', // Leftwards Two Headed Arrow - Direction::Right => '\u{21a0}', // Rightwards Two Headed Arrow + Direction::Up => '\u{2191}', // Upwards Arrow + Direction::Down => '\u{2193}', // Downwards Arrow + Direction::Left => '\u{2190}', // Leftwards Arrow + Direction::Right => '\u{2192}', // Rightwards Arrow }; surface.set(x, y, TextChar::from_state(ch, state));