init builtins

This commit is contained in:
2026-03-16 18:03:40 +08:00
parent 198d847151
commit 1950d4de6c
22 changed files with 2197 additions and 665 deletions
Generated
+1 -8
View File
@@ -188,13 +188,6 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "boxing"
version = "0.1.3"
dependencies = [
"sptr",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.1" version = "1.12.1"
@@ -798,7 +791,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
"boxing",
"bumpalo", "bumpalo",
"bzip2", "bzip2",
"clap", "clap",
@@ -833,6 +825,7 @@ dependencies = [
"sha2", "sha2",
"small-map", "small-map",
"smallvec", "smallvec",
"sptr",
"string-interner", "string-interner",
"tap", "tap",
"tar", "tar",
-1
View File
@@ -2,7 +2,6 @@
resolver = "3" resolver = "3"
members = [ members = [
"fix", "fix",
"boxing",
] ]
[profile.profiling] [profile.profiling]
-8
View File
@@ -1,8 +0,0 @@
[package]
name = "boxing"
version = "0.1.3"
edition = "2021"
description = "NaN-boxing primitives (local fork with bool fix)"
[dependencies]
sptr = "0.3"
-2
View File
@@ -1,2 +0,0 @@
pub mod nan;
mod utils;
-7
View File
@@ -1,7 +0,0 @@
pub mod raw;
pub use raw::RawBox;
const SIGN_MASK: u64 = 0x7FFF_FFFF_FFFF_FFFF;
const QUIET_NAN: u64 = 0x7FF8_0000_0000_0000;
const NEG_QUIET_NAN: u64 = 0xFFF8_0000_0000_0000;
-16
View File
@@ -1,16 +0,0 @@
pub trait ArrayExt<const LEN: usize> {
type Elem;
fn truncate_to<const M: usize>(self) -> [Self::Elem; M];
}
impl<T: Default + Copy, const N: usize> ArrayExt<N> for [T; N] {
type Elem = T;
fn truncate_to<const M: usize>(self) -> [Self::Elem; M] {
let copy_len = usize::min(N, M);
let mut out = [T::default(); M];
out[0..copy_len].copy_from_slice(&self[0..copy_len]);
out
}
}
+1 -1
View File
@@ -79,7 +79,7 @@ tap = "1.0.1"
ghost-cell = "0.2" ghost-cell = "0.2"
colored = "3.1" colored = "3.1"
boxing = { path = "../boxing" } sptr = "0.3"
sealed = "0.6" sealed = "0.6"
small-map = "0.1" small-map = "0.1"
smallvec = "1.15" smallvec = "1.15"
+53 -46
View File
@@ -1,11 +1,28 @@
use super::{NEG_QUIET_NAN, QUIET_NAN, SIGN_MASK};
use crate::utils::ArrayExt;
use sptr::Strict;
use std::fmt; use std::fmt;
use std::mem::ManuallyDrop;
use std::num::NonZeroU8; use std::num::NonZeroU8;
pub trait RawStore: Sized { use sptr::Strict;
const SIGN_MASK: u64 = 0x7FFF_FFFF_FFFF_FFFF;
const QUIET_NAN: u64 = 0x7FF8_0000_0000_0000;
const NEG_QUIET_NAN: u64 = 0xFFF8_0000_0000_0000;
pub(crate) trait ArrayExt<const LEN: usize> {
type Elem;
fn truncate_to<const M: usize>(self) -> [Self::Elem; M];
}
impl<T: Default + Copy, const N: usize> ArrayExt<N> for [T; N] {
type Elem = T;
fn truncate_to<const M: usize>(self) -> [Self::Elem; M] {
let copy_len = usize::min(N, M);
let mut out = [T::default(); M];
out[0..copy_len].copy_from_slice(&self[0..copy_len]);
out
}
}
pub(crate) trait RawStore: Sized {
fn to_val(self, value: &mut Value); fn to_val(self, value: &mut Value);
fn from_val(value: &Value) -> Self; fn from_val(value: &Value) -> Self;
} }
@@ -138,18 +155,18 @@ enum TagVal {
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub struct RawTag(TagVal); pub(crate) struct RawTag(TagVal);
impl RawTag { impl RawTag {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new(neg: bool, val: NonZeroU8) -> RawTag { pub(crate) fn new(neg: bool, val: NonZeroU8) -> RawTag {
unsafe { Self::new_unchecked(neg, val.get() & 0x07) } unsafe { Self::new_unchecked(neg, val.get() & 0x07) }
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new_checked(neg: bool, val: u8) -> Option<RawTag> { pub(crate) fn new_checked(neg: bool, val: u8) -> Option<RawTag> {
Some(RawTag(match (neg, val) { Some(RawTag(match (neg, val) {
(false, 1) => TagVal::_P1, (false, 1) => TagVal::_P1,
(false, 2) => TagVal::_P2, (false, 2) => TagVal::_P2,
@@ -176,7 +193,7 @@ impl RawTag {
/// `val` must be in the range `1..8` /// `val` must be in the range `1..8`
#[inline] #[inline]
#[must_use] #[must_use]
pub unsafe fn new_unchecked(neg: bool, val: u8) -> RawTag { pub(crate) unsafe fn new_unchecked(neg: bool, val: u8) -> RawTag {
RawTag(match (neg, val) { RawTag(match (neg, val) {
(false, 1) => TagVal::_P1, (false, 1) => TagVal::_P1,
(false, 2) => TagVal::_P2, (false, 2) => TagVal::_P2,
@@ -200,7 +217,7 @@ impl RawTag {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_neg(self) -> bool { pub(crate) fn is_neg(self) -> bool {
matches!(self.0, |TagVal::_N1| TagVal::_N2 matches!(self.0, |TagVal::_N1| TagVal::_N2
| TagVal::_N3 | TagVal::_N3
| TagVal::_N4 | TagVal::_N4
@@ -211,7 +228,7 @@ impl RawTag {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn val(self) -> NonZeroU8 { pub(crate) fn val(self) -> NonZeroU8 {
match self.0 { match self.0 {
TagVal::_P1 | TagVal::_N1 => NonZeroU8::MIN, TagVal::_P1 | TagVal::_N1 => NonZeroU8::MIN,
TagVal::_P2 | TagVal::_N2 => NonZeroU8::MIN.saturating_add(1), TagVal::_P2 | TagVal::_N2 => NonZeroU8::MIN.saturating_add(1),
@@ -225,7 +242,7 @@ impl RawTag {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn neg_val(self) -> (bool, u8) { pub(crate) fn neg_val(self) -> (bool, u8) {
match self.0 { match self.0 {
TagVal::_P1 => (false, 1), TagVal::_P1 => (false, 1),
TagVal::_P2 => (false, 2), TagVal::_P2 => (false, 2),
@@ -286,9 +303,9 @@ impl Header {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C, align(8))] #[repr(C, align(8))]
pub struct Value { pub(crate) struct Value {
#[cfg(target_endian = "big")] #[cfg(target_endian = "big")]
header: Header, header: Header,
data: [u8; 6], data: [u8; 6],
@@ -298,7 +315,7 @@ pub struct Value {
impl Value { impl Value {
#[inline] #[inline]
pub fn new(tag: RawTag, data: [u8; 6]) -> Value { pub(crate) fn new(tag: RawTag, data: [u8; 6]) -> Value {
Value { Value {
header: Header::new(tag), header: Header::new(tag),
data, data,
@@ -306,23 +323,23 @@ impl Value {
} }
#[inline] #[inline]
pub fn empty(tag: RawTag) -> Value { pub(crate) fn empty(tag: RawTag) -> Value {
Value::new(tag, [0; 6]) Value::new(tag, [0; 6])
} }
pub fn store<T: RawStore>(tag: RawTag, val: T) -> Value { pub(crate) fn store<T: RawStore>(tag: RawTag, val: T) -> Value {
let mut v = Value::new(tag, [0; 6]); let mut v = Value::new(tag, [0; 6]);
T::to_val(val, &mut v); T::to_val(val, &mut v);
v v
} }
pub fn load<T: RawStore>(self) -> T { pub(crate) fn load<T: RawStore>(self) -> T {
T::from_val(&self) T::from_val(&self)
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub fn tag(&self) -> RawTag { pub(crate) fn tag(&self) -> RawTag {
self.header.tag() self.header.tag()
} }
@@ -332,41 +349,42 @@ impl Value {
} }
#[inline] #[inline]
pub fn set_data(&mut self, val: [u8; 6]) { pub(crate) fn set_data(&mut self, val: [u8; 6]) {
self.data = val; self.data = val;
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub fn data(&self) -> &[u8; 6] { pub(crate) fn data(&self) -> &[u8; 6] {
&self.data &self.data
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub fn data_mut(&mut self) -> &mut [u8; 6] { pub(crate) fn data_mut(&mut self) -> &mut [u8; 6] {
&mut self.data &mut self.data
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub unsafe fn whole(&self) -> &[u8; 8] { unsafe fn whole(&self) -> &[u8; 8] {
let ptr = (self as *const Value).cast::<[u8; 8]>(); let ptr = (self as *const Value).cast::<[u8; 8]>();
unsafe { &*ptr } unsafe { &*ptr }
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub unsafe fn whole_mut(&mut self) -> &mut [u8; 8] { unsafe fn whole_mut(&mut self) -> &mut [u8; 8] {
let ptr = (self as *mut Value).cast::<[u8; 8]>(); let ptr = (self as *mut Value).cast::<[u8; 8]>();
unsafe { &mut *ptr } unsafe { &mut *ptr }
} }
} }
#[repr(C)] #[repr(C)]
pub union RawBox { #[derive(Copy, Clone)]
pub(crate) union RawBox {
float: f64, float: f64,
value: ManuallyDrop<Value>, value: Value,
bits: u64, bits: u64,
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
ptr: *const (), ptr: *const (),
@@ -377,7 +395,7 @@ pub union RawBox {
impl RawBox { impl RawBox {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn from_float(val: f64) -> RawBox { pub(crate) fn from_float(val: f64) -> RawBox {
match (val.is_nan(), val.is_sign_positive()) { match (val.is_nan(), val.is_sign_positive()) {
(true, true) => RawBox { (true, true) => RawBox {
float: f64::from_bits(QUIET_NAN), float: f64::from_bits(QUIET_NAN),
@@ -391,15 +409,13 @@ impl RawBox {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn from_value(value: Value) -> RawBox { pub(crate) fn from_value(value: Value) -> RawBox {
RawBox { RawBox { value }
value: ManuallyDrop::new(value),
}
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub fn tag(&self) -> Option<RawTag> { pub(crate) fn tag(&self) -> Option<RawTag> {
if self.is_value() { if self.is_value() {
Some(unsafe { self.value.tag() }) Some(unsafe { self.value.tag() })
} else { } else {
@@ -409,19 +425,19 @@ impl RawBox {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_float(&self) -> bool { pub(crate) fn is_float(&self) -> bool {
(unsafe { !self.float.is_nan() } || unsafe { self.bits & SIGN_MASK == QUIET_NAN }) (unsafe { !self.float.is_nan() } || unsafe { self.bits & SIGN_MASK == QUIET_NAN })
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_value(&self) -> bool { pub(crate) fn is_value(&self) -> bool {
(unsafe { self.float.is_nan() } && unsafe { self.bits & SIGN_MASK != QUIET_NAN }) (unsafe { self.float.is_nan() } && unsafe { self.bits & SIGN_MASK != QUIET_NAN })
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub fn float(&self) -> Option<&f64> { pub(crate) fn float(&self) -> Option<&f64> {
if self.is_float() { if self.is_float() {
Some(unsafe { &self.float }) Some(unsafe { &self.float })
} else { } else {
@@ -431,7 +447,7 @@ impl RawBox {
#[inline] #[inline]
#[must_use] #[must_use]
pub fn value(&self) -> Option<&Value> { pub(crate) fn value(&self) -> Option<&Value> {
if self.is_value() { if self.is_value() {
Some(unsafe { &self.value }) Some(unsafe { &self.value })
} else { } else {
@@ -440,20 +456,11 @@ impl RawBox {
} }
#[inline] #[inline]
pub fn into_float_unchecked(self) -> f64 { pub(crate) fn into_float_unchecked(self) -> f64 {
unsafe { self.float } unsafe { self.float }
} }
} }
impl Clone for RawBox {
#[inline]
fn clone(&self) -> Self {
RawBox {
ptr: unsafe { self.ptr },
}
}
}
impl fmt::Debug for RawBox { impl fmt::Debug for RawBox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.float() { match self.float() {
+2 -5
View File
@@ -125,8 +125,6 @@ pub enum Error {
#[label("error occurred here")] #[label("error occurred here")]
span: Option<SourceSpan>, span: Option<SourceSpan>,
message: String, message: String,
#[help]
js_backtrace: Option<String>,
#[related] #[related]
stack_trace: Vec<StackFrame>, stack_trace: Vec<StackFrame>,
}, },
@@ -163,12 +161,11 @@ impl Error {
.into() .into()
} }
pub fn eval_error(msg: String, backtrace: Option<String>) -> Box<Self> { pub fn eval_error(msg: impl Into<String>) -> Box<Self> {
Error::EvalError { Error::EvalError {
src: None, src: None,
span: None, span: None,
message: msg, message: msg.into(),
js_backtrace: backtrace,
stack_trace: Vec::new(), stack_trace: Vec::new(),
} }
.into() .into()
+1
View File
@@ -1,6 +1,7 @@
#![warn(clippy::unwrap_used)] #![warn(clippy::unwrap_used)]
#![allow(dead_code)] #![allow(dead_code)]
mod boxing;
pub mod error; pub mod error;
pub mod logging; pub mod logging;
pub mod runtime; pub mod runtime;
+3 -3
View File
@@ -10,7 +10,7 @@ use rustyline::DefaultEditor;
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "nix-js", about = "Nix expression evaluator")] #[command(name = "fix", about = "Nix expression evaluator")]
struct Cli { struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Command, command: Command,
@@ -40,8 +40,8 @@ struct ExprSource {
file: Option<PathBuf>, file: Option<PathBuf>,
} }
fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> { fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result<()> {
let src = if let Some(expr) = src.expr { let _src = if let Some(expr) = src.expr {
Source::new_eval(expr)? Source::new_eval(expr)?
} else if let Some(file) = src.file { } else if let Some(file) = src.file {
Source::new_file(file)? Source::new_file(file)?
+13 -5
View File
@@ -16,6 +16,7 @@ use crate::store::{DaemonStore, StoreConfig};
use crate::value::Symbol; use crate::value::Symbol;
mod builtins; mod builtins;
mod primops;
mod stack; mod stack;
mod value; mod value;
mod vm; mod vm;
@@ -43,6 +44,7 @@ impl Runtime {
let store = DaemonStore::connect(&config.daemon_socket)?; let store = DaemonStore::connect(&config.daemon_socket)?;
Ok(Self { Ok(Self {
arena: Arena::new(|mc| VM::new(mc, &mut strings)),
global_env, global_env,
store, store,
strings, strings,
@@ -50,7 +52,6 @@ impl Runtime {
bytecode: Vec::new(), bytecode: Vec::new(),
sources: Vec::new(), sources: Vec::new(),
spans: Vec::new(), spans: Vec::new(),
arena: Arena::new(|mc| VM::new(mc)),
}) })
} }
@@ -108,8 +109,11 @@ impl Runtime {
bump, bump,
token, token,
strings, strings,
source: sources.last().unwrap().clone(), source: sources.last().expect("no current source").clone(),
scopes: [Scope::Global(global_env)].into_iter().chain(extra_scope.into_iter()).collect(), scopes: [Scope::Global(global_env)]
.into_iter()
.chain(extra_scope)
.collect(),
with_scope_count: 0, with_scope_count: 0,
arg_count: 0, arg_count: 0,
thunk_count, thunk_count,
@@ -117,7 +121,11 @@ impl Runtime {
} }
} }
fn downgrade<'a>(&'a mut self, source: Source, extra_scope: Option<Scope<'a>>) -> Result<OwnedIr> { fn downgrade<'a>(
&'a mut self,
source: Source,
extra_scope: Option<Scope<'a>>,
) -> Result<OwnedIr> {
tracing::debug!("Parsing Nix expression"); tracing::debug!("Parsing Nix expression");
self.sources.push(source.clone()); self.sources.push(source.clone());
@@ -496,7 +504,7 @@ impl OwnedIr {
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self { unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
Self { Self {
_bump: bump, _bump: bump,
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) } ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
} }
} }
+196
View File
@@ -1,11 +1,204 @@
use gc_arena::Collect;
use hashbrown::HashMap; use hashbrown::HashMap;
use num_enum::TryFromPrimitive;
use string_interner::DefaultStringInterner; use string_interner::DefaultStringInterner;
use super::value::*;
use crate::ir::{Ir, RawIrRef, StringId}; use crate::ir::{Ir, RawIrRef, StringId};
/// Generates both the BUILTINS const table and the BuiltinId enum
/// from a single source of truth, preventing index desync.
macro_rules! define_builtins {
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
/// Builtin function registry.
/// Array index IS the PrimOp id. (name, arity) pairs.
pub(super) const BUILTINS: &[(&str, u8)] = &[
$(($name, $arity),)*
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, Collect)]
#[repr(u8)]
#[collect(require_static)]
pub(super) enum BuiltinId {
$($variant,)*
}
};
}
define_builtins! {
("abort", Abort, 1),
("add", Add, 2),
("addErrorContext", AddErrorContext, 2),
("all", All, 2),
("any", Any, 2),
("appendContext", AppendContext, 2),
("attrNames", AttrNames, 1),
("attrValues", AttrValues, 1),
("baseNameOf", BaseNameOf, 1),
("bitAnd", BitAnd, 2),
("bitOr", BitOr, 2),
("bitXor", BitXor, 2),
("catAttrs", CatAttrs, 2),
("ceil", Ceil, 1),
("compareVersions", CompareVersions, 2),
("concatLists", ConcatLists, 1),
("concatMap", ConcatMap, 2),
("concatStringsSep", ConcatStringsSep, 2),
("convertHash", ConvertHash, 1),
("deepSeq", DeepSeq, 2),
("derivation", Derivation, 1),
("derivationStrict", DerivationStrict, 1),
("dirOf", DirOf, 1),
("div", Div, 2),
("elem", Elem, 2),
("elemAt", ElemAt, 2),
("fetchGit", FetchGit, 1),
("fetchMercurial", FetchMercurial, 1),
("fetchTarball", FetchTarball, 1),
("fetchTree", FetchTree, 1),
("fetchurl", FetchUrl, 1),
("filter", Filter, 2),
("filterSource", FilterSource, 2),
("findFile", FindFile, 2),
("floor", Floor, 1),
("foldl'", FoldlStrict, 3),
("fromJSON", FromJSON, 1),
("fromTOML", FromTOML, 1),
("functionArgs", FunctionArgs, 1),
("genList", GenList, 2),
("genericClosure", GenericClosure, 1),
("getAttr", GetAttr, 2),
("getContext", GetContext, 1),
("getEnv", GetEnv, 1),
("groupBy", GroupBy, 2),
("hasAttr", HasAttr, 2),
("hasContext", HasContext, 1),
("hashFile", HashFile, 2),
("hashString", HashString, 2),
("head", Head, 1),
("import", Import, 1),
("intersectAttrs", IntersectAttrs, 2),
("isAttrs", IsAttrs, 1),
("isBool", IsBool, 1),
("isFloat", IsFloat, 1),
("isFunction", IsFunction, 1),
("isInt", IsInt, 1),
("isList", IsList, 1),
("isNull", IsNull, 1),
("isPath", IsPath, 1),
("isString", IsString, 1),
("length", Length, 1),
("lessThan", LessThan, 2),
("listToAttrs", ListToAttrs, 1),
("map", Map, 2),
("mapAttrs", MapAttrs, 2),
("match", Match, 2),
("mul", Mul, 2),
("null", Null, 0), // constant, not a function
("parseDrvName", ParseDrvName, 1),
("partition", Partition, 2),
("path", Path, 1),
("pathExists", PathExists, 1),
("placeholder", Placeholder, 1),
("readDir", ReadDir, 1),
("readFile", ReadFile, 1),
("readFileType", ReadFileType, 1),
("removeAttrs", RemoveAttrs, 2),
("replaceStrings", ReplaceStrings, 3),
("scopedImport", ScopedImport, 2),
("seq", Seq, 2),
("sort", Sort, 2),
("split", Split, 2),
("splitVersion", SplitVersion, 1),
("storePath", StorePath, 1),
("stringLength", StringLength, 1),
("sub", Sub, 2),
("substring", Substring, 3),
("tail", Tail, 1),
("throw", Throw, 1),
("toFile", ToFile, 2),
("toJSON", ToJSON, 1),
("toPath", ToPath, 1),
("toString", ToString, 1),
("toXML", ToXML, 1),
("trace", Trace, 2),
("tryEval", TryEval, 1),
("typeOf", TypeOf, 1),
("unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
("unsafeGetAttrPos", UnsafeGetAttrPos, 2),
("warn", Warn, 2),
("zipAttrsWith", ZipAttrsWith, 2),
("break", Break, 1),
}
/// Names that need to be pre-interned for builtin implementations.
const EXTRA_INTERN_NAMES: &[&str] = &[
"builtins",
"currentSystem",
"langVersion",
"nixVersion",
"storeDir",
"nixPath",
"true",
"false",
// typeOf return values
"int",
"float",
"bool",
"string",
"path",
"null",
"set",
"list",
"lambda",
// attrset keys used by builtins
"name",
"value",
"success",
"right",
"wrong",
"key",
"operator",
"startSet",
"__toString",
"outPath",
"__functor",
"drvPath",
"type",
"derivation",
"version",
];
/// Returns true if this builtin has lazy argument semantics
/// (not all args should be forced before dispatch).
pub(super) fn is_lazy_builtin(id: BuiltinId) -> bool {
matches!(
id,
BuiltinId::Seq
| BuiltinId::DeepSeq
| BuiltinId::Trace
| BuiltinId::Warn
| BuiltinId::TryEval
| BuiltinId::AddErrorContext
| BuiltinId::Break
)
}
/// Intern all builtin names and extra names needed at runtime.
fn intern_all_builtins(interner: &mut DefaultStringInterner) {
for &(name, _) in BUILTINS {
interner.get_or_intern(name);
}
for &name in EXTRA_INTERN_NAMES {
interner.get_or_intern(name);
}
}
pub(super) fn new_builtins_env( pub(super) fn new_builtins_env(
interner: &mut DefaultStringInterner, interner: &mut DefaultStringInterner,
) -> HashMap<StringId, Ir<'static, RawIrRef<'static>>> { ) -> HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
intern_all_builtins(interner);
let mut builtins = HashMap::new(); let mut builtins = HashMap::new();
let builtins_sym = StringId(interner.get_or_intern("builtins")); let builtins_sym = StringId(interner.get_or_intern("builtins"));
builtins.insert(builtins_sym, Ir::Builtins); builtins.insert(builtins_sym, Ir::Builtins);
@@ -49,3 +242,6 @@ pub(super) fn new_builtins_env(
builtins builtins
} }
pub(super) type PrimOpArgs<'gc> = [Value<'gc>; 3];
pub(super) type PrimOpStrictArgs<'gc> = [StrictValue<'gc>; 3];
+929
View File
@@ -0,0 +1,929 @@
use gc_arena::{Collect, Gc, Mutation, RefLock};
use smallvec::SmallVec;
use string_interner::DefaultStringInterner;
use super::builtins::{BuiltinId, PrimOpArgs, PrimOpStrictArgs};
use super::value::*;
use super::vm::{ForceResult, VM, VmError};
use crate::ir::StringId;
pub(super) enum BuiltinResult<'gc> {
Done(Value<'gc>),
Force(BuiltinState<'gc>, Value<'gc>),
Call(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>),
CallAndForce(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>),
Error(VmError),
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
pub(super) enum BuiltinState<'gc> {
FoldlStrict(FoldlStrict<'gc>),
// future: Filter, GenericClosure, Sort, All, Any, ConcatMap, ...
}
impl<'gc> BuiltinState<'gc> {
pub(super) fn resume(
self,
val: StrictValue<'gc>,
ctx: &PrimOpCtx<'_, 'gc>,
) -> BuiltinResult<'gc> {
match self {
BuiltinState::FoldlStrict(s) => s.resume(val, ctx),
}
}
}
pub(super) struct PrimOpCtx<'a, 'gc> {
pub(super) vm: &'a VM<'gc>,
pub(super) mc: &'a Mutation<'gc>,
pub(super) strings: &'a DefaultStringInterner,
}
macro_rules! force_inline_or_err {
($ctx:expr, $val:expr) => {{
let val = $val;
match $ctx.vm.force_inline(val) {
Ok(ForceResult::Ready(v)) => v,
Ok(_) => {
return BuiltinResult::Error(VM::err(
"value requires evaluation in non-stateful builtin context",
));
}
Err(e) => return BuiltinResult::Error(e),
}
}};
}
macro_rules! force {
($ctx:expr, $state:expr, $val:expr) => {{
let val = $val;
match $ctx.vm.force_inline(val) {
Ok(ForceResult::Ready(v)) => v,
Ok(ForceResult::NeedEval { .. } | ForceResult::NeedApply(_)) => {
return BuiltinResult::Force($state, val);
}
Err(e) => return BuiltinResult::Error(e),
}
}};
}
macro_rules! call {
($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{
let func = $func;
let arg = $arg;
return BuiltinResult::Call($state, func, arg);
}};
}
macro_rules! call_and_force {
($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{
let func = $func;
let arg = $arg;
return BuiltinResult::CallAndForce($state, func, arg);
}};
}
pub(super) fn dispatch_strict_builtin<'gc>(
id: BuiltinId,
args: PrimOpStrictArgs<'gc>,
_arity: u8,
ctx: &PrimOpCtx<'_, 'gc>,
) -> BuiltinResult<'gc> {
match id {
BuiltinId::TypeOf => {
let val = args[0];
let name = if val.as_inline::<i32>().is_some() || val.as_gc::<i64>().is_some() {
"int"
} else if val.as_float().is_some() {
"float"
} else if val.as_inline::<bool>().is_some() {
"bool"
} else if VM::get_string(val, ctx.strings).is_some() {
"string"
} else if val.is::<Null>() {
"null"
} else if val.as_gc::<AttrSet<'gc>>().is_some() {
"set"
} else if val.as_gc::<List<'gc>>().is_some() {
"list"
} else if val.as_gc::<Closure<'gc>>().is_some()
|| val.as_inline::<PrimOp>().is_some()
|| val.as_gc::<PrimOpApp<'gc>>().is_some()
{
"lambda"
} else {
return BuiltinResult::Error(VM::err("typeOf: unknown type"));
};
let sid = ctx.strings.get(name).expect("typeOf string not interned");
BuiltinResult::Done(Value::new_inline(StringId(sid)))
}
BuiltinId::IsNull => BuiltinResult::Done(Value::new_inline(args[0].is::<Null>())),
BuiltinId::IsAttrs => {
BuiltinResult::Done(Value::new_inline(args[0].as_gc::<AttrSet<'gc>>().is_some()))
}
BuiltinId::IsBool => {
BuiltinResult::Done(Value::new_inline(args[0].as_inline::<bool>().is_some()))
}
BuiltinId::IsFloat => BuiltinResult::Done(Value::new_inline(args[0].as_float().is_some())),
BuiltinId::IsFunction => {
let v = args[0];
let is_func = v.as_gc::<Closure<'gc>>().is_some()
|| v.as_inline::<PrimOp>().is_some()
|| v.as_gc::<PrimOpApp<'gc>>().is_some();
BuiltinResult::Done(Value::new_inline(is_func))
}
BuiltinId::IsInt => {
let v = args[0];
let is_int = v.as_inline::<i32>().is_some() || v.as_gc::<i64>().is_some();
BuiltinResult::Done(Value::new_inline(is_int))
}
BuiltinId::IsList => {
BuiltinResult::Done(Value::new_inline(args[0].as_gc::<List<'gc>>().is_some()))
}
BuiltinId::IsString => {
let v = args[0];
let is_str = v.as_inline::<StringId>().is_some() || v.as_gc::<NixString>().is_some();
BuiltinResult::Done(Value::new_inline(is_str))
}
BuiltinId::IsPath => BuiltinResult::Done(Value::new_inline(false)),
BuiltinId::Length => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.length: not a list"));
};
BuiltinResult::Done(VM::make_int(list.inner.len() as i64, ctx.mc))
}
BuiltinId::Head => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.head: not a list"));
};
if list.inner.is_empty() {
return BuiltinResult::Error(VM::err("builtins.head: empty list"));
}
BuiltinResult::Done(list.inner[0])
}
BuiltinId::Tail => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.tail: not a list"));
};
if list.inner.is_empty() {
return BuiltinResult::Error(VM::err("builtins.tail: empty list"));
}
let tail = List {
inner: SmallVec::from_slice(&list.inner[1..]),
};
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, tail)))
}
BuiltinId::AttrNames => {
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.attrNames: not a set"));
};
let items: SmallVec<[Value<'gc>; 4]> =
attrs.iter().map(|(k, _)| Value::new_inline(*k)).collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
}
BuiltinId::AttrValues => {
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.attrValues: not a set"));
};
let items: SmallVec<[Value<'gc>; 4]> = attrs.iter().map(|(_, v)| *v).collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
}
BuiltinId::Map => {
let f = args[0];
let Some(list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.map: second argument is not a list",
));
};
if list.inner.is_empty() {
return BuiltinResult::Done(Value::new_gc(Gc::new(
ctx.mc,
List {
inner: SmallVec::new(),
},
)));
}
let new_elems: SmallVec<[Value<'gc>; 4]> = list
.inner
.iter()
.map(|elem| {
let thunk: Gc<'gc, Thunk<'gc>> = Gc::new(
ctx.mc,
RefLock::new(ThunkState::Apply {
func: f.relax(),
arg: *elem,
}),
);
Value::new_gc(thunk)
})
.collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: new_elems })))
}
BuiltinId::GenList => {
let f = args[0];
let len_val = args[1];
let Some(len) = VM::as_num(len_val) else {
return BuiltinResult::Error(VM::err(
"builtins.genList: second argument is not a number",
));
};
let super::vm::NixNum::Int(len) = len else {
return BuiltinResult::Error(VM::err(
"builtins.genList: second argument is not an integer",
));
};
if len < 0 {
return BuiltinResult::Error(VM::err("builtins.genList: negative length"));
}
let items: SmallVec<[Value<'gc>; 4]> = (0..len)
.map(|i| {
let arg = VM::make_int(i, ctx.mc);
let thunk: Gc<'gc, Thunk<'gc>> = Gc::new(
ctx.mc,
RefLock::new(ThunkState::Apply {
func: f.relax(),
arg,
}),
);
Value::new_gc(thunk)
})
.collect();
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
}
BuiltinId::ElemAt => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.elemAt: not a list"));
};
let Some(idx) = VM::as_num(args[1]) else {
return BuiltinResult::Error(VM::err("builtins.elemAt: index is not a number"));
};
let super::vm::NixNum::Int(idx) = idx else {
return BuiltinResult::Error(VM::err("builtins.elemAt: index is not an integer"));
};
if idx < 0 || idx as usize >= list.inner.len() {
return BuiltinResult::Error(VM::err(format!(
"builtins.elemAt: index {} out of bounds for list of length {}",
idx,
list.inner.len()
)));
}
BuiltinResult::Done(list.inner[idx as usize])
}
BuiltinId::GetAttr => {
let Some(name) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.getAttr: first argument is not a string",
));
};
let Some(sid) = ctx.strings.get(name) else {
return BuiltinResult::Error(VM::err(format!(
"builtins.getAttr: attribute '{}' not found",
name
)));
};
let Some(attrs) = args[1].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.getAttr: second argument is not a set",
));
};
match attrs.lookup(StringId(sid)) {
Some(v) => BuiltinResult::Done(v),
None => BuiltinResult::Error(VM::err(format!("attribute '{}' missing", name))),
}
}
BuiltinId::HasAttr => {
let Some(name) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.hasAttr: first argument is not a string",
));
};
let Some(attrs) = args[1].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.hasAttr: second argument is not a set",
));
};
let has = ctx
.strings
.get(name)
.map(|sid| attrs.has(StringId(sid)))
.unwrap_or(false);
BuiltinResult::Done(Value::new_inline(has))
}
BuiltinId::RemoveAttrs => {
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.removeAttrs: first argument is not a set",
));
};
let Some(remove_list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.removeAttrs: second argument is not a list",
));
};
let mut to_remove = Vec::new();
for item in remove_list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
if let Some(s) = VM::get_string(sv, ctx.strings)
&& let Some(sid) = ctx.strings.get(s)
{
to_remove.push(StringId(sid));
}
}
let entries: SmallVec<[(StringId, Value<'gc>); 4]> = attrs
.iter()
.filter(|(k, _)| !to_remove.contains(k))
.cloned()
.collect();
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(new_attrs))
}
BuiltinId::IntersectAttrs => {
let Some(a) = args[0].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.intersectAttrs: first argument is not a set",
));
};
let Some(b) = args[1].as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.intersectAttrs: second argument is not a set",
));
};
let entries: SmallVec<[(StringId, Value<'gc>); 4]> =
b.iter().filter(|(k, _)| a.has(*k)).cloned().collect();
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(new_attrs))
}
BuiltinId::ListToAttrs => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.listToAttrs: not a list"));
};
let name_sid = ctx.strings.get("name").expect("'name' not interned");
let value_sid = ctx.strings.get("value").expect("'value' not interned");
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for item in list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
let Some(attr_set) = sv.as_gc::<AttrSet<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: element is not a set",
));
};
let Some(name_val) = attr_set.lookup(StringId(name_sid)) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: element missing 'name'",
));
};
let name_sv = force_inline_or_err!(ctx, name_val);
let Some(name_str) = VM::get_string(name_sv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: 'name' is not a string",
));
};
let Some(value_val) = attr_set.lookup(StringId(value_sid)) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: element missing 'value'",
));
};
let Some(key_sym) = ctx.strings.get(name_str) else {
return BuiltinResult::Error(VM::err(
"builtins.listToAttrs: name not interned",
));
};
entries.push((StringId(key_sym), value_val));
}
entries.sort_by_key(|(k, _)| *k);
entries.dedup_by_key(|(k, _)| *k);
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(new_attrs))
}
BuiltinId::ConcatLists => {
let Some(list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.concatLists: not a list"));
};
let mut result = SmallVec::<[Value<'gc>; 4]>::new();
for item in list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
let Some(inner) = sv.as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.concatLists: element is not a list",
));
};
result.extend(inner.inner.iter().cloned());
}
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: result })))
}
BuiltinId::LessThan => {
match ctx
.vm
.compare_values(args[0], args[1], ctx.strings, |o| o.is_lt())
{
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Add => {
match ctx.vm.compute_binop(
super::vm::BinOpTag::Add,
args[0],
args[1],
ctx.mc,
ctx.strings,
) {
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Sub => {
match ctx
.vm
.numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_sub, |a, b| a - b)
{
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Mul => {
match ctx
.vm
.numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_mul, |a, b| a * b)
{
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::Div => {
match ctx.vm.compute_binop(
super::vm::BinOpTag::Div,
args[0],
args[1],
ctx.mc,
ctx.strings,
) {
Ok(v) => BuiltinResult::Done(v),
Err(e) => BuiltinResult::Error(e),
}
}
BuiltinId::BitAnd => {
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
(VM::as_num(args[0]), VM::as_num(args[1]))
else {
return BuiltinResult::Error(VM::err(
"builtins.bitAnd: arguments must be integers",
));
};
BuiltinResult::Done(VM::make_int(a & b, ctx.mc))
}
BuiltinId::BitOr => {
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
(VM::as_num(args[0]), VM::as_num(args[1]))
else {
return BuiltinResult::Error(VM::err("builtins.bitOr: arguments must be integers"));
};
BuiltinResult::Done(VM::make_int(a | b, ctx.mc))
}
BuiltinId::BitXor => {
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
(VM::as_num(args[0]), VM::as_num(args[1]))
else {
return BuiltinResult::Error(VM::err(
"builtins.bitXor: arguments must be integers",
));
};
BuiltinResult::Done(VM::make_int(a ^ b, ctx.mc))
}
BuiltinId::Ceil => {
if let Some(f) = args[0].as_float() {
BuiltinResult::Done(VM::make_int(f.ceil() as i64, ctx.mc))
} else if VM::as_num(args[0]).is_some() {
BuiltinResult::Done(args[0].relax())
} else {
BuiltinResult::Error(VM::err("builtins.ceil: not a number"))
}
}
BuiltinId::Floor => {
if let Some(f) = args[0].as_float() {
BuiltinResult::Done(VM::make_int(f.floor() as i64, ctx.mc))
} else if VM::as_num(args[0]).is_some() {
BuiltinResult::Done(args[0].relax())
} else {
BuiltinResult::Error(VM::err("builtins.floor: not a number"))
}
}
BuiltinId::StringLength => {
let Some(s) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.stringLength: not a string"));
};
BuiltinResult::Done(VM::make_int(s.len() as i64, ctx.mc))
}
BuiltinId::Substring => {
let Some(super::vm::NixNum::Int(start)) = VM::as_num(args[0]) else {
return BuiltinResult::Error(VM::err(
"builtins.substring: start is not an integer",
));
};
let Some(super::vm::NixNum::Int(len)) = VM::as_num(args[1]) else {
return BuiltinResult::Error(VM::err(
"builtins.substring: length is not an integer",
));
};
let Some(s) = VM::get_string(args[2], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.substring: third argument is not a string",
));
};
let start = start.max(0) as usize;
if start >= s.len() {
let ns = Gc::new(ctx.mc, NixString::new(""));
return BuiltinResult::Done(Value::new_gc(ns));
}
let end = if len < 0 {
s.len()
} else {
(start + len as usize).min(s.len())
};
let result = &s[start..end];
let ns = Gc::new(ctx.mc, NixString::new(result));
BuiltinResult::Done(Value::new_gc(ns))
}
BuiltinId::ToString => {
let v = args[0];
if let Some(s) = VM::get_string(v, ctx.strings) {
let ns = Gc::new(ctx.mc, NixString::new(s));
BuiltinResult::Done(Value::new_gc(ns))
} else if let Some(b) = v.as_inline::<bool>() {
let s = if b { "1" } else { "" };
let ns = Gc::new(ctx.mc, NixString::new(s));
BuiltinResult::Done(Value::new_gc(ns))
} else if v.is::<Null>() {
let ns = Gc::new(ctx.mc, NixString::new(""));
BuiltinResult::Done(Value::new_gc(ns))
} else if let Some(n) = VM::as_num(v) {
let s = match n {
super::vm::NixNum::Int(i) => i.to_string(),
super::vm::NixNum::Float(f) => format!("{f}"),
};
let ns = Gc::new(ctx.mc, NixString::new(s));
BuiltinResult::Done(Value::new_gc(ns))
} else {
BuiltinResult::Error(VM::err("builtins.toString: cannot coerce to string"))
}
}
BuiltinId::Abort => {
let Some(msg) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.abort: argument is not a string"));
};
BuiltinResult::Error(VmError::Uncatchable(crate::error::Error::eval_error(
format!("evaluation aborted with the following error message: '{msg}'"),
)))
}
BuiltinId::Throw => {
let Some(msg) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.throw: argument is not a string"));
};
BuiltinResult::Error(VmError::Catchable(msg.to_owned()))
}
BuiltinId::FunctionArgs => {
let v = args[0];
if let Some(closure) = v.as_gc::<Closure<'gc>>() {
if let Some(ref pattern) = closure.pattern {
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for &name in &pattern.required {
entries.push((name, Value::new_inline(false)));
}
for &name in &pattern.optional {
entries.push((name, Value::new_inline(true)));
}
entries.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
BuiltinResult::Done(Value::new_gc(attrs))
} else {
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default())))
}
} else {
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default())))
}
}
BuiltinId::FoldlStrict => FoldlStrict::call(args, ctx),
BuiltinId::Elem => {
let Some(list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.elem: second argument is not a list",
));
};
let needle = args[0];
for item in list.inner.iter() {
let sv = force_inline_or_err!(ctx, *item);
if ctx.vm.values_equal(needle, sv, ctx.strings) {
return BuiltinResult::Done(Value::new_inline(true));
}
}
BuiltinResult::Done(Value::new_inline(false))
}
BuiltinId::ReplaceStrings => {
let Some(from_list) = args[0].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: first argument is not a list",
));
};
let Some(to_list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: second argument is not a list",
));
};
let Some(s) = VM::get_string(args[2], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: third argument is not a string",
));
};
if from_list.inner.len() != to_list.inner.len() {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: lists must have same length",
));
}
let mut from_strs = Vec::new();
let mut to_strs = Vec::new();
for (f, t) in from_list.inner.iter().zip(to_list.inner.iter()) {
let fv = force_inline_or_err!(ctx, *f);
let tv = force_inline_or_err!(ctx, *t);
let Some(fs) = VM::get_string(fv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: from element is not a string",
));
};
let Some(ts) = VM::get_string(tv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.replaceStrings: to element is not a string",
));
};
from_strs.push(fs.to_owned());
to_strs.push(ts.to_owned());
}
let s = s.to_owned();
let mut result = String::new();
let mut i = 0;
while i < s.len() {
let mut found = false;
for (j, from) in from_strs.iter().enumerate() {
if from.is_empty() {
result.push_str(&to_strs[j]);
result.push(s.as_bytes()[i] as char);
i += 1;
found = true;
break;
}
if s[i..].starts_with(from.as_str()) {
result.push_str(&to_strs[j]);
i += from.len();
found = true;
break;
}
}
if !found {
result.push(s.as_bytes()[i] as char);
i += 1;
}
}
if from_strs.iter().any(|f| f.is_empty()) {
let j = from_strs
.iter()
.position(|f| f.is_empty())
.expect("just checked");
result.push_str(&to_strs[j]);
}
let ns = Gc::new(ctx.mc, NixString::new(result));
BuiltinResult::Done(Value::new_gc(ns))
}
BuiltinId::ConcatStringsSep => {
let Some(sep) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.concatStringsSep: first argument is not a string",
));
};
let sep = sep.to_owned();
let Some(list) = args[1].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err(
"builtins.concatStringsSep: second argument is not a list",
));
};
let mut result = String::new();
for (i, item) in list.inner.iter().enumerate() {
if i > 0 {
result.push_str(&sep);
}
let sv = force_inline_or_err!(ctx, *item);
let Some(s) = VM::get_string(sv, ctx.strings) else {
return BuiltinResult::Error(VM::err(
"builtins.concatStringsSep: element is not a string",
));
};
result.push_str(s);
}
let ns = Gc::new(ctx.mc, NixString::new(result));
BuiltinResult::Done(Value::new_gc(ns))
}
BuiltinId::FromJSON => {
let Some(s) = VM::get_string(args[0], ctx.strings) else {
return BuiltinResult::Error(VM::err("builtins.fromJSON: not a string"));
};
match serde_json::from_str::<serde_json::Value>(s) {
Ok(json) => {
let v = json_to_nix(&json, ctx.mc, ctx.strings);
BuiltinResult::Done(v)
}
Err(e) => BuiltinResult::Error(VM::err(format!("builtins.fromJSON: {e}"))),
}
}
BuiltinId::ToJSON => BuiltinResult::Error(VM::err("builtins.toJSON: not yet implemented")),
BuiltinId::Null => BuiltinResult::Done(Value::new_inline(Null)),
_ => BuiltinResult::Error(VM::err(format!("builtin {:?} not yet implemented", id))),
}
}
pub(super) fn dispatch_lazy_builtin<'gc>(
id: BuiltinId,
args: &PrimOpArgs<'gc>,
_arity: u8,
ctx: &PrimOpCtx<'_, 'gc>,
) -> BuiltinResult<'gc> {
match id {
BuiltinId::Seq => {
let _ = force_inline_or_err!(ctx, args[0]);
BuiltinResult::Done(args[1])
}
BuiltinId::DeepSeq => {
// TODO: deep force
let _ = force_inline_or_err!(ctx, args[0]);
BuiltinResult::Done(args[1])
}
BuiltinId::Trace => {
let sv = force_inline_or_err!(ctx, args[0]);
if let Some(s) = VM::get_string(sv, ctx.strings) {
eprintln!("trace: {s}");
} else {
eprintln!("trace: <non-string>");
}
BuiltinResult::Done(args[1])
}
BuiltinId::Warn => {
let sv = force_inline_or_err!(ctx, args[0]);
if let Some(s) = VM::get_string(sv, ctx.strings) {
eprintln!("warning: {s}");
} else {
eprintln!("warning: <non-string>");
}
BuiltinResult::Done(args[1])
}
BuiltinId::TryEval => BuiltinResult::Error(VM::err(
"builtins.tryEval: requires catch frame support (TODO)",
)),
BuiltinId::AddErrorContext => BuiltinResult::Done(args[1]),
BuiltinId::Break => BuiltinResult::Done(args[0]),
_ => BuiltinResult::Error(VM::err(format!(
"lazy builtin {:?} not yet implemented",
id
))),
}
}
fn json_to_nix<'gc>(
json: &serde_json::Value,
mc: &Mutation<'gc>,
strings: &DefaultStringInterner,
) -> Value<'gc> {
match json {
serde_json::Value::Null => Value::new_inline(Null),
serde_json::Value::Bool(b) => Value::new_inline(*b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
VM::make_int(i, mc)
} else if let Some(f) = n.as_f64() {
Value::new_float(f)
} else {
Value::new_inline(Null)
}
}
serde_json::Value::String(s) => {
let ns = Gc::new(mc, NixString::new(s.as_str()));
Value::new_gc(ns)
}
serde_json::Value::Array(arr) => {
let items: SmallVec<[Value<'gc>; 4]> =
arr.iter().map(|v| json_to_nix(v, mc, strings)).collect();
Value::new_gc(Gc::new(mc, List { inner: items }))
}
serde_json::Value::Object(obj) => {
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
for (k, v) in obj {
if let Some(sym) = strings.get(k.as_str()) {
entries.push((StringId(sym), json_to_nix(v, mc, strings)));
}
}
entries.sort_by_key(|(k, _)| *k);
let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
Value::new_gc(attrs)
}
}
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
pub(super) struct FoldlStrict<'gc> {
op: StrictValue<'gc>,
list: Gc<'gc, List<'gc>>,
acc: StrictValue<'gc>,
index: usize,
phase: FoldlPhase<'gc>,
}
#[derive(Collect, Debug)]
#[collect(no_drop)]
enum FoldlPhase<'gc> {
CallOp,
CallPartial(StrictValue<'gc>),
}
impl<'gc> FoldlStrict<'gc> {
fn call(args: PrimOpStrictArgs<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
let op = args[0];
let nul = args[1];
let Some(list) = args[2].as_gc::<List<'gc>>() else {
return BuiltinResult::Error(VM::err("builtins.foldl': third argument is not a list"));
};
if list.inner.is_empty() {
return BuiltinResult::Done(nul.relax());
}
let state = FoldlStrict {
op,
list,
acc: nul,
index: 0,
phase: FoldlPhase::CallOp,
};
state.step(ctx)
}
fn step(self, _ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
let state = BuiltinState::FoldlStrict(FoldlStrict {
op: self.op,
list: self.list,
acc: self.acc,
index: self.index,
phase: FoldlPhase::CallOp,
});
call!(ctx, state, self.op, self.acc.relax())
}
fn resume(mut self, val: StrictValue<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
match self.phase {
FoldlPhase::CallOp => {
let partial = val;
let elem = self.list.inner[self.index];
self.phase = FoldlPhase::CallPartial(partial);
let state = BuiltinState::FoldlStrict(self);
call_and_force!(ctx, state, partial, elem)
}
FoldlPhase::CallPartial(_) => {
self.acc = val;
self.index += 1;
self.phase = FoldlPhase::CallOp;
if self.index >= self.list.inner.len() {
return BuiltinResult::Done(self.acc.relax());
}
self.step(ctx)
}
}
}
}
+8 -2
View File
@@ -3,14 +3,13 @@ use std::mem::MaybeUninit;
use gc_arena::Collect; use gc_arena::Collect;
use smallvec::SmallVec; use smallvec::SmallVec;
// FIXME: Drop???
pub(super) struct Stack<const N: usize, T> { pub(super) struct Stack<const N: usize, T> {
inner: Box<[MaybeUninit<T>; N]>, inner: Box<[MaybeUninit<T>; N]>,
len: usize, len: usize,
} }
unsafe impl<'gc, const N: usize, T: Collect<'gc> + 'gc> Collect<'gc> for Stack<N, T> { unsafe impl<'gc, const N: usize, T: Collect<'gc> + 'gc> Collect<'gc> for Stack<N, T> {
const NEEDS_TRACE: bool = true; const NEEDS_TRACE: bool = T::NEEDS_TRACE;
fn trace<U: gc_arena::collect::Trace<'gc>>(&self, cc: &mut U) { fn trace<U: gc_arena::collect::Trace<'gc>>(&self, cc: &mut U) {
for item in self.inner[..self.len].iter() { for item in self.inner[..self.len].iter() {
unsafe { unsafe {
@@ -34,6 +33,13 @@ impl<const N: usize, T> Stack<N, T> {
} }
} }
pub(super) unsafe fn push_unchecked(&mut self, val: T) {
unsafe {
self.inner.get_unchecked_mut(self.len).write(val);
}
self.len += 1;
}
pub(super) fn push(&mut self, val: T) -> Result<(), T> { pub(super) fn push(&mut self, val: T) -> Result<(), T> {
if self.len == N { if self.len == N {
return Err(val); return Err(val);
+39 -37
View File
@@ -3,16 +3,19 @@ use std::marker::PhantomData;
use std::mem::size_of; use std::mem::size_of;
use std::ops::Deref; use std::ops::Deref;
use boxing::nan::raw::{RawBox, RawStore, RawTag, Value as RawValue};
use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace}; use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace};
use num_enum::TryFromPrimitive;
use sealed::sealed; use sealed::sealed;
use smallvec::SmallVec; use smallvec::SmallVec;
use string_interner::{Symbol, symbol::SymbolU32}; use string_interner::{Symbol, symbol::SymbolU32};
use crate::ir::StringId; use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
use crate::{ir::StringId, runtime::builtins::BuiltinId};
#[sealed] #[sealed]
pub(crate) trait Storable { /// # Safety
/// TAG must be unique among all implementors.
pub(crate) unsafe trait Storable {
const TAG: (bool, u8); const TAG: (bool, u8);
} }
pub(crate) trait InlineStorable: Storable + RawStore {} pub(crate) trait InlineStorable: Storable + RawStore {}
@@ -25,14 +28,14 @@ macro_rules! define_value_types {
) => { ) => {
$( $(
#[sealed] #[sealed]
impl Storable for $itype { unsafe impl Storable for $itype {
const TAG: (bool, u8) = $itag; const TAG: (bool, u8) = $itag;
} }
impl InlineStorable for $itype {} impl InlineStorable for $itype {}
)* )*
$( $(
#[sealed] #[sealed]
impl Storable for $gtype { unsafe impl Storable for $gtype {
const TAG: (bool, u8) = $gtag; const TAG: (bool, u8) = $gtag;
} }
impl GcStorable for $gtype {} impl GcStorable for $gtype {}
@@ -116,22 +119,13 @@ define_value_types! {
/// # Nix runtime value representation /// # Nix runtime value representation
/// ///
/// NaN-boxed value fitting in 8 bytes. /// NaN-boxed value fitting in 8 bytes.
#[derive(Copy, Clone)]
#[repr(transparent)] #[repr(transparent)]
pub(crate) struct Value<'gc> { pub(crate) struct Value<'gc> {
raw: RawBox, raw: RawBox,
_marker: PhantomData<Gc<'gc, ()>>, _marker: PhantomData<Gc<'gc, ()>>,
} }
impl Clone for Value<'_> {
#[inline]
fn clone(&self) -> Self {
Self {
raw: self.raw.clone(),
_marker: PhantomData,
}
}
}
impl Default for Value<'_> { impl Default for Value<'_> {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
@@ -291,14 +285,23 @@ impl fmt::Debug for NixString {
} }
} }
#[derive(Collect, Debug)] #[derive(Collect, Debug, Default)]
#[collect(no_drop)] #[collect(no_drop)]
pub(crate) struct AttrSet<'gc> { pub(crate) struct AttrSet<'gc> {
pub(crate) entries: SmallVec<[(StringId, Value<'gc>); 4]>, entries: SmallVec<[(StringId, Value<'gc>); 4]>,
}
impl<'gc> Deref for AttrSet<'gc> {
type Target = [(StringId, Value<'gc>)];
fn deref(&self) -> &Self::Target {
&self.entries
}
} }
impl<'gc> AttrSet<'gc> { impl<'gc> AttrSet<'gc> {
pub(crate) fn from_sorted(entries: SmallVec<[(StringId, Value<'gc>); 4]>) -> Self { pub(crate) unsafe fn from_sorted_unchecked(
entries: SmallVec<[(StringId, Value<'gc>); 4]>,
) -> Self {
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key)); debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
Self { entries } Self { entries }
} }
@@ -307,13 +310,11 @@ impl<'gc> AttrSet<'gc> {
self.entries self.entries
.binary_search_by_key(&key, |(k, _)| *k) .binary_search_by_key(&key, |(k, _)| *k)
.ok() .ok()
.map(|i| self.entries[i].1.clone()) .map(|i| self.entries[i].1)
} }
pub(crate) fn has(&self, key: StringId) -> bool { pub(crate) fn has(&self, key: StringId) -> bool {
self.entries self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
.binary_search_by_key(&key, |(k, _)| *k)
.is_ok()
} }
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> { pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
@@ -328,15 +329,15 @@ impl<'gc> AttrSet<'gc> {
while i < self.entries.len() && j < other.entries.len() { while i < self.entries.len() && j < other.entries.len() {
match self.entries[i].0.cmp(&other.entries[j].0) { match self.entries[i].0.cmp(&other.entries[j].0) {
Less => { Less => {
entries.push(self.entries[i].clone()); entries.push(self.entries[i]);
i += 1; i += 1;
} }
Greater => { Greater => {
entries.push(other.entries[j].clone()); entries.push(other.entries[j]);
j += 1; j += 1;
} }
Equal => { Equal => {
entries.push(other.entries[j].clone()); entries.push(other.entries[j]);
i += 1; i += 1;
j += 1; j += 1;
} }
@@ -351,7 +352,7 @@ impl<'gc> AttrSet<'gc> {
} }
} }
#[derive(Collect, Debug)] #[derive(Collect, Debug, Default)]
#[collect(no_drop)] #[collect(no_drop)]
pub(crate) struct List<'gc> { pub(crate) struct List<'gc> {
pub(crate) inner: SmallVec<[Value<'gc>; 4]>, pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
@@ -366,6 +367,10 @@ pub(crate) enum ThunkState<'gc> {
ip: u32, ip: u32,
env: Gc<'gc, RefLock<Env<'gc>>>, env: Gc<'gc, RefLock<Env<'gc>>>,
}, },
Apply {
func: Value<'gc>,
arg: Value<'gc>,
},
Blackhole, Blackhole,
Evaluated(Value<'gc>), Evaluated(Value<'gc>),
} }
@@ -420,17 +425,20 @@ pub(crate) struct PatternInfo {
#[derive(Clone, Copy, Debug, Collect)] #[derive(Clone, Copy, Debug, Collect)]
#[collect(require_static)] #[collect(require_static)]
pub(crate) struct PrimOp { pub(crate) struct PrimOp {
pub(crate) id: u8, pub(crate) id: BuiltinId,
pub(crate) arity: u8, pub(crate) arity: u8,
} }
impl RawStore for PrimOp { impl RawStore for PrimOp {
fn to_val(self, value: &mut RawValue) { fn to_val(self, value: &mut RawValue) {
value.set_data([0, 0, 0, 0, self.id, self.arity]); value.set_data([0, 0, 0, 0, self.id as u8, self.arity]);
} }
fn from_val(value: &RawValue) -> Self { fn from_val(value: &RawValue) -> Self {
let [.., id, arity] = *value.data(); let [.., id, arity] = *value.data();
Self { id, arity } Self {
id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"),
arity,
}
} }
} }
@@ -441,6 +449,7 @@ pub(crate) struct PrimOpApp<'gc> {
pub(crate) args: SmallVec<[Value<'gc>; 2]>, pub(crate) args: SmallVec<[Value<'gc>; 2]>,
} }
#[derive(Copy, Clone, Default)]
#[repr(transparent)] #[repr(transparent)]
pub(crate) struct StrictValue<'gc>(Value<'gc>); pub(crate) struct StrictValue<'gc>(Value<'gc>);
@@ -455,7 +464,7 @@ impl<'gc> StrictValue<'gc> {
} }
#[inline] #[inline]
pub(crate) fn into_relaxed(self) -> Value<'gc> { pub(crate) fn relax(self) -> Value<'gc> {
self.0 self.0
} }
} }
@@ -468,13 +477,6 @@ impl<'gc> Deref for StrictValue<'gc> {
} }
} }
impl Clone for StrictValue<'_> {
#[inline]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl fmt::Debug for StrictValue<'_> { impl fmt::Debug for StrictValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f) fmt::Debug::fmt(&self.0, f)
+895 -470
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -7,7 +7,6 @@ mod validation;
pub use config::StoreConfig; pub use config::StoreConfig;
pub use daemon::DaemonStore; pub use daemon::DaemonStore;
pub use validation::validate_store_path;
pub trait Store: Send + Sync { pub trait Store: Send + Sync {
fn get_store_dir(&self) -> &str; fn get_store_dir(&self) -> &str;
+4 -7
View File
@@ -69,13 +69,10 @@ impl Store for DaemonStore {
fn ensure_path(&self, path: &str) -> Result<()> { fn ensure_path(&self, path: &str) -> Result<()> {
self.block_on(async { self.block_on(async {
self.connection.ensure_path(path).await.map_err(|e| { self.connection.ensure_path(path).await.map_err(|e| {
Error::eval_error( Error::eval_error(format!(
format!( "builtins.storePath: path '{}' is not valid in nix store: {}",
"builtins.storePath: path '{}' is not valid in nix store: {}", path, e
path, e ))
),
None,
)
}) })
}) })
} }
+34 -37
View File
@@ -2,75 +2,72 @@ use crate::error::{Error, Result};
pub fn validate_store_path(store_dir: &str, path: &str) -> Result<()> { pub fn validate_store_path(store_dir: &str, path: &str) -> Result<()> {
if !path.starts_with(store_dir) { if !path.starts_with(store_dir) {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!("path '{}' is not in the Nix store", path), "path '{}' is not in the Nix store",
None, path
)); )));
} }
let relative = path let relative = path
.strip_prefix(store_dir) .strip_prefix(store_dir)
.and_then(|s| s.strip_prefix('/')) .and_then(|s| s.strip_prefix('/'))
.ok_or_else(|| Error::eval_error(format!("invalid store path format: {}", path), None))?; .ok_or_else(|| Error::eval_error(format!("invalid store path format: {}", path)))?;
if relative.is_empty() { if relative.is_empty() {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!("store path cannot be store directory itself: {}", path), "store path cannot be store directory itself: {}",
None, path
)); )));
} }
let parts: Vec<&str> = relative.splitn(2, '-').collect(); let parts: Vec<&str> = relative.splitn(2, '-').collect();
if parts.len() != 2 { if parts.len() != 2 {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!("invalid store path format (missing name): {}", path), "invalid store path format (missing name): {}",
None, path
)); )));
} }
let hash = parts[0]; let hash = parts[0];
let name = parts[1]; let name = parts[1];
if hash.len() != 32 { if hash.len() != 32 {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!( "invalid store path hash length (expected 32, got {}): {}",
"invalid store path hash length (expected 32, got {}): {}", hash.len(),
hash.len(), hash
hash )));
),
None,
));
} }
for ch in hash.chars() { for ch in hash.chars() {
if !matches!(ch, '0'..='9' | 'a'..='d' | 'f'..='n' | 'p'..='s' | 'v'..='z') { if !matches!(ch, '0'..='9' | 'a'..='d' | 'f'..='n' | 'p'..='s' | 'v'..='z') {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!("invalid character '{}' in store path hash: {}", ch, hash), "invalid character '{}' in store path hash: {}",
None, ch, hash
)); )));
} }
} }
if name.is_empty() { if name.is_empty() {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!("store path has empty name: {}", path), "store path has empty name: {}",
None, path
)); )));
} }
if name.starts_with('.') { if name.starts_with('.') {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!("store path name cannot start with '.': {}", name), "store path name cannot start with '.': {}",
None, name
)); )));
} }
for ch in name.chars() { for ch in name.chars() {
if !matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '+' | '-' | '.' | '_' | '?' | '=') { if !matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '+' | '-' | '.' | '_' | '?' | '=') {
return Err(Error::eval_error( return Err(Error::eval_error(format!(
format!("invalid character '{}' in store path name: {}", ch, name), "invalid character '{}' in store path name: {}",
None, ch, name
)); )));
} }
} }
+9
View File
@@ -267,6 +267,15 @@ fn escape_quote_string(s: &str) -> String {
ret ret
} }
/// Wrapper to format a float in Nix style (C printf `%g` with precision 6).
pub(crate) struct NixFloat(pub f64);
impl Display for NixFloat {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
fmt_nix_float(f, self.0)
}
}
/// Format a float matching C's `printf("%g", x)` with default precision 6. /// Format a float matching C's `printf("%g", x)` with default precision 6.
fn fmt_nix_float(f: &mut Formatter<'_>, x: f64) -> FmtResult { fn fmt_nix_float(f: &mut Formatter<'_>, x: f64) -> FmtResult {
if !x.is_finite() { if !x.is_finite() {
Generated
+9 -9
View File
@@ -8,11 +8,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1773471952, "lastModified": 1774076307,
"narHash": "sha256-kIRggXyT8RzijtfvyRIzj+zIDWM2fnCp8t0X4BkkTVc=", "narHash": "sha256-v8axK9HGgVERw9oG3SKdsuE+ri0GPUZDyRBN4GLqQ1c=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "a1b770adbc3f6c27485d03b90462ec414d4e1ce5", "rev": "556198cc6c69c0a13228a15e33b2360f333b0092",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -37,11 +37,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1773282481, "lastModified": 1773821835,
"narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=", "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "fe416aaedd397cacb33a610b33d60ff2b431b127", "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -61,11 +61,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1773326183, "lastModified": 1774036669,
"narHash": "sha256-tj3piRd9RnnP36HwHmQD4O4XZeowsH/rvMeyp9Pmot0=", "narHash": "sha256-EWhsBSh/h1VcyLKXuTEyH8lNVB2ktuKVkqx8dkQ6hxk=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "6254616e97f358e67b70dfc0463687f5f7911c1a", "rev": "0cf3e8a07f0e29825f5db78840e646a4bb519742",
"type": "github" "type": "github"
}, },
"original": { "original": {