use crate::runtime::{FormatSpec, Value};
use crate::Hash;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Pre-canned panic reasons.
///
/// To formulate a custom reason, use [crate::runtime::Panic::custom].
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PanicReason {
/// Not implemented.
NotImplemented,
/// A pattern didn't match where it unconditionally has to.
UnmatchedPattern,
/// Tried to poll a future that has already been completed.
FutureCompleted,
}
impl PanicReason {
/// The identifier of the panic.
fn ident(&self) -> &'static str {
match *self {
Self::NotImplemented => "not implemented",
Self::UnmatchedPattern => "unmatched pattern",
Self::FutureCompleted => "future completed",
}
}
}
impl fmt::Display for PanicReason {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::NotImplemented => write!(fmt, "functionality has not been implemented yet")?,
Self::UnmatchedPattern => write!(fmt, "pattern did not match")?,
Self::FutureCompleted => {
write!(fmt, "tried to poll future that has already been completed")?
}
}
Ok(())
}
}
/// Type checks for built-in types.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TypeCheck {
/// Matches a unit type.
Unit,
/// Matches an anonymous tuple.
Tuple,
/// Matches an anonymous object.
Object,
/// Matches a vector.
Vec,
/// An option type, and the specified variant index.
Option(usize),
/// A result type, and the specified variant index.
Result(usize),
/// A generator state type, and the specified variant index.
GeneratorState(usize),
}
impl fmt::Display for TypeCheck {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unit => write!(fmt, "Unit"),
Self::Tuple => write!(fmt, "Tuple"),
Self::Object => write!(fmt, "Object"),
Self::Vec => write!(fmt, "Vec"),
Self::Option(0) => write!(fmt, "Option::Some"),
Self::Option(..) => write!(fmt, "Option::None"),
Self::Result(0) => write!(fmt, "Result::Ok"),
Self::Result(..) => write!(fmt, "Result::Err"),
Self::GeneratorState(0) => write!(fmt, "GeneratorState::Yielded"),
Self::GeneratorState(..) => write!(fmt, "GeneratorState::Complete"),
}
}
}
/// An operation in the stack-based virtual machine.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Inst {
/// Not operator. Takes a boolean from the top of the stack and inverts its
/// logical value.
///
/// # Operation
///
/// ```text
/// <bool>
/// => <bool>
/// ```
Not,
/// Negate the numerical value on the stack.
///
/// # Operation
///
/// ```text
/// <number>
/// => <number>
/// ```
Neg,
/// Construct a closure that takes the given number of arguments and
/// captures `count` elements from the top of the stack.
///
/// # Operation
///
/// ```text
/// <value..>
/// => <fn>
/// ```
Closure {
/// The hash of the internally stored closure function.
hash: Hash,
/// The number of arguments to store in the environment on the stack.
count: usize,
},
/// Perform a function call.
///
/// It will construct a new stack frame which includes the last `args`
/// number of entries.
Call {
/// The hash of the function to call.
hash: Hash,
/// The number of arguments expected on the stack for this call.
args: usize,
},
/// Perform a instance function call.
///
/// The instance being called on should be on top of the stack, followed by
/// `args` number of arguments.
CallInstance {
/// The hash of the name of the function to call.
hash: Hash,
/// The number of arguments expected on the stack for this call.
args: usize,
},
/// Lookup the specified instance function and put it on the stack.
/// This might help in cases where a single instance function is called many
/// times (like in a loop) since it avoids calculating its full hash on
/// every iteration.
///
/// Note that this does not resolve that the instance function exists, only
/// that the instance does.
///
/// # Operation
///
/// ```text
/// <value>
/// => <fn>
/// ```
LoadInstanceFn {
/// The name hash of the instance function.
hash: Hash,
},
/// Perform a function call on a function pointer stored on the stack.
///
/// # Operation
///
/// ```text
/// <fn>
/// <args...>
/// => <ret>
/// ```
CallFn {
/// The number of arguments expected on the stack for this call.
args: usize,
},
/// Perform an index get operation. Pushing the result on the stack.
///
/// # Operation
///
/// ```text
/// <target>
/// <index>
/// => <value>
/// ```
IndexGet {
/// How the target is addressed.
target: InstAddress,
/// How the index is addressed.
index: InstAddress,
},
/// Get the given index out of a tuple on the top of the stack.
/// Errors if the item doesn't exist or the item is not a tuple.
///
/// # Operation
///
/// ```text
/// <tuple>
/// => <value>
/// ```
TupleIndexGet {
/// The index to fetch.
index: usize,
},
/// Set the given index of the tuple on the stack, with the given value.
///
/// # Operation
///
/// ```text
/// <value>
/// <tuple>
/// => *nothing*
/// ```
TupleIndexSet {
/// The index to set.
index: usize,
},
/// Get the given index out of a tuple from the given variable slot.
/// Errors if the item doesn't exist or the item is not a tuple.
///
/// # Operation
///
/// ```text
/// => <value>
/// ```
TupleIndexGetAt {
/// The slot offset to load the tuple from.
offset: usize,
/// The index to fetch.
index: usize,
},
/// Get the given index out of an object on the top of the stack.
/// Errors if the item doesn't exist or the item is not an object.
///
/// The index is identifier by a static string slot, which is provided as an
/// argument.
///
/// # Operation
///
/// ```text
/// <object>
/// => <value>
/// ```
ObjectIndexGet {
/// The static string slot corresponding to the index to fetch.
slot: usize,
},
/// Set the given index out of an object on the top of the stack.
/// Errors if the item doesn't exist or the item is not an object.
///
/// The index is identifier by a static string slot, which is provided as an
/// argument.
///
/// # Operation
///
/// ```text
/// <object>
/// <value>
/// =>
/// ```
ObjectIndexSet {
/// The static string slot corresponding to the index to set.
slot: usize,
},
/// Get the given index out of an object from the given variable slot.
/// Errors if the item doesn't exist or the item is not an object.
///
/// The index is identifier by a static string slot, which is provided as an
/// argument.
///
/// # Operation
///
/// ```text
/// => <value>
/// ```
ObjectIndexGetAt {
/// The slot offset to get the value to load from.
offset: usize,
/// The static string slot corresponding to the index to fetch.
slot: usize,
},
/// Perform an index set operation.
///
/// # Operation
///
/// ```text
/// <target>
/// <index>
/// <value>
/// => *noop*
/// ```
IndexSet,
/// Await the future that is on the stack and push the value that it
/// produces.
///
/// # Operation
///
/// ```text
/// <future>
/// => <value>
/// ```
Await,
/// Select over `len` futures on the stack. Sets the `branch` register to
/// the index of the branch that completed. And pushes its value on the
/// stack.
///
/// This operation will block the VM until at least one of the underlying
/// futures complete.
///
/// # Operation
///
/// ```text
/// <future...>
/// => <value>
/// ```
Select {
/// The number of futures to poll.
len: usize,
},
/// Load the given function by hash and push onto the stack.
///
/// # Operation
///
/// ```text
/// => <value>
/// ```
LoadFn {
/// The hash of the function to push.
hash: Hash,
},
/// Push a value onto the stack.
///
/// # Operation
///
/// ```text
/// => <value>
/// ```
Push {
/// The value to push.
value: InstValue,
},
/// Pop the value on the stack, discarding its result.
///
/// # Operation
///
/// ```text
/// <value>
/// =>
/// ```
Pop,
/// Pop the given number of elements from the stack.
///
/// # Operation
///
/// ```text
/// <value..>
/// => *noop*
/// ```
PopN {
/// The number of elements to pop from the stack.
count: usize,
},
/// If the stop of the stack is false, will pop the given `count` entries on
/// the stack and jump to the given offset.
///
/// # Operation
///
/// ```text
/// <bool>
/// => *noop*
/// ```
PopAndJumpIfNot {
/// The number of entries to pop of the condition is true.
count: usize,
/// The offset to jump if the condition is true.
offset: isize,
},
/// Clean the stack by keeping the top of it, and popping `count` values
/// under it.
///
/// # Operation
///
/// ```text
/// <top>
/// <value..>
/// => <top>
/// ```
Clean {
/// The number of entries in the stack to pop.
count: usize,
},
/// Copy a variable from a location `offset` relative to the current call
/// frame.
///
/// A copy is very cheap. It simply means pushing a reference to the stack.
Copy {
/// Offset to copy value from.
offset: usize,
},
/// Move a variable from a location `offset` relative to the current call
/// frame.
Move {
/// Offset to move value from.
offset: usize,
},
/// Drop the value in the given frame offset, cleaning out it's slot in
/// memory.
///
/// # Operation
///
/// ```text
/// => *noop*
/// ```
Drop {
/// Frame offset to drop.
offset: usize,
},
/// Duplicate the value at the top of the stack.
///
/// # Operation
///
/// ```text
/// => <value>
/// ```
Dup,
/// Replace a value at the offset relative from the top of the stack, with
/// the top of the stack.
Replace {
/// Offset to swap value from.
offset: usize,
},
/// Pop the current stack frame and restore the instruction pointer from it.
///
/// The stack frame will be cleared, and the value on the top of the stack
/// will be left on top of it.
Return {
/// The address of the value to return.
address: InstAddress,
/// Number of variables to clean. If address is top, this should only
/// specify variables in excess of the top variable. Otherwise, this
/// includes the return value.
clean: usize,
},
/// Pop the current stack frame and restore the instruction pointer from it.
///
/// The stack frame will be cleared, and a unit value will be pushed to the
/// top of the stack.
ReturnUnit,
/// Unconditionally jump to `offset` relative to the current instruction
/// pointer.
///
/// # Operation
///
/// ```text
/// *nothing*
/// => *nothing*
/// ```
Jump {
/// Offset to jump to.
offset: isize,
},
/// Jump to `offset` relative to the current instruction pointer if the
/// condition is `true`.
///
/// # Operation
///
/// ```text
/// <boolean>
/// => *nothing*
/// ```
JumpIf {
/// Offset to jump to.
offset: isize,
},
/// Jump to `offset` relative to the current instruction pointer if the
/// condition is `true`. Will only pop the stack is a jump is not performed.
///
/// # Operation
///
/// ```text
/// <boolean>
/// => *nothing*
/// ```
JumpIfOrPop {
/// Offset to jump to.
offset: isize,
},
/// Jump to `offset` relative to the current instruction pointer if the
/// condition is `false`. Will only pop the stack is a jump is not performed.
///
/// # Operation
///
/// ```text
/// <boolean>
/// => *nothing*
/// ```
JumpIfNotOrPop {
/// Offset to jump to.
offset: isize,
},
/// Compares the `branch` register with the top of the stack, and if they
/// match pops the top of the stack and performs the jump to offset.
///
/// # Operation
///
/// ```text
/// <integer>
/// => *nothing*
/// ```
JumpIfBranch {
/// The branch value to compare against.
branch: i64,
/// The offset to jump.
offset: isize,
},
/// Construct a push a vector value onto the stack. The number of elements
/// in the vector are determined by `count` and are popped from the stack.
///
/// # Operation
///
/// ```text
/// <value..>
/// => <vec>
/// ```
Vec {
/// The size of the vector.
count: usize,
},
/// Construct a push a one-tuple value onto the stack.
///
/// # Operation
///
/// ```text
/// => <tuple>
/// ```
Tuple1 {
/// First element of the tuple.
args: [InstAddress; 1],
},
/// Construct a push a two-tuple value onto the stack.
///
/// # Operation
///
/// ```text
/// => <tuple>
/// ```
Tuple2 {
/// Tuple arguments.
args: [InstAddress; 2],
},
/// Construct a push a three-tuple value onto the stack.
///
/// # Operation
///
/// ```text
/// => <tuple>
/// ```
Tuple3 {
/// Tuple arguments.
args: [InstAddress; 3],
},
/// Construct a push a four-tuple value onto the stack.
///
/// # Operation
///
/// ```text
/// => <tuple>
/// ```
Tuple4 {
/// Tuple arguments.
args: [InstAddress; 4],
},
/// Construct a push a tuple value onto the stack. The number of elements
/// in the tuple are determined by `count` and are popped from the stack.
///
/// # Operation
///
/// ```text
/// <value..>
/// => <tuple>
/// ```
Tuple {
/// The size of the tuple.
count: usize,
},
/// Take the tuple that is on top of the stack and push its content onto the
/// stack.
///
/// Note: this is used by closures to "unpack" their environment into local
/// variables.
///
/// # Operation
///
/// ```text
/// <tuple>
/// => <value...>
/// ```
PushTuple,
/// Construct a push an object onto the stack. The number of elements
/// in the object are determined the slot of the object keys `slot` and are
/// popped from the stack.
///
/// For each element, a value is popped corresponding to the object key.
///
/// # Operation
///
/// ```text
/// <value..>
/// => <object>
/// ```
Object {
/// The static slot of the object keys.
slot: usize,
},
/// Construct a range. This will pop the start and end of the range from the
/// stack.
///
/// # Operation
///
/// ```text
/// <from>
/// <to>
/// => <range>
/// ```
Range {
/// The limits of the range.
limits: InstRangeLimits,
},
/// Construct a push an object of the given type onto the stack. The type is
/// an empty struct.
///
/// # Operation
///
/// ```text
/// => <object>
/// ```
UnitStruct {
/// The type of the object to construct.
hash: Hash,
},
/// Construct a push an object of the given type onto the stack. The number
/// of elements in the object are determined the slot of the object keys
/// `slot` and are popped from the stack.
///
/// For each element, a value is popped corresponding to the object key.
///
/// # Operation
///
/// ```text
/// <value..>
/// => <object>
/// ```
Struct {
/// The type of the object to construct.
hash: Hash,
/// The static slot of the object keys.
slot: usize,
},
/// Construct a push an object variant of the given type onto the stack. The
/// type is an empty struct.
///
/// # Operation
///
/// ```text
/// => <object>
/// ```
UnitVariant {
/// The type hash of the object variant to construct.
hash: Hash,
},
/// Construct a push an object variant of the given type onto the stack. The
/// number of elements in the object are determined the slot of the object
/// keys `slot` and are popped from the stack.
///
/// For each element, a value is popped corresponding to the object key.
///
/// # Operation
///
/// ```text
/// <value..>
/// => <object>
/// ```
StructVariant {
/// The type hash of the object variant to construct.
hash: Hash,
/// The static slot of the object keys.
slot: usize,
},
/// Load a literal string from a static string slot.
///
/// # Operation
///
/// ```text
/// => <string>
/// ```
String {
/// The static string slot to load the string from.
slot: usize,
},
/// Load a literal byte string from a static byte string slot.
///
/// # Operation
///
/// ```text
/// => <bytes>
/// ```
Bytes {
/// The static byte string slot to load the string from.
slot: usize,
},
/// Pop the given number of values from the stack, and concatenate a string
/// from them.
///
/// This is a dedicated template-string optimization.
///
/// # Operation
///
/// ```text
/// <value...>
/// => <string>
/// ```
StringConcat {
/// The number of items to pop from the stack.
len: usize,
/// The minimum string size used.
size_hint: usize,
},
/// Push a combined format specification and value onto the stack. The value
/// used is the last value on the stack.
Format {
/// The format specification to use.
spec: FormatSpec,
},
/// Test if the top of the stack is a unit.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
IsUnit,
/// Perform the try operation which takes the value at the given `address`
/// and tries to unwrap it or return from the current call frame.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
Try {
/// Address to test if value.
address: InstAddress,
/// Variable count that needs to be cleaned in case the operation
/// results in a return.
clean: usize,
/// If the value on top of the stack should be preserved.
preserve: bool,
},
/// Test if the top of the stack is a specific byte.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
EqByte {
/// The byte to test against.
byte: u8,
},
/// Test if the top of the stack is a specific character.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
EqCharacter {
/// The character to test against.
character: char,
},
/// Test if the top of the stack is a specific integer.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
EqInteger {
/// The integer to test against.
integer: i64,
},
/// Test if the top of the stack is a specific boolean.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
EqBool {
/// The bool to test against.
boolean: bool,
},
/// Compare the top of the stack against a static string slot.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
EqStaticString {
/// The slot to test against.
slot: usize,
},
/// Test that the top of the stack has the given type.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
MatchType {
/// The type hash to match against.
hash: Hash,
},
/// Test if the specified variant matches. This is distinct from
/// [Inst::MatchType] because it will match immediately on the variant type
/// if appropriate which is possible for internal types, but external types
/// will require an additional runtime check for matching.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
MatchVariant {
/// The exact type hash of the variant.
variant_hash: Hash,
/// The container type.
enum_hash: Hash,
/// The index of the variant.
index: usize,
},
/// Test if the top of the stack is the given builtin type or variant.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
MatchBuiltIn {
/// The type to check for.
type_check: TypeCheck,
},
/// Test that the top of the stack is a tuple with the given length
/// requirements.
///
/// # Operation
///
/// ```text
/// <value>
/// => <boolean>
/// ```
MatchSequence {
/// Type constraints that the sequence must match.
type_check: TypeCheck,
/// The minimum length to test for.
len: usize,
/// Whether the operation should check exact `true` or minimum length
/// `false`.
exact: bool,
},
/// Test that the top of the stack is an object matching the given slot of
/// object keys.
///
/// # Operation
///
/// ```text
/// <object>
/// => <boolean>
/// ```
MatchObject {
/// The slot of object keys to use.
slot: usize,
/// Whether the operation should check exact `true` or minimum length
/// `false`.
exact: bool,
},
/// Perform a generator yield where the value yielded is expected to be
/// found at the top of the stack.
///
/// This causes the virtual machine to suspend itself.
///
/// # Operation
///
/// ```text
/// <value>
/// => <value>
/// ```
Yield,
/// Perform a generator yield with a unit.
///
/// This causes the virtual machine to suspend itself.
///
/// # Operation
///
/// ```text
/// => <unit>
/// ```
YieldUnit,
/// Construct a built-in variant onto the stack.
///
/// The variant will pop as many values of the stack as necessary to
/// construct it.
///
/// # Operation
///
/// ```text
/// <value..>
/// => <variant>
/// ```
Variant {
/// The kind of built-in variant to construct.
variant: InstVariant,
},
/// A built-in operation like `a + b` that takes its operands and pushes its
/// result to and from the stack.
///
/// # Operation
///
/// ```text
/// => <value>
/// ```
Op {
/// The actual operation.
op: InstOp,
/// The address of the first argument.
a: InstAddress,
/// The address of the second argument.
b: InstAddress,
},
/// A built-in operation that assigns to the left-hand side operand. Like
/// `a += b`.
///
/// The target determines the left hand side operation.
///
/// # Operation
///
/// ```text
/// <value>
/// =>
/// ```
Assign {
/// The target of the operation.
target: InstTarget,
/// The actual operation.
op: InstAssignOp,
},
/// Advance an iterator at the given position.
IterNext {
/// The offset of the value being advanced.
offset: usize,
/// A relative jump to perform if the iterator could not be advanced.
jump: isize,
},
/// Cause the VM to panic and error out without a reason.
///
/// This should only be used during testing or extreme scenarios that are
/// completely unrecoverable.
Panic {
/// The reason for the panic.
reason: PanicReason,
},
}
impl Inst {
/// Construct an instruction to push a unit.
pub fn unit() -> Self {
Self::Push {
value: InstValue::Unit,
}
}
/// Construct an instruction to push a boolean.
pub fn bool(b: bool) -> Self {
Self::Push {
value: InstValue::Bool(b),
}
}
/// Construct an instruction to push a byte.
pub fn byte(b: u8) -> Self {
Self::Push {
value: InstValue::Byte(b),
}
}
/// Construct an instruction to push a character.
pub fn char(c: char) -> Self {
Self::Push {
value: InstValue::Char(c),
}
}
/// Construct an instruction to push an integer.
pub fn integer(v: i64) -> Self {
Self::Push {
value: InstValue::Integer(v),
}
}
/// Construct an instruction to push a float.
pub fn float(v: f64) -> Self {
Self::Push {
value: InstValue::Float(v),
}
}
}
impl fmt::Display for Inst {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Drop { offset } => {
write!(fmt, "drop offset={}", offset)?;
}
Self::Not => {
write!(fmt, "not")?;
}
Self::Neg => {
write!(fmt, "neg")?;
}
Self::Call { hash, args } => {
write!(fmt, "call hash={}, args={}", hash, args)?;
}
Self::CallInstance { hash, args } => {
write!(fmt, "call-instance hash={}, args={}", hash, args)?;
}
Self::Closure { hash, count } => {
write!(fmt, "closure hash={}, count={}", hash, count)?;
}
Self::CallFn { args } => {
write!(fmt, "call-fn args={}", args)?;
}
Self::LoadInstanceFn { hash } => {
write!(fmt, "load-instance-fn hash={}", hash)?;
}
Self::IndexGet { target, index } => {
write!(fmt, "index-get target={}, index={}", target, index)?;
}
Self::TupleIndexGet { index } => {
write!(fmt, "tuple-index-get index={}", index)?;
}
Self::TupleIndexSet { index } => {
write!(fmt, "tuple-index-set index={}", index)?;
}
Self::TupleIndexGetAt { offset, index } => {
write!(fmt, "tuple-index-get-at offset={}, index={}", offset, index)?;
}
Self::ObjectIndexGet { slot } => {
write!(fmt, "object-index-get slot={}", slot)?;
}
Self::ObjectIndexSet { slot } => {
write!(fmt, "object-index-set slot={}", slot)?;
}
Self::ObjectIndexGetAt { offset, slot } => {
write!(fmt, "object-index-get-at offset={}, slot={}", offset, slot)?;
}
Self::IndexSet => {
write!(fmt, "index-set")?;
}
Self::Await => {
write!(fmt, "await")?;
}
Self::Select { len } => {
write!(fmt, "select len={}", len)?;
}
Self::LoadFn { hash } => {
write!(fmt, "load-fn hash={}", hash)?;
}
Self::Push { value } => {
write!(fmt, "push value={}", value)?;
}
Self::Pop => {
write!(fmt, "pop")?;
}
Self::PopN { count } => {
write!(fmt, "pop-n count={}", count)?;
}
Self::PopAndJumpIfNot { count, offset } => {
write!(
fmt,
"pop-and-jump-if-not count={}, offset={}",
count, offset
)?;
}
Self::Clean { count } => {
write!(fmt, "clean count={}", count)?;
}
Self::Copy { offset } => {
write!(fmt, "copy offset={}", offset)?;
}
Self::Move { offset } => {
write!(fmt, "move offset={}", offset)?;
}
Self::Dup => {
write!(fmt, "dup")?;
}
Self::Replace { offset } => {
write!(fmt, "replace offset={}", offset)?;
}
Self::Return { address, clean } => {
write!(fmt, "return address={}, clean={}", address, clean)?;
}
Self::ReturnUnit => {
write!(fmt, "return-unit")?;
}
Self::Jump { offset } => {
write!(fmt, "jump offset={}", offset)?;
}
Self::JumpIf { offset } => {
write!(fmt, "jump-if offset={}", offset)?;
}
Self::JumpIfOrPop { offset } => {
write!(fmt, "jump-if-or-pop offset={}", offset)?;
}
Self::JumpIfNotOrPop { offset } => {
write!(fmt, "jump-if-not-or-pop offset={}", offset)?;
}
Self::JumpIfBranch { branch, offset } => {
write!(fmt, "jump-if-branch branch={}, offset={}", branch, offset)?;
}
Self::Vec { count } => {
write!(fmt, "vec count={}", count)?;
}
Self::Tuple1 { args: [a] } => {
write!(fmt, "tuple-1 {}", a)?;
}
Self::Tuple2 { args: [a, b] } => {
write!(fmt, "tuple-2 {}, {}", a, b)?;
}
Self::Tuple3 { args: [a, b, c] } => {
write!(fmt, "tuple-3 {}, {}, {}", a, b, c)?;
}
Self::Tuple4 { args: [a, b, c, d] } => {
write!(fmt, "tuple-4 {}, {}, {}, {}", a, b, c, d)?;
}
Self::Tuple { count } => {
write!(fmt, "tuple count={}", count)?;
}
Self::PushTuple => {
write!(fmt, "push-tuple")?;
}
Self::UnitStruct { hash } => {
write!(fmt, "unit-struct hash={}", hash)?;
}
Self::Struct { hash, slot } => {
write!(fmt, "struct hash={}, slot={}", hash, slot)?;
}
Self::UnitVariant { hash } => {
write!(fmt, "unit-variant hash={}", hash)?;
}
Self::StructVariant { hash, slot } => {
write!(fmt, "struct-variant hash={}, slot={}", hash, slot)?;
}
Self::Object { slot } => {
write!(fmt, "object slot={}", slot)?;
}
Self::Range { limits } => {
write!(fmt, "range limits={}", limits)?;
}
Self::String { slot } => {
write!(fmt, "string slot={}", slot)?;
}
Self::Bytes { slot } => {
write!(fmt, "bytes slot={}", slot)?;
}
Self::StringConcat { len, size_hint } => {
write!(fmt, "string-concat len={}, size_hint={}", len, size_hint)?;
}
Self::Format { spec } => {
write!(
fmt,
"format {fill:?}, {align}, {flags:?}, {width}, {precision}, {format_type}",
fill = spec.fill,
align = spec.align,
flags = spec.flags,
width = option(&spec.width),
precision = option(&spec.precision),
format_type = spec.format_type
)?;
}
Self::IsUnit => {
write!(fmt, "is-unit")?;
}
Self::Try {
address,
clean,
preserve,
} => {
write!(
fmt,
"try address={}, clean={}, preserve={}",
address, clean, preserve
)?;
}
Self::EqByte { byte } => {
write!(fmt, "eq-byte byte={:?}", byte)?;
}
Self::EqCharacter { character } => {
write!(fmt, "eq-character character={:?}", character)?;
}
Self::EqInteger { integer } => {
write!(fmt, "eq-integer integer={}", integer)?;
}
Self::EqBool { boolean } => {
write!(fmt, "eq-integer boolean={}", boolean)?;
}
Self::EqStaticString { slot } => {
write!(fmt, "eq-static-string slot={}", slot)?;
}
Self::MatchType { hash } => {
write!(fmt, "match-type hash={}", hash,)?;
}
Self::MatchVariant {
variant_hash,
enum_hash,
index,
} => {
write!(
fmt,
"match-variant variant_hash={}, enum_hash={}, index={}",
enum_hash, variant_hash, index
)?;
}
Self::MatchBuiltIn { type_check } => {
write!(fmt, "match-builtin type_check={}", type_check)?;
}
Self::MatchSequence {
type_check,
len,
exact,
} => {
write!(
fmt,
"match-sequence type_check={}, len={}, exact={}",
type_check, len, exact
)?;
}
Self::MatchObject { slot, exact } => {
write!(fmt, "match-object slot={}, exact={}", slot, exact)?;
}
Self::Yield => {
write!(fmt, "yield")?;
}
Self::YieldUnit => {
write!(fmt, "yield-unit")?;
}
Self::Variant { variant } => {
write!(fmt, "variant variant={}", variant)?;
}
Self::Op { op, a, b } => {
write!(fmt, "op op={}, a={}, b={}", op, a, b)?;
}
Self::Assign { target, op } => {
write!(fmt, "assign target={}, op={}", target, op)?;
}
Self::IterNext { offset, jump } => {
write!(fmt, "iter-next offset={}, jump={}", offset, jump)?;
}
Self::Panic { reason } => {
write!(fmt, "panic reason={}", reason.ident())?;
}
}
return Ok(());
fn option<T>(value: &Option<T>) -> OptionDebug<'_, T> {
OptionDebug(value.as_ref())
}
struct OptionDebug<'a, T>(Option<&'a T>);
impl<'a, T> fmt::Display for OptionDebug<'a, T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(value) => write!(f, "{}", value),
None => write!(f, "?"),
}
}
}
}
}
/// How an instruction addresses a value.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InstAddress {
/// Addressed from the top of the stack.
Top,
/// Value addressed at the given offset.
Offset(usize),
}
impl fmt::Display for InstAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Top => write!(f, "top"),
Self::Offset(offset) => write!(f, "offset({})", offset),
}
}
}
/// Range limits of a range expression.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InstRangeLimits {
/// A half-open range `a .. b`.
HalfOpen,
/// A closed range `a ..= b`.
Closed,
}
impl fmt::Display for InstRangeLimits {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HalfOpen => write!(f, ".."),
Self::Closed => write!(f, "..="),
}
}
}
/// The target of an operation.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InstTarget {
/// Target is an offset to the current call frame.
Offset(usize),
/// Target the field of an object.
Field(usize),
/// Target a tuple field.
TupleField(usize),
}
impl fmt::Display for InstTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Offset(offset) => write!(f, "offset({})", offset),
Self::Field(slot) => write!(f, "field({})", slot),
Self::TupleField(slot) => write!(f, "tuple-field({})", slot),
}
}
}
/// An operation between two values on the machine.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InstAssignOp {
/// The add operation. `a + b`.
Add,
/// The sub operation. `a - b`.
Sub,
/// The multiply operation. `a * b`.
Mul,
/// The division operation. `a / b`.
Div,
/// The remainder operation. `a % b`.
Rem,
/// The bitwise and operation. `a & b`.
BitAnd,
/// The bitwise xor operation. `a ^ b`.
BitXor,
/// The bitwise or operation. `a | b`.
BitOr,
/// The shift left operation. `a << b`.
Shl,
/// The shift right operation. `a << b`.
Shr,
}
impl fmt::Display for InstAssignOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Add => {
write!(f, "+")?;
}
Self::Sub => {
write!(f, "-")?;
}
Self::Mul => {
write!(f, "*")?;
}
Self::Div => {
write!(f, "/")?;
}
Self::Rem => {
write!(f, "%")?;
}
Self::BitAnd => {
write!(f, "&")?;
}
Self::BitXor => {
write!(f, "^")?;
}
Self::BitOr => {
write!(f, "|")?;
}
Self::Shl => {
write!(f, "<<")?;
}
Self::Shr => {
write!(f, ">>")?;
}
}
Ok(())
}
}
/// An operation between two values on the machine.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InstOp {
/// The add operation. `a + b`.
Add,
/// The sub operation. `a - b`.
Sub,
/// The multiply operation. `a * b`.
Mul,
/// The division operation. `a / b`.
Div,
/// The remainder operation. `a % b`.
Rem,
/// The bitwise and operation. `a & b`.
BitAnd,
/// The bitwise xor operation. `a ^ b`.
BitXor,
/// The bitwise or operation. `a | b`.
BitOr,
/// The shift left operation. `a << b`.
Shl,
/// The shift right operation. `a << b`.
Shr,
/// Compare two values on the stack for lt and push the result as a
/// boolean on the stack.
Lt,
/// Compare two values on the stack for gt and push the result as a
/// boolean on the stack.
Gt,
/// Compare two values on the stack for lte and push the result as a
/// boolean on the stack.
Lte,
/// Compare two values on the stack for gte and push the result as a
/// boolean on the stack.
Gte,
/// Compare two values on the stack for equality and push the result as a
/// boolean on the stack.
///
/// # Operation
///
/// ```text
/// <b>
/// <a>
/// => <bool>
/// ```
Eq,
/// Compare two values on the stack for inequality and push the result as a
/// boolean on the stack.
///
/// # Operation
///
/// ```text
/// <b>
/// <a>
/// => <bool>
/// ```
Neq,
/// Test if the top of the stack is an instance of the second item on the
/// stack.
///
/// # Operation
///
/// ```text
/// <type>
/// <value>
/// => <boolean>
/// ```
Is,
/// Test if the top of the stack is not an instance of the second item on
/// the stack.
///
/// # Operation
///
/// ```text
/// <type>
/// <value>
/// => <boolean>
/// ```
IsNot,
/// Pop two values from the stack and test if they are both boolean true.
///
/// # Operation
///
/// ```text
/// <boolean>
/// <boolean>
/// => <boolean>
/// ```
And,
/// Pop two values from the stack and test if either of them are boolean
/// true.
///
/// # Operation
///
/// ```text
/// <boolean>
/// <boolean>
/// => <boolean>
/// ```
Or,
}
impl fmt::Display for InstOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Add => {
write!(f, "+")?;
}
Self::Sub => {
write!(f, "-")?;
}
Self::Mul => {
write!(f, "*")?;
}
Self::Div => {
write!(f, "/")?;
}
Self::Rem => {
write!(f, "%")?;
}
Self::BitAnd => {
write!(f, "&")?;
}
Self::BitXor => {
write!(f, "^")?;
}
Self::BitOr => {
write!(f, "|")?;
}
Self::Shl => {
write!(f, "<<")?;
}
Self::Shr => {
write!(f, ">>")?;
}
Self::Lt => {
write!(f, "<")?;
}
Self::Gt => {
write!(f, ">")?;
}
Self::Lte => {
write!(f, "<=")?;
}
Self::Gte => {
write!(f, ">=")?;
}
Self::Eq => {
write!(f, "==")?;
}
Self::Neq => {
write!(f, "!=")?;
}
Self::Is => {
write!(f, "is")?;
}
Self::IsNot => {
write!(f, "is not")?;
}
Self::And => {
write!(f, "&&")?;
}
Self::Or => {
write!(f, "||")?;
}
}
Ok(())
}
}
/// A literal value that can be pushed.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InstValue {
/// A unit.
Unit,
/// A boolean.
Bool(bool),
/// A byte.
Byte(u8),
/// A character.
Char(char),
/// An integer.
Integer(i64),
/// A float.
Float(f64),
/// A type hash.
Type(Hash),
}
impl InstValue {
/// Convert into a value that can be pushed onto the stack.
pub fn into_value(self) -> Value {
match self {
Self::Unit => Value::Unit,
Self::Bool(v) => Value::Bool(v),
Self::Byte(v) => Value::Byte(v),
Self::Char(v) => Value::Char(v),
Self::Integer(v) => Value::Integer(v),
Self::Float(v) => Value::Float(v),
Self::Type(v) => Value::Type(v),
}
}
}
impl fmt::Display for InstValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unit => write!(f, "()")?,
Self::Bool(v) => write!(f, "{}", v)?,
Self::Byte(v) => {
if v.is_ascii_graphic() {
write!(f, "b'{}'", *v as char)?
} else {
write!(f, "b'\\x{:02x}'", v)?
}
}
Self::Char(v) => write!(f, "{:?}", v)?,
Self::Integer(v) => write!(f, "{}", v)?,
Self::Float(v) => write!(f, "{}", v)?,
Self::Type(v) => write!(f, "{}", v)?,
}
Ok(())
}
}
/// A variant that can be constructed.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum InstVariant {
/// `Option::Some`, which uses one value.
Some,
/// `Option::None`, which uses no values.
None,
/// `Result::Ok`, which uses one value.
Ok,
/// `Result::Err`, which uses one value.
Err,
}
impl fmt::Display for InstVariant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Some => {
write!(f, "Some")?;
}
Self::None => {
write!(f, "None")?;
}
Self::Ok => {
write!(f, "Ok")?;
}
Self::Err => {
write!(f, "Err")?;
}
}
Ok(())
}
}