init builtins
This commit is contained in:
+1
-1
@@ -79,7 +79,7 @@ tap = "1.0.1"
|
||||
|
||||
ghost-cell = "0.2"
|
||||
colored = "3.1"
|
||||
boxing = { path = "../boxing" }
|
||||
sptr = "0.3"
|
||||
sealed = "0.6"
|
||||
small-map = "0.1"
|
||||
smallvec = "1.15"
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
use std::fmt;
|
||||
use std::num::NonZeroU8;
|
||||
|
||||
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 from_val(value: &Value) -> Self;
|
||||
}
|
||||
|
||||
impl RawStore for [u8; 6] {
|
||||
#[inline]
|
||||
fn to_val(self, value: &mut Value) {
|
||||
value.set_data(self);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_val(value: &Value) -> Self {
|
||||
*value.data()
|
||||
}
|
||||
}
|
||||
|
||||
impl RawStore for bool {
|
||||
#[inline]
|
||||
fn to_val(self, value: &mut Value) {
|
||||
value.set_data([u8::from(self)].truncate_to());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_val(value: &Value) -> Self {
|
||||
value.data()[0] == 1
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! int_store {
|
||||
($ty:ty) => {
|
||||
impl RawStore for $ty {
|
||||
#[inline]
|
||||
fn to_val(self, value: &mut Value) {
|
||||
let bytes = self.to_ne_bytes();
|
||||
value.set_data(bytes.truncate_to());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_val(value: &Value) -> Self {
|
||||
<$ty>::from_ne_bytes(value.data().truncate_to())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int_store!(u8);
|
||||
int_store!(u16);
|
||||
int_store!(u32);
|
||||
|
||||
int_store!(i8);
|
||||
int_store!(i16);
|
||||
int_store!(i32);
|
||||
|
||||
fn store_ptr<P: Strict + Copy>(value: &mut Value, ptr: P) {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
assert!(
|
||||
ptr.addr() <= 0x0000_FFFF_FFFF_FFFF,
|
||||
"Pointer too large to store in NaN box"
|
||||
);
|
||||
|
||||
let val = (unsafe { value.whole_mut() } as *mut [u8; 8]).cast::<P>();
|
||||
|
||||
let ptr = Strict::map_addr(ptr, |addr| {
|
||||
addr | (usize::from(value.header().into_raw()) << 48)
|
||||
});
|
||||
|
||||
unsafe { val.write(ptr) };
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let _ = (value, ptr);
|
||||
unimplemented!("32-bit pointer storage not supported");
|
||||
}
|
||||
}
|
||||
|
||||
fn load_ptr<P: Strict>(value: &Value) -> P {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
let val = (unsafe { value.whole() } as *const [u8; 8]).cast::<P>();
|
||||
let ptr = unsafe { val.read() };
|
||||
Strict::map_addr(ptr, |addr| addr & 0x0000_FFFF_FFFF_FFFF)
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
let _ = value;
|
||||
unimplemented!("32-bit pointer storage not supported");
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RawStore for *const T {
|
||||
fn to_val(self, value: &mut Value) {
|
||||
store_ptr::<*const T>(value, self);
|
||||
}
|
||||
|
||||
fn from_val(value: &Value) -> Self {
|
||||
load_ptr::<*const T>(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RawStore for *mut T {
|
||||
fn to_val(self, value: &mut Value) {
|
||||
store_ptr::<*mut T>(value, self);
|
||||
}
|
||||
|
||||
fn from_val(value: &Value) -> Self {
|
||||
load_ptr::<*mut T>(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
enum TagVal {
|
||||
_P1,
|
||||
_P2,
|
||||
_P3,
|
||||
_P4,
|
||||
_P5,
|
||||
_P6,
|
||||
_P7,
|
||||
|
||||
_N1,
|
||||
_N2,
|
||||
_N3,
|
||||
_N4,
|
||||
_N5,
|
||||
_N6,
|
||||
_N7,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct RawTag(TagVal);
|
||||
|
||||
impl RawTag {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn new(neg: bool, val: NonZeroU8) -> RawTag {
|
||||
unsafe { Self::new_unchecked(neg, val.get() & 0x07) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn new_checked(neg: bool, val: u8) -> Option<RawTag> {
|
||||
Some(RawTag(match (neg, val) {
|
||||
(false, 1) => TagVal::_P1,
|
||||
(false, 2) => TagVal::_P2,
|
||||
(false, 3) => TagVal::_P3,
|
||||
(false, 4) => TagVal::_P4,
|
||||
(false, 5) => TagVal::_P5,
|
||||
(false, 6) => TagVal::_P6,
|
||||
(false, 7) => TagVal::_P7,
|
||||
|
||||
(true, 1) => TagVal::_N1,
|
||||
(true, 2) => TagVal::_N2,
|
||||
(true, 3) => TagVal::_N3,
|
||||
(true, 4) => TagVal::_N4,
|
||||
(true, 5) => TagVal::_N5,
|
||||
(true, 6) => TagVal::_N6,
|
||||
(true, 7) => TagVal::_N7,
|
||||
|
||||
_ => return None,
|
||||
}))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// `val` must be in the range `1..8`
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) unsafe fn new_unchecked(neg: bool, val: u8) -> RawTag {
|
||||
RawTag(match (neg, val) {
|
||||
(false, 1) => TagVal::_P1,
|
||||
(false, 2) => TagVal::_P2,
|
||||
(false, 3) => TagVal::_P3,
|
||||
(false, 4) => TagVal::_P4,
|
||||
(false, 5) => TagVal::_P5,
|
||||
(false, 6) => TagVal::_P6,
|
||||
(false, 7) => TagVal::_P7,
|
||||
|
||||
(true, 1) => TagVal::_N1,
|
||||
(true, 2) => TagVal::_N2,
|
||||
(true, 3) => TagVal::_N3,
|
||||
(true, 4) => TagVal::_N4,
|
||||
(true, 5) => TagVal::_N5,
|
||||
(true, 6) => TagVal::_N6,
|
||||
(true, 7) => TagVal::_N7,
|
||||
|
||||
_ => unsafe { core::hint::unreachable_unchecked() },
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn is_neg(self) -> bool {
|
||||
matches!(self.0, |TagVal::_N1| TagVal::_N2
|
||||
| TagVal::_N3
|
||||
| TagVal::_N4
|
||||
| TagVal::_N5
|
||||
| TagVal::_N6
|
||||
| TagVal::_N7)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn val(self) -> NonZeroU8 {
|
||||
match self.0 {
|
||||
TagVal::_P1 | TagVal::_N1 => NonZeroU8::MIN,
|
||||
TagVal::_P2 | TagVal::_N2 => NonZeroU8::MIN.saturating_add(1),
|
||||
TagVal::_P3 | TagVal::_N3 => NonZeroU8::MIN.saturating_add(2),
|
||||
TagVal::_P4 | TagVal::_N4 => NonZeroU8::MIN.saturating_add(3),
|
||||
TagVal::_P5 | TagVal::_N5 => NonZeroU8::MIN.saturating_add(4),
|
||||
TagVal::_P6 | TagVal::_N6 => NonZeroU8::MIN.saturating_add(5),
|
||||
TagVal::_P7 | TagVal::_N7 => NonZeroU8::MIN.saturating_add(6),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn neg_val(self) -> (bool, u8) {
|
||||
match self.0 {
|
||||
TagVal::_P1 => (false, 1),
|
||||
TagVal::_P2 => (false, 2),
|
||||
TagVal::_P3 => (false, 3),
|
||||
TagVal::_P4 => (false, 4),
|
||||
TagVal::_P5 => (false, 5),
|
||||
TagVal::_P6 => (false, 6),
|
||||
TagVal::_P7 => (false, 7),
|
||||
TagVal::_N1 => (true, 1),
|
||||
TagVal::_N2 => (true, 2),
|
||||
TagVal::_N3 => (true, 3),
|
||||
TagVal::_N4 => (true, 4),
|
||||
TagVal::_N5 => (true, 5),
|
||||
TagVal::_N6 => (true, 6),
|
||||
TagVal::_N7 => (true, 7),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for RawTag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("RawTag")
|
||||
.field("neg", &self.is_neg())
|
||||
.field("val", &self.val())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
struct Header(u16);
|
||||
|
||||
impl Header {
|
||||
#[inline]
|
||||
fn new(tag: RawTag) -> Header {
|
||||
let (neg, val) = tag.neg_val();
|
||||
Header(0x7FF8 | (u16::from(neg) << 15) | u16::from(val))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tag(self) -> RawTag {
|
||||
unsafe { RawTag::new_unchecked(self.get_sign(), self.get_tag()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_sign(self) -> bool {
|
||||
self.0 & 0x8000 != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_tag(self) -> u8 {
|
||||
(self.0 & 0x0007) as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_raw(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(C, align(8))]
|
||||
pub(crate) struct Value {
|
||||
#[cfg(target_endian = "big")]
|
||||
header: Header,
|
||||
data: [u8; 6],
|
||||
#[cfg(target_endian = "little")]
|
||||
header: Header,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
#[inline]
|
||||
pub(crate) fn new(tag: RawTag, data: [u8; 6]) -> Value {
|
||||
Value {
|
||||
header: Header::new(tag),
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn empty(tag: RawTag) -> Value {
|
||||
Value::new(tag, [0; 6])
|
||||
}
|
||||
|
||||
pub(crate) fn store<T: RawStore>(tag: RawTag, val: T) -> Value {
|
||||
let mut v = Value::new(tag, [0; 6]);
|
||||
T::to_val(val, &mut v);
|
||||
v
|
||||
}
|
||||
|
||||
pub(crate) fn load<T: RawStore>(self) -> T {
|
||||
T::from_val(&self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn tag(&self) -> RawTag {
|
||||
self.header.tag()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn header(&self) -> &Header {
|
||||
&self.header
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_data(&mut self, val: [u8; 6]) {
|
||||
self.data = val;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn data(&self) -> &[u8; 6] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn data_mut(&mut self) -> &mut [u8; 6] {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
unsafe fn whole(&self) -> &[u8; 8] {
|
||||
let ptr = (self as *const Value).cast::<[u8; 8]>();
|
||||
unsafe { &*ptr }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
unsafe fn whole_mut(&mut self) -> &mut [u8; 8] {
|
||||
let ptr = (self as *mut Value).cast::<[u8; 8]>();
|
||||
unsafe { &mut *ptr }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) union RawBox {
|
||||
float: f64,
|
||||
value: Value,
|
||||
bits: u64,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
ptr: *const (),
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
ptr: (u32, *const ()),
|
||||
}
|
||||
|
||||
impl RawBox {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn from_float(val: f64) -> RawBox {
|
||||
match (val.is_nan(), val.is_sign_positive()) {
|
||||
(true, true) => RawBox {
|
||||
float: f64::from_bits(QUIET_NAN),
|
||||
},
|
||||
(true, false) => RawBox {
|
||||
float: f64::from_bits(NEG_QUIET_NAN),
|
||||
},
|
||||
(false, _) => RawBox { float: val },
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn from_value(value: Value) -> RawBox {
|
||||
RawBox { value }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn tag(&self) -> Option<RawTag> {
|
||||
if self.is_value() {
|
||||
Some(unsafe { self.value.tag() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn is_float(&self) -> bool {
|
||||
(unsafe { !self.float.is_nan() } || unsafe { self.bits & SIGN_MASK == QUIET_NAN })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn is_value(&self) -> bool {
|
||||
(unsafe { self.float.is_nan() } && unsafe { self.bits & SIGN_MASK != QUIET_NAN })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn float(&self) -> Option<&f64> {
|
||||
if self.is_float() {
|
||||
Some(unsafe { &self.float })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn value(&self) -> Option<&Value> {
|
||||
if self.is_value() {
|
||||
Some(unsafe { &self.value })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn into_float_unchecked(self) -> f64 {
|
||||
unsafe { self.float }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for RawBox {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.float() {
|
||||
Some(val) => f.debug_tuple("RawBox::Float").field(val).finish(),
|
||||
None => {
|
||||
let val = self.value().expect("RawBox is neither float nor value");
|
||||
|
||||
f.debug_struct("RawBox::Data")
|
||||
.field("tag", &val.tag())
|
||||
.field("data", val.data())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -125,8 +125,6 @@ pub enum Error {
|
||||
#[label("error occurred here")]
|
||||
span: Option<SourceSpan>,
|
||||
message: String,
|
||||
#[help]
|
||||
js_backtrace: Option<String>,
|
||||
#[related]
|
||||
stack_trace: Vec<StackFrame>,
|
||||
},
|
||||
@@ -163,12 +161,11 @@ impl Error {
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn eval_error(msg: String, backtrace: Option<String>) -> Box<Self> {
|
||||
pub fn eval_error(msg: impl Into<String>) -> Box<Self> {
|
||||
Error::EvalError {
|
||||
src: None,
|
||||
span: None,
|
||||
message: msg,
|
||||
js_backtrace: backtrace,
|
||||
message: msg.into(),
|
||||
stack_trace: Vec::new(),
|
||||
}
|
||||
.into()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod boxing;
|
||||
pub mod error;
|
||||
pub mod logging;
|
||||
pub mod runtime;
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ use rustyline::DefaultEditor;
|
||||
use rustyline::error::ReadlineError;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "nix-js", about = "Nix expression evaluator")]
|
||||
#[command(name = "fix", about = "Nix expression evaluator")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
@@ -40,8 +40,8 @@ struct ExprSource {
|
||||
file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> {
|
||||
let src = if let Some(expr) = src.expr {
|
||||
fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result<()> {
|
||||
let _src = if let Some(expr) = src.expr {
|
||||
Source::new_eval(expr)?
|
||||
} else if let Some(file) = src.file {
|
||||
Source::new_file(file)?
|
||||
|
||||
+13
-5
@@ -16,6 +16,7 @@ use crate::store::{DaemonStore, StoreConfig};
|
||||
use crate::value::Symbol;
|
||||
|
||||
mod builtins;
|
||||
mod primops;
|
||||
mod stack;
|
||||
mod value;
|
||||
mod vm;
|
||||
@@ -43,6 +44,7 @@ impl Runtime {
|
||||
let store = DaemonStore::connect(&config.daemon_socket)?;
|
||||
|
||||
Ok(Self {
|
||||
arena: Arena::new(|mc| VM::new(mc, &mut strings)),
|
||||
global_env,
|
||||
store,
|
||||
strings,
|
||||
@@ -50,7 +52,6 @@ impl Runtime {
|
||||
bytecode: Vec::new(),
|
||||
sources: Vec::new(),
|
||||
spans: Vec::new(),
|
||||
arena: Arena::new(|mc| VM::new(mc)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,8 +109,11 @@ impl Runtime {
|
||||
bump,
|
||||
token,
|
||||
strings,
|
||||
source: sources.last().unwrap().clone(),
|
||||
scopes: [Scope::Global(global_env)].into_iter().chain(extra_scope.into_iter()).collect(),
|
||||
source: sources.last().expect("no current source").clone(),
|
||||
scopes: [Scope::Global(global_env)]
|
||||
.into_iter()
|
||||
.chain(extra_scope)
|
||||
.collect(),
|
||||
with_scope_count: 0,
|
||||
arg_count: 0,
|
||||
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");
|
||||
|
||||
self.sources.push(source.clone());
|
||||
@@ -496,7 +504,7 @@ impl OwnedIr {
|
||||
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||
Self {
|
||||
_bump: bump,
|
||||
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) }
|
||||
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,204 @@
|
||||
use gc_arena::Collect;
|
||||
use hashbrown::HashMap;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use string_interner::DefaultStringInterner;
|
||||
|
||||
use super::value::*;
|
||||
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(
|
||||
interner: &mut DefaultStringInterner,
|
||||
) -> HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
|
||||
intern_all_builtins(interner);
|
||||
|
||||
let mut builtins = HashMap::new();
|
||||
let builtins_sym = StringId(interner.get_or_intern("builtins"));
|
||||
builtins.insert(builtins_sym, Ir::Builtins);
|
||||
@@ -49,3 +242,6 @@ pub(super) fn new_builtins_env(
|
||||
|
||||
builtins
|
||||
}
|
||||
|
||||
pub(super) type PrimOpArgs<'gc> = [Value<'gc>; 3];
|
||||
pub(super) type PrimOpStrictArgs<'gc> = [StrictValue<'gc>; 3];
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,13 @@ use std::mem::MaybeUninit;
|
||||
use gc_arena::Collect;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
// FIXME: Drop???
|
||||
pub(super) struct Stack<const N: usize, T> {
|
||||
inner: Box<[MaybeUninit<T>; N]>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
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) {
|
||||
for item in self.inner[..self.len].iter() {
|
||||
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> {
|
||||
if self.len == N {
|
||||
return Err(val);
|
||||
|
||||
+39
-37
@@ -3,16 +3,19 @@ use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::ops::Deref;
|
||||
|
||||
use boxing::nan::raw::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||
use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use sealed::sealed;
|
||||
use smallvec::SmallVec;
|
||||
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]
|
||||
pub(crate) trait Storable {
|
||||
/// # Safety
|
||||
/// TAG must be unique among all implementors.
|
||||
pub(crate) unsafe trait Storable {
|
||||
const TAG: (bool, u8);
|
||||
}
|
||||
pub(crate) trait InlineStorable: Storable + RawStore {}
|
||||
@@ -25,14 +28,14 @@ macro_rules! define_value_types {
|
||||
) => {
|
||||
$(
|
||||
#[sealed]
|
||||
impl Storable for $itype {
|
||||
unsafe impl Storable for $itype {
|
||||
const TAG: (bool, u8) = $itag;
|
||||
}
|
||||
impl InlineStorable for $itype {}
|
||||
)*
|
||||
$(
|
||||
#[sealed]
|
||||
impl Storable for $gtype {
|
||||
unsafe impl Storable for $gtype {
|
||||
const TAG: (bool, u8) = $gtag;
|
||||
}
|
||||
impl GcStorable for $gtype {}
|
||||
@@ -116,22 +119,13 @@ define_value_types! {
|
||||
/// # Nix runtime value representation
|
||||
///
|
||||
/// NaN-boxed value fitting in 8 bytes.
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct Value<'gc> {
|
||||
raw: RawBox,
|
||||
_marker: PhantomData<Gc<'gc, ()>>,
|
||||
}
|
||||
|
||||
impl Clone for Value<'_> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
raw: self.raw.clone(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Value<'_> {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
@@ -291,14 +285,23 @@ impl fmt::Debug for NixString {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[derive(Collect, Debug, Default)]
|
||||
#[collect(no_drop)]
|
||||
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> {
|
||||
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));
|
||||
Self { entries }
|
||||
}
|
||||
@@ -307,13 +310,11 @@ impl<'gc> AttrSet<'gc> {
|
||||
self.entries
|
||||
.binary_search_by_key(&key, |(k, _)| *k)
|
||||
.ok()
|
||||
.map(|i| self.entries[i].1.clone())
|
||||
.map(|i| self.entries[i].1)
|
||||
}
|
||||
|
||||
pub(crate) fn has(&self, key: StringId) -> bool {
|
||||
self.entries
|
||||
.binary_search_by_key(&key, |(k, _)| *k)
|
||||
.is_ok()
|
||||
self.entries.binary_search_by_key(&key, |(k, _)| *k).is_ok()
|
||||
}
|
||||
|
||||
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() {
|
||||
match self.entries[i].0.cmp(&other.entries[j].0) {
|
||||
Less => {
|
||||
entries.push(self.entries[i].clone());
|
||||
entries.push(self.entries[i]);
|
||||
i += 1;
|
||||
}
|
||||
Greater => {
|
||||
entries.push(other.entries[j].clone());
|
||||
entries.push(other.entries[j]);
|
||||
j += 1;
|
||||
}
|
||||
Equal => {
|
||||
entries.push(other.entries[j].clone());
|
||||
entries.push(other.entries[j]);
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
@@ -351,7 +352,7 @@ impl<'gc> AttrSet<'gc> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[derive(Collect, Debug, Default)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct List<'gc> {
|
||||
pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
|
||||
@@ -366,6 +367,10 @@ pub(crate) enum ThunkState<'gc> {
|
||||
ip: u32,
|
||||
env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
},
|
||||
Apply {
|
||||
func: Value<'gc>,
|
||||
arg: Value<'gc>,
|
||||
},
|
||||
Blackhole,
|
||||
Evaluated(Value<'gc>),
|
||||
}
|
||||
@@ -420,17 +425,20 @@ pub(crate) struct PatternInfo {
|
||||
#[derive(Clone, Copy, Debug, Collect)]
|
||||
#[collect(require_static)]
|
||||
pub(crate) struct PrimOp {
|
||||
pub(crate) id: u8,
|
||||
pub(crate) id: BuiltinId,
|
||||
pub(crate) arity: u8,
|
||||
}
|
||||
|
||||
impl RawStore for PrimOp {
|
||||
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 {
|
||||
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]>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct StrictValue<'gc>(Value<'gc>);
|
||||
|
||||
@@ -455,7 +464,7 @@ impl<'gc> StrictValue<'gc> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn into_relaxed(self) -> Value<'gc> {
|
||||
pub(crate) fn relax(self) -> Value<'gc> {
|
||||
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<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
|
||||
+895
-470
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@ mod validation;
|
||||
|
||||
pub use config::StoreConfig;
|
||||
pub use daemon::DaemonStore;
|
||||
pub use validation::validate_store_path;
|
||||
|
||||
pub trait Store: Send + Sync {
|
||||
fn get_store_dir(&self) -> &str;
|
||||
|
||||
@@ -69,13 +69,10 @@ impl Store for DaemonStore {
|
||||
fn ensure_path(&self, path: &str) -> Result<()> {
|
||||
self.block_on(async {
|
||||
self.connection.ensure_path(path).await.map_err(|e| {
|
||||
Error::eval_error(
|
||||
format!(
|
||||
"builtins.storePath: path '{}' is not valid in nix store: {}",
|
||||
path, e
|
||||
),
|
||||
None,
|
||||
)
|
||||
Error::eval_error(format!(
|
||||
"builtins.storePath: path '{}' is not valid in nix store: {}",
|
||||
path, e
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+34
-37
@@ -2,75 +2,72 @@ use crate::error::{Error, Result};
|
||||
|
||||
pub fn validate_store_path(store_dir: &str, path: &str) -> Result<()> {
|
||||
if !path.starts_with(store_dir) {
|
||||
return Err(Error::eval_error(
|
||||
format!("path '{}' is not in the Nix store", path),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"path '{}' is not in the Nix store",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
let relative = path
|
||||
.strip_prefix(store_dir)
|
||||
.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() {
|
||||
return Err(Error::eval_error(
|
||||
format!("store path cannot be store directory itself: {}", path),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"store path cannot be store directory itself: {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = relative.splitn(2, '-').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err(Error::eval_error(
|
||||
format!("invalid store path format (missing name): {}", path),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"invalid store path format (missing name): {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
let hash = parts[0];
|
||||
let name = parts[1];
|
||||
|
||||
if hash.len() != 32 {
|
||||
return Err(Error::eval_error(
|
||||
format!(
|
||||
"invalid store path hash length (expected 32, got {}): {}",
|
||||
hash.len(),
|
||||
hash
|
||||
),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"invalid store path hash length (expected 32, got {}): {}",
|
||||
hash.len(),
|
||||
hash
|
||||
)));
|
||||
}
|
||||
|
||||
for ch in hash.chars() {
|
||||
if !matches!(ch, '0'..='9' | 'a'..='d' | 'f'..='n' | 'p'..='s' | 'v'..='z') {
|
||||
return Err(Error::eval_error(
|
||||
format!("invalid character '{}' in store path hash: {}", ch, hash),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"invalid character '{}' in store path hash: {}",
|
||||
ch, hash
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
if name.is_empty() {
|
||||
return Err(Error::eval_error(
|
||||
format!("store path has empty name: {}", path),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"store path has empty name: {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
if name.starts_with('.') {
|
||||
return Err(Error::eval_error(
|
||||
format!("store path name cannot start with '.': {}", name),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"store path name cannot start with '.': {}",
|
||||
name
|
||||
)));
|
||||
}
|
||||
|
||||
for ch in name.chars() {
|
||||
if !matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '+' | '-' | '.' | '_' | '?' | '=') {
|
||||
return Err(Error::eval_error(
|
||||
format!("invalid character '{}' in store path name: {}", ch, name),
|
||||
None,
|
||||
));
|
||||
return Err(Error::eval_error(format!(
|
||||
"invalid character '{}' in store path name: {}",
|
||||
ch, name
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,6 +267,15 @@ fn escape_quote_string(s: &str) -> String {
|
||||
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.
|
||||
fn fmt_nix_float(f: &mut Formatter<'_>, x: f64) -> FmtResult {
|
||||
if !x.is_finite() {
|
||||
|
||||
Reference in New Issue
Block a user