implement Path type

This commit is contained in:
2026-05-08 19:01:13 +08:00
parent 3d07f89afe
commit 21899f7380
8 changed files with 209 additions and 26 deletions
+52
View File
@@ -1 +1,53 @@
use fix_common::StringId;
use fix_error::Error;
use gc_arena::Mutation;
use crate::bytecode_reader::BytecodeReader;
use crate::value::*;
use crate::{Step, Vm, VmRuntimeCtx};
impl<'gc> Vm<'gc> {
pub(crate) fn primop_to_string(
&mut self,
_ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
if val.is::<StringId>() || val.is::<NixString>() {
return self.return_from_primop(val.relax(), reader);
}
if let Some(p) = val.as_inline::<Path>() {
return self.return_from_primop(Value::new_inline(p.0), reader);
}
// TODO: derivations / `__toString` / `outPath`,
// numbers, lists.
self.finish_err(Error::eval_error(format!(
"cannot coerce {} to a string",
val.ty()
)))
}
pub(crate) fn primop_type_of(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
let name: &str = match val.ty() {
NixType::Int => "int",
NixType::Float => "float",
NixType::Bool => "bool",
NixType::Null => "null",
NixType::String => "string",
NixType::Path => "path",
NixType::AttrSet => "set",
NixType::List => "list",
NixType::Closure | NixType::PrimOp | NixType::PrimOpApp => "lambda",
NixType::Thunk => unreachable!("forced"),
};
let sid = ctx.intern_string(name);
self.return_from_primop(Value::new_inline(sid), reader)
}
}
+25 -13
View File
@@ -7,6 +7,7 @@ use gc_arena::{Gc, Mutation};
use hashbrown::HashSet;
use crate::bytecode_reader::BytecodeReader;
use crate::instructions::misc::canon_path_str;
use crate::value::*;
use crate::{Break, CallFrame, PendingLoad, PendingScope, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
@@ -19,11 +20,11 @@ impl<'gc> Vm<'gc> {
) -> Step {
// stack: [path]
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
let path_str = match ctx.get_string(path_val) {
let path_str = match ctx.get_string_or_path(path_val) {
Some(s) => s.to_owned(),
None => {
return self.finish_err(Error::eval_error(format!(
"expected a path string, got {}",
"expected a path or string, got {}",
path_val.ty()
)));
}
@@ -93,11 +94,11 @@ impl<'gc> Vm<'gc> {
// stack: [scope, path]
let (scope_attrs, path_val) =
self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
let path_str = match ctx.get_string(path_val) {
let path_str = match ctx.get_string_or_path(path_val) {
Some(s) => s.to_owned(),
None => {
return self.finish_err(Error::eval_error(format!(
"expected a path string, got {}",
"expected a path or string, got {}",
path_val.ty()
)));
}
@@ -145,17 +146,28 @@ impl<'gc> Vm<'gc> {
mc: &Mutation<'gc>,
) -> Step {
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
let path = match ctx.get_string(path_val) {
Some(s) => s.to_owned(),
None => {
return self.finish_err(Error::eval_error(format!(
"expected a path string, got {}",
path_val.ty()
)));
}
// CppNix: pathExists requires an absolute path. A `Path` value is
// always absolute; a string is accepted only if it starts with `/`.
let (path, is_path_value) = if let Some(p) = path_val.as_inline::<Path>() {
(ctx.resolve_string(p.0).to_owned(), true)
} else if let Some(s) = ctx.get_string(path_val) {
(s.to_owned(), false)
} else {
return self.finish_err(Error::eval_error(format!(
"expected a path or string, got {}",
path_val.ty()
)));
};
if !is_path_value && !path.starts_with('/') {
return self.finish_err(Error::eval_error(format!(
"string '{path}' doesn't represent an absolute path"
)));
}
// CppNix collapses consecutive slashes and resolves `.` / `..` lexically
// before checking. Trailing-slash / trailing-dot mean "must be a directory".
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
let p = std::path::Path::new(&path);
let canon = canon_path_str(&path);
let p = std::path::Path::new(&canon);
let exists = if must_be_dir {
std::fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
} else {
+4
View File
@@ -62,6 +62,10 @@ impl<'gc> Vm<'gc> {
ScopedImportFinalize => self.primop_scoped_import_finalize(ctx, reader, mc),
PathExists => self.primop_path_exists(ctx, reader, mc),
ToPath => self.primop_to_path(ctx, reader, mc),
IsPath => self.primop_is_path(reader, mc),
ToString => self.primop_to_string(ctx, reader, mc),
TypeOf => self.primop_type_of(ctx, reader, mc),
phase => todo!("primop phase {phase:?}"),
}
+45
View File
@@ -1 +1,46 @@
use fix_error::Error;
use gc_arena::Mutation;
use crate::bytecode_reader::BytecodeReader;
use crate::instructions::misc::canon_path_str;
use crate::value::*;
use crate::{Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
impl<'gc> Vm<'gc> {
pub(crate) fn primop_to_path(
&mut self,
ctx: &mut impl VmRuntimeCtx,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
// coerce to path THEN TO STRING
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
if let Some(Path(s)) = val.as_inline::<Path>() {
return self.return_from_primop(Value::new_inline(s), reader);
}
let Some(s) = ctx.get_string(val) else {
return self.finish_err(Error::eval_error(format!(
"cannot coerce {} to a path",
val.ty()
)));
};
if !s.starts_with('/') {
return self.finish_err(Error::eval_error(format!(
"string '{s}' doesn't represent an absolute path"
)));
}
let canon = canon_path_str(s);
let sid = ctx.intern_string(canon);
self.return_from_primop(Value::new_inline(sid), reader)
}
pub(crate) fn primop_is_path(
&mut self,
reader: &mut BytecodeReader<'_>,
mc: &Mutation<'gc>,
) -> Step {
let val = self.force_and_retry::<StrictValue>(reader, mc)?;
let is_path = val.is::<Path>();
self.return_from_primop(Value::new_inline(is_path), reader)
}
}