Improve build.rs, basic stack ops, methods for World

main
Shad Amethyst 2 years ago
parent 399883d8ba
commit 8654d25c65
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -33,33 +33,28 @@ fn main() {
.unwrap_or_else(|err| panic!("Couldn't read {:?}: {}", src_path, err));
let mut local_names: Vec<String> = Vec::new();
// TODO: don't throw an error when a parsing error occured;
// Instead, include the file so that rustc can give a helpful error
let syntax = syn::parse_file(&contents)
.unwrap_or_else(|err| panic!("Unable to parse file {:?}: {}", src_path, err));
for item in syntax.items.iter() {
#[allow(clippy::single_match)]
// I'd like to keep the match for future-proofing, for instance if I need to match for a macro call
match item {
Item::Impl(item) => {
if let Some(name) = parse_impl_tile(item) {
local_names.push(name);
match syn::parse_file(&contents) {
Ok(syntax) => {
for item in syntax.items.iter() {
if let Item::Impl(item) = item {
if let Some(name) = parse_impl_tile(item) {
local_names.push(name);
}
}
}
_ => {}
}
Err(err) => {
eprintln!("Unable to parse file {:?}: {}", src_path, err);
}
}
if !local_names.is_empty() {
let canonical = fs::canonicalize(src_path.clone()).unwrap_or_else(|err| {
panic!("Couldn't canonicalize {}: {}", src_path.display(), err)
});
for name in local_names.iter() {
names.push(name.clone());
}
files.push((canonical, local_names));
let canonical = fs::canonicalize(src_path.clone()).unwrap_or_else(|err| {
panic!("Couldn't canonicalize {}: {}", src_path.display(), err)
});
for name in local_names.iter() {
names.push(name.clone());
}
files.push((canonical, local_names));
}
}

@ -9,6 +9,7 @@ This library is the rust implementation of the core logic of the language.
#![feature(div_duration)]
#![feature(drain_filter)]
#![feature(try_blocks)]
use std::num::NonZeroUsize;

@ -28,8 +28,8 @@ impl From<f64> for Value {
}
}
impl From<u32> for Value {
fn from(x: u32) -> Value {
impl From<i32> for Value {
fn from(x: i32) -> Value {
Value::Number(x.into())
}
}
@ -55,6 +55,24 @@ impl std::fmt::Display for Value {
}
}
impl std::ops::Add for Value {
type Output = Self;
fn add(self, other: Self) -> Self {
use Value::*;
use std::fmt::Write;
match (self, other) {
(Number(x), Number(y)) => Number(x + y),
(String(mut x), y) => {
write!(&mut x, "{}", y).unwrap();
String(x)
}
(x, String(y)) => String(format!("{}{}", x, y)),
}
}
}
/// The unit of information that [`Tile`]s transmit between each other.
/// A `Signal` is made up of a [`stack`](Signal::stack), and tracks its [`position`](Signal::position) and [`direction`](Signal::direction).
///
@ -191,6 +209,12 @@ impl Signal {
}
}
impl PartialEq<Signal> for Signal {
fn eq(&self, other: &Self) -> bool {
self.stack == other.stack
}
}
/// Creates a signal with initial values in its stack.
///
/// The syntax for the macro is `signal!(position, direction, [value1, value2, ...])`, where:

@ -57,6 +57,8 @@ impl World {
self.panes.insert(name, pane);
}
// TODO: get_pane_at, get_pane_at_mut
pub fn get(&self, (x, y): (i32, i32)) -> Option<VecRef<'_, FullTile>> {
for pane in self.panes.values() {
let x2 = x - pane.position().0;
@ -95,6 +97,45 @@ impl World {
None
}
pub fn get_as<T: Tile>(&self, (x, y): (i32, i32)) -> Option<VecRef<'_, T>>
where
for<'c> &'c AnyTile: TryInto<&'c T>,
{
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_as::<T>((x2, y2)) {
return Some(tile);
}
}
}
None
}
pub fn set_signal(&mut self, (x, y): (i32, i32), signal: Signal) {
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;
pane.set_signal((x2, y2), signal);
break
}
}
}
pub fn get_mut(&mut self, (x, y): (i32, i32)) -> Option<&mut FullTile> {
for pane in self.panes.values_mut() {
let x2 = x - pane.position().0;

@ -29,8 +29,9 @@ macro_rules! assert_signal {
( $world:expr, $x:expr, $y:expr ) => {{
let guard = $world
.get(($x, $y))
.expect(&format!("Couldn't get tile at {}:{}", $x, $y));
.unwrap_or_else(|| panic!("Couldn't get tile at {}:{}", $x, $y));
let signal = guard.signal();
assert!(
signal.is_some(),
"Expected signal at {}:{}!\n{}",
@ -38,12 +39,13 @@ macro_rules! assert_signal {
$y,
$world
);
signal
signal.unwrap()
}};
( $world:expr, $x:expr, $y:expr, [ $( $data:expr ),* ] ) => {{
let signal = assert_signal!($pane, $x, $y);
// TODO: check that signal.data == data
( $world:expr, $x:expr, $y:expr, $signal:expr ) => {{
let signal = assert_signal!($world, $x, $y);
assert_eq!(signal, $signal);
}};
}
@ -52,8 +54,9 @@ macro_rules! assert_no_signal {
( $world:expr, $x:expr, $y:expr ) => {{
let guard = $world
.get(($x, $y))
.expect(&format!("Couldn't get tile at {}:{}", $x, $y));
.unwrap_or_else(|| panic!("Couldn't get tile at {}:{}", $x, $y));
let signal = guard.signal();
assert!(
signal.is_none(),
"Expected no signal at {}:{}!\n{}",
@ -64,6 +67,56 @@ macro_rules! assert_no_signal {
}};
}
#[macro_export]
macro_rules! assert_stored {
( $world:expr, $x:expr, $y:expr ) => {{
let guard = $world
.get_as::<stackline::tile::Store>(($x, $y))
.unwrap_or_else(|| panic!("Couldn't get store tile at {}:{}", $x, $y));
let signal = guard.signal().cloned();
assert!(
signal.is_some(),
"Expected stored signal at {}:{}!\n{}",
$x,
$y,
$world
);
signal.unwrap()
}};
( $world:expr, $x:expr, $y:expr, $signal:expr ) => {{
let signal = assert_stored!($world, $x, $y);
assert_eq!(
signal,
$signal,
concat!("Expected stored signal at {}:{} to be equal to ", stringify!($signal), "!\n{}"),
$x,
$y,
$world
);
}};
}
#[macro_export]
macro_rules! assert_no_stored {
( $world:expr, $x:expr, $y:expr ) => {{
let guard = $world
.get_as::<stackline::tile::Store>(($x, $y))
.unwrap_or_else(|| panic!("Couldn't get store tile at {}:{}", $x, $y));
let signal = guard.signal();
assert!(
signal.is_none(),
"Expected no stored signal at {}:{}!\n{}",
$x,
$y,
$world
);
}};
}
#[macro_export]
macro_rules! assert_display_eq {
( $world:expr, $path:expr ) => {{

@ -0,0 +1,39 @@
#[allow(unused_imports)]
use stackline::prelude::*;
use stackline::signal;
mod common;
#[test]
fn test_reader() {
let mut world = load_test!("tests/storage/reader.json");
world.init();
world.set_signal((0, 0), signal!(Direction::Down, [0]));
run!(world, 5);
assert_stored!(world, 0, 1, signal!([0]));
assert_no_stored!(world, 2, 0);
world.set_signal((1, 2), signal!(Direction::Up, [-1]));
run!(world, 5);
assert_stored!(world, 2, 0, signal!([0]));
world.set_signal((0, 0), signal!(Direction::Down, [1]));
run!(world, 5);
assert_stored!(world, 0, 1, signal!([1]));
assert_stored!(world, 2, 0, signal!([0]));
}
#[test]
fn test_counter() {
let mut world = load_test!("tests/storage/counter.json");
world.init();
for n in 1..10 {
world.set_signal((1, 3), signal!(Direction::Up));
println!("{}", world);
run!(world, 11);
assert_stored!(world, 0, 2, signal!([n]));
}
}

@ -0,0 +1 @@
{"panes":{"main":{"tiles":[{"cell":{"Diode":"Down"},"signal":null,"state":"Idle","updated":false},{"cell":{"Add":null},"signal":null,"state":"Idle","updated":false},{"cell":{"Diode":"Left"},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":{"Wire":"Vertical"},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":{"Push":{"value":{"Number":1.0}}},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":{"Store":{"signal":{"direction":"Up","stack":[{"Number":0.0}]}}},"signal":null,"state":"Idle","updated":false},{"cell":{"Reader":"Right"},"signal":null,"state":"Idle","updated":false},{"cell":{"Diode":"Up"},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":{"Diode":"Up"},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false}],"width":4,"height":4,"position":[0,0]}},"blink_speed":{"secs":0,"nanos":250000000}}

@ -0,0 +1 @@
{"panes":{"main":{"tiles":[{"cell":{"Diode":"Down"},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":{"Store":{"signal":null}},"signal":null,"state":"Idle","updated":false},{"cell":{"Store":{"signal":null}},"signal":null,"state":"Idle","updated":false},{"cell":{"Reader":"Right"},"signal":null,"state":"Idle","updated":false},{"cell":{"Diode":"Up"},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false},{"cell":{"Diode":"Up"},"signal":null,"state":"Idle","updated":false},{"cell":null,"signal":null,"state":"Idle","updated":false}],"width":3,"height":3,"position":[0,0]}},"blink_speed":{"secs":0,"nanos":250000000}}

@ -0,0 +1,147 @@
//! Arithmetic operations: add, subtract, etc.
use crate::prelude::*;
use crate::tile::prelude::*;
macro_rules! binary_op {
() => {
fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) {
if let Some(mut signal) = context.take_signal() {
if signal.len() >= 2 {
let first = signal.pop().unwrap_or_else(|| unreachable!());
let second = signal.pop().unwrap_or_else(|| unreachable!());
if let Some(res) = self.binary_op(second, first) {
signal.push(res);
}
}
if let Some(coords) = context.offset(signal.direction().into_offset()) {
context.send(coords, signal.direction(), signal).unwrap_or_else(|_| unreachable!());
}
}
if context.state() != State::Idle {
context.next_state();
}
}
}
}
/// Adds two values together: `[..., a, b] -> [..., a+b]`
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Add;
impl Add {
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
Some(left + right)
}
}
impl Tile for Add {
binary_op!();
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('\u{2295}', ctx.state) // CIRCLED PLUS
}
}
/// Subtracts two values: `[..., a, b] -> [..., a-b]`
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Sub;
impl Sub {
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
use Value::*;
match (left, right) {
(Number(x), Number(y)) => Some(Number(x - y)),
(String(mut x), Number(y)) => {
x.truncate(y as usize);
Some(String(x))
}
(String(x), String(y)) => {
Some(String(x.split(&y).collect()))
}
_ => None
}
}
}
impl Tile for Sub {
binary_op!();
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('\u{2296}', ctx.state) // CIRCLED MINUS
}
}
/// Multiplies two values together: `[..., a, b] -> [..., a*b]`
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Mul;
impl Mul {
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
use Value::*;
match (left, right) {
(Number(x), Number(y)) => Some(Number(x * y)),
(String(x), Number(y)) => Some(String(x.repeat(y as usize))),
_ => None
}
}
}
impl Tile for Mul {
binary_op!();
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('\u{2297}', ctx.state) // CIRCLED TIMES
}
}
/// Divides two values together: `[..., a, b] -> [..., a/b]`
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Div;
impl Div {
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
use Value::*;
match (left, right) {
(Number(x), Number(y)) => Some(Number(x / y)),
_ => None
}
}
}
impl Tile for Div {
binary_op!();
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('\u{2298}', ctx.state) // CIRCLED DIVISION SLASH
}
}
/// Computes the modulo of two values: `[..., a, b] -> [..., a%b]`
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Mod;
impl Mod {
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
use Value::*;
match (left, right) {
(Number(x), Number(y)) => Some(Number(x % y)),
_ => None
}
}
}
impl Tile for Mod {
binary_op!();
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('\u{2299}', ctx.state) // CIRCLED DOT OPERATOR
}
}

@ -39,7 +39,7 @@ impl Tile for Push {
}
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
TextChar::from_state('p', ctx.state)
TextChar::from_state('P', ctx.state)
}
}
@ -82,9 +82,9 @@ impl Tile for Pop {
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
if self.amount == 1 {
TextChar::from_state('P', ctx.state)
TextChar::from_state('\u{2c63}', ctx.state) // Latin Capital Letter P with Stroke
} else {
TextChar::from_state(ctx.blink.scroll(&format!("PPP{}", self.amount)), ctx.state)
TextChar::from_state(ctx.blink.scroll(&format!("\u{2c63}\u{2c63}\u{2c63}{}", self.amount)), ctx.state)
}
}
}

@ -0,0 +1,103 @@
//! Arithmetic operations: add, subtract, etc.
use crate::prelude::*;
use crate::tile::prelude::*;
use veccell::VecRef;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Store {
signal: Option<Signal>,
}
impl Store {
pub fn signal(&self) -> Option<&Signal> {
self.signal.as_ref()
}
}
impl Tile for Store {
fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) {
if let Some(signal) = context.take_signal() {
let position = context.position();
// NOTE: we *could* write the signal immediately,
// but by delaying the write we can read from a `Store` without being order-dependent
context.callback(move |pane| {
if let Some(mut this) = pane.borrow_mut_as::<Self>(position) {
this.signal = Some(signal);
}
});
}
if context.state() != State::Idle {
context.next_state();
}
}
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
if self.signal.is_some() {
TextChar::from_state('\u{25c9}', ctx.state) // FISHEYE
} else {
TextChar::from_state('\u{25cb}', ctx.state) // WHITE CIRCLE
}
}
}
/// When a signal is received, reads a signal from a [`Store`] (at the reader's tail),
/// then outputs that signal (at the reader's head).
///
/// # Example
///
/// The following circuit can receive a value in `B`, then store it indefinitely.
/// Sending a signal in `A` will output the stored signal from `B` in `OUT`.
///
/// ```text
/// o = Store
/// ▸ = Reader(Right)
///
/// (A) ---+
/// |
/// (B) --o▸-- (OUT)
/// ```
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Reader(Direction);
impl Tile for Reader {
fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) {
if let Some(_signal) = context.take_signal() {
let _: Option<()> = try {
let store_position = context.offset(self.0.opposite().into_offset())?;
let store = context.get(store_position).and_then(get_store)?;
let signal = store.signal.clone()?;
drop(store);
let target_position = context.offset(self.0.into_offset())?;
context.send(target_position, self.0, signal);
};
}
if context.state() != State::Idle {
context.next_state();
}
}
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
match self.0 {
Direction::Down => TextChar::from_state('\u{25bd}', ctx.state), // WHITE DOWN-POINTING TRIANGLE
Direction::Left => TextChar::from_state('\u{25c1}', ctx.state), // WHITE LEFT-POINTING TRIANGLE
Direction::Right => TextChar::from_state('\u{25b7}', ctx.state), // WHITE RIGHT-POINTING TRIANGLE
Direction::Up => TextChar::from_state('\u{25b3}', ctx.state), // WHITE UP-POINTING TRIANGLE
}
}
}
/// Tries to convert a [`FullTile`] to a [`Store`]
fn get_store<'a>(full: VecRef<'a, FullTile>) -> Option<VecRef<'a, Store>> {
VecRef::try_map(full, |tile| {
let tile = tile.get().ok_or(())?;
let store = tile.try_into().map_err(|_| ())?;
Ok::<&Store, ()>(store)
}).ok()
}

@ -36,9 +36,9 @@ impl Tile for Wire {
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
let ch = match self.0 {
Orientation::Horizontal => '-',
Orientation::Vertical => '|',
Orientation::Any => '+',
Orientation::Horizontal => '-', // BOX DRAWINGS LIGHT HORIZONTAL
Orientation::Vertical => '|', // BOX DRAWINGS LIGHT VERTICAL
Orientation::Any => '+', // BOX DRAWINGS LIGHT HORIZONTAL AND VERTICAL
};
TextChar::from_state(ch, ctx.state)
@ -79,7 +79,7 @@ impl Tile for Diode {
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
let ch = match self.0 {
Direction::Up => '^',
Direction::Down => 'v',
Direction::Down => '\u{2335}', // COUNTERSINK
Direction::Left => '<',
Direction::Right => '>',
};

Loading…
Cancel
Save