feat: more and better math operators
This commit is contained in:
parent
cdebdd2ce9
commit
7602e9f7b9
|
@ -9,6 +9,7 @@ pub enum ValueType {
|
|||
Int_ = 0x49, /* I */
|
||||
Pointer = 0x50, /* P */
|
||||
}
|
||||
impl Sealed for ValueType {}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
|
||||
|
@ -20,27 +21,45 @@ pub enum OpType {
|
|||
CallLDefer = 0x64, /* d */
|
||||
JumpCond = 0x6a, /* j */
|
||||
Return = 0x52, /* R */
|
||||
|
||||
// stack operations
|
||||
Push = 0x50, /* P */
|
||||
Pop = 0x51, /* Q */
|
||||
Dup = 0x44, /* D */
|
||||
Swap = 0x53, /* S */
|
||||
|
||||
// atom operations
|
||||
ABuild = 0x2e, /* . */
|
||||
ADecon = 0x2a, /* * */
|
||||
|
||||
// extensions
|
||||
DoMath1 = 0x6d, /* m */
|
||||
DoMath2 = 0x4d, /* M */
|
||||
}
|
||||
impl Sealed for OpType {}
|
||||
|
||||
// u16 because we assume we may add many additional math operations in the future
|
||||
#[repr(u16)]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
|
||||
pub enum MathUnOp {
|
||||
// bit operations
|
||||
Not = 0x7e, /* ~ */
|
||||
}
|
||||
impl Sealed for MathUnOp {}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
|
||||
pub enum MathBinOp {
|
||||
NotAnd = 0x7e26, /* ~& */
|
||||
Add = 0x202b, /* + */
|
||||
Mul = 0x202a, /* * */
|
||||
}
|
||||
Concat = 0x2e, /* . */
|
||||
|
||||
impl Sealed for ValueType {}
|
||||
impl Sealed for OpType {}
|
||||
// bit operations
|
||||
And = 0x26, /* & */
|
||||
Or = 0x7c, /* | */
|
||||
Xor = 0x5e, /* ^ */
|
||||
|
||||
// arithmetic operations
|
||||
Rem = 0x25, /* % */
|
||||
Mul = 0x2a, /* * */
|
||||
Add = 0x2b, /* + */
|
||||
Sub = 0x2d, /* - */
|
||||
Div = 0x2f, /* / */
|
||||
}
|
||||
impl Sealed for MathBinOp {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::consts::{MathBinOp, OpType};
|
||||
use crate::{Atom, Value};
|
||||
use crate::consts::{MathBinOp, MathUnOp, OpType};
|
||||
use crate::Value;
|
||||
use core::fmt;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -9,10 +9,11 @@ pub enum Instr<'a> {
|
|||
Label,
|
||||
|
||||
/// pops the descriptor table pointer from the stack,
|
||||
/// then tries to call the $0 on it.
|
||||
/// $1 is the amount of arguments (arity) extracted
|
||||
/// from the stack after the pointer
|
||||
CallRemote(Atom, u8),
|
||||
/// then tries to call it with the top $0 stack elements as arguments.
|
||||
/// thus all-in-all this pops `1+$0` values from the stack, then
|
||||
/// places an arbitrary amount of values on the stack,
|
||||
/// followed by the `Int(return value count)`
|
||||
CallRemote,
|
||||
|
||||
/// calls the destination $0 inside of the current module
|
||||
CallLocal(u64),
|
||||
|
@ -45,10 +46,10 @@ pub enum Instr<'a> {
|
|||
Swap(u16),
|
||||
|
||||
/// basic atom operations
|
||||
ABuild,
|
||||
ADecon,
|
||||
|
||||
/// basic math operations (on integers and atoms)
|
||||
DoMath1(MathUnOp),
|
||||
DoMath2(MathBinOp),
|
||||
}
|
||||
|
||||
|
@ -57,7 +58,7 @@ impl Instr<'_> {
|
|||
pub fn typ(&self) -> OpType {
|
||||
match self {
|
||||
Instr::Label => OpType::Label,
|
||||
Instr::CallRemote(_, _) => OpType::CallRemote,
|
||||
Instr::CallRemote => OpType::CallRemote,
|
||||
Instr::CallLocal(_) => OpType::CallLocal,
|
||||
Instr::CallLDefer(_) => OpType::CallLDefer,
|
||||
Instr::JumpCond(_) => OpType::JumpCond,
|
||||
|
@ -66,8 +67,8 @@ impl Instr<'_> {
|
|||
Instr::Pop(_) => OpType::Pop,
|
||||
Instr::Dup(_) => OpType::Dup,
|
||||
Instr::Swap(_) => OpType::Swap,
|
||||
Instr::ABuild => OpType::ABuild,
|
||||
Instr::ADecon => OpType::ADecon,
|
||||
Instr::DoMath1(_) => OpType::DoMath1,
|
||||
Instr::DoMath2(_) => OpType::DoMath2,
|
||||
}
|
||||
}
|
||||
|
@ -106,11 +107,6 @@ impl<'a> crate::Parse<'a> for Instr<'a> {
|
|||
}
|
||||
let (inp, otyp) = OpType::parse(inp)?;
|
||||
match otyp {
|
||||
OpType::CallRemote => {
|
||||
let (inp, val) = Atom::parse(inp)?;
|
||||
let (inp, arity) = u8::parse(inp)?;
|
||||
Ok((inp, Instr::CallRemote(val, arity)))
|
||||
}
|
||||
OpType::CallLocal => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLocal(val))),
|
||||
OpType::CallLDefer => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLDefer(val))),
|
||||
OpType::JumpCond => u64::parse(inp).map(|(inp, val)| (inp, Instr::JumpCond(val))),
|
||||
|
@ -118,10 +114,11 @@ impl<'a> crate::Parse<'a> for Instr<'a> {
|
|||
OpType::Pop => u16::parse(inp).map(|(inp, val)| (inp, Instr::Pop(val))),
|
||||
OpType::Dup => u16::parse(inp).map(|(inp, val)| (inp, Instr::Dup(val))),
|
||||
OpType::Swap => u16::parse(inp).map(|(inp, val)| (inp, Instr::Swap(val))),
|
||||
OpType::DoMath1 => MathUnOp::parse(inp).map(|(inp, val)| (inp, Instr::DoMath1(val))),
|
||||
OpType::DoMath2 => MathBinOp::parse(inp).map(|(inp, val)| (inp, Instr::DoMath2(val))),
|
||||
OpType::Label => Ok((inp, Instr::Label)),
|
||||
OpType::CallRemote => Ok((inp, Instr::CallRemote)),
|
||||
OpType::Return => Ok((inp, Instr::Return)),
|
||||
OpType::ABuild => Ok((inp, Instr::ABuild)),
|
||||
OpType::ADecon => Ok((inp, Instr::ADecon)),
|
||||
}
|
||||
.map_err(|()| ParseError)
|
||||
|
@ -134,21 +131,17 @@ impl Instr<'_> {
|
|||
use int_enum::IntEnum;
|
||||
writer.write_all(&self.typ().int_value().to_be_bytes())?;
|
||||
match self {
|
||||
Instr::CallRemote(val, arity) => {
|
||||
val.write_to(&mut writer)?;
|
||||
writer.write_all(&[*arity])?;
|
||||
}
|
||||
Instr::CallLocal(val) => writer.write_all(&val.to_be_bytes())?,
|
||||
Instr::CallLDefer(val) => writer.write_all(&val.to_be_bytes())?,
|
||||
Instr::JumpCond(val) => writer.write_all(&val.to_be_bytes())?,
|
||||
Instr::Push(val) => val.write_to(writer)?,
|
||||
Instr::Pop(val) => writer.write_all(&val.to_be_bytes())?,
|
||||
Instr::Dup(val) => writer.write_all(&val.to_be_bytes())?,
|
||||
Instr::Swap(val) => writer.write_all(&val.to_be_bytes())?,
|
||||
Instr::DoMath2(val) => writer.write_all(&val.int_value().to_be_bytes())?,
|
||||
Instr::Label | Instr::Return | Instr::ABuild | Instr::ADecon => {}
|
||||
Instr::CallLocal(val) => writer.write_all(&val.to_be_bytes()),
|
||||
Instr::CallLDefer(val) => writer.write_all(&val.to_be_bytes()),
|
||||
Instr::JumpCond(val) => writer.write_all(&val.to_be_bytes()),
|
||||
Instr::Push(val) => val.write_to(writer),
|
||||
Instr::Pop(val) => writer.write_all(&val.to_be_bytes()),
|
||||
Instr::Dup(val) => writer.write_all(&val.to_be_bytes()),
|
||||
Instr::Swap(val) => writer.write_all(&val.to_be_bytes()),
|
||||
Instr::DoMath1(val) => writer.write_all(&val.int_value().to_be_bytes()),
|
||||
Instr::DoMath2(val) => writer.write_all(&val.int_value().to_be_bytes()),
|
||||
Instr::Label | Instr::CallRemote | Instr::Return | Instr::ADecon => Ok(()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use async_trait::async_trait;
|
||||
use fogtix_bytecode::{Atom, Instr, Parse, Pointer, Value as BcValue};
|
||||
use fogtix_bytecode::{consts, Atom, Instr, Parse, Pointer, Value as BcValue};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub type Module = Arc<dyn ModuleKind>;
|
||||
|
@ -44,7 +44,7 @@ impl InstrPtr {
|
|||
|
||||
#[async_trait]
|
||||
pub trait Origin: Send + Sync + core::fmt::Debug {
|
||||
async fn call(&self, p: &Pointer, a: &Atom, stack: &mut Vec<StackEntValue>) -> InstrPtr;
|
||||
async fn call(&self, p: &Pointer, stack: &mut Vec<StackEntValue>) -> InstrPtr;
|
||||
}
|
||||
|
||||
pub type KnownOrigins = Arc<[RwLock<Option<Box<dyn Origin>>>; 0x10000]>;
|
||||
|
@ -91,6 +91,9 @@ pub enum Error {
|
|||
|
||||
#[error("operand from stack doesn't have correct data type: expected={expected}, got={got}")]
|
||||
StackedInvalidType { expected: &'static str, got: String },
|
||||
|
||||
#[error("tried to divide by zero")]
|
||||
DivisionByZero,
|
||||
}
|
||||
|
||||
pub struct Process {
|
||||
|
@ -117,14 +120,43 @@ fn verify_jumptarget_explicit(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn u128_binmath(a: u128, b: u128, mbo: consts::MathBinOp) -> Result<u128, Error> {
|
||||
use consts::MathBinOp as B;
|
||||
Ok(match mbo {
|
||||
B::Concat => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "bytes,bytes",
|
||||
got: "[int|atom, int|atom]".to_string(),
|
||||
})
|
||||
}
|
||||
B::And => a & b,
|
||||
B::Or => a | b,
|
||||
B::Xor => a ^ b,
|
||||
B::Add => a.wrapping_add(b),
|
||||
B::Sub => a.wrapping_sub(b),
|
||||
B::Mul => a.wrapping_mul(b),
|
||||
B::Div => match a.checked_div(b) {
|
||||
Some(x) => x,
|
||||
None => return Err(Error::DivisionByZero),
|
||||
},
|
||||
B::Rem => match a.checked_rem(b) {
|
||||
Some(x) => x,
|
||||
None => return Err(Error::DivisionByZero),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
impl Process {
|
||||
fn verify_jumptarget(&self, previptr: usize, jinstr: &'static str) -> Result<(), Error> {
|
||||
verify_jumptarget_explicit(previptr, jinstr, &self.instrp)
|
||||
}
|
||||
|
||||
fn stpop(&mut self) -> Result<StackEntValue, Error> {
|
||||
self.stack.pop().ok_or(Error::NotEnoughStacked)
|
||||
}
|
||||
|
||||
pub async fn run(&mut self, mut fuel: Option<&mut u64>) -> Result<(), Error> {
|
||||
loop {
|
||||
use fogtix_bytecode::consts::MathBinOp;
|
||||
let previptr = self.instrp.pos;
|
||||
tracing::trace!("previptr = {}", previptr);
|
||||
if let Some(ref mut x) = fuel {
|
||||
|
@ -141,39 +173,50 @@ impl Process {
|
|||
self.instrp.pos += nxtidelta;
|
||||
match nxti {
|
||||
Instr::Label => {}
|
||||
Instr::CallRemote(atom, arity) => {
|
||||
Instr::CallRemote => {
|
||||
if !self.instrp.is_call2jump() {
|
||||
self.callstack.push(self.instrp.clone());
|
||||
}
|
||||
self.verify_jumptarget(previptr, "call-r")?;
|
||||
match self.stack.pop() {
|
||||
None => {
|
||||
return Err(Error::NotEnoughStacked);
|
||||
}
|
||||
Some(StackEntValue::Pointer(mut wp)) => {
|
||||
let ssl = self.stack.len();
|
||||
let mut args = self.stack.drain(ssl - usize::from(arity)..).collect();
|
||||
let origin_id = wp.origin();
|
||||
let origin = self.korigs[usize::from(origin_id)].read();
|
||||
match origin {
|
||||
Ok(origin) => match origin.as_ref() {
|
||||
Some(origin) => {
|
||||
wp.set_origin(0);
|
||||
self.instrp = origin.call(&wp, &atom, &mut args).await;
|
||||
self.stack.extend(args);
|
||||
}
|
||||
None => return Err(Error::InvalidOrigin(origin_id)),
|
||||
},
|
||||
Err(_) => return Err(Error::InvalidOrigin(origin_id)),
|
||||
}
|
||||
}
|
||||
Some(x) => {
|
||||
let mut ptr = match self.stpop()? {
|
||||
StackEntValue::Pointer(ptr) => ptr,
|
||||
x => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "pointer",
|
||||
got: format!("{:?}", x),
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
let argc = match self.stpop()? {
|
||||
StackEntValue::Int(i) => i,
|
||||
x => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "int",
|
||||
got: format!("{:?}", x),
|
||||
})
|
||||
}
|
||||
};
|
||||
let ssl = self.stack.len();
|
||||
let argstart = match usize::try_from(argc)
|
||||
.ok()
|
||||
.and_then(|argc| ssl.checked_sub(argc))
|
||||
{
|
||||
Some(x) => x,
|
||||
None => return Err(Error::NotEnoughStacked),
|
||||
};
|
||||
let mut args = self.stack.drain(argstart..).collect();
|
||||
let origin_id = ptr.origin();
|
||||
ptr.set_origin(0);
|
||||
self.instrp = match self.korigs[usize::from(origin_id)].read() {
|
||||
Ok(origin) => match origin.as_ref() {
|
||||
Some(origin) => origin.call(&ptr, &mut args).await,
|
||||
None => return Err(Error::InvalidOrigin(origin_id)),
|
||||
},
|
||||
Err(_) => return Err(Error::InvalidOrigin(origin_id)),
|
||||
};
|
||||
self.verify_jumptarget(previptr, "call-r")?;
|
||||
let rargc = u64::try_from(args.len()).unwrap();
|
||||
self.stack.extend(args);
|
||||
self.stack.push(StackEntValue::Int(rargc));
|
||||
}
|
||||
Instr::CallLocal(x) => {
|
||||
if !self.instrp.is_call2jump() {
|
||||
|
@ -205,15 +248,14 @@ impl Process {
|
|||
Ok(y) => y,
|
||||
Err(_) => return Err(Error::InstrpOutOfBounds),
|
||||
};
|
||||
let doit = match self.stack.pop() {
|
||||
None => return Err(Error::NotEnoughStacked),
|
||||
Some(StackEntValue::Int(i)) => i != 0,
|
||||
Some(StackEntValue::Atom(a)) => a != Atom([0; 16]),
|
||||
Some(z) => {
|
||||
let doit = match self.stpop()? {
|
||||
StackEntValue::Int(i) => i != 0,
|
||||
StackEntValue::Atom(a) => a != Atom([0; 16]),
|
||||
z => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "atom | int",
|
||||
got: format!("{:?}", z),
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
if doit {
|
||||
|
@ -259,23 +301,8 @@ impl Process {
|
|||
};
|
||||
core::mem::swap(&mut y[0], &mut z[0]);
|
||||
}
|
||||
Instr::ABuild => {
|
||||
let a = self.stack.pop();
|
||||
let b = self.stack.pop();
|
||||
match (a, b) {
|
||||
(Some(StackEntValue::Int(a)), Some(StackEntValue::Int(b))) => {
|
||||
self.stack.push(StackEntValue::Atom((b, a).into()));
|
||||
}
|
||||
x => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "[int,int]",
|
||||
got: format!("{:?}", x),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Instr::ADecon => match self.stack.pop() {
|
||||
Some(StackEntValue::Atom(atom)) => {
|
||||
Instr::ADecon => match self.stpop()? {
|
||||
StackEntValue::Atom(atom) => {
|
||||
let (b, a) = atom.into();
|
||||
self.stack.push(StackEntValue::Int(b));
|
||||
self.stack.push(StackEntValue::Int(a));
|
||||
|
@ -287,49 +314,104 @@ impl Process {
|
|||
})
|
||||
}
|
||||
},
|
||||
Instr::DoMath2(mbo) => {
|
||||
let a = self.stack.pop();
|
||||
let b = self.stack.pop();
|
||||
self.stack.push(match (a, b) {
|
||||
(Some(StackEntValue::Int(a)), Some(StackEntValue::Int(b))) => match mbo {
|
||||
MathBinOp::NotAnd => StackEntValue::Int(!(a & b)),
|
||||
MathBinOp::Add => StackEntValue::Int(a.wrapping_add(b)),
|
||||
MathBinOp::Mul => {
|
||||
let c = u128::from(a).wrapping_mul(u128::from(b));
|
||||
StackEntValue::Atom(Atom::from(c))
|
||||
}
|
||||
},
|
||||
(Some(StackEntValue::Atom(a)), Some(StackEntValue::Atom(b))) => {
|
||||
StackEntValue::Atom(Atom::from(match mbo {
|
||||
MathBinOp::NotAnd => !(u128::from(a) & u128::from(b)),
|
||||
MathBinOp::Add => u128::from(a).wrapping_add(u128::from(b)),
|
||||
MathBinOp::Mul => u128::from(a).wrapping_mul(u128::from(b)),
|
||||
}))
|
||||
Instr::DoMath1(ubo) => {
|
||||
use consts::MathUnOp as U;
|
||||
use StackEntValue as V;
|
||||
let x = self.stpop()?;
|
||||
self.stack.push(match (x, ubo) {
|
||||
(V::Int(x), U::Not) => V::Int(!x),
|
||||
(V::Atom(x), U::Not) => V::Atom(Atom::from(!u128::from(x))),
|
||||
(V::Bytes(mut x), U::Not) => {
|
||||
x.iter_mut().for_each(|i| *i = !*i);
|
||||
V::Bytes(x)
|
||||
}
|
||||
(Some(StackEntValue::Bytes(mut a)), Some(StackEntValue::Bytes(mut b))) => {
|
||||
StackEntValue::Bytes(match mbo {
|
||||
MathBinOp::NotAnd if a.len() == b.len() => {
|
||||
a.iter_mut()
|
||||
.zip(b.into_iter())
|
||||
.for_each(|(x, y)| *x = !(*x & y));
|
||||
a
|
||||
}
|
||||
MathBinOp::Add => {
|
||||
a.append(&mut b);
|
||||
a
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "[int,int] | [atom,atom]",
|
||||
got: "[bytes, bytes]".to_string(),
|
||||
})
|
||||
}
|
||||
(x, _) => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "int|atom|bytes",
|
||||
got: format!("{:?}", x),
|
||||
})
|
||||
}
|
||||
x => {
|
||||
});
|
||||
}
|
||||
Instr::DoMath2(mbo) => {
|
||||
let b = self.stpop()?;
|
||||
let a = self.stpop()?;
|
||||
use consts::MathBinOp as B;
|
||||
use StackEntValue as V;
|
||||
self.stack.push(match (a, b) {
|
||||
(V::Int(a), V::Int(b)) => match mbo {
|
||||
B::Concat => V::Atom(Atom::from((a, b))),
|
||||
B::And => V::Int(a & b),
|
||||
B::Or => V::Int(a | b),
|
||||
B::Xor => V::Int(a ^ b),
|
||||
B::Add => V::Int(a.wrapping_add(b)),
|
||||
B::Sub => V::Int(a.wrapping_sub(b)),
|
||||
B::Mul => {
|
||||
V::Atom(Atom::from(u128::from(a).wrapping_mul(u128::from(b))))
|
||||
}
|
||||
B::Div => V::Int(match a.checked_div(b) {
|
||||
Some(x) => x,
|
||||
None => return Err(Error::DivisionByZero),
|
||||
}),
|
||||
B::Rem => V::Int(match a.checked_rem(b) {
|
||||
Some(x) => x,
|
||||
None => return Err(Error::DivisionByZero),
|
||||
}),
|
||||
},
|
||||
(V::Atom(a), V::Atom(b)) => {
|
||||
V::Atom(Atom::from(u128_binmath(u128::from(a), u128::from(b), mbo)?))
|
||||
}
|
||||
(V::Int(a), V::Atom(b)) => {
|
||||
V::Atom(Atom::from(u128_binmath(u128::from(a), u128::from(b), mbo)?))
|
||||
}
|
||||
(V::Atom(a), V::Int(b)) => {
|
||||
V::Atom(Atom::from(u128_binmath(u128::from(a), u128::from(b), mbo)?))
|
||||
}
|
||||
(V::Bytes(a), V::Bytes(b)) => V::Bytes(match mbo {
|
||||
B::Concat => {
|
||||
let mut x = Vec::with_capacity(a.len() + b.len());
|
||||
x.extend_from_slice(&a);
|
||||
x.extend_from_slice(&b);
|
||||
x
|
||||
}
|
||||
B::And => {
|
||||
let mut x: Vec<_> =
|
||||
a.iter().zip(b.iter()).map(|(w, v)| w & v).collect();
|
||||
let minp = x.len();
|
||||
x.extend_from_slice(
|
||||
&(if a.len() < b.len() { b } else { a })[minp..],
|
||||
);
|
||||
x
|
||||
}
|
||||
B::Or => {
|
||||
let mut x: Vec<_> =
|
||||
a.iter().zip(b.iter()).map(|(w, v)| w | v).collect();
|
||||
let minp = x.len();
|
||||
x.extend_from_slice(
|
||||
&(if a.len() < b.len() { b } else { a })[minp..],
|
||||
);
|
||||
x
|
||||
}
|
||||
B::Xor => {
|
||||
let mut x: Vec<_> =
|
||||
a.iter().zip(b.iter()).map(|(w, v)| w ^ v).collect();
|
||||
let minp = x.len();
|
||||
x.extend_from_slice(
|
||||
&(if a.len() < b.len() { b } else { a })[minp..],
|
||||
);
|
||||
x
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "[int|atom, int|atom]",
|
||||
got: "[bytes, bytes]".to_string(),
|
||||
})
|
||||
}
|
||||
}),
|
||||
(a, b) => {
|
||||
return Err(Error::StackedInvalidType {
|
||||
expected: "[int,int] | [atom,atom] | [bytes,bytes]",
|
||||
got: format!("{:?}", x),
|
||||
expected: "[int|atom, int|atom] | [bytes,bytes]",
|
||||
got: format!("{:?}, {:?}", a, b),
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue