184 lines
6.7 KiB
Rust
184 lines
6.7 KiB
Rust
use runa_core::{ActorRef, MAX_VALUE_BYTES, MAX_VALUE_DEPTH, MAX_VALUE_NODES, Value};
|
|
use rune::runtime::{Object, Vec as RuneVec};
|
|
use rune::{Any, TypeHash, Value as RuneValue};
|
|
|
|
use crate::ExperimentResult;
|
|
|
|
#[derive(Any, Clone)]
|
|
#[rune(item = ::runa)]
|
|
pub(crate) struct ActorReference(pub ActorRef);
|
|
|
|
/// Allocate a fresh Rune object graph for each invocation input.
|
|
pub fn value_to_rune(value: &Value) -> ExperimentResult<RuneValue> {
|
|
value.validate()?;
|
|
fn convert(value: &Value) -> ExperimentResult<RuneValue> {
|
|
Ok(match value {
|
|
Value::Null => RuneValue::from(()),
|
|
Value::Bool(value) => rune::to_value(*value)?,
|
|
Value::Integer(value) => rune::to_value(*value)?,
|
|
Value::String(value) => rune::to_value(value.as_str())?,
|
|
Value::ActorRef(value) => RuneValue::new(ActorReference(*value))?,
|
|
Value::List(values) => {
|
|
let mut result = RuneVec::with_capacity(values.len())?;
|
|
for value in values {
|
|
result.push(convert(value)?)?;
|
|
}
|
|
RuneValue::new(result)?
|
|
}
|
|
Value::Record(values) => {
|
|
let mut result = Object::with_capacity(values.len())?;
|
|
for (key, value) in values {
|
|
result.insert(
|
|
rune::alloc::String::try_from(key.as_str())?,
|
|
convert(value)?,
|
|
)?;
|
|
}
|
|
RuneValue::new(result)?
|
|
}
|
|
})
|
|
}
|
|
convert(value)
|
|
}
|
|
|
|
/// Copy only portable data out of Rune; cycles and unsupported objects fail.
|
|
pub fn value_from_rune(value: &RuneValue) -> ExperimentResult<Value> {
|
|
fn convert(
|
|
value: &RuneValue,
|
|
depth: usize,
|
|
nodes: &mut usize,
|
|
bytes: &mut usize,
|
|
) -> ExperimentResult<Value> {
|
|
*nodes += 1;
|
|
if depth > MAX_VALUE_DEPTH || *nodes > MAX_VALUE_NODES {
|
|
return Err("Rune actor value exceeds depth or node limit (possibly cyclic)".into());
|
|
}
|
|
|
|
let hash = value.type_hash();
|
|
let result = if value.into_unit().is_ok() {
|
|
Value::Null
|
|
} else if hash == bool::HASH {
|
|
Value::Bool(value.as_bool()?)
|
|
} else if hash == i64::HASH {
|
|
Value::Integer(value.as_integer()?)
|
|
} else if hash == rune::alloc::String::HASH {
|
|
let value = value.borrow_string_ref()?;
|
|
*bytes += value.len();
|
|
if *bytes > MAX_VALUE_BYTES {
|
|
return Err("Rune actor value exceeds string byte limit".into());
|
|
}
|
|
Value::String(value.to_string())
|
|
} else if hash == ActorReference::HASH {
|
|
Value::ActorRef(value.borrow_ref::<ActorReference>()?.0)
|
|
} else if hash == RuneVec::HASH {
|
|
let values = value.borrow_ref::<RuneVec>()?;
|
|
let mut result = Vec::new();
|
|
for value in values.iter() {
|
|
result.push(convert(value, depth + 1, nodes, bytes)?);
|
|
}
|
|
Value::List(result)
|
|
} else if hash == Object::HASH {
|
|
let values = value.borrow_ref::<Object>()?;
|
|
let mut result = std::collections::BTreeMap::new();
|
|
for (key, value) in values.iter() {
|
|
*bytes += key.len();
|
|
if *bytes > MAX_VALUE_BYTES {
|
|
return Err("Rune actor value exceeds string byte limit".into());
|
|
}
|
|
result.insert(key.to_string(), convert(value, depth + 1, nodes, bytes)?);
|
|
}
|
|
Value::Record(result)
|
|
} else {
|
|
return Err(format!("unsupported Rune actor value: {}", value.type_info()).into());
|
|
};
|
|
Ok(result)
|
|
}
|
|
convert(value, 0, &mut 0, &mut 0)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::CompiledProgram;
|
|
use std::collections::BTreeMap;
|
|
|
|
#[test]
|
|
fn portable_values_round_trip_without_turning_strings_into_references() -> ExperimentResult<()>
|
|
{
|
|
let actor = ActorRef::new();
|
|
let value = Value::Record(BTreeMap::from([(
|
|
"values".to_owned(),
|
|
Value::List(vec![
|
|
Value::Null,
|
|
Value::Bool(true),
|
|
Value::Integer(i64::MIN),
|
|
Value::Integer(i64::MAX),
|
|
Value::String("hello world".to_owned()),
|
|
Value::ActorRef(actor),
|
|
Value::String(actor.to_string()),
|
|
Value::List(vec![]),
|
|
Value::Record(BTreeMap::new()),
|
|
]),
|
|
)]));
|
|
assert_eq!(value_from_rune(&value_to_rune(&value)?)?, value);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn rune_mutations_do_not_alias_host_values_or_other_invocation_inputs() -> ExperimentResult<()>
|
|
{
|
|
let original = Value::Record(BTreeMap::from([(
|
|
"items".to_owned(),
|
|
Value::List(vec![Value::Integer(1)]),
|
|
)]));
|
|
let program =
|
|
CompiledProgram::compile("pub fn change(state) { state.items.push(2); state }")?;
|
|
let first = value_to_rune(&original)?;
|
|
let second = value_to_rune(&original)?;
|
|
let result = program.vm().call(["change"], (first,))?;
|
|
assert_eq!(value_from_rune(&second)?, original);
|
|
assert_ne!(value_from_rune(&result)?, original);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn references_can_be_compared_cloned_and_retained_in_rune_records() -> ExperimentResult<()> {
|
|
let actor = ActorRef::new();
|
|
let other = ActorRef::new();
|
|
let program = CompiledProgram::compile(
|
|
"pub fn check(left, right) { #{same: left == right, reference: left.clone()} }",
|
|
)?;
|
|
for (right, expected) in [(actor, true), (other, false)] {
|
|
let result = program.vm().call(
|
|
["check"],
|
|
(
|
|
value_to_rune(&Value::ActorRef(actor))?,
|
|
value_to_rune(&Value::ActorRef(right))?,
|
|
),
|
|
)?;
|
|
assert_eq!(
|
|
value_from_rune(&result)?,
|
|
Value::Record(BTreeMap::from([
|
|
("same".to_owned(), Value::Bool(expected)),
|
|
("reference".to_owned(), Value::ActorRef(actor)),
|
|
]))
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn cyclic_and_unsupported_rune_values_are_explicit_errors() -> ExperimentResult<()> {
|
|
for source in [
|
|
"pub fn make() { 1.5 }",
|
|
"pub fn make() { (1, 2) }",
|
|
"pub fn make() { Some(1) }",
|
|
"pub fn make() { let x = []; x.push(x); x }",
|
|
] {
|
|
let program = CompiledProgram::compile(source)?;
|
|
let value = program.vm().call(["make"], ())?;
|
|
assert!(value_from_rune(&value).is_err(), "{source}");
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|