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) -> 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, ctx: &mut impl VmRuntimeCtx, ) -> fix_error::Result; } 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; /// 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 VmRuntimeCtxExt for T { fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str> { if let Some(sid) = val.as_inline::() { Some(self.resolve_string(sid)) } else { val.as_gc::().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::() { 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 { if let Some(sid) = val.as_inline::() { Ok(sid) } else if let Some(s) = val.as_gc::().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::() { 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) -> fix_common::Value; } impl ConvertValueWithSeen for T { fn convert_value_with_seen(&self, val: Value, seen: &mut HashSet) -> fix_common::Value { use fix_common::Value; if let Some(i) = val.as_inline::() { Value::Int(i as i64) } else if let Some(gc_i) = val.as_gc::() { Value::Int(*gc_i) } else if let Some(f) = val.as_float() { Value::Float(f) } else if let Some(b) = val.as_inline::() { Value::Bool(b) } else if val.is::() { Value::Null } else if let Some(sid) = val.as_inline::() { let s = self.resolve_string(sid).to_owned(); Value::String(s) } else if let Some(ns) = val.as_gc::() { Value::String(ns.as_str().to_owned()) } else if let Some(p) = val.as_inline::() { Value::Path(self.resolve_string(p.0).to_owned()) } else if let Some(attrs) = val.as_gc::() { 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::() { 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::() { Value::Func } else if let Some(thunk) = val.as_gc::() { 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::() { 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::() { let name = fix_builtins::BUILTINS[app.primop.id as usize].0; Value::PrimOpApp(name.strip_prefix("__").unwrap_or(name)) } else { Value::Null } } }