more documentation, some opcodes
This commit is contained in:
parent
d09d68cc30
commit
7c992a5c84
5 changed files with 209 additions and 22 deletions
|
@ -1,3 +1,5 @@
|
|||
use crate::intern::Sealed;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
|
||||
pub enum ValueType {
|
||||
|
@ -6,3 +8,16 @@ pub enum ValueType {
|
|||
Int_ = 0x49, /* I */
|
||||
Pointer = 0x50, /* P */
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
|
||||
pub enum OpType {
|
||||
Call = 0x636c, /* cl */
|
||||
Jump = 0x6a70, /* jp */
|
||||
Return = 0x7274, /* rt */
|
||||
Push = 0x7073, /* ps */
|
||||
Pop = 0x7071, /* pq */
|
||||
}
|
||||
|
||||
impl Sealed for ValueType {}
|
||||
impl Sealed for OpType {}
|
||||
|
|
104
crates/fogtix-bytecode/src/instr.rs
Normal file
104
crates/fogtix-bytecode/src/instr.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use crate::{consts::OpType, Value};
|
||||
use core::fmt;
|
||||
|
||||
pub enum Instr<'a> {
|
||||
Call(Value<'a>),
|
||||
Jump(u32),
|
||||
Return,
|
||||
Push(Value<'a>),
|
||||
Pop,
|
||||
}
|
||||
|
||||
impl Instr<'_> {
|
||||
#[inline]
|
||||
pub fn typ(&self) -> OpType {
|
||||
match self {
|
||||
Instr::Call(_) => OpType::Call,
|
||||
Instr::Jump(_) => OpType::Jump,
|
||||
Instr::Return => OpType::Return,
|
||||
Instr::Push(_) => OpType::Push,
|
||||
Instr::Pop => OpType::Pop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ParseError;
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "unable to parse instruction from buffer")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
impl From<()> for ParseError {
|
||||
fn from(_: ()) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::ValueParseError> for ParseError {
|
||||
fn from(_: crate::ValueParseError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> crate::Parse<'a> for Instr<'a> {
|
||||
type Err = ParseError;
|
||||
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), ParseError> {
|
||||
if inp.len() < 2 {
|
||||
return Err(ParseError);
|
||||
}
|
||||
let (inp, otyp) = OpType::parse(inp)?;
|
||||
Ok(match otyp {
|
||||
OpType::Call => {
|
||||
let (inp, val) = Value::parse(inp)?;
|
||||
(inp, Instr::Call(val))
|
||||
}
|
||||
OpType::Jump => {
|
||||
let (inp, val) = u32::parse(inp)?;
|
||||
(inp, Instr::Jump(val))
|
||||
}
|
||||
OpType::Push => {
|
||||
let (inp, val) = Value::parse(inp)?;
|
||||
(inp, Instr::Push(val))
|
||||
}
|
||||
OpType::Return => (inp, Instr::Return),
|
||||
OpType::Pop => (inp, Instr::Pop),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "std"))]
|
||||
impl Instr<'_> {
|
||||
pub fn write_to<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
|
||||
use int_enum::IntEnum;
|
||||
writer.write_all(&self.typ().int_value().to_be_bytes())?;
|
||||
match self {
|
||||
Instr::Call(val) => val.write_to(writer)?,
|
||||
Instr::Jump(val) => writer.write_all(&val.to_be_bytes())?,
|
||||
Instr::Push(val) => val.write_to(writer)?,
|
||||
Instr::Return | Instr::Pop => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn doesnt_crash(inp in proptest::collection::vec(0..=u8::MAX, 0..256)) {
|
||||
if let Ok((_, v)) = <Instr as crate::Parse<'_>>::parse(&inp[..]) {
|
||||
let mut buf = alloc::vec::Vec::with_capacity(inp.len());
|
||||
v.write_to(&mut buf).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,10 @@
|
|||
#[cfg(test)]
|
||||
extern crate alloc;
|
||||
|
||||
mod intern {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
/// parse an object from a byte stream
|
||||
pub trait Parse<'a>: Sized {
|
||||
type Err: core::fmt::Debug + Send + Sync;
|
||||
|
@ -12,7 +16,65 @@ pub trait Parse<'a>: Sized {
|
|||
}
|
||||
|
||||
pub mod consts;
|
||||
mod instr;
|
||||
pub use instr::{Instr, ParseError as InstrParseError};
|
||||
mod pointer;
|
||||
pub use pointer::*;
|
||||
mod value;
|
||||
pub use value::*;
|
||||
pub use value::{ParseError as ValueParseError, Value};
|
||||
|
||||
impl<'a> Parse<'a> for u8 {
|
||||
type Err = ();
|
||||
#[inline]
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
|
||||
if inp.is_empty() {
|
||||
Err(())
|
||||
} else {
|
||||
Ok((&inp[1..], inp[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for u16 {
|
||||
type Err = ();
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
|
||||
if inp.len() < 2 {
|
||||
Err(())
|
||||
} else {
|
||||
Ok((&inp[2..], Self::from_be_bytes(inp[..2].try_into().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for u32 {
|
||||
type Err = ();
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
|
||||
if inp.len() < 4 {
|
||||
Err(())
|
||||
} else {
|
||||
Ok((&inp[4..], Self::from_be_bytes(inp[..4].try_into().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for u64 {
|
||||
type Err = ();
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
|
||||
if inp.len() < 8 {
|
||||
Err(())
|
||||
} else {
|
||||
Ok((&inp[8..], Self::from_be_bytes(inp[..8].try_into().unwrap())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: int_enum::IntEnum + intern::Sealed> Parse<'a> for T
|
||||
where
|
||||
T::Int: Parse<'a, Err = ()>,
|
||||
{
|
||||
type Err = ();
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
|
||||
let (inp, this) = T::Int::parse(inp)?;
|
||||
T::from_int(this).map(|this| (inp, this)).map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,65 +23,64 @@ impl Value<'_> {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ValueParseError;
|
||||
pub struct ParseError;
|
||||
|
||||
impl fmt::Display for ValueParseError {
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "unable to parse value from buffer")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for ValueParseError {}
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
impl<T: int_enum::IntEnum> From<int_enum::IntEnumError<T>> for ValueParseError {
|
||||
impl From<()> for ParseError {
|
||||
fn from(_: ()) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: int_enum::IntEnum> From<int_enum::IntEnumError<T>> for ParseError {
|
||||
fn from(_: int_enum::IntEnumError<T>) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::num::TryFromIntError> for ValueParseError {
|
||||
impl From<core::num::TryFromIntError> for ParseError {
|
||||
fn from(_: core::num::TryFromIntError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> crate::Parse<'a> for Value<'a> {
|
||||
type Err = ValueParseError;
|
||||
type Err = ParseError;
|
||||
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), ValueParseError> {
|
||||
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), ParseError> {
|
||||
if inp.len() < 2 {
|
||||
return Err(ValueParseError);
|
||||
return Err(ParseError);
|
||||
}
|
||||
let (vtyp, inp) = (ValueType::from_int(inp[0])?, &inp[1..]);
|
||||
match vtyp {
|
||||
ValueType::Bytes => {
|
||||
if inp.len() < 8 {
|
||||
return Err(ValueParseError);
|
||||
}
|
||||
let (len, inp) = inp.split_at(8);
|
||||
let len: usize = u64::from_be_bytes(len.try_into().unwrap()).try_into()?;
|
||||
let (inp, len) = u64::parse(inp)?;
|
||||
let len = usize::try_from(len)?;
|
||||
if inp.len() < len {
|
||||
return Err(ValueParseError);
|
||||
return Err(ParseError);
|
||||
}
|
||||
let (data, inp) = inp.split_at(len);
|
||||
Ok((inp, Value::Bytes(data)))
|
||||
}
|
||||
ValueType::Int_ => {
|
||||
if inp.len() < 8 {
|
||||
return Err(ValueParseError);
|
||||
}
|
||||
let (data, inp) = inp.split_at(8);
|
||||
let data = u64::from_be_bytes(data.try_into().unwrap());
|
||||
let (inp, data) = u64::parse(inp)?;
|
||||
Ok((inp, Value::Int(data)))
|
||||
}
|
||||
|
||||
ValueType::Pointer => {
|
||||
let (inp, ptr) = Pointer::parse(inp).map_err(|()| ValueParseError)?;
|
||||
let (inp, ptr) = Pointer::parse(inp)?;
|
||||
Ok((inp, Value::Pointer(ptr)))
|
||||
}
|
||||
ValueType::Atom => {
|
||||
let (inp, ptr) = Atom::parse(inp).map_err(|()| ValueParseError)?;
|
||||
let (inp, ptr) = Atom::parse(inp)?;
|
||||
Ok((inp, Value::Atom(ptr)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,3 +3,10 @@
|
|||
* use tagged, signed pointers for memory addresses
|
||||
* use a separate call stack to prevent ROP
|
||||
* implement message passing
|
||||
|
||||
# values
|
||||
|
||||
* atoms (u128): atomic values, also used as pointer keys to sign pointers
|
||||
* bytes ([]u8): arbitrary byte strings
|
||||
* int (u64): the default integer type
|
||||
* pointer (u128): signed pointer (can only be accessed with matching key)
|
||||
|
|
Loading…
Reference in a new issue