init builtins
This commit is contained in:
Generated
+1
-8
@@ -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",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = [
|
members = [
|
||||||
"fix",
|
"fix",
|
||||||
"boxing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.profiling]
|
[profile.profiling]
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod nan;
|
|
||||||
mod utils;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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,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
@@ -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
@@ -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) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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 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
@@ -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)
|
||||||
|
|||||||
+847
-422
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-34
@@ -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
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user