implement string context

This commit is contained in:
2026-05-17 17:02:49 +08:00
parent 9a17990d5e
commit d98e389606
11 changed files with 698 additions and 224 deletions
+12 -2
View File
@@ -4,8 +4,7 @@ use fix_error::Source;
use hashbrown::HashSet;
use crate::{
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp,
StaticValue, StrictValue, Thunk, ThunkState, Value,
AttrSet, Closure, ExtraScope, List, NixString, NixType, Null, Path, PrimOp, PrimOpApp, StaticValue, StrictValue, StringContext, Thunk, ThunkState, Value
};
pub trait VmContext {
@@ -36,6 +35,9 @@ pub trait VmRuntimeCtxExt: VmRuntimeCtx {
&'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;
}
@@ -73,6 +75,14 @@ impl<T: VmRuntimeCtx> VmRuntimeCtxExt for T {
}
}
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())
}
+2
View File
@@ -6,6 +6,7 @@ mod machine;
mod path_util;
mod resolve;
mod state;
mod string_context;
mod value;
pub use bytecode_reader::*;
@@ -15,4 +16,5 @@ pub use machine::*;
pub use path_util::*;
pub use resolve::*;
pub use state::*;
pub use string_context::*;
pub use value::*;
+161
View File
@@ -0,0 +1,161 @@
use std::cmp::Ordering;
use smallvec::SmallVec;
/// A string context element
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StringContextElem {
// Plain store path reference
Opaque {
path: Box<str>,
},
// All outputs of a derivation
// encoded `=<drvPath>`
DrvDeep {
drv_path: Box<str>,
},
// A specific output of a derivation
// encoded `!<output>!<drvPath>`
Built {
drv_path: Box<str>,
output: Box<str>,
},
}
impl StringContextElem {
/// Decode the CppNix wire form (`!out!/p`, `=/p`, `/p`). Falls back to
/// `Opaque` for malformed `!`-prefixed inputs (matching nix-js).
pub fn decode(encoded: &str) -> Self {
if let Some(drv_path) = encoded.strip_prefix('=') {
Self::DrvDeep {
drv_path: drv_path.into(),
}
} else if let Some(rest) = encoded.strip_prefix('!') {
if let Some(second_bang) = rest.find('!') {
Self::Built {
output: rest[..second_bang].into(),
drv_path: rest[second_bang + 1..].into(),
}
} else {
Self::Opaque {
path: encoded.into(),
}
}
} else {
Self::Opaque {
path: encoded.into(),
}
}
}
pub fn encode(&self) -> String {
match self {
Self::Opaque { path } => path.to_string(),
Self::DrvDeep { drv_path } => format!("={drv_path}"),
Self::Built { drv_path, output } => format!("!{output}!{drv_path}"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct StringContext {
data: SmallVec<[StringContextElem; 1]>,
}
impl IntoIterator for StringContext {
type Item = StringContextElem;
type IntoIter = <SmallVec<[StringContextElem; 1]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.data.into_iter()
}
}
impl<'a> IntoIterator for &'a StringContext {
type Item = &'a StringContextElem;
type IntoIter = <&'a SmallVec<[StringContextElem; 1]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
impl<'a> IntoIterator for &'a mut StringContext {
type Item = &'a mut StringContextElem;
type IntoIter = <&'a mut SmallVec<[StringContextElem; 1]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.data.iter_mut()
}
}
impl FromIterator<StringContextElem> for StringContext {
fn from_iter<T: IntoIterator<Item = StringContextElem>>(iter: T) -> Self {
Self {
data: iter.into_iter().collect()
}
}
}
impl StringContext {
pub fn empty() -> &'static Self {
static EMPTY: StringContext = StringContext {
data: SmallVec::new_const(),
};
&EMPTY
}
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn insert(&mut self, elem: StringContextElem) {
match self.data.binary_search(&elem) {
Ok(_) => {}
Err(pos) => self.data.insert(pos, elem),
}
}
pub fn merge(&self, other: &Self) -> Self {
if self.data.is_empty() {
return other.clone();
}
if other.data.is_empty() {
return self.clone();
}
let a = &self.data;
let b = &other.data;
let mut out = SmallVec::with_capacity(a.len() + b.len());
let (mut i, mut j) = (0, 0);
while i < a.len() && j < b.len() {
match a[i].cmp(&b[j]) {
Ordering::Less => {
out.push(a[i].clone());
i += 1;
}
Ordering::Greater => {
out.push(b[j].clone());
j += 1;
}
Ordering::Equal => {
out.push(a[i].clone());
i += 1;
j += 1;
}
}
}
out.extend(a[i..].iter().cloned());
out.extend(b[j..].iter().cloned());
Self { data: out }
}
pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
self.into_iter()
}
pub fn iter_mut(&mut self) -> <&mut Self as IntoIterator>::IntoIter {
self.into_iter()
}
}
+23 -7
View File
@@ -17,6 +17,7 @@ use string_interner::Symbol;
use string_interner::symbol::SymbolU32;
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
use crate::string_context::StringContext;
mod private {
pub trait Cealed {}
@@ -420,26 +421,41 @@ impl RawStore for Path {
}
}
/// Heap-allocated Nix string.
///
/// Stored on the GC heap via `Gc<'gc, NixString>`. The string data itself
/// lives in a standard `Box<str>` owned by this struct; the GC only manages
/// the outer allocation.
#[derive(Collect)]
#[collect(require_static)]
pub struct NixString {
data: Box<str>,
// TODO: string context for derivation dependency tracking
context: StringContext,
}
impl NixString {
pub fn new(s: impl Into<Box<str>>) -> Self {
Self { data: s.into() }
Self {
data: s.into(),
context: StringContext::new(),
}
}
/// Construct a `NixString` whose `context` is already sorted+deduped.
/// The caller is responsible for invariant maintenance.
pub fn with_context(s: impl Into<Box<str>>, context: StringContext) -> Self {
Self {
data: s.into(),
context,
}
}
pub fn as_str(&self) -> &str {
&self.data
}
pub fn context(&self) -> &StringContext {
&self.context
}
pub fn has_context(&self) -> bool {
!self.context.is_empty()
}
}
impl fmt::Debug for NixString {