use crate::func::{calc_switch_value_hash, FnAccess, StraightHashMap};
use crate::types::{fn_ptr::FnPtrType, StringsInterner, Token};
use crate::{Dynamic, FnPtr, ThinVec};
use std::convert::{TryFrom, TryInto};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use crate::grain::bytecode::{
AssignOp, BadTable, Chain, Chunk, CustomSyntaxSite, Positions, Root, Step, StepFlags, Strings,
Switch, SwitchRange, TableError, Tail, VerifyError,
};
use crate::grain::format::abi::{Abi, AbiMismatch, Caps};
use crate::grain::format::{constant, root_tag, step_tag, tail_tag, Cursor, MAGIC, VERSION};
use crate::grain::program::{Function, Parts, Program};
const MAX_CONSTANT_DEPTH: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ReadError {
BadMagic,
UnsupportedVersion {
found: u16,
supported: u16,
},
Abi(AbiMismatch),
Truncated,
MalformedVarint,
BadUtf8,
UnknownTag {
section: &'static str,
tag: u8,
},
UnknownToken {
syntax: String,
},
#[deprecated(since = "1.27.0", note = "this error is no longer used")]
HashSeedMismatch {
artifact: u64,
host: u64,
},
ConstantTooDeep,
TrailingBytes {
count: usize,
},
Unverifiable(VerifyError),
Positions(TableError),
Names(BadTable),
}
impl core::fmt::Display for ReadError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::BadMagic => f.write_str("not a Rhai Grain artifact"),
Self::UnsupportedVersion { found, supported } => write!(
f,
"artifact is format version {found}, and this build reads {supported}"
),
Self::Abi(mismatch) => write!(f, "{mismatch}"),
Self::Truncated => f.write_str("artifact ends mid-value"),
Self::MalformedVarint => f.write_str("malformed varint"),
Self::BadUtf8 => f.write_str("a string is not valid UTF-8"),
Self::UnknownTag { section, tag } => {
write!(f, "unknown {section} tag {tag:#04x}")
}
Self::UnknownToken { syntax } => write!(f, "`{syntax}` is not an operator"),
#[allow(deprecated)]
Self::HashSeedMismatch { .. } => write!(f, "this error is no longer used"),
Self::ConstantTooDeep => write!(
f,
"a constant nests deeper than {MAX_CONSTANT_DEPTH} levels"
),
Self::TrailingBytes { count } => {
write!(f, "{count} byte(s) follow the last section")
}
Self::Unverifiable(err) => write!(f, "chunk failed verification: {err:?}"),
Self::Positions(err) => write!(f, "{err}"),
Self::Names(err) => write!(f, "name table is malformed: {err:?}"),
}
}
}
pub(super) fn read(bytes: &[u8]) -> Result<Program<'_>, ReadError> {
let mut strings_interner = StringsInterner::new(64);
let mut cursor = Cursor::new(bytes);
if cursor.take(MAGIC.len())? != MAGIC {
return Err(ReadError::BadMagic);
}
let version = u16::from_le_bytes(cursor.take(2)?.try_into().expect("two bytes"));
if version != VERSION {
return Err(ReadError::UnsupportedVersion {
found: version,
supported: VERSION,
});
}
let artifact_abi = Abi {
int_bytes: cursor.byte()?,
float_bytes: cursor.byte()?,
caps: Caps::from_bits_retain(u32::from_le_bytes(
cursor.take(4)?.try_into().expect("four bytes"),
)),
};
if let Some(mismatch) = artifact_abi.is_incompatible_with(Abi::host()) {
return Err(ReadError::Abi(mismatch));
}
let debug_id = u128::from_le_bytes(cursor.take(16)?.try_into().expect("sixteen bytes"));
let source = cursor.str()?;
let source = (!source.is_empty()).then(|| strings_interner.get(source));
let count = cursor.count()?;
let mut starts = Vec::with_capacity(count + 1);
starts.push(0u32);
for _ in 0..count {
starts.push(cursor.index()?);
}
let blob_len = usize::try_from(cursor.uvarint()?).map_err(|_| ReadError::Truncated)?;
let names = Strings::borrowed(cursor.take(blob_len)?, starts)?;
let mut consts = Vec::new();
for _ in 0..cursor.uvarint()? {
consts.push(get_constant(&mut cursor, &mut strings_interner, 0)?);
}
let mut tokens = Vec::new();
for _ in 0..cursor.uvarint()? {
tokens.push(get_token(&mut cursor)?);
}
let mut assign_ops = Vec::new();
for _ in 0..cursor.uvarint()? {
assign_ops.push(AssignOp {
op_assign: get_token(&mut cursor)?,
op_assign_name: cursor.index()?,
op: get_token(&mut cursor)?,
op_name: cursor.index()?,
});
}
let mut chains = Vec::new();
for _ in 0..cursor.uvarint()? {
chains.push(get_chain(&mut cursor)?);
}
let switches = get_switches(&mut cursor, &consts)?;
let custom_syntax = get_custom_syntax(&mut cursor)?;
let main = get_chunk(&mut cursor)?;
let mut functions = Vec::new();
for _ in 0..cursor.uvarint()? {
let name = cursor.index()?;
let access = match cursor.uvarint()? {
0 => FnAccess::Public,
1 => FnAccess::Private,
_ => return Err(ReadError::Truncated),
};
let this_type = match cursor.uvarint()? {
0 => None,
raw => Some(u32::try_from(raw - 1).map_err(|_| ReadError::Truncated)?),
};
let mut params = Vec::new();
for _ in 0..cursor.uvarint()? {
params.push(cursor.index()?);
}
functions.push(Function {
name,
this_type,
access,
params,
chunk: get_chunk(&mut cursor)?,
});
}
let code_len = usize::try_from(cursor.uvarint()?).map_err(|_| ReadError::Truncated)?;
let code = cursor.take(code_len)?;
let table_len = usize::try_from(cursor.uvarint()?).map_err(|_| ReadError::Truncated)?;
let positions = if table_len == 0 {
Positions::Stripped
} else {
Positions::from_table(cursor.take(table_len)?, code)?
};
if !cursor.at_end() {
return Err(ReadError::TrailingBytes {
count: bytes.len() - cursor.pos,
});
}
let program = Program::new(
artifact_abi.caps,
code.into(),
main,
functions,
Parts {
positions,
debug_id: Some(debug_id),
#[cfg(not(feature = "no_ast"))]
residuals: Vec::new(),
consts,
names,
tokens,
assign_ops,
chains,
switches,
custom_syntax,
lib: None,
#[cfg(not(feature = "no_module"))]
resolver: None,
source,
},
);
program.verify()?;
Ok(program)
}
fn get_position(cursor: &mut Cursor) -> Result<rhai::Position, ReadError> {
let line = cursor.small()?;
let column = cursor.small()?;
Ok(if line == 0 {
rhai::Position::NONE
} else {
rhai::Position::new(line, column)
})
}
fn get_chain(cursor: &mut Cursor) -> Result<Chain, ReadError> {
let root = match cursor.byte()? {
root_tag::LOCAL => Root::Local {
slot: cursor.small()?,
name: cursor.index()?,
},
root_tag::NAMED => Root::Named {
name: cursor.index()?,
pos: get_position(cursor)?,
},
root_tag::THIS => Root::This {
pos: get_position(cursor)?,
},
root_tag::TEMPORARY => Root::Temporary,
tag => {
return Err(ReadError::UnknownTag {
section: "chain root",
tag,
})
}
};
let operands = cursor.small()?;
let mut steps = Vec::new();
for _ in 0..cursor.uvarint()? {
let tag = cursor.byte()?;
let flags = cursor.byte()?;
let flags = StepFlags::from_bits(flags).ok_or(ReadError::UnknownTag {
section: "chain step flags",
tag: flags,
})?;
steps.push(match tag {
step_tag::INDEX => Step::Index {
operand: cursor.small()?,
flags,
pos: get_position(cursor)?,
bracket: get_position(cursor)?,
},
step_tag::PROPERTY => Step::Property {
name: cursor.index()?,
getter: cursor.index()?,
setter: cursor.index()?,
flags,
pos: get_position(cursor)?,
},
step_tag::METHOD => Step::Method {
name: cursor.index()?,
argc: cursor.byte()?,
operand: cursor.small()?,
flags,
pos: get_position(cursor)?,
},
_ => {
return Err(ReadError::UnknownTag {
section: "chain step",
tag,
})
}
});
}
let tail = match cursor.byte()? {
tail_tag::READ => Tail::Read,
tail_tag::ASSIGN => Tail::Assign { op: None },
tail_tag::ASSIGN_OP => Tail::Assign {
op: Some(cursor.index()?),
},
tag => {
return Err(ReadError::UnknownTag {
section: "chain tail",
tag,
})
}
};
Ok(Chain {
root,
steps,
tail,
operands,
})
}
fn get_switches(cursor: &mut Cursor, consts: &[Dynamic]) -> Result<Vec<Switch>, ReadError> {
let count = cursor.uvarint()?;
if count == 0 {
return Ok(Vec::new());
}
let mut switches = Vec::new();
for _ in 0..count {
let mut cases = StraightHashMap::default();
for _ in 0..cursor.uvarint()? {
let blocks = cursor.index()?;
let at = cursor.pos;
let index = cursor.index()?;
let value = consts.get(index as usize).ok_or(ReadError::Unverifiable(
VerifyError::BadIndex {
what: "constant",
at,
index,
},
))?;
let hash = calc_switch_value_hash(&value);
cases.insert(hash, (blocks, index));
}
let mut ranges = Vec::new();
for _ in 0..cursor.uvarint()? {
ranges.push(SwitchRange {
from: bounded_int(cursor.ivarint()?)?,
to: bounded_int(cursor.ivarint()?)?,
inclusive: cursor.byte()? != 0,
target: cursor.index()?,
});
}
switches.push(Switch {
cases: (!cases.is_empty()).then_some(cases),
ranges,
default: cursor.index()?,
});
}
Ok(switches)
}
fn get_custom_syntax(cursor: &mut Cursor) -> Result<Vec<CustomSyntaxSite>, ReadError> {
let mut custom_syntax = Vec::new();
for _ in 0..cursor.uvarint()? {
let key = cursor.index()?;
let state = cursor.index()?;
let mut inputs = Vec::new();
for _ in 0..cursor.uvarint()? {
let chunk = get_chunk(cursor)?;
let literal = cursor.index()?;
inputs.push((chunk, (literal != u32::MAX).then_some(literal)));
}
custom_syntax.push(CustomSyntaxSite { key, state, inputs });
}
Ok(custom_syntax)
}
fn bounded_int(value: i64) -> Result<rhai::INT, ReadError> {
rhai::INT::try_from(value).map_err(|_| ReadError::MalformedVarint)
}
fn get_chunk(cursor: &mut Cursor) -> Result<Chunk, ReadError> {
let entry = cursor.index()?;
let end = cursor.index()?;
Ok(Chunk::new(entry, end, cursor.small()?))
}
fn get_token(cursor: &mut Cursor) -> Result<Token, ReadError> {
let syntax = cursor.str()?;
Token::lookup_symbol_from_syntax(syntax).ok_or_else(|| ReadError::UnknownToken {
syntax: syntax.to_string(),
})
}
fn get_constant(
cursor: &mut Cursor,
strings_interner: &mut StringsInterner,
depth: usize,
) -> Result<Dynamic, ReadError> {
if depth > MAX_CONSTANT_DEPTH {
return Err(ReadError::ConstantTooDeep);
}
Ok(match cursor.byte()? {
constant::UNIT => Dynamic::UNIT,
constant::FALSE => Dynamic::from(false),
constant::TRUE => Dynamic::from(true),
constant::INT => {
let value = cursor.ivarint()?;
Dynamic::from_int(rhai::INT::try_from(value).map_err(|_| ReadError::MalformedVarint)?)
}
#[cfg(not(feature = "no_float"))]
constant::FLOAT => {
let width = core::mem::size_of::<rhai::FLOAT>();
let bits = cursor.take(width)?;
Dynamic::from_float(rhai::FLOAT::from_le_bytes(
bits.try_into().expect("width matches the fingerprint"),
))
}
#[cfg(feature = "decimal")]
constant::DECIMAL => {
let width = core::mem::size_of::<i128>() + core::mem::size_of::<u32>();
let bits = cursor.take(width)?;
let value = i128::from_le_bytes(
bits[0..core::mem::size_of::<i128>()]
.try_into()
.expect("width matches the fingerprint"),
);
let scale = u32::from_le_bytes(
bits[core::mem::size_of::<i128>()..]
.try_into()
.expect("width matches the fingerprint"),
);
Dynamic::from_decimal(
rust_decimal::Decimal::try_from_i128_with_scale(value, scale)
.map_err(|_| ReadError::MalformedVarint)?,
)
}
constant::CHAR => {
let code = cursor.index()?;
Dynamic::from(char::from_u32(code).ok_or(ReadError::MalformedVarint)?)
}
constant::STRING => Dynamic::from(strings_interner.get(cursor.str()?)),
#[cfg(not(feature = "no_index"))]
constant::ARRAY => {
let count = cursor.uvarint()?;
let mut array = rhai::Array::new();
for _ in 0..count {
array.push(get_constant(cursor, strings_interner, depth + 1)?);
}
Dynamic::from(array)
}
#[cfg(not(feature = "no_object"))]
constant::MAP => {
let count = cursor.uvarint()?;
let mut map = rhai::Map::new();
for _ in 0..count {
let key = cursor.str()?.into();
map.insert(key, get_constant(cursor, strings_interner, depth + 1)?);
}
Dynamic::from(map)
}
constant::RANGE => {
let start = bounded_int(cursor.ivarint()?)?;
Dynamic::from(start..bounded_int(cursor.ivarint()?)?)
}
constant::RANGE_INCLUSIVE => {
let start = bounded_int(cursor.ivarint()?)?;
Dynamic::from(start..=bounded_int(cursor.ivarint()?)?)
}
constant::FN_PTR => {
let name = strings_interner.get(cursor.str()?);
let count = cursor.uvarint()?;
let mut curry = ThinVec::new();
for _ in 0..count {
curry.push(get_constant(cursor, strings_interner, depth + 1)?);
}
let typ = match cursor.byte()? {
constant::FN_PTR_TYPE_NORMAL => FnPtrType::Normal,
#[cfg(not(feature = "no_function"))]
constant::FN_PTR_TYPE_SCRIPT => {
let num_params = cursor.uvarint()? as usize;
FnPtrType::Script {
num_params,
hash: crate::calc_fn_hash(None, &name, num_params),
}
}
tag => {
return Err(ReadError::UnknownTag {
section: "constant",
tag,
})
}
};
Dynamic::from(FnPtr { name, curry, typ })
}
#[cfg(not(feature = "no_index"))]
constant::BLOB => {
let len = usize::try_from(cursor.uvarint()?).map_err(|_| ReadError::Truncated)?;
Dynamic::from(cursor.take(len)?.to_vec())
}
tag => {
return Err(ReadError::UnknownTag {
section: "constant",
tag,
})
}
})
}