stackline-cli

main
Shad Amethyst 2 years ago
parent 73119e7e8e
commit 5c5fd81b38
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -2,7 +2,8 @@
members = [
"stackline-wasm",
"stackline"
"stackline-cli",
"stackline",
]
[profile.release]

@ -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"

@ -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::<usize>()) {
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 <x> <y>`: prints the JSON-serialized data of the tile at (x, y)");
println!("- `set <x> <y> <tilename>`: sets the tile at (x, y) to a default tilename");
println!("- `remove <x> <y>`: removes the tile at (x, y)");
println!("- `prop <x> <y> <prop_name> [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 <x> <y> <state>`: sets the state at (x, y) to `state`");
println!("- `signal <x> <y>`: adds an empty signal to the tile at (x, y)");
println!("- `push <x> <y> <value>`: pushes `value` to the signal at (x, y)");
println!("- `pop <x> <y>`: pops a value from the signal at (x, y)");
println!("- `clear <x> <y>`: clears the signal of the tile at (x, y)");
println!("- `dir <x> <y> <dir>`: sets the direction of the signal at (x, y)");
println!("- `run <steps>`: 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::<usize>() {
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<Path>) {
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<Path>) {
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);
}
}
}

@ -129,6 +129,17 @@ fn generate_code(files: Vec<(PathBuf, Vec<String>)>, names: Vec<String>) -> Stri
res += "\n";
res += "impl AnyTile {\n";
res += " pub fn new(name: &str) -> Option<Self> {\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<T: Tile> TryInto<&T> for &AnyTile
res += &format!(

@ -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 {

@ -37,7 +37,7 @@ impl FullTile {
}
/// Returns `Some` iff self.cell.is_some()
pub(crate) fn set_signal(&mut self, signal: Option<Signal>) -> Option<()> {
pub fn set_signal(&mut self, signal: Option<Signal>) -> Option<()> {
if self.cell.is_some() {
self.signal = signal;
Some(())

@ -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<VecRef<'b, FullTile>> {
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)
}

@ -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

@ -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<Signal>,
@ -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));

Loading…
Cancel
Save