165 lines
6.1 KiB
Rust
165 lines
6.1 KiB
Rust
use fix_codegen::InstructionPtr;
|
|
use fix_common::StringId;
|
|
use fix_error::Source;
|
|
use hashbrown::HashSet;
|
|
|
|
use crate::{
|
|
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp, StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value
|
|
};
|
|
|
|
pub trait VmContext {
|
|
fn split(&mut self) -> (&mut impl VmCode, &mut impl VmRuntimeCtx);
|
|
}
|
|
|
|
pub trait VmRuntimeCtx {
|
|
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId;
|
|
fn resolve_string(&self, id: StringId) -> &str;
|
|
fn get_const(&self, id: u32) -> StaticValue;
|
|
fn add_const(&mut self, val: StaticValue) -> u32;
|
|
}
|
|
|
|
pub trait VmCode {
|
|
fn bytecode(&self) -> &[u8];
|
|
fn compile_with_scope(
|
|
&mut self,
|
|
source: Source,
|
|
extra_scope: Option<ExtraScope>,
|
|
ctx: &mut impl VmRuntimeCtx,
|
|
) -> fix_error::Result<InstructionPtr>;
|
|
}
|
|
|
|
pub trait VmRuntimeCtxExt: VmRuntimeCtx {
|
|
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
|
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
|
fn get_string_id<'a, 'gc: 'a>(
|
|
&'a mut self,
|
|
val: StrictValue<'gc>,
|
|
) -> std::result::Result<StringId, NixType>;
|
|
/// Returns the string context attached to `val`, or `&[]` if `val` is
|
|
/// either a non-string or a string without context.
|
|
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext;
|
|
fn convert_value(&self, val: Value) -> fix_common::Value;
|
|
}
|
|
|
|
impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
|
|
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
|
if let Some(sid) = val.as_inline::<StringId>() {
|
|
Some(self.resolve_string(sid))
|
|
} else {
|
|
val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str())
|
|
}
|
|
}
|
|
|
|
/// Like `get_string`, but also accepts `Path` values (returning their
|
|
/// underlying canonical-path string). Use this in places where Nix
|
|
/// would coerce a path to a string (string interpolation, file IO
|
|
/// builtins, etc.).
|
|
fn get_string_or_path<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> {
|
|
if let Some(p) = val.as_inline::<Path>() {
|
|
Some(self.resolve_string(p.0))
|
|
} else {
|
|
self.get_string(val)
|
|
}
|
|
}
|
|
|
|
fn get_string_id<'a, 'gc: 'a>(
|
|
&'a mut self,
|
|
val: StrictValue<'gc>,
|
|
) -> std::result::Result<StringId, NixType> {
|
|
if let Some(sid) = val.as_inline::<StringId>() {
|
|
Ok(sid)
|
|
} else if let Some(s) = val.as_gc::<NixString>().map(|ns| ns.as_ref().as_str()) {
|
|
Ok(self.intern_string(s))
|
|
} else {
|
|
Err(val.ty())
|
|
}
|
|
}
|
|
|
|
fn get_string_context<'gc>(&self, val: StrictValue<'gc>) -> &'gc StringContext {
|
|
if let Some(ns) = val.as_gc::<NixString>() {
|
|
ns.as_ref().context()
|
|
} else {
|
|
StringContext::empty()
|
|
}
|
|
}
|
|
|
|
fn convert_value(&self, val: Value) -> fix_common::Value {
|
|
self.convert_value_with_seen(val, &mut HashSet::new())
|
|
}
|
|
}
|
|
|
|
pub(crate) trait ConvertValueWithSeen: VmRuntimeCtx {
|
|
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value;
|
|
}
|
|
|
|
impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
|
fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet<u64>) -> fix_common::Value {
|
|
use fix_common::Value;
|
|
if let Some(i) = val.as_inline::<i32>() {
|
|
Value::Int(i as i64)
|
|
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
|
Value::Int(*gc_i)
|
|
} else if let Some(f) = val.as_float() {
|
|
Value::Float(f)
|
|
} else if let Some(b) = val.as_inline::<bool>() {
|
|
Value::Bool(b)
|
|
} else if val.is::<Null>() {
|
|
Value::Null
|
|
} else if let Some(sid) = val.as_inline::<StringId>() {
|
|
let s = self.resolve_string(sid).to_owned();
|
|
Value::String(s)
|
|
} else if let Some(ns) = val.as_gc::<NixString>() {
|
|
Value::String(ns.as_str().to_owned())
|
|
} else if let Some(p) = val.as_inline::<Path>() {
|
|
Value::Path(self.resolve_string(p.0).to_owned())
|
|
} else if let Some(attrs) = val.as_gc::<AttrSet>() {
|
|
let bits = val.to_bits();
|
|
if attrs.entries.is_empty() {
|
|
return Value::AttrSet(Default::default());
|
|
}
|
|
if !seen.insert(bits) {
|
|
return Value::Repeated;
|
|
}
|
|
let mut map = std::collections::BTreeMap::new();
|
|
for &(key, val) in attrs.entries.iter() {
|
|
let key = self.resolve_string(key).to_owned();
|
|
let converted = self.convert_value_with_seen(val, seen);
|
|
map.insert(fix_common::Symbol::from(key), converted);
|
|
}
|
|
Value::AttrSet(fix_common::AttrSet::new(map))
|
|
} else if let Some(list) = val.as_gc::<List>() {
|
|
let bits = val.to_bits();
|
|
if list.inner.borrow().is_empty() {
|
|
return Value::List(Default::default());
|
|
}
|
|
if !seen.insert(bits) {
|
|
return Value::Repeated;
|
|
}
|
|
let items: Vec<_> = list
|
|
.inner
|
|
.borrow()
|
|
.iter()
|
|
.copied()
|
|
.map(|v| self.convert_value_with_seen(v, seen))
|
|
.collect();
|
|
Value::List(fix_common::List::new(items))
|
|
} else if val.is::<Closure>() {
|
|
Value::Func
|
|
} else if let Some(thunk) = val.as_gc::<Thunk>() {
|
|
if let ThunkState::Evaluated(v) = *thunk.borrow() {
|
|
self.convert_value_with_seen(v.relax(), seen)
|
|
} else {
|
|
Value::Thunk
|
|
}
|
|
} else if let Some(primop) = val.as_inline::<PrimOp>() {
|
|
let name = fix_builtins::BUILTINS[primop.id as usize].0;
|
|
Value::PrimOp(name.strip_prefix("__").unwrap_or(name))
|
|
} else if let Some(app) = val.as_gc::<PrimOpApp>() {
|
|
let name = fix_builtins::BUILTINS[app.primop.id as usize].0;
|
|
Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name))
|
|
} else {
|
|
Value::Null
|
|
}
|
|
}
|
|
}
|