|
|
|
//! 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
|
|
pub struct Arithmetic(ArithOp);
|
|
|
|
|
|
|
|
impl Arithmetic {
|
|
|
|
pub fn new(op: ArithOp) -> Self {
|
|
|
|
Self(op)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
|
|
|
|
self.0.binary_op(left, right)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Tile for Arithmetic {
|
|
|
|
binary_op!();
|
|
|
|
|
|
|
|
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
|
|
|
|
use ArithOp::*;
|
|
|
|
|
|
|
|
match self.0 {
|
|
|
|
Add => TextChar::from_state('\u{2295}', ctx.state), // CIRCLED PLUS
|
|
|
|
Sub => TextChar::from_state('\u{2296}', ctx.state), // CIRCLED MINUS
|
|
|
|
Mul => TextChar::from_state('\u{2297}', ctx.state), // CIRCLED TIMES
|
|
|
|
Div => TextChar::from_state('\u{2298}', ctx.state), // CIRCLED DIVISION SLASH
|
|
|
|
Mod => TextChar::from_state('\u{2299}', ctx.state), // CIRCLED DOT OPERATOR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn schema(&self) -> TileSchema {
|
|
|
|
TileSchema::value("Operator", "ArithOp")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq)]
|
|
|
|
pub enum ArithOp {
|
|
|
|
Add,
|
|
|
|
Sub,
|
|
|
|
Mul,
|
|
|
|
Div,
|
|
|
|
Mod
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ArithOp {
|
|
|
|
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
|
|
|
|
use Value::*;
|
|
|
|
use ArithOp::*;
|
|
|
|
match (self, left, right) {
|
|
|
|
// == Add ==
|
|
|
|
(Add, left, right) => Some(left + right),
|
|
|
|
// == Sub ==
|
|
|
|
(Sub, Number(x), Number(y)) => Some(Number(x - y)),
|
|
|
|
(Sub, String(mut x), Number(y)) => {
|
|
|
|
x.truncate(y as usize);
|
|
|
|
Some(String(x))
|
|
|
|
}
|
|
|
|
(Sub, String(x), String(y)) => {
|
|
|
|
Some(String(x.split(&y).collect()))
|
|
|
|
}
|
|
|
|
// == Mul ==
|
|
|
|
(Mul, Number(x), Number(y)) => Some(Number(x * y)),
|
|
|
|
(Mul, String(x), Number(y)) => Some(String(x.repeat(y as usize))),
|
|
|
|
// == Div ==
|
|
|
|
(Div, Number(x), Number(y)) => Some(Number(x / y)),
|
|
|
|
// == Mod ==
|
|
|
|
(Mod, Number(x), Number(y)) => Some(Number(x % y)),
|
|
|
|
// Default
|
|
|
|
_ => None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ArithOp {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Add
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Compares the top two values: `[..., a, b] -> [..., a ?? b]`
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
|
|
pub struct Cmp(CmpOp);
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq)]
|
|
|
|
pub enum CmpOp {
|
|
|
|
Eq,
|
|
|
|
Neq,
|
|
|
|
Gt,
|
|
|
|
Gte,
|
|
|
|
Lt,
|
|
|
|
Lte,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Cmp {
|
|
|
|
fn binary_op(&self, left: Value, right: Value) -> Option<Value> {
|
|
|
|
use Value::*;
|
|
|
|
use CmpOp::*;
|
|
|
|
|
|
|
|
let res = match (self.0, left, right) {
|
|
|
|
(Eq, Number(x), Number(y)) => Some(x == y),
|
|
|
|
(Eq, String(x), String(y)) => Some(x == y),
|
|
|
|
|
|
|
|
(Neq, Number(x), Number(y)) => Some(x != y),
|
|
|
|
(Neq, String(x), String(y)) => Some(x != y),
|
|
|
|
|
|
|
|
(Gt, Number(x), Number(y)) => Some(x > y),
|
|
|
|
(Gt, String(x), String(y)) => Some(x > y),
|
|
|
|
|
|
|
|
(Gte, Number(x), Number(y)) => Some(x >= y),
|
|
|
|
(Gte, String(x), String(y)) => Some(x >= y),
|
|
|
|
|
|
|
|
(Lt, Number(x), Number(y)) => Some(x < y),
|
|
|
|
(Lt, String(x), String(y)) => Some(x < y),
|
|
|
|
|
|
|
|
(Lte, Number(x), Number(y)) => Some(x <= y),
|
|
|
|
(Lte, String(x), String(y)) => Some(x <= y),
|
|
|
|
|
|
|
|
_ => None
|
|
|
|
}?;
|
|
|
|
|
|
|
|
Some(Number(if res { 1.0 } else { 0.0 }))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Tile for Cmp {
|
|
|
|
binary_op!();
|
|
|
|
|
|
|
|
fn draw_simple(&self, ctx: DrawContext) -> TextChar {
|
|
|
|
use CmpOp::*;
|
|
|
|
|
|
|
|
match self.0 {
|
|
|
|
Eq => TextChar::from_state('\u{229c}', ctx.state), // CIRCLED EQUALS
|
|
|
|
Neq => TextChar::from_state('\u{2260}', ctx.state), // NOT EQUALS (the circled version doesn't exist)
|
|
|
|
Lt => TextChar::from_state('\u{29c0}', ctx.state), // CIRCLED LESS-THAN
|
|
|
|
Lte => TextChar::from_state('\u{2264}', ctx.state), // LESS-THAN OR EQUAL TO (no circled version)
|
|
|
|
Gt => TextChar::from_state('\u{29c1}', ctx.state), // CIRCLED GREATER-THAN
|
|
|
|
Gte => TextChar::from_state('\u{2265}', ctx.state), // GREATER-THAN OR EQUAL TO (no circled version)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn schema(&self) -> TileSchema {
|
|
|
|
TileSchema::value("CmpOp", "CmpOp")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for CmpOp {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Eq
|
|
|
|
}
|
|
|
|
}
|