refactor
This commit is contained in:
@@ -1,484 +0,0 @@
|
||||
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 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub(crate) fn to_bits(self) -> u64 {
|
||||
unsafe { self.bits }
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,919 +0,0 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use gc_arena::Collect;
|
||||
use hashbrown::HashMap;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use rnix::TextRange;
|
||||
use string_interner::Symbol as _;
|
||||
|
||||
use crate::ir::{Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
|
||||
use crate::runtime::BUILTINS;
|
||||
use crate::runtime::value::{Null, PrimOp, StaticValue};
|
||||
|
||||
pub struct InstructionPtr(pub(crate) usize);
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(require_static)]
|
||||
pub struct Bytecode {
|
||||
pub code: Box<[u8]>,
|
||||
pub current_dir: String,
|
||||
}
|
||||
|
||||
pub(crate) trait BytecodeContext {
|
||||
fn intern_string(&mut self, s: &str) -> StringId;
|
||||
fn register_span(&mut self, range: TextRange) -> u32;
|
||||
fn get_code(&self) -> &[u8];
|
||||
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
||||
fn add_constant(&mut self, val: crate::runtime::value::StaticValue) -> u32;
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum Op {
|
||||
PushSmi,
|
||||
PushBigInt,
|
||||
PushFloat,
|
||||
PushString,
|
||||
PushNull,
|
||||
PushTrue,
|
||||
PushFalse,
|
||||
|
||||
LoadLocal,
|
||||
LoadOuter,
|
||||
StoreLocal,
|
||||
AllocLocals,
|
||||
|
||||
MakeThunk,
|
||||
MakeClosure,
|
||||
MakePatternClosure,
|
||||
|
||||
Call,
|
||||
CallNoSpan,
|
||||
|
||||
MakeAttrs,
|
||||
MakeEmptyAttrs,
|
||||
Select,
|
||||
SelectDefault,
|
||||
HasAttr,
|
||||
|
||||
MakeList,
|
||||
MakeEmptyList,
|
||||
|
||||
OpAdd,
|
||||
OpSub,
|
||||
OpMul,
|
||||
OpDiv,
|
||||
OpEq,
|
||||
OpNeq,
|
||||
OpLt,
|
||||
OpGt,
|
||||
OpLeq,
|
||||
OpGeq,
|
||||
OpConcat,
|
||||
OpUpdate,
|
||||
|
||||
OpNeg,
|
||||
OpNot,
|
||||
|
||||
JumpIfFalse,
|
||||
JumpIfTrue,
|
||||
Jump,
|
||||
|
||||
ConcatStrings,
|
||||
ResolvePath,
|
||||
|
||||
Assert,
|
||||
|
||||
PushWith,
|
||||
PopWith,
|
||||
WithLookup,
|
||||
|
||||
LoadBuiltins,
|
||||
LoadBuiltin,
|
||||
|
||||
MkPos,
|
||||
|
||||
LoadReplBinding,
|
||||
LoadScopedBinding,
|
||||
|
||||
Return,
|
||||
}
|
||||
|
||||
struct ScopeInfo {
|
||||
depth: u16,
|
||||
thunk_map: HashMap<ThunkId, u32>,
|
||||
}
|
||||
|
||||
struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
||||
ctx: &'a mut Ctx,
|
||||
scope_stack: Vec<ScopeInfo>,
|
||||
}
|
||||
|
||||
pub(crate) const OPERAND_CONST: u8 = 0;
|
||||
pub(crate) const OPERAND_LOCAL: u8 = 1;
|
||||
pub(crate) const OPERAND_BUILTINS: u8 = 2;
|
||||
pub(crate) const OPERAND_BIGINT: u8 = 3;
|
||||
|
||||
pub(crate) const KEY_STATIC: u8 = 0;
|
||||
pub(crate) const KEY_DYNAMIC: u8 = 1;
|
||||
|
||||
enum InlineOperand {
|
||||
Const(StaticValue),
|
||||
Local { layer: u16, local: u32 },
|
||||
Builtins,
|
||||
BigInt(i64),
|
||||
}
|
||||
|
||||
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
||||
let ip = ctx.get_code().len();
|
||||
let mut emitter = BytecodeEmitter::new(ctx);
|
||||
emitter.emit_toplevel(ir);
|
||||
InstructionPtr(ip)
|
||||
}
|
||||
|
||||
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
fn new(ctx: &'a mut Ctx) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
scope_stack: Vec::with_capacity(32),
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_value(&mut self, ir: RawIrRef<'_>) -> InlineOperand {
|
||||
match ir.deref() {
|
||||
&Ir::Int(x) => {
|
||||
if x <= i32::MAX as i64 {
|
||||
InlineOperand::Const(StaticValue::new_inline(x as i32))
|
||||
} else {
|
||||
InlineOperand::BigInt(x)
|
||||
}
|
||||
}
|
||||
&Ir::Float(x) => InlineOperand::Const(StaticValue::new_float(x)),
|
||||
&Ir::Bool(b) => InlineOperand::Const(StaticValue::new_inline(b)),
|
||||
Ir::Null => InlineOperand::Const(StaticValue::new_inline(Null)),
|
||||
Ir::Str(s) => {
|
||||
let sid = self.ctx.intern_string(s.deref());
|
||||
InlineOperand::Const(StaticValue::new_inline(sid))
|
||||
}
|
||||
&Ir::Thunk(id) => {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
InlineOperand::Local { layer, local }
|
||||
}
|
||||
&Ir::Arg { layer } => InlineOperand::Local {
|
||||
layer: layer.try_into().expect("scope too deep!"),
|
||||
local: 0,
|
||||
},
|
||||
&Ir::Builtin(id) => {
|
||||
let arity = BUILTINS[id as usize].1;
|
||||
InlineOperand::Const(StaticValue::new_inline(PrimOp { id, arity }))
|
||||
}
|
||||
Ir::Builtins => InlineOperand::Builtins,
|
||||
_ => panic!("cannot classify IR node as inline operand"),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_inline_operand(&mut self, operand: InlineOperand) {
|
||||
match operand {
|
||||
InlineOperand::Const(val) => {
|
||||
let idx = self.ctx.add_constant(val);
|
||||
self.emit_u8(OPERAND_CONST);
|
||||
self.emit_u32(idx);
|
||||
}
|
||||
InlineOperand::Local { layer, local } => {
|
||||
self.emit_u8(OPERAND_LOCAL);
|
||||
self.emit_u8(layer as u8);
|
||||
self.emit_u32(local);
|
||||
}
|
||||
InlineOperand::Builtins => {
|
||||
self.emit_u8(OPERAND_BUILTINS);
|
||||
}
|
||||
InlineOperand::BigInt(val) => {
|
||||
self.emit_u8(OPERAND_BIGINT);
|
||||
self.emit_i64(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_op(&mut self, op: Op) {
|
||||
self.ctx.get_code_mut().push(op as u8);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_u8(&mut self, val: u8) {
|
||||
self.ctx.get_code_mut().push(val);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_u16(&mut self, val: u16) {
|
||||
self.ctx
|
||||
.get_code_mut()
|
||||
.extend_from_slice(&val.to_le_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_u32(&mut self, val: u32) {
|
||||
self.ctx
|
||||
.get_code_mut()
|
||||
.extend_from_slice(&val.to_le_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_i32(&mut self, val: i32) {
|
||||
self.ctx
|
||||
.get_code_mut()
|
||||
.extend_from_slice(&val.to_le_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_i64(&mut self, val: i64) {
|
||||
self.ctx
|
||||
.get_code_mut()
|
||||
.extend_from_slice(&val.to_le_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_f64(&mut self, val: f64) {
|
||||
self.ctx
|
||||
.get_code_mut()
|
||||
.extend_from_slice(&val.to_le_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_i32_placeholder(&mut self) -> usize {
|
||||
let offset = self.ctx.get_code_mut().len();
|
||||
self.ctx.get_code_mut().extend_from_slice(&[0u8; 4]);
|
||||
offset
|
||||
}
|
||||
#[inline]
|
||||
fn patch_i32(&mut self, offset: usize, val: i32) {
|
||||
self.ctx.get_code_mut()[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_jump_placeholder(&mut self) -> usize {
|
||||
self.emit_op(Op::Jump);
|
||||
self.emit_i32_placeholder()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn patch_jump_target(&mut self, placeholder_offset: usize) {
|
||||
let current_pos = self.ctx.get_code_mut().len();
|
||||
let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4;
|
||||
self.patch_i32(placeholder_offset, relative_offset);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn emit_str_id(&mut self, id: StringId) {
|
||||
self.ctx
|
||||
.get_code_mut()
|
||||
.extend_from_slice(&(id.0.to_usize() as u32).to_le_bytes());
|
||||
}
|
||||
|
||||
fn current_depth(&self) -> u16 {
|
||||
self.scope_stack.last().map_or(0, |s| s.depth)
|
||||
}
|
||||
|
||||
fn resolve_thunk(&self, id: ThunkId) -> (u16, u32) {
|
||||
for scope in self.scope_stack.iter().rev() {
|
||||
if let Some(&local_idx) = scope.thunk_map.get(&id) {
|
||||
let layer = self.current_depth() - scope.depth;
|
||||
return (layer, local_idx);
|
||||
}
|
||||
}
|
||||
panic!("ThunkId {:?} not found in any scope", id);
|
||||
}
|
||||
|
||||
fn emit_load(&mut self, layer: u16, local: u32) {
|
||||
if layer == 0 {
|
||||
self.emit_op(Op::LoadLocal);
|
||||
self.emit_u32(local);
|
||||
} else {
|
||||
self.emit_op(Op::LoadOuter);
|
||||
self.emit_u8(layer as u8);
|
||||
self.emit_u32(local);
|
||||
}
|
||||
}
|
||||
|
||||
fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize {
|
||||
match ir.deref() {
|
||||
Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body),
|
||||
Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body),
|
||||
Ir::If { cond, consq, alter } => {
|
||||
self.count_with_thunks(*cond)
|
||||
+ self.count_with_thunks(*consq)
|
||||
+ self.count_with_thunks(*alter)
|
||||
}
|
||||
Ir::BinOp { lhs, rhs, .. } => {
|
||||
self.count_with_thunks(*lhs) + self.count_with_thunks(*rhs)
|
||||
}
|
||||
Ir::UnOp { rhs, .. } => self.count_with_thunks(*rhs),
|
||||
Ir::Call { func, arg, .. } => {
|
||||
self.count_with_thunks(*func) + self.count_with_thunks(*arg)
|
||||
}
|
||||
Ir::Assert {
|
||||
assertion, expr, ..
|
||||
} => self.count_with_thunks(*assertion) + self.count_with_thunks(*expr),
|
||||
Ir::Select { expr, default, .. } => {
|
||||
self.count_with_thunks(*expr) + default.map_or(0, |d| self.count_with_thunks(d))
|
||||
}
|
||||
Ir::HasAttr { lhs, .. } => self.count_with_thunks(*lhs),
|
||||
Ir::ConcatStrings { parts, .. } => {
|
||||
parts.iter().map(|p| self.count_with_thunks(*p)).sum()
|
||||
}
|
||||
Ir::Path(p) => self.count_with_thunks(*p),
|
||||
Ir::List { items } => items.iter().map(|item| self.count_with_thunks(*item)).sum(),
|
||||
Ir::AttrSet { stcs, dyns } => {
|
||||
stcs.iter()
|
||||
.map(|(_, &(val, _))| self.count_with_thunks(val))
|
||||
.sum::<usize>()
|
||||
+ dyns
|
||||
.iter()
|
||||
.map(|&(k, v, _)| self.count_with_thunks(k) + self.count_with_thunks(v))
|
||||
.sum::<usize>()
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_all_thunks<'ir>(
|
||||
&self,
|
||||
own_thunks: &[(ThunkId, RawIrRef<'ir>)],
|
||||
body: RawIrRef<'ir>,
|
||||
) -> Vec<(ThunkId, RawIrRef<'ir>)> {
|
||||
let mut all = Vec::from(own_thunks);
|
||||
self.collect_with_thunks_recursive(body, &mut all);
|
||||
let mut i = 0;
|
||||
while i < all.len() {
|
||||
let thunk_body = all[i].1;
|
||||
self.collect_with_thunks_recursive(thunk_body, &mut all);
|
||||
i += 1;
|
||||
}
|
||||
all
|
||||
}
|
||||
|
||||
fn collect_with_thunks_recursive<'ir>(
|
||||
&self,
|
||||
ir: RawIrRef<'ir>,
|
||||
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
|
||||
) {
|
||||
match ir.deref() {
|
||||
Ir::With { thunks, body, .. } => {
|
||||
for &(id, inner) in thunks.iter() {
|
||||
out.push((id, inner));
|
||||
}
|
||||
self.collect_with_thunks_recursive(*body, out);
|
||||
}
|
||||
Ir::TopLevel { thunks, body } => {
|
||||
for &(id, inner) in thunks.iter() {
|
||||
out.push((id, inner));
|
||||
}
|
||||
self.collect_with_thunks_recursive(*body, out);
|
||||
}
|
||||
Ir::If { cond, consq, alter } => {
|
||||
self.collect_with_thunks_recursive(*cond, out);
|
||||
self.collect_with_thunks_recursive(*consq, out);
|
||||
self.collect_with_thunks_recursive(*alter, out);
|
||||
}
|
||||
Ir::BinOp { lhs, rhs, .. } => {
|
||||
self.collect_with_thunks_recursive(*lhs, out);
|
||||
self.collect_with_thunks_recursive(*rhs, out);
|
||||
}
|
||||
Ir::UnOp { rhs, .. } => self.collect_with_thunks_recursive(*rhs, out),
|
||||
Ir::Call { func, arg, .. } => {
|
||||
self.collect_with_thunks_recursive(*func, out);
|
||||
self.collect_with_thunks_recursive(*arg, out);
|
||||
}
|
||||
Ir::Assert {
|
||||
assertion, expr, ..
|
||||
} => {
|
||||
self.collect_with_thunks_recursive(*assertion, out);
|
||||
self.collect_with_thunks_recursive(*expr, out);
|
||||
}
|
||||
Ir::Select { expr, default, .. } => {
|
||||
self.collect_with_thunks_recursive(*expr, out);
|
||||
if let Some(d) = default {
|
||||
self.collect_with_thunks_recursive(*d, out);
|
||||
}
|
||||
}
|
||||
Ir::HasAttr { lhs, .. } => self.collect_with_thunks_recursive(*lhs, out),
|
||||
Ir::ConcatStrings { parts, .. } => {
|
||||
for p in parts.iter() {
|
||||
self.collect_with_thunks_recursive(*p, out);
|
||||
}
|
||||
}
|
||||
Ir::Path(p) => self.collect_with_thunks_recursive(*p, out),
|
||||
Ir::List { items } => {
|
||||
for item in items.iter() {
|
||||
self.collect_with_thunks_recursive(*item, out);
|
||||
}
|
||||
}
|
||||
Ir::AttrSet { stcs, dyns } => {
|
||||
for (_, &(val, _)) in stcs.iter() {
|
||||
self.collect_with_thunks_recursive(val, out);
|
||||
}
|
||||
for &(key, val, _) in dyns.iter() {
|
||||
self.collect_with_thunks_recursive(key, out);
|
||||
self.collect_with_thunks_recursive(val, out);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) {
|
||||
let depth = self.scope_stack.len() as u16;
|
||||
let thunk_base = if has_arg { 1u32 } else { 0u32 };
|
||||
let thunk_map = thunk_ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &id)| (id, thunk_base + i as u32))
|
||||
.collect();
|
||||
self.scope_stack.push(ScopeInfo { depth, thunk_map });
|
||||
}
|
||||
|
||||
fn pop_scope(&mut self) {
|
||||
self.scope_stack.pop();
|
||||
}
|
||||
|
||||
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir.deref() {
|
||||
&Ir::TopLevel { body, ref thunks } => {
|
||||
let with_thunk_count = self.count_with_thunks(body);
|
||||
let total_slots = thunks.len() + with_thunk_count;
|
||||
|
||||
let all_thunks = self.collect_all_thunks(thunks, body);
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
self.push_scope(false, &thunk_ids);
|
||||
|
||||
if total_slots > 0 {
|
||||
self.emit_op(Op::AllocLocals);
|
||||
self.emit_u32(total_slots as u32);
|
||||
}
|
||||
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
self.emit_op(Op::Return);
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
_ => {
|
||||
self.push_scope(false, &[]);
|
||||
self.emit_expr(ir);
|
||||
self.emit_op(Op::Return);
|
||||
self.pop_scope();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||
for &(id, inner) in thunks {
|
||||
let skip_patch = self.emit_jump_placeholder();
|
||||
let entry_point = self.ctx.get_code_mut().len() as u32;
|
||||
self.emit_expr(inner);
|
||||
self.emit_op(Op::Return);
|
||||
self.patch_jump_target(skip_patch);
|
||||
self.emit_op(Op::MakeThunk);
|
||||
self.emit_u32(entry_point);
|
||||
let (_, local_idx) = self.resolve_thunk(id);
|
||||
self.emit_op(Op::StoreLocal);
|
||||
self.emit_u32(local_idx);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir.deref() {
|
||||
&Ir::Int(x) => {
|
||||
if x <= i32::MAX as i64 {
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_i32(x as i32);
|
||||
} else {
|
||||
self.emit_op(Op::PushBigInt);
|
||||
self.emit_i64(x);
|
||||
}
|
||||
}
|
||||
&Ir::Float(x) => {
|
||||
self.emit_op(Op::PushFloat);
|
||||
self.emit_f64(x);
|
||||
}
|
||||
&Ir::Bool(true) => self.emit_op(Op::PushTrue),
|
||||
&Ir::Bool(false) => self.emit_op(Op::PushFalse),
|
||||
Ir::Null => self.emit_op(Op::PushNull),
|
||||
Ir::Str(s) => {
|
||||
let idx = self.ctx.intern_string(s.deref());
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_str_id(idx);
|
||||
}
|
||||
&Ir::Path(p) => {
|
||||
self.emit_expr(p);
|
||||
self.emit_op(Op::ResolvePath);
|
||||
}
|
||||
&Ir::If { cond, consq, alter } => {
|
||||
self.emit_expr(cond);
|
||||
|
||||
self.emit_op(Op::JumpIfFalse);
|
||||
let else_placeholder = self.emit_i32_placeholder();
|
||||
let after_jif = self.ctx.get_code_mut().len();
|
||||
|
||||
self.emit_expr(consq);
|
||||
|
||||
self.emit_op(Op::Jump);
|
||||
let end_placeholder = self.emit_i32_placeholder();
|
||||
let after_jump = self.ctx.get_code_mut().len();
|
||||
|
||||
let else_offset = (after_jump as i32) - (after_jif as i32);
|
||||
self.patch_i32(else_placeholder, else_offset);
|
||||
|
||||
self.emit_expr(alter);
|
||||
|
||||
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||
self.patch_i32(end_placeholder, end_offset);
|
||||
}
|
||||
&Ir::BinOp { lhs, rhs, kind } => {
|
||||
self.emit_binop(lhs, rhs, kind);
|
||||
}
|
||||
&Ir::UnOp { rhs, kind } => match kind {
|
||||
UnOpKind::Neg => {
|
||||
self.emit_expr(rhs);
|
||||
self.emit_op(Op::OpNeg);
|
||||
}
|
||||
UnOpKind::Not => {
|
||||
self.emit_expr(rhs);
|
||||
self.emit_op(Op::OpNot);
|
||||
}
|
||||
},
|
||||
&Ir::Func {
|
||||
body,
|
||||
ref param,
|
||||
ref thunks,
|
||||
} => {
|
||||
self.emit_func(thunks, param, body);
|
||||
}
|
||||
Ir::AttrSet { stcs, dyns } => {
|
||||
self.emit_attrset(stcs, dyns);
|
||||
}
|
||||
Ir::List { items } => {
|
||||
if items.is_empty() {
|
||||
self.emit_op(Op::MakeEmptyList);
|
||||
} else {
|
||||
self.emit_op(Op::MakeList);
|
||||
self.emit_u32(items.len() as u32);
|
||||
for &item in items.iter() {
|
||||
let operand = self.classify_value(item);
|
||||
self.emit_inline_operand(operand);
|
||||
}
|
||||
}
|
||||
}
|
||||
&Ir::Call { func, arg, span } => {
|
||||
self.emit_expr(arg);
|
||||
self.emit_expr(func);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::Call);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
&Ir::Arg { layer } => {
|
||||
self.emit_load(layer.try_into().expect("scope too deep!"), 0);
|
||||
}
|
||||
&Ir::TopLevel { body, ref thunks } => {
|
||||
self.emit_toplevel_inner(body, thunks);
|
||||
}
|
||||
&Ir::Select {
|
||||
expr,
|
||||
ref attrpath,
|
||||
default,
|
||||
span,
|
||||
} => {
|
||||
self.emit_select(expr, attrpath, default, span);
|
||||
}
|
||||
&Ir::Thunk(id) => {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
self.emit_load(layer, local);
|
||||
}
|
||||
Ir::Builtins => {
|
||||
self.emit_op(Op::LoadBuiltins);
|
||||
}
|
||||
&Ir::Builtin(id) => {
|
||||
self.emit_op(Op::LoadBuiltin);
|
||||
self.emit_u8(id as u8);
|
||||
}
|
||||
&Ir::BuiltinConst(id) => {
|
||||
self.emit_select(
|
||||
RawIrRef(&Ir::Builtins),
|
||||
&[Attr::Str(id, TextRange::default())],
|
||||
None,
|
||||
TextRange::default(),
|
||||
);
|
||||
}
|
||||
&Ir::ConcatStrings {
|
||||
ref parts,
|
||||
force_string,
|
||||
} => {
|
||||
self.emit_op(Op::ConcatStrings);
|
||||
self.emit_u16(parts.len() as u16);
|
||||
self.emit_u8(if force_string { 1 } else { 0 });
|
||||
for &part in parts.iter() {
|
||||
let operand = self.classify_value(part);
|
||||
self.emit_inline_operand(operand);
|
||||
}
|
||||
}
|
||||
&Ir::HasAttr { lhs, ref rhs } => {
|
||||
self.emit_has_attr(lhs, rhs);
|
||||
}
|
||||
Ir::Assert {
|
||||
assertion,
|
||||
expr,
|
||||
assertion_raw,
|
||||
span,
|
||||
} => {
|
||||
let raw_idx = self.ctx.intern_string(assertion_raw);
|
||||
let span_id = self.ctx.register_span(*span);
|
||||
self.emit_expr(*assertion);
|
||||
self.emit_expr(*expr);
|
||||
self.emit_op(Op::Assert);
|
||||
self.emit_str_id(raw_idx);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
&Ir::CurPos(span) => {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::MkPos);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
&Ir::ReplBinding(name) => {
|
||||
self.emit_op(Op::LoadReplBinding);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
&Ir::ScopedImportBinding(name) => {
|
||||
self.emit_op(Op::LoadScopedBinding);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
&Ir::With {
|
||||
namespace,
|
||||
body,
|
||||
ref thunks,
|
||||
} => {
|
||||
self.emit_with(namespace, body, thunks);
|
||||
}
|
||||
&Ir::WithLookup(name) => {
|
||||
self.emit_op(Op::WithLookup);
|
||||
self.emit_str_id(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) {
|
||||
use BinOpKind::*;
|
||||
match kind {
|
||||
And => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_op(Op::JumpIfFalse);
|
||||
let skip_placeholder = self.emit_i32_placeholder();
|
||||
let after_jif = self.ctx.get_code_mut().len();
|
||||
|
||||
self.emit_expr(rhs);
|
||||
self.emit_op(Op::Jump);
|
||||
let end_placeholder = self.emit_i32_placeholder();
|
||||
let after_jump = self.ctx.get_code_mut().len();
|
||||
|
||||
let false_offset = (after_jump as i32) - (after_jif as i32);
|
||||
self.patch_i32(skip_placeholder, false_offset);
|
||||
|
||||
self.emit_op(Op::PushFalse);
|
||||
|
||||
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||
self.patch_i32(end_placeholder, end_offset);
|
||||
}
|
||||
Or => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_op(Op::JumpIfTrue);
|
||||
let skip_placeholder = self.emit_i32_placeholder();
|
||||
let after_jit = self.ctx.get_code_mut().len();
|
||||
|
||||
self.emit_expr(rhs);
|
||||
self.emit_op(Op::Jump);
|
||||
let end_placeholder = self.emit_i32_placeholder();
|
||||
let after_jump = self.ctx.get_code_mut().len();
|
||||
|
||||
let true_offset = (after_jump as i32) - (after_jit as i32);
|
||||
self.patch_i32(skip_placeholder, true_offset);
|
||||
|
||||
self.emit_op(Op::PushTrue);
|
||||
|
||||
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||
self.patch_i32(end_placeholder, end_offset);
|
||||
}
|
||||
Impl => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_op(Op::JumpIfFalse);
|
||||
let skip_placeholder = self.emit_i32_placeholder();
|
||||
let after_jif = self.ctx.get_code_mut().len();
|
||||
|
||||
self.emit_expr(rhs);
|
||||
self.emit_op(Op::Jump);
|
||||
let end_placeholder = self.emit_i32_placeholder();
|
||||
let after_jump = self.ctx.get_code_mut().len();
|
||||
|
||||
let true_offset = (after_jump as i32) - (after_jif as i32);
|
||||
self.patch_i32(skip_placeholder, true_offset);
|
||||
|
||||
self.emit_op(Op::PushTrue);
|
||||
|
||||
let end_offset = (self.ctx.get_code_mut().len() as i32) - (after_jump as i32);
|
||||
self.patch_i32(end_placeholder, end_offset);
|
||||
}
|
||||
PipeL => {
|
||||
self.emit_expr(rhs);
|
||||
self.emit_expr(lhs);
|
||||
self.emit_op(Op::CallNoSpan);
|
||||
}
|
||||
PipeR => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_expr(rhs);
|
||||
self.emit_op(Op::CallNoSpan);
|
||||
}
|
||||
_ => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_expr(rhs);
|
||||
self.emit_op(match kind {
|
||||
Add => Op::OpAdd,
|
||||
Sub => Op::OpSub,
|
||||
Mul => Op::OpMul,
|
||||
Div => Op::OpDiv,
|
||||
Eq => Op::OpEq,
|
||||
Neq => Op::OpNeq,
|
||||
Lt => Op::OpLt,
|
||||
Gt => Op::OpGt,
|
||||
Leq => Op::OpLeq,
|
||||
Geq => Op::OpGeq,
|
||||
Con => Op::OpConcat,
|
||||
Upd => Op::OpUpdate,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_func(
|
||||
&mut self,
|
||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||
param: &Option<Param<'_>>,
|
||||
body: RawIrRef<'_>,
|
||||
) {
|
||||
let with_thunk_count = self.count_with_thunks(body);
|
||||
let total_slots = thunks.len() + with_thunk_count;
|
||||
|
||||
let all_thunks = self.collect_all_thunks(thunks, body);
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
let skip_patch = self.emit_jump_placeholder();
|
||||
let entry_point = self.ctx.get_code().len() as u32;
|
||||
self.push_scope(true, &thunk_ids);
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
self.emit_op(Op::Return);
|
||||
self.pop_scope();
|
||||
self.patch_jump_target(skip_patch);
|
||||
|
||||
if let Some(Param {
|
||||
required,
|
||||
optional,
|
||||
ellipsis,
|
||||
}) = param
|
||||
{
|
||||
self.emit_op(Op::MakePatternClosure);
|
||||
self.emit_u32(entry_point);
|
||||
self.emit_u32(total_slots as u32);
|
||||
self.emit_u16(required.len() as u16);
|
||||
self.emit_u16(optional.len() as u16);
|
||||
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
||||
|
||||
for &(sym, _) in required.iter() {
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
for &(sym, _) in optional.iter() {
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
for &(sym, span) in required.iter().chain(optional.iter()) {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_str_id(sym);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
} else {
|
||||
self.emit_op(Op::MakeClosure);
|
||||
self.emit_u32(entry_point);
|
||||
self.emit_u32(total_slots as u32);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_attrset(
|
||||
&mut self,
|
||||
stcs: &crate::ir::HashMap<'_, StringId, (RawIrRef<'_>, TextRange)>,
|
||||
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
|
||||
) {
|
||||
if stcs.is_empty() && dyns.is_empty() {
|
||||
self.emit_op(Op::MakeEmptyAttrs);
|
||||
return;
|
||||
}
|
||||
|
||||
let total = stcs.len() + dyns.len();
|
||||
self.emit_op(Op::MakeAttrs);
|
||||
self.emit_u32(total as u32);
|
||||
|
||||
for (&sym, &(val, span)) in stcs.iter() {
|
||||
self.emit_u8(KEY_STATIC);
|
||||
self.emit_str_id(sym);
|
||||
let val_operand = self.classify_value(val);
|
||||
self.emit_inline_operand(val_operand);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
for &(key, val, span) in dyns.iter() {
|
||||
self.emit_u8(KEY_DYNAMIC);
|
||||
let key_operand = self.classify_value(key);
|
||||
self.emit_inline_operand(key_operand);
|
||||
let val_operand = self.classify_value(val);
|
||||
self.emit_inline_operand(val_operand);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_select(
|
||||
&mut self,
|
||||
expr: RawIrRef<'_>,
|
||||
attrpath: &[Attr<RawIrRef<'_>>],
|
||||
default: Option<RawIrRef<'_>>,
|
||||
span: TextRange,
|
||||
) {
|
||||
self.emit_expr(expr);
|
||||
for attr in attrpath.iter() {
|
||||
if let Attr::Dynamic(expr, _) = *attr {
|
||||
self.emit_expr(expr);
|
||||
}
|
||||
}
|
||||
if let Some(default) = default {
|
||||
self.emit_expr(default);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::SelectDefault);
|
||||
self.emit_u16(attrpath.len() as u16);
|
||||
self.emit_u32(span_id);
|
||||
} else {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::Select);
|
||||
self.emit_u16(attrpath.len() as u16);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
for attr in attrpath.iter() {
|
||||
match *attr {
|
||||
Attr::Str(sym, _) => {
|
||||
self.emit_u8(KEY_STATIC);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
Attr::Dynamic(_, _) => {
|
||||
self.emit_u8(KEY_DYNAMIC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
||||
self.emit_expr(lhs);
|
||||
for attr in rhs.iter() {
|
||||
if let Attr::Dynamic(expr, _) = *attr {
|
||||
self.emit_expr(expr);
|
||||
}
|
||||
}
|
||||
self.emit_op(Op::HasAttr);
|
||||
self.emit_u16(rhs.len() as u16);
|
||||
for attr in rhs.iter() {
|
||||
match *attr {
|
||||
Attr::Str(sym, _) => {
|
||||
self.emit_u8(KEY_STATIC);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
Attr::Dynamic(_, _) => {
|
||||
self.emit_u8(KEY_DYNAMIC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_with(
|
||||
&mut self,
|
||||
namespace: RawIrRef<'_>,
|
||||
body: RawIrRef<'_>,
|
||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||
) {
|
||||
self.emit_expr(namespace);
|
||||
self.emit_op(Op::PushWith);
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
self.emit_op(Op::PopWith);
|
||||
}
|
||||
|
||||
fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(body);
|
||||
}
|
||||
}
|
||||
@@ -1,369 +0,0 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use colored::Colorize;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::codegen::{InstructionPtr, Op};
|
||||
|
||||
pub(crate) trait DisassemblerContext {
|
||||
fn lookup_string(&self, id: u32) -> &str;
|
||||
fn get_code(&self) -> &[u8];
|
||||
}
|
||||
|
||||
pub(crate) struct Disassembler<'a, Ctx> {
|
||||
code: &'a [u8],
|
||||
ctx: &'a Ctx,
|
||||
pc: usize,
|
||||
}
|
||||
|
||||
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self {
|
||||
Self {
|
||||
code: ctx.get_code(),
|
||||
ctx,
|
||||
pc: ip.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> u8 {
|
||||
let b = self.code[self.pc];
|
||||
self.pc += 1;
|
||||
b
|
||||
}
|
||||
|
||||
fn read_u16(&mut self) -> u16 {
|
||||
let bytes = self.code[self.pc..self.pc + 2]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pc += 2;
|
||||
u16::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> u32 {
|
||||
let bytes = self.code[self.pc..self.pc + 4]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pc += 4;
|
||||
u32::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_i32(&mut self) -> i32 {
|
||||
let bytes = self.code[self.pc..self.pc + 4]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pc += 4;
|
||||
i32::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_i64(&mut self) -> i64 {
|
||||
let bytes = self.code[self.pc..self.pc + 8]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pc += 8;
|
||||
i64::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_f64(&mut self) -> f64 {
|
||||
let bytes = self.code[self.pc..self.pc + 8]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pc += 8;
|
||||
f64::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
pub fn disassemble(&mut self) -> String {
|
||||
self.disassemble_impl(false)
|
||||
}
|
||||
|
||||
pub fn disassemble_colored(&mut self) -> String {
|
||||
self.disassemble_impl(true)
|
||||
}
|
||||
|
||||
fn disassemble_impl(&mut self, color: bool) -> String {
|
||||
let mut out = String::new();
|
||||
if color {
|
||||
let _ = writeln!(out, "{}", "=== Bytecode Disassembly ===".bold().white());
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"{} {}",
|
||||
"Length:".white(),
|
||||
format!("{} bytes", self.code.len()).cyan()
|
||||
);
|
||||
} else {
|
||||
let _ = writeln!(out, "=== Bytecode Disassembly ===");
|
||||
let _ = writeln!(out, "Length: {} bytes", self.code.len());
|
||||
}
|
||||
|
||||
while self.pc < self.code.len() {
|
||||
let start_pos = self.pc;
|
||||
let op_byte = self.read_u8();
|
||||
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
|
||||
|
||||
let bytes_slice = &self.code[start_pos + 1..self.pc];
|
||||
let mut chunks = bytes_slice.chunks(4);
|
||||
|
||||
let first_chunk = chunks.next().unwrap_or(&[]);
|
||||
let bytes_str = {
|
||||
let mut temp = format!("{:02x}", self.code[start_pos]);
|
||||
for b in first_chunk {
|
||||
let _ = write!(&mut temp, " {:02x}", b);
|
||||
}
|
||||
temp
|
||||
};
|
||||
|
||||
if color {
|
||||
let sep = if args.is_empty() { "" } else { " " };
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"{} {:<14} | {}{}{}",
|
||||
format!("{:04x}", start_pos).dimmed(),
|
||||
bytes_str.green(),
|
||||
mnemonic.yellow().bold(),
|
||||
sep,
|
||||
args.cyan()
|
||||
);
|
||||
} else {
|
||||
let op_str = if args.is_empty() {
|
||||
mnemonic.to_string()
|
||||
} else {
|
||||
format!("{} {}", mnemonic, args)
|
||||
};
|
||||
let _ = writeln!(out, "{:04x} {:<14} | {}", start_pos, bytes_str, op_str);
|
||||
}
|
||||
|
||||
for chunk in chunks {
|
||||
let bytes_str = {
|
||||
let mut temp = String::from(" ");
|
||||
for b in chunk {
|
||||
let _ = write!(&mut temp, " {:02x}", b);
|
||||
}
|
||||
temp
|
||||
};
|
||||
|
||||
let extra_width = if start_pos > 0 {
|
||||
start_pos.ilog2() >> 4
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if color {
|
||||
let _ = write!(out, " ");
|
||||
for _ in 0..extra_width {
|
||||
let _ = write!(out, " ");
|
||||
}
|
||||
let _ = writeln!(out, " {:<14} |", bytes_str.green());
|
||||
} else {
|
||||
let _ = write!(out, " ");
|
||||
for _ in 0..extra_width {
|
||||
let _ = write!(out, " ");
|
||||
}
|
||||
let _ = writeln!(out, " {:<14} |", bytes_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
|
||||
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
|
||||
|
||||
match op {
|
||||
Op::PushSmi => {
|
||||
let val = self.read_i32();
|
||||
("PushSmi", format!("{}", val))
|
||||
}
|
||||
Op::PushBigInt => {
|
||||
let val = self.read_i64();
|
||||
("PushBigInt", format!("{}", val))
|
||||
}
|
||||
Op::PushFloat => {
|
||||
let val = self.read_f64();
|
||||
("PushFloat", format!("{}", val))
|
||||
}
|
||||
Op::PushString => {
|
||||
let idx = self.read_u32();
|
||||
let s = self.ctx.lookup_string(idx);
|
||||
let len = s.len();
|
||||
let mut s_fmt = format!("{:?}", s);
|
||||
if s_fmt.len() > 60 {
|
||||
s_fmt.truncate(57);
|
||||
#[allow(clippy::unwrap_used)]
|
||||
write!(s_fmt, "...\" (total {len} bytes)").unwrap();
|
||||
}
|
||||
("PushString", format!("@{} {}", idx, s_fmt))
|
||||
}
|
||||
Op::PushNull => ("PushNull", String::new()),
|
||||
Op::PushTrue => ("PushTrue", String::new()),
|
||||
Op::PushFalse => ("PushFalse", String::new()),
|
||||
|
||||
Op::LoadLocal => {
|
||||
let idx = self.read_u32();
|
||||
("LoadLocal", format!("[{}]", idx))
|
||||
}
|
||||
Op::LoadOuter => {
|
||||
let depth = self.read_u8();
|
||||
let idx = self.read_u32();
|
||||
("LoadOuter", format!("depth={} [{}]", depth, idx))
|
||||
}
|
||||
Op::StoreLocal => {
|
||||
let idx = self.read_u32();
|
||||
("StoreLocal", format!("[{}]", idx))
|
||||
}
|
||||
Op::AllocLocals => {
|
||||
let count = self.read_u32();
|
||||
("AllocLocals", format!("count={}", count))
|
||||
}
|
||||
|
||||
Op::MakeThunk => {
|
||||
let offset = self.read_u32();
|
||||
("MakeThunk", format!("-> {:04x}", offset))
|
||||
}
|
||||
Op::MakeClosure => {
|
||||
let offset = self.read_u32();
|
||||
let slots = self.read_u32();
|
||||
("MakeClosure", format!("-> {:04x} slots={}", offset, slots))
|
||||
}
|
||||
Op::MakePatternClosure => {
|
||||
let offset = self.read_u32();
|
||||
let slots = self.read_u32();
|
||||
let req_count = self.read_u16();
|
||||
let opt_count = self.read_u16();
|
||||
let ellipsis = self.read_u8() != 0;
|
||||
|
||||
let mut arg_str = format!(
|
||||
"-> {:04x} slots={} req={} opt={} ...={})",
|
||||
offset, slots, req_count, opt_count, ellipsis
|
||||
);
|
||||
|
||||
arg_str.push_str(" Args=[");
|
||||
for _ in 0..req_count {
|
||||
let idx = self.read_u32();
|
||||
arg_str.push_str(&format!("Req({}) ", self.ctx.lookup_string(idx)));
|
||||
}
|
||||
for _ in 0..opt_count {
|
||||
let idx = self.read_u32();
|
||||
arg_str.push_str(&format!("Opt({}) ", self.ctx.lookup_string(idx)));
|
||||
}
|
||||
|
||||
let total_args = req_count + opt_count;
|
||||
for _ in 0..total_args {
|
||||
let _name_idx = self.read_u32();
|
||||
let _span_id = self.read_u32();
|
||||
}
|
||||
arg_str.push(']');
|
||||
|
||||
("MakePatternClosure", arg_str)
|
||||
}
|
||||
|
||||
Op::Call => {
|
||||
let span_id = self.read_u32();
|
||||
("Call", format!("span={}", span_id))
|
||||
}
|
||||
Op::CallNoSpan => ("CallNoSpan", String::new()),
|
||||
|
||||
Op::MakeAttrs => {
|
||||
let count = self.read_u32();
|
||||
("MakeAttrs", format!("size={}", count))
|
||||
}
|
||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||
|
||||
Op::Select => {
|
||||
let path_len = self.read_u16();
|
||||
let span_id = self.read_u32();
|
||||
("Select", format!("path_len={} span={}", path_len, span_id))
|
||||
}
|
||||
Op::SelectDefault => {
|
||||
let path_len = self.read_u16();
|
||||
let span_id = self.read_u32();
|
||||
(
|
||||
"SelectDefault",
|
||||
format!("path_len={} span={}", path_len, span_id),
|
||||
)
|
||||
}
|
||||
Op::HasAttr => {
|
||||
let path_len = self.read_u16();
|
||||
("HasAttr", format!("path_len={}", path_len))
|
||||
}
|
||||
|
||||
Op::MakeList => {
|
||||
let count = self.read_u32();
|
||||
("MakeList", format!("size={}", count))
|
||||
}
|
||||
Op::MakeEmptyList => ("MakeEmptyList", String::new()),
|
||||
|
||||
Op::OpAdd => ("OpAdd", String::new()),
|
||||
Op::OpSub => ("OpSub", String::new()),
|
||||
Op::OpMul => ("OpMul", String::new()),
|
||||
Op::OpDiv => ("OpDiv", String::new()),
|
||||
Op::OpEq => ("OpEq", String::new()),
|
||||
Op::OpNeq => ("OpNeq", String::new()),
|
||||
Op::OpLt => ("OpLt", String::new()),
|
||||
Op::OpGt => ("OpGt", String::new()),
|
||||
Op::OpLeq => ("OpLeq", String::new()),
|
||||
Op::OpGeq => ("OpGeq", String::new()),
|
||||
Op::OpConcat => ("OpConcat", String::new()),
|
||||
Op::OpUpdate => ("OpUpdate", String::new()),
|
||||
Op::OpNeg => ("OpNeg", String::new()),
|
||||
Op::OpNot => ("OpNot", String::new()),
|
||||
|
||||
Op::JumpIfFalse => {
|
||||
let offset = self.read_i32();
|
||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||
(
|
||||
"JumpIfFalse",
|
||||
format!("-> {:04x} offset={}", target, offset),
|
||||
)
|
||||
}
|
||||
Op::JumpIfTrue => {
|
||||
let offset = self.read_i32();
|
||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||
("JumpIfTrue", format!("-> {:04x} offset={}", target, offset))
|
||||
}
|
||||
Op::Jump => {
|
||||
let offset = self.read_i32();
|
||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||
("Jump", format!("-> {:04x} offset={}", target, offset))
|
||||
}
|
||||
|
||||
Op::ConcatStrings => {
|
||||
let count = self.read_u16();
|
||||
let force = self.read_u8();
|
||||
("ConcatStrings", format!("count={} force={}", count, force))
|
||||
}
|
||||
Op::ResolvePath => ("ResolvePath", String::new()),
|
||||
Op::Assert => {
|
||||
let raw_idx = self.read_u32();
|
||||
let span_id = self.read_u32();
|
||||
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
||||
}
|
||||
Op::PushWith => ("PushWith", String::new()),
|
||||
Op::PopWith => ("PopWith", String::new()),
|
||||
Op::WithLookup => {
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.lookup_string(idx);
|
||||
("WithLookup", format!("{:?}", name))
|
||||
}
|
||||
|
||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||
Op::LoadBuiltin => {
|
||||
let id = self.read_u8();
|
||||
("LoadBuiltin", format!("id={}", id))
|
||||
}
|
||||
Op::MkPos => {
|
||||
let span_id = self.read_u32();
|
||||
("MkPos", format!("id={}", span_id))
|
||||
}
|
||||
Op::LoadReplBinding => {
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.lookup_string(idx);
|
||||
("LoadReplBinding", format!("{:?}", name))
|
||||
}
|
||||
Op::LoadScopedBinding => {
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.lookup_string(idx);
|
||||
("LoadScopedBinding", format!("{:?}", name))
|
||||
}
|
||||
Op::Return => ("Return", String::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-260
@@ -1,260 +0,0 @@
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use bumpalo::boxed::Box;
|
||||
use bumpalo::collections::Vec;
|
||||
use gc_arena::Collect;
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use rnix::{TextRange, ast};
|
||||
use string_interner::symbol::SymbolU32;
|
||||
|
||||
use crate::runtime::BuiltinId;
|
||||
|
||||
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>);
|
||||
|
||||
impl<'id, 'ir> IrRef<'id, 'ir> {
|
||||
pub fn new(ir: &'ir GhostCell<'id, Ir<'ir, Self>>) -> Self {
|
||||
Self(ir)
|
||||
}
|
||||
|
||||
pub fn alloc(bump: &'ir Bump, ir: Ir<'ir, Self>) -> Self {
|
||||
Self(bump.alloc(GhostCell::new(ir)))
|
||||
}
|
||||
|
||||
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a Ir<'ir, Self> {
|
||||
self.0.borrow(token)
|
||||
}
|
||||
|
||||
/// Freeze a mutable IR reference into a read-only one, consuming the
|
||||
/// `GhostToken` to prevent any further mutation.
|
||||
///
|
||||
/// # Safety
|
||||
/// The transmute is sound because:
|
||||
/// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`
|
||||
/// - `IrRef<'id, 'ir>` is `#[repr(transparent)]` over
|
||||
/// `&'ir GhostCell<'id, Ir<'ir, Self>>`
|
||||
/// - `RawIrRef<'ir>` is `#[repr(transparent)]` over `&'ir Ir<'ir, Self>`
|
||||
/// - `Ir<'ir, Ref>` is `#[repr(C)]` and both ref types are pointer-sized
|
||||
///
|
||||
/// Consuming the `GhostToken` guarantees no `borrow_mut` calls can occur
|
||||
/// afterwards, so the shared `&Ir` references from `RawIrRef::Deref` can
|
||||
/// never alias with mutable references.
|
||||
pub fn freeze(self, _token: GhostToken<'id>) -> RawIrRef<'ir> {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RawIrRef<'ir>(pub &'ir Ir<'ir, Self>);
|
||||
|
||||
impl<'ir> Deref for RawIrRef<'ir> {
|
||||
type Target = Ir<'ir, RawIrRef<'ir>>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub enum Ir<'ir, Ref> {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Null,
|
||||
Str(Box<'ir, String>),
|
||||
AttrSet {
|
||||
stcs: HashMap<'ir, StringId, (Ref, TextRange)>,
|
||||
dyns: Vec<'ir, (Ref, Ref, TextRange)>,
|
||||
},
|
||||
List {
|
||||
items: Vec<'ir, Ref>,
|
||||
},
|
||||
Path(Ref),
|
||||
ConcatStrings {
|
||||
parts: Vec<'ir, Ref>,
|
||||
force_string: bool,
|
||||
},
|
||||
|
||||
// OPs
|
||||
UnOp {
|
||||
rhs: Ref,
|
||||
kind: UnOpKind,
|
||||
},
|
||||
BinOp {
|
||||
lhs: Ref,
|
||||
rhs: Ref,
|
||||
kind: BinOpKind,
|
||||
},
|
||||
HasAttr {
|
||||
lhs: Ref,
|
||||
rhs: Vec<'ir, Attr<Ref>>,
|
||||
},
|
||||
Select {
|
||||
expr: Ref,
|
||||
attrpath: Vec<'ir, Attr<Ref>>,
|
||||
default: Option<Ref>,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
// Conditionals
|
||||
If {
|
||||
cond: Ref,
|
||||
consq: Ref,
|
||||
alter: Ref,
|
||||
},
|
||||
Assert {
|
||||
assertion: Ref,
|
||||
expr: Ref,
|
||||
assertion_raw: String,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
With {
|
||||
namespace: Ref,
|
||||
body: Ref,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
WithLookup(StringId),
|
||||
|
||||
// Function related
|
||||
Func {
|
||||
body: Ref,
|
||||
param: Option<Param<'ir>>,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
Arg {
|
||||
layer: usize,
|
||||
},
|
||||
Call {
|
||||
func: Ref,
|
||||
arg: Ref,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
// Builtins
|
||||
Builtins,
|
||||
Builtin(BuiltinId),
|
||||
BuiltinConst(StringId),
|
||||
|
||||
// Misc
|
||||
TopLevel {
|
||||
body: Ref,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
},
|
||||
Thunk(ThunkId),
|
||||
CurPos(TextRange),
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ThunkId(pub usize);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Collect)]
|
||||
#[collect(require_static)]
|
||||
pub struct StringId(pub SymbolU32);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct SpanId(pub u32);
|
||||
|
||||
/// Represents a key in an attribute path.
|
||||
#[allow(unused)]
|
||||
#[derive(Debug)]
|
||||
pub enum Attr<Ref> {
|
||||
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||
/// Example: `attrs.${key}`
|
||||
Dynamic(Ref, TextRange),
|
||||
/// A static attribute key.
|
||||
/// Example: `attrs.key`
|
||||
Str(StringId, TextRange),
|
||||
}
|
||||
|
||||
/// The kinds of binary operations supported in Nix.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum BinOpKind {
|
||||
// Arithmetic
|
||||
Add,
|
||||
Sub,
|
||||
Div,
|
||||
Mul,
|
||||
|
||||
// Comparison
|
||||
Eq,
|
||||
Neq,
|
||||
Lt,
|
||||
Gt,
|
||||
Leq,
|
||||
Geq,
|
||||
|
||||
// Logical
|
||||
And,
|
||||
Or,
|
||||
Impl,
|
||||
|
||||
// Set/String/Path operations
|
||||
Con, // List concatenation (`++`)
|
||||
Upd, // AttrSet update (`//`)
|
||||
|
||||
// Not standard, but part of rnix AST
|
||||
PipeL,
|
||||
PipeR,
|
||||
}
|
||||
|
||||
impl From<ast::BinOpKind> for BinOpKind {
|
||||
fn from(op: ast::BinOpKind) -> Self {
|
||||
use BinOpKind::*;
|
||||
use ast::BinOpKind as kind;
|
||||
match op {
|
||||
kind::Concat => Con,
|
||||
kind::Update => Upd,
|
||||
kind::Add => Add,
|
||||
kind::Sub => Sub,
|
||||
kind::Mul => Mul,
|
||||
kind::Div => Div,
|
||||
kind::And => And,
|
||||
kind::Equal => Eq,
|
||||
kind::Implication => Impl,
|
||||
kind::Less => Lt,
|
||||
kind::LessOrEq => Leq,
|
||||
kind::More => Gt,
|
||||
kind::MoreOrEq => Geq,
|
||||
kind::NotEqual => Neq,
|
||||
kind::Or => Or,
|
||||
kind::PipeLeft => PipeL,
|
||||
kind::PipeRight => PipeR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kinds of unary operations.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum UnOpKind {
|
||||
Neg, // Negation (`-`)
|
||||
Not, // Logical not (`!`)
|
||||
}
|
||||
|
||||
impl From<ast::UnaryOpKind> for UnOpKind {
|
||||
fn from(value: ast::UnaryOpKind) -> Self {
|
||||
match value {
|
||||
ast::UnaryOpKind::Invert => UnOpKind::Not,
|
||||
ast::UnaryOpKind::Negate => UnOpKind::Neg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the parameters of a function.
|
||||
#[derive(Debug)]
|
||||
pub struct Param<'ir> {
|
||||
pub required: Vec<'ir, (StringId, TextRange)>,
|
||||
pub optional: Vec<'ir, (StringId, TextRange)>,
|
||||
pub ellipsis: bool,
|
||||
}
|
||||
+547
-14
@@ -1,22 +1,555 @@
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod boxing;
|
||||
pub mod error;
|
||||
pub mod logging;
|
||||
pub mod runtime;
|
||||
pub mod value;
|
||||
use bumpalo::Bump;
|
||||
use fix_codegen::{BytecodeContext, InstructionPtr};
|
||||
use fix_common::{StringId, Symbol};
|
||||
use fix_error::{Error, Result, Source};
|
||||
use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
|
||||
use fix_ir::{Ir, IrRef, RawIrRef, ThunkId};
|
||||
use fix_vm::{ForceMode, StaticValue, Vm, VmContext};
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use string_interner::DefaultStringInterner;
|
||||
|
||||
mod codegen;
|
||||
mod derivation;
|
||||
mod disassembler;
|
||||
mod downgrade;
|
||||
// mod fetcher;
|
||||
mod ir;
|
||||
mod nar;
|
||||
mod nix_utils;
|
||||
mod store;
|
||||
mod string_context;
|
||||
// mod nar;
|
||||
// mod nix_utils;
|
||||
// mod store;
|
||||
// mod string_context;
|
||||
mod derivation;
|
||||
pub mod logging;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
pub struct Evaluator {
|
||||
bytecode: Vec<u8>,
|
||||
constants: Constants,
|
||||
strings: DefaultStringInterner,
|
||||
|
||||
sources: Vec<Source>,
|
||||
spans: Vec<(usize, rnix::TextRange)>,
|
||||
// FIXME: remove?
|
||||
thunk_count: usize,
|
||||
|
||||
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
}
|
||||
|
||||
impl Default for Evaluator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluator {
|
||||
pub fn new() -> Self {
|
||||
let mut strings = DefaultStringInterner::new();
|
||||
let global_env = fix_ir::new_global_env(&mut strings);
|
||||
Self {
|
||||
sources: Vec::new(),
|
||||
spans: Vec::new(),
|
||||
strings,
|
||||
thunk_count: 0,
|
||||
bytecode: Vec::new(),
|
||||
constants: Constants::default(),
|
||||
|
||||
global_env,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval(&mut self, source: Source) -> Result<fix_common::Value> {
|
||||
self.do_eval(source, None, ForceMode::AsIs)
|
||||
}
|
||||
|
||||
pub fn eval_shallow(&mut self, source: Source) -> Result<fix_common::Value> {
|
||||
self.do_eval(source, None, ForceMode::Shallow)
|
||||
}
|
||||
|
||||
pub fn eval_deep(&mut self, source: Source) -> Result<fix_common::Value> {
|
||||
self.do_eval(source, None, ForceMode::Deep)
|
||||
}
|
||||
|
||||
pub fn eval_repl(
|
||||
&mut self,
|
||||
source: Source,
|
||||
scope: &HashSet<StringId>,
|
||||
) -> Result<fix_common::Value> {
|
||||
self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow)
|
||||
}
|
||||
|
||||
fn do_eval<'ctx>(
|
||||
&'ctx mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
force_mode: ForceMode,
|
||||
) -> Result<fix_common::Value> {
|
||||
let root = self.downgrade(source, extra_scope)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
let vm = Vm::new(self, ip, force_mode);
|
||||
vm.run()
|
||||
}
|
||||
|
||||
pub fn add_binding(
|
||||
&mut self,
|
||||
_ident: &str,
|
||||
_expr: &str,
|
||||
_scope: &mut HashSet<StringId>,
|
||||
) -> Result<fix_common::Value> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
||||
let root = self.downgrade(source, None)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
Ok(ip)
|
||||
}
|
||||
|
||||
fn downgrade_ctx<'a, 'bump, 'id>(
|
||||
&'a mut self,
|
||||
bump: &'bump Bump,
|
||||
token: GhostToken<'id>,
|
||||
extra_scope: Option<Scope<'a>>,
|
||||
) -> DowngradeCtx<'a, 'id, 'bump> {
|
||||
let Self {
|
||||
global_env,
|
||||
sources,
|
||||
thunk_count,
|
||||
strings,
|
||||
..
|
||||
} = self;
|
||||
DowngradeCtx {
|
||||
bump,
|
||||
token,
|
||||
strings,
|
||||
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,
|
||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
let root = rnix::Root::parse(&source.src);
|
||||
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
||||
|
||||
tracing::debug!("Downgrading Nix expression");
|
||||
let expr = root
|
||||
.tree()
|
||||
.expr()
|
||||
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
||||
let bump = Bump::new();
|
||||
GhostToken::new(|token| {
|
||||
let ir = self
|
||||
.downgrade_ctx(&bump, token, extra_scope)
|
||||
.downgrade_toplevel(expr)?;
|
||||
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
||||
Ok(OwnedIr { _bump: bump, ir })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl VmContext for &mut Evaluator {
|
||||
fn intern_string(&mut self, s: impl AsRef<str>) -> StringId {
|
||||
StringId(self.strings.get_or_intern(s))
|
||||
}
|
||||
fn resolve_string(&self, id: StringId) -> &str {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.strings.resolve(id.0).unwrap()
|
||||
}
|
||||
fn bytecode(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
fn get_const(&self, id: u32) -> StaticValue {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.constants.get(id).unwrap()
|
||||
}
|
||||
|
||||
fn compile(&mut self, _source: Source) {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Constants {
|
||||
data: Vec<StaticValue>,
|
||||
dedup: HashMap<u64, u32>,
|
||||
}
|
||||
|
||||
impl Constants {
|
||||
fn insert(&mut self, val: StaticValue) -> u32 {
|
||||
let bits = val.to_bits();
|
||||
*self.dedup.entry(bits).or_insert_with(|| {
|
||||
let idx = self.data.len() as u32;
|
||||
self.data.push(val);
|
||||
idx
|
||||
})
|
||||
}
|
||||
|
||||
fn get(&self, id: u32) -> Option<StaticValue> {
|
||||
self.data.get(id as usize).copied()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
||||
use rnix::ParseError::*;
|
||||
match error {
|
||||
Unexpected(range)
|
||||
| UnexpectedExtra(range)
|
||||
| UnexpectedWanted(_, range, _)
|
||||
| UnexpectedDoubleBind(range)
|
||||
| DuplicatedArgs(range, _) => Some(*range),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_parse_error<'a>(
|
||||
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
||||
source: Source,
|
||||
) -> Option<Box<Error>> {
|
||||
for err in errors {
|
||||
if let Some(span) = parse_error_span(err) {
|
||||
return Some(
|
||||
Error::parse_error(err.to_string())
|
||||
.with_source(source)
|
||||
.with_span(span),
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
struct DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
strings: &'ctx mut DefaultStringInterner,
|
||||
source: Source,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
with_scope_count: u32,
|
||||
arg_count: u32,
|
||||
thunk_count: &'ctx mut usize,
|
||||
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
||||
}
|
||||
|
||||
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
||||
!matches!(
|
||||
ir.borrow(token),
|
||||
Ir::Builtin(_)
|
||||
| Ir::Builtins
|
||||
| Ir::Int(_)
|
||||
| Ir::Float(_)
|
||||
| Ir::Bool(_)
|
||||
| Ir::Null
|
||||
| Ir::Str(_)
|
||||
| Ir::Thunk(_)
|
||||
)
|
||||
}
|
||||
|
||||
impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
fn new(
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
symbols: &'ctx mut DefaultStringInterner,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
thunk_count: &'ctx mut usize,
|
||||
source: Source,
|
||||
) -> Self {
|
||||
Self {
|
||||
bump,
|
||||
token,
|
||||
strings: symbols,
|
||||
source,
|
||||
scopes: std::iter::once(Scope::Global(global))
|
||||
.chain(extra_scope)
|
||||
.collect(),
|
||||
thunk_count,
|
||||
arg_count: 0,
|
||||
with_scope_count: 0,
|
||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||
}
|
||||
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
|
||||
if !should_thunk(ir, &self.token) {
|
||||
return ir;
|
||||
}
|
||||
|
||||
let id = ThunkId(*self.thunk_count);
|
||||
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
||||
self.thunk_scopes
|
||||
.last_mut()
|
||||
.expect("no active cache scope")
|
||||
.add_binding(id, ir, &self.token);
|
||||
IrRef::alloc(self.bump, Ir::Thunk(id))
|
||||
}
|
||||
|
||||
fn new_sym(&mut self, sym: String) -> StringId {
|
||||
StringId(self.strings.get_or_intern(sym))
|
||||
}
|
||||
|
||||
fn get_sym(&self, id: StringId) -> Symbol<'_> {
|
||||
self.strings.resolve(id.0).expect("no symbol found").into()
|
||||
}
|
||||
|
||||
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<IrRef<'id, 'ir>> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
match scope {
|
||||
&Scope::Global(global_scope) => {
|
||||
if let Some(expr) = global_scope.get(&sym) {
|
||||
let ir = match expr {
|
||||
Ir::Builtins => Ir::Builtins,
|
||||
Ir::Builtin(s) => Ir::Builtin(*s),
|
||||
Ir::Bool(b) => Ir::Bool(*b),
|
||||
Ir::Null => Ir::Null,
|
||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
||||
};
|
||||
return Ok(self.new_expr(ir));
|
||||
}
|
||||
}
|
||||
&Scope::Repl(repl_bindings) => {
|
||||
if repl_bindings.contains(&sym) {
|
||||
return Ok(self.new_expr(Ir::ReplBinding(sym)));
|
||||
}
|
||||
}
|
||||
Scope::ScopedImport(scoped_bindings) => {
|
||||
if scoped_bindings.contains(&sym) {
|
||||
return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
|
||||
}
|
||||
}
|
||||
Scope::Let(let_scope) => {
|
||||
if let Some(&expr) = let_scope.get(&sym) {
|
||||
return Ok(self.new_expr(Ir::Thunk(expr)));
|
||||
}
|
||||
}
|
||||
&Scope::Param {
|
||||
sym: param_sym,
|
||||
abs_layer,
|
||||
} => {
|
||||
if param_sym == sym {
|
||||
return Ok(self.new_expr(Ir::Arg {
|
||||
layer: self.thunk_scopes.len() - abs_layer,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.with_scope_count > 0 {
|
||||
Ok(self.new_expr(Ir::WithLookup(sym)))
|
||||
} else {
|
||||
Err(Error::downgrade_error(
|
||||
format!("'{}' not found", self.get_sym(sym)),
|
||||
self.get_current_source(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_source(&self) -> Source {
|
||||
self.source.clone()
|
||||
}
|
||||
|
||||
fn with_let_scope<F, R>(&mut self, keys: &[StringId], f: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
|
||||
{
|
||||
let base = *self.thunk_count;
|
||||
*self.thunk_count = self
|
||||
.thunk_count
|
||||
.checked_add(keys.len())
|
||||
.expect("thunk id overflow");
|
||||
let iter = keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(offset, &key)| (key, ThunkId(base + offset)));
|
||||
self.scopes.push(Scope::Let(iter.collect()));
|
||||
let (vals, ret) = {
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())?
|
||||
};
|
||||
assert_eq!(keys.len(), vals.len());
|
||||
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
||||
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn with_param_scope<F, R>(&mut self, sym: StringId, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.scopes.push(Scope::Param {
|
||||
sym,
|
||||
abs_layer: self.thunk_scopes.len(),
|
||||
});
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())
|
||||
}
|
||||
|
||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.with_scope_count += 1;
|
||||
let ret = f(self);
|
||||
self.with_scope_count -= 1;
|
||||
ret
|
||||
}
|
||||
|
||||
fn with_thunk_scope<F, R>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> (
|
||||
R,
|
||||
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||
)
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
||||
let ret = f(self);
|
||||
(
|
||||
ret,
|
||||
self.thunk_scopes
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings,
|
||||
)
|
||||
}
|
||||
|
||||
fn bump(&self) -> &'ir bumpalo::Bump {
|
||||
self.bump
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
||||
let body = root.downgrade(&mut self)?;
|
||||
let thunks = self
|
||||
.thunk_scopes
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings;
|
||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
||||
Ok(ir.freeze(self.token))
|
||||
}
|
||||
}
|
||||
|
||||
struct ThunkScope<'id, 'ir> {
|
||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||
}
|
||||
|
||||
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||
fn new_in(bump: &'ir Bump) -> Self {
|
||||
Self {
|
||||
bindings: bumpalo::collections::Vec::new_in(bump),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
|
||||
self.bindings.push((id, ir));
|
||||
}
|
||||
|
||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
||||
self.bindings.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
enum Scope<'ctx> {
|
||||
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
||||
Repl(&'ctx HashSet<StringId>),
|
||||
ScopedImport(HashSet<StringId>),
|
||||
Let(HashMap<StringId, ThunkId>),
|
||||
Param { sym: StringId, abs_layer: usize },
|
||||
}
|
||||
|
||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
|
||||
}
|
||||
|
||||
impl Drop for ScopeGuard<'_, '_, '_, '_> {
|
||||
fn drop(&mut self) {
|
||||
self.ctx.scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
|
||||
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
struct OwnedIr {
|
||||
_bump: Bump,
|
||||
ir: RawIrRef<'static>,
|
||||
}
|
||||
|
||||
impl OwnedIr {
|
||||
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||
Self {
|
||||
_bump: bump,
|
||||
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref(&self) -> RawIrRef<'_> {
|
||||
self.ir
|
||||
}
|
||||
}
|
||||
|
||||
impl BytecodeContext for Evaluator {
|
||||
fn intern_string(&mut self, s: &str) -> StringId {
|
||||
StringId(self.strings.get_or_intern(s))
|
||||
}
|
||||
|
||||
fn register_span(&mut self, range: rnix::TextRange) -> u32 {
|
||||
let id = self.spans.len();
|
||||
let source_id = self
|
||||
.sources
|
||||
.len()
|
||||
.checked_sub(1)
|
||||
.expect("current_source not set");
|
||||
self.spans.push((source_id, range));
|
||||
id as u32
|
||||
}
|
||||
|
||||
fn get_code(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
|
||||
fn get_code_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.bytecode
|
||||
}
|
||||
|
||||
fn add_constant(&mut self, val: fix_codegen::Const) -> u32 {
|
||||
use fix_codegen::Const::*;
|
||||
let val = match val {
|
||||
Smi(x) => StaticValue::new_inline(x),
|
||||
Float(x) => StaticValue::new_float(x),
|
||||
Bool(x) => StaticValue::new_inline(x),
|
||||
String(x) => StaticValue::new_inline(x),
|
||||
PrimOp { id, arity } => StaticValue::new_primop(id, arity),
|
||||
Null => StaticValue::default(),
|
||||
};
|
||||
self.constants.insert(val)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-18
@@ -3,8 +3,8 @@ use std::process::exit;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use fix::error::Source;
|
||||
use fix::runtime::Runtime;
|
||||
use fix::Evaluator;
|
||||
use fix_error::Source;
|
||||
use hashbrown::HashSet;
|
||||
use rustyline::DefaultEditor;
|
||||
use rustyline::error::ReadlineError;
|
||||
@@ -40,7 +40,7 @@ struct ExprSource {
|
||||
file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> {
|
||||
fn run_compile(eval: &mut Evaluator, src: ExprSource, _silent: bool) -> Result<()> {
|
||||
let src = if let Some(expr) = src.expr {
|
||||
Source::new_eval(expr)?
|
||||
} else if let Some(file) = src.file {
|
||||
@@ -48,11 +48,11 @@ fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<(
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
match runtime.compile_bytecode(src) {
|
||||
Ok(ip) => {
|
||||
if !silent {
|
||||
println!("{}", runtime.disassemble_colored(ip));
|
||||
}
|
||||
match eval.compile_bytecode(src) {
|
||||
Ok(_ip) => {
|
||||
// if !silent {
|
||||
// println!("{}", eval.disassemble_colored(ip));
|
||||
// }
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{:?}", miette::Report::new(*err));
|
||||
@@ -62,7 +62,7 @@ fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
||||
fn run_eval(eval: &mut Evaluator, src: ExprSource) -> Result<()> {
|
||||
let src = if let Some(expr) = src.expr {
|
||||
Source::new_eval(expr)?
|
||||
} else if let Some(file) = src.file {
|
||||
@@ -70,7 +70,7 @@ fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
match runtime.eval_deep(src) {
|
||||
match eval.eval_deep(src) {
|
||||
Ok(value) => {
|
||||
println!("{}", value.display_compat());
|
||||
}
|
||||
@@ -82,7 +82,7 @@ fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_repl(runtime: &mut Runtime) -> Result<()> {
|
||||
fn run_repl(eval: &mut Evaluator) -> Result<()> {
|
||||
let mut rl = DefaultEditor::new()?;
|
||||
let mut scope = HashSet::new();
|
||||
const RE: ere::Regex<3> = ere::compile_regex!("^[ \t]*([a-zA-Z_][a-zA-Z0-9_'-]*)[ \t]*(.*)$");
|
||||
@@ -101,20 +101,20 @@ fn run_repl(runtime: &mut Runtime) -> Result<()> {
|
||||
eprintln!("Error: missing expression after '='");
|
||||
continue;
|
||||
}
|
||||
match runtime.add_binding(ident, expr, &mut scope) {
|
||||
match eval.add_binding(ident, expr, &mut scope) {
|
||||
Ok(value) => println!("{} = {}", ident, value),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||
}
|
||||
} else {
|
||||
let src = Source::new_repl(line)?;
|
||||
match runtime.eval_repl(src, &scope) {
|
||||
match eval.eval_repl(src, &scope) {
|
||||
Ok(value) => println!("{value}"),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let src = Source::new_repl(line)?;
|
||||
match runtime.eval_repl(src, &scope) {
|
||||
match eval.eval_repl(src, &scope) {
|
||||
Ok(value) => println!("{value}"),
|
||||
Err(err) => eprintln!("{:?}", miette::Report::new(*err)),
|
||||
}
|
||||
@@ -141,11 +141,11 @@ fn main() -> Result<()> {
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut runtime = Runtime::new()?;
|
||||
let mut eval = Evaluator::new();
|
||||
|
||||
match cli.command {
|
||||
Command::Compile { source, silent } => run_compile(&mut runtime, source, silent),
|
||||
Command::Eval { source } => run_eval(&mut runtime, source),
|
||||
Command::Repl => run_repl(&mut runtime),
|
||||
Command::Compile { source, silent } => run_compile(&mut eval, source, silent),
|
||||
Command::Eval { source } => run_eval(&mut eval, source),
|
||||
Command::Repl => run_repl(&mut eval),
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use std::path::Path;
|
||||
use nix_nar::Encoder;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use fix_error::{Error, Result};
|
||||
|
||||
pub fn compute_nar_hash(path: &Path) -> Result<[u8; 32]> {
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
@@ -1,559 +0,0 @@
|
||||
use bumpalo::Bump;
|
||||
use gc_arena::{Arena, Rootable};
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rnix::TextRange;
|
||||
use string_interner::symbol::SymbolU32;
|
||||
use string_interner::{DefaultStringInterner, Symbol as _};
|
||||
|
||||
use crate::codegen::{BytecodeContext, InstructionPtr};
|
||||
use crate::disassembler::{Disassembler, DisassemblerContext};
|
||||
use crate::downgrade::{Downgrade as _, DowngradeContext};
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::ir::{Ir, IrRef, RawIrRef, StringId, ThunkId};
|
||||
use crate::runtime::builtins::init_builtins;
|
||||
use crate::runtime::value::StaticValue;
|
||||
use crate::runtime::vm::{ForceMode, new_gc_root};
|
||||
use crate::store::{DaemonStore, StoreConfig};
|
||||
use crate::value::Symbol;
|
||||
|
||||
mod builtins;
|
||||
mod stack;
|
||||
pub(crate) mod value;
|
||||
mod vm;
|
||||
pub(crate) use builtins::{BUILTINS, BuiltinId};
|
||||
use stack::Stack;
|
||||
use vm::{ErrorFrame, GcRoot};
|
||||
|
||||
pub struct Runtime {
|
||||
// global
|
||||
sources: Vec<Source>,
|
||||
spans: Vec<(usize, TextRange)>,
|
||||
store: DaemonStore,
|
||||
strings: DefaultStringInterner,
|
||||
// FIXME: remove?
|
||||
thunk_count: usize,
|
||||
bytecode: Vec<u8>,
|
||||
pub(crate) constants: Vec<StaticValue>,
|
||||
constant_dedup: HashMap<u64, u32>,
|
||||
|
||||
// downgrade
|
||||
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
|
||||
// eval
|
||||
pc: usize,
|
||||
fuel: usize,
|
||||
error_contexts: Stack<8192, ErrorFrame>,
|
||||
arena: Arena<Rootable![GcRoot<'_>]>,
|
||||
force_mode: ForceMode,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
const DEFAULT_FUEL_AMOUNT: usize = 2048;
|
||||
|
||||
pub fn new() -> Result<Self> {
|
||||
// HACK: what the heck...
|
||||
let mut global_env = HashMap::new();
|
||||
let mut strings = DefaultStringInterner::new();
|
||||
let arena = Arena::new(|mc| {
|
||||
let (root, env) = new_gc_root(mc, &mut strings);
|
||||
global_env = env;
|
||||
root
|
||||
});
|
||||
|
||||
let config = StoreConfig::from_env();
|
||||
let store = DaemonStore::connect(&config.daemon_socket)?;
|
||||
|
||||
Ok(Self {
|
||||
sources: Vec::new(),
|
||||
spans: Vec::new(),
|
||||
store,
|
||||
strings,
|
||||
thunk_count: 0,
|
||||
bytecode: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
constant_dedup: HashMap::new(),
|
||||
|
||||
global_env,
|
||||
|
||||
pc: 0,
|
||||
fuel: Self::DEFAULT_FUEL_AMOUNT,
|
||||
error_contexts: Stack::new(),
|
||||
arena,
|
||||
force_mode: ForceMode::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eval(&mut self, source: Source) -> Result<crate::value::Value> {
|
||||
self.do_eval(source, None, ForceMode::AsIs)
|
||||
}
|
||||
|
||||
pub fn eval_shallow(&mut self, source: Source) -> Result<crate::value::Value> {
|
||||
self.do_eval(source, None, ForceMode::Shallow)
|
||||
}
|
||||
|
||||
pub fn eval_deep(&mut self, source: Source) -> Result<crate::value::Value> {
|
||||
self.do_eval(source, None, ForceMode::Deep)
|
||||
}
|
||||
|
||||
pub fn eval_repl(
|
||||
&mut self,
|
||||
source: Source,
|
||||
scope: &HashSet<StringId>,
|
||||
) -> Result<crate::value::Value> {
|
||||
self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow)
|
||||
}
|
||||
|
||||
fn do_eval<'ctx>(
|
||||
&'ctx mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
force_mode: ForceMode,
|
||||
) -> Result<crate::value::Value> {
|
||||
let root = self.downgrade(source, extra_scope)?;
|
||||
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
|
||||
self.run(ip, force_mode)
|
||||
}
|
||||
|
||||
pub fn add_binding(
|
||||
&mut self,
|
||||
_ident: &str,
|
||||
_expr: &str,
|
||||
_scope: &mut HashSet<StringId>,
|
||||
) -> Result<crate::value::Value> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn compile_bytecode(&mut self, source: Source) -> Result<InstructionPtr> {
|
||||
let root = self.downgrade(source, None)?;
|
||||
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
|
||||
Ok(ip)
|
||||
}
|
||||
|
||||
pub fn disassemble_colored(&mut self, ip: InstructionPtr) -> String {
|
||||
Disassembler::new(ip, self).disassemble_colored()
|
||||
}
|
||||
|
||||
pub fn disassemble(&mut self, ip: InstructionPtr) -> String {
|
||||
Disassembler::new(ip, self).disassemble()
|
||||
}
|
||||
|
||||
fn downgrade_ctx<'a, 'bump, 'id>(
|
||||
&'a mut self,
|
||||
bump: &'bump Bump,
|
||||
token: GhostToken<'id>,
|
||||
extra_scope: Option<Scope<'a>>,
|
||||
) -> DowngradeCtx<'a, 'id, 'bump> {
|
||||
let Runtime {
|
||||
global_env,
|
||||
sources,
|
||||
thunk_count,
|
||||
strings,
|
||||
..
|
||||
} = self;
|
||||
DowngradeCtx {
|
||||
bump,
|
||||
token,
|
||||
strings,
|
||||
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,
|
||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
let root = rnix::Root::parse(&source.src);
|
||||
handle_parse_error(root.errors(), source).map_or(Ok(()), Err)?;
|
||||
|
||||
tracing::debug!("Downgrading Nix expression");
|
||||
let expr = root
|
||||
.tree()
|
||||
.expr()
|
||||
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
|
||||
let bump = Bump::new();
|
||||
GhostToken::new(|token| {
|
||||
let ir = self
|
||||
.downgrade_ctx(&bump, token, extra_scope)
|
||||
.downgrade_toplevel(expr)?;
|
||||
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
|
||||
Ok(OwnedIr { _bump: bump, ir })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
||||
use rnix::ParseError::*;
|
||||
match error {
|
||||
Unexpected(range)
|
||||
| UnexpectedExtra(range)
|
||||
| UnexpectedWanted(_, range, _)
|
||||
| UnexpectedDoubleBind(range)
|
||||
| DuplicatedArgs(range, _) => Some(*range),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_parse_error<'a>(
|
||||
errors: impl IntoIterator<Item = &'a rnix::ParseError>,
|
||||
source: Source,
|
||||
) -> Option<Box<Error>> {
|
||||
for err in errors {
|
||||
if let Some(span) = parse_error_span(err) {
|
||||
return Some(
|
||||
Error::parse_error(err.to_string())
|
||||
.with_source(source)
|
||||
.with_span(span),
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
struct DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
strings: &'ctx mut DefaultStringInterner,
|
||||
source: Source,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
with_scope_count: u32,
|
||||
arg_count: u32,
|
||||
thunk_count: &'ctx mut usize,
|
||||
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
|
||||
}
|
||||
|
||||
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
|
||||
!matches!(
|
||||
ir.borrow(token),
|
||||
Ir::Builtin(_)
|
||||
| Ir::Builtins
|
||||
| Ir::Int(_)
|
||||
| Ir::Float(_)
|
||||
| Ir::Bool(_)
|
||||
| Ir::Null
|
||||
| Ir::Str(_)
|
||||
| Ir::Thunk(_)
|
||||
)
|
||||
}
|
||||
|
||||
impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
fn new(
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
symbols: &'ctx mut DefaultStringInterner,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
thunk_count: &'ctx mut usize,
|
||||
source: Source,
|
||||
) -> Self {
|
||||
Self {
|
||||
bump,
|
||||
token,
|
||||
strings: symbols,
|
||||
source,
|
||||
scopes: std::iter::once(Scope::Global(global))
|
||||
.chain(extra_scope)
|
||||
.collect(),
|
||||
thunk_count,
|
||||
arg_count: 0,
|
||||
with_scope_count: 0,
|
||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||
}
|
||||
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
|
||||
if !should_thunk(ir, &self.token) {
|
||||
return ir;
|
||||
}
|
||||
|
||||
let id = ThunkId(*self.thunk_count);
|
||||
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
|
||||
self.thunk_scopes
|
||||
.last_mut()
|
||||
.expect("no active cache scope")
|
||||
.add_binding(id, ir, &self.token);
|
||||
IrRef::alloc(self.bump, Ir::Thunk(id))
|
||||
}
|
||||
|
||||
fn new_sym(&mut self, sym: String) -> StringId {
|
||||
StringId(self.strings.get_or_intern(sym))
|
||||
}
|
||||
|
||||
fn get_sym(&self, id: StringId) -> Symbol<'_> {
|
||||
self.strings.resolve(id.0).expect("no symbol found").into()
|
||||
}
|
||||
|
||||
fn lookup(&self, sym: StringId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
match scope {
|
||||
&Scope::Global(global_scope) => {
|
||||
if let Some(expr) = global_scope.get(&sym) {
|
||||
let ir = match expr {
|
||||
Ir::Builtins => Ir::Builtins,
|
||||
Ir::Builtin(s) => Ir::Builtin(*s),
|
||||
Ir::Bool(b) => Ir::Bool(*b),
|
||||
Ir::Null => Ir::Null,
|
||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
||||
};
|
||||
return Ok(self.new_expr(ir));
|
||||
}
|
||||
}
|
||||
&Scope::Repl(repl_bindings) => {
|
||||
if repl_bindings.contains(&sym) {
|
||||
return Ok(self.new_expr(Ir::ReplBinding(sym)));
|
||||
}
|
||||
}
|
||||
Scope::ScopedImport(scoped_bindings) => {
|
||||
if scoped_bindings.contains(&sym) {
|
||||
return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
|
||||
}
|
||||
}
|
||||
Scope::Let(let_scope) => {
|
||||
if let Some(&expr) = let_scope.get(&sym) {
|
||||
return Ok(self.new_expr(Ir::Thunk(expr)));
|
||||
}
|
||||
}
|
||||
&Scope::Param {
|
||||
sym: param_sym,
|
||||
abs_layer,
|
||||
} => {
|
||||
if param_sym == sym {
|
||||
return Ok(self.new_expr(Ir::Arg {
|
||||
layer: self.thunk_scopes.len() - abs_layer,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.with_scope_count > 0 {
|
||||
Ok(self.new_expr(Ir::WithLookup(sym)))
|
||||
} else {
|
||||
Err(Error::downgrade_error(
|
||||
format!("'{}' not found", self.get_sym(sym)),
|
||||
self.get_current_source(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_source(&self) -> Source {
|
||||
self.source.clone()
|
||||
}
|
||||
|
||||
fn with_let_scope<F, R>(&mut self, keys: &[StringId], f: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
|
||||
{
|
||||
let base = *self.thunk_count;
|
||||
*self.thunk_count = self
|
||||
.thunk_count
|
||||
.checked_add(keys.len())
|
||||
.expect("thunk id overflow");
|
||||
let iter = keys.iter().enumerate().map(|(offset, &key)| {
|
||||
(
|
||||
key,
|
||||
ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }),
|
||||
)
|
||||
});
|
||||
self.scopes.push(Scope::Let(iter.collect()));
|
||||
let (vals, ret) = {
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())?
|
||||
};
|
||||
assert_eq!(keys.len(), vals.len());
|
||||
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
||||
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn with_param_scope<F, R>(&mut self, sym: StringId, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.scopes.push(Scope::Param {
|
||||
sym,
|
||||
abs_layer: self.thunk_scopes.len(),
|
||||
});
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())
|
||||
}
|
||||
|
||||
fn with_with_scope<F, R>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.with_scope_count += 1;
|
||||
let ret = f(self);
|
||||
self.with_scope_count -= 1;
|
||||
ret
|
||||
}
|
||||
|
||||
fn with_thunk_scope<F, R>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> (
|
||||
R,
|
||||
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||
)
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
||||
let ret = f(self);
|
||||
(
|
||||
ret,
|
||||
self.thunk_scopes
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings,
|
||||
)
|
||||
}
|
||||
|
||||
fn bump(&self) -> &'ir bumpalo::Bump {
|
||||
self.bump
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
|
||||
let body = root.downgrade(&mut self)?;
|
||||
let thunks = self
|
||||
.thunk_scopes
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings;
|
||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
||||
Ok(ir.freeze(self.token))
|
||||
}
|
||||
}
|
||||
|
||||
struct ThunkScope<'id, 'ir> {
|
||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
|
||||
}
|
||||
|
||||
impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||
fn new_in(bump: &'ir Bump) -> Self {
|
||||
Self {
|
||||
bindings: bumpalo::collections::Vec::new_in(bump),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
|
||||
self.bindings.push((id, ir));
|
||||
}
|
||||
|
||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
|
||||
self.bindings.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
enum Scope<'ctx> {
|
||||
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
||||
Repl(&'ctx HashSet<StringId>),
|
||||
ScopedImport(HashSet<StringId>),
|
||||
Let(HashMap<StringId, ThunkId>),
|
||||
Param { sym: StringId, abs_layer: usize },
|
||||
}
|
||||
|
||||
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
|
||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
|
||||
}
|
||||
|
||||
impl Drop for ScopeGuard<'_, '_, '_, '_> {
|
||||
fn drop(&mut self) {
|
||||
self.ctx.scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
|
||||
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
|
||||
self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
struct OwnedIr {
|
||||
_bump: Bump,
|
||||
ir: RawIrRef<'static>,
|
||||
}
|
||||
|
||||
impl OwnedIr {
|
||||
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
|
||||
Self {
|
||||
_bump: bump,
|
||||
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref(&self) -> RawIrRef<'_> {
|
||||
self.ir
|
||||
}
|
||||
}
|
||||
|
||||
impl BytecodeContext for Runtime {
|
||||
fn intern_string(&mut self, s: &str) -> StringId {
|
||||
StringId(self.strings.get_or_intern(s))
|
||||
}
|
||||
|
||||
fn register_span(&mut self, range: TextRange) -> u32 {
|
||||
let id = self.spans.len();
|
||||
let source_id = self
|
||||
.sources
|
||||
.len()
|
||||
.checked_sub(1)
|
||||
.expect("current_source not set");
|
||||
self.spans.push((source_id, range));
|
||||
id as u32
|
||||
}
|
||||
|
||||
fn get_code(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
|
||||
fn get_code_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.bytecode
|
||||
}
|
||||
|
||||
fn add_constant(&mut self, val: StaticValue) -> u32 {
|
||||
let bits = val.to_bits();
|
||||
*self.constant_dedup.entry(bits).or_insert_with(|| {
|
||||
let idx = self.constants.len() as u32;
|
||||
self.constants.push(val);
|
||||
idx
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DisassemblerContext for Runtime {
|
||||
fn lookup_string(&self, id: u32) -> &str {
|
||||
self.strings
|
||||
.resolve(SymbolU32::try_from_usize(id as usize).expect("invalid string id"))
|
||||
.expect("string not found")
|
||||
}
|
||||
fn get_code(&self) -> &[u8] {
|
||||
&self.bytecode
|
||||
}
|
||||
}
|
||||
|
||||
struct WellKnownSymbols {
|
||||
// TODO:
|
||||
}
|
||||
@@ -1,410 +0,0 @@
|
||||
use gc_arena::{Collect, Gc, Mutation, RefLock};
|
||||
use hashbrown::HashMap;
|
||||
use num_enum::TryFromPrimitive;
|
||||
use smallvec::SmallVec;
|
||||
use string_interner::DefaultStringInterner;
|
||||
use string_interner::symbol::SymbolU32;
|
||||
|
||||
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(crate) const BUILTINS: &[(&str, u8)] = &[
|
||||
$(($name, $arity),)*
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)]
|
||||
#[repr(u8)]
|
||||
#[collect(require_static)]
|
||||
pub(crate) 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),
|
||||
("break", Break, 1),
|
||||
("__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),
|
||||
}
|
||||
|
||||
/// 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 init_builtins<'gc>(
|
||||
mc: &Mutation<'gc>,
|
||||
strings: &mut DefaultStringInterner,
|
||||
) -> (
|
||||
HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
Value<'gc>,
|
||||
) {
|
||||
let mut global_env = HashMap::new();
|
||||
let builtins_sym = StringId(strings.get_or_intern("builtins"));
|
||||
global_env.insert(builtins_sym, Ir::Builtins);
|
||||
let mut entries = SmallVec::with_capacity(BUILTINS.len());
|
||||
|
||||
for (idx, &(name, arity)) in BUILTINS.iter().enumerate() {
|
||||
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
||||
if let Some(local_name) = name.strip_prefix("__") {
|
||||
// scoped builtins
|
||||
let name = StringId(strings.get_or_intern(name));
|
||||
let local_name = StringId(strings.get_or_intern(local_name));
|
||||
global_env.insert(name, Ir::Builtin(id));
|
||||
entries.push((local_name, Value::new_inline(PrimOp { id, arity })));
|
||||
} else {
|
||||
// global builtins
|
||||
let name = StringId(strings.get_or_intern(name));
|
||||
global_env.insert(name, Ir::Builtin(id));
|
||||
entries.push((name, Value::new_inline(PrimOp { id, arity })));
|
||||
}
|
||||
}
|
||||
|
||||
let consts = [
|
||||
(
|
||||
"__currentSystem",
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))),
|
||||
// FIXME: detect currentSystem
|
||||
Value::new_gc(Gc::new(mc, NixString::new("x86_64-linux"))),
|
||||
),
|
||||
("__langVersion", Ir::Int(6), Value::new_inline(6i32)),
|
||||
(
|
||||
"__nixVersion",
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))),
|
||||
Value::new_gc(Gc::new(mc, NixString::new("2.24.0"))),
|
||||
),
|
||||
(
|
||||
"__storeDir",
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("storeDir"))),
|
||||
Value::new_gc(Gc::new(mc, NixString::new("/nix/store"))),
|
||||
),
|
||||
(
|
||||
"__nixPath",
|
||||
Ir::BuiltinConst(StringId(strings.get_or_intern("nixPath"))),
|
||||
// FIXME: get from config
|
||||
Value::new_gc(Gc::new(
|
||||
mc,
|
||||
List {
|
||||
inner: SmallVec::new(),
|
||||
},
|
||||
)),
|
||||
),
|
||||
("null", Ir::Null, Value::new_inline(Null)),
|
||||
("true", Ir::Bool(true), Value::new_inline(true)),
|
||||
("false", Ir::Bool(false), Value::new_inline(false)),
|
||||
];
|
||||
|
||||
for (idx, &(name, arity)) in BUILTINS.iter().enumerate() {
|
||||
let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible");
|
||||
if let Some(local_name) = name.strip_prefix("__") {
|
||||
// scoped builtins
|
||||
let name = StringId(strings.get_or_intern(name));
|
||||
let local_name = StringId(strings.get_or_intern(local_name));
|
||||
global_env.insert(name, Ir::Builtin(id));
|
||||
entries.push((local_name, Value::new_inline(PrimOp { id, arity })));
|
||||
} else {
|
||||
// global builtins
|
||||
let name = StringId(strings.get_or_intern(name));
|
||||
global_env.insert(name, Ir::Builtin(id));
|
||||
entries.push((name, Value::new_inline(PrimOp { id, arity })));
|
||||
}
|
||||
}
|
||||
for (name, ir, val) in consts {
|
||||
if let Some(local_name) = name.strip_prefix("__") {
|
||||
let name = StringId(strings.get_or_intern(name));
|
||||
let local_name = StringId(strings.get_or_intern(local_name));
|
||||
global_env.insert(name, ir);
|
||||
entries.push((local_name, val));
|
||||
} else {
|
||||
let name = StringId(strings.get_or_intern(name));
|
||||
global_env.insert(name, ir);
|
||||
entries.push((name, val));
|
||||
}
|
||||
}
|
||||
|
||||
// Self-reference thunk for builtins.builtins
|
||||
let self_ref_thunk: Gc<'gc, Thunk<'gc>> = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
|
||||
let sym = strings.get_or_intern("builtins");
|
||||
entries.push((StringId(sym), Value::new_gc(self_ref_thunk)));
|
||||
|
||||
entries.sort_by_key(|(k, _)| *k);
|
||||
|
||||
let builtins_set = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
let builtins_val = Value::new_gc(builtins_set);
|
||||
|
||||
(global_env, builtins_val)
|
||||
}
|
||||
|
||||
pub(super) type PrimOpArgs<'gc> = [Value<'gc>; 3];
|
||||
pub(super) type PrimOpStrictArgs<'gc> = [StrictValue<'gc>; 3];
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(super) struct WellKnownSymbols {
|
||||
abort: SymbolU32,
|
||||
add: SymbolU32,
|
||||
addErrorContext: SymbolU32,
|
||||
all: SymbolU32,
|
||||
any: SymbolU32,
|
||||
appendContext: SymbolU32,
|
||||
attrNames: SymbolU32,
|
||||
attrValues: SymbolU32,
|
||||
baseNameOf: SymbolU32,
|
||||
bitAnd: SymbolU32,
|
||||
bitOr: SymbolU32,
|
||||
bitXor: SymbolU32,
|
||||
catAttrs: SymbolU32,
|
||||
ceil: SymbolU32,
|
||||
compareVersions: SymbolU32,
|
||||
concatLists: SymbolU32,
|
||||
concatMap: SymbolU32,
|
||||
concatStringsSep: SymbolU32,
|
||||
convertHash: SymbolU32,
|
||||
deepSeq: SymbolU32,
|
||||
derivation: SymbolU32,
|
||||
derivationStrict: SymbolU32,
|
||||
dirOf: SymbolU32,
|
||||
div: SymbolU32,
|
||||
elem: SymbolU32,
|
||||
elemAt: SymbolU32,
|
||||
fetchGit: SymbolU32,
|
||||
fetchMercurial: SymbolU32,
|
||||
fetchTarball: SymbolU32,
|
||||
fetchTree: SymbolU32,
|
||||
fetchurl: SymbolU32,
|
||||
filter: SymbolU32,
|
||||
filterSource: SymbolU32,
|
||||
findFile: SymbolU32,
|
||||
floor: SymbolU32,
|
||||
foldl: SymbolU32,
|
||||
fromJSON: SymbolU32,
|
||||
fromTOML: SymbolU32,
|
||||
functionArgs: SymbolU32,
|
||||
genList: SymbolU32,
|
||||
genericClosure: SymbolU32,
|
||||
getAttr: SymbolU32,
|
||||
getContext: SymbolU32,
|
||||
getEnv: SymbolU32,
|
||||
groupBy: SymbolU32,
|
||||
hasAttr: SymbolU32,
|
||||
hasContext: SymbolU32,
|
||||
hashFile: SymbolU32,
|
||||
hashString: SymbolU32,
|
||||
head: SymbolU32,
|
||||
import: SymbolU32,
|
||||
intersectAttrs: SymbolU32,
|
||||
isAttrs: SymbolU32,
|
||||
isBool: SymbolU32,
|
||||
isFloat: SymbolU32,
|
||||
isFunction: SymbolU32,
|
||||
isInt: SymbolU32,
|
||||
isList: SymbolU32,
|
||||
isNull: SymbolU32,
|
||||
isPath: SymbolU32,
|
||||
isString: SymbolU32,
|
||||
length: SymbolU32,
|
||||
lessThan: SymbolU32,
|
||||
listToAttrs: SymbolU32,
|
||||
map: SymbolU32,
|
||||
mapAttrs: SymbolU32,
|
||||
match_: SymbolU32,
|
||||
mul: SymbolU32,
|
||||
null: SymbolU32,
|
||||
parseDrvName: SymbolU32,
|
||||
partition: SymbolU32,
|
||||
path: SymbolU32,
|
||||
pathExists: SymbolU32,
|
||||
placeholder: SymbolU32,
|
||||
readDir: SymbolU32,
|
||||
readFile: SymbolU32,
|
||||
readFileType: SymbolU32,
|
||||
removeAttrs: SymbolU32,
|
||||
replaceStrings: SymbolU32,
|
||||
scopedImport: SymbolU32,
|
||||
seq: SymbolU32,
|
||||
sort: SymbolU32,
|
||||
split: SymbolU32,
|
||||
splitVersion: SymbolU32,
|
||||
storePath: SymbolU32,
|
||||
stringLength: SymbolU32,
|
||||
sub: SymbolU32,
|
||||
substring: SymbolU32,
|
||||
tail: SymbolU32,
|
||||
throw: SymbolU32,
|
||||
toFile: SymbolU32,
|
||||
toJSON: SymbolU32,
|
||||
toPath: SymbolU32,
|
||||
toString: SymbolU32,
|
||||
toXML: SymbolU32,
|
||||
trace: SymbolU32,
|
||||
tryEval: SymbolU32,
|
||||
typeOf: SymbolU32,
|
||||
unsafeDiscardStringContext: SymbolU32,
|
||||
unsafeGetAttrPos: SymbolU32,
|
||||
warn: SymbolU32,
|
||||
zipAttrsWith: SymbolU32,
|
||||
break_: SymbolU32,
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
drvAttrs@{
|
||||
outputs ? [ "out" ],
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
strict = derivationStrict drvAttrs;
|
||||
|
||||
commonAttrs =
|
||||
drvAttrs
|
||||
// (builtins.listToAttrs outputsList)
|
||||
// {
|
||||
all = map (x: x.value) outputsList;
|
||||
inherit drvAttrs;
|
||||
};
|
||||
|
||||
outputToAttrListElement = outputName: {
|
||||
name = outputName;
|
||||
value = commonAttrs // {
|
||||
outPath = builtins.getAttr outputName strict;
|
||||
drvPath = strict.drvPath;
|
||||
type = "derivation";
|
||||
inherit outputName;
|
||||
};
|
||||
};
|
||||
|
||||
outputsList = map outputToAttrListElement outputs;
|
||||
|
||||
in
|
||||
(builtins.head outputsList).value
|
||||
@@ -1,76 +0,0 @@
|
||||
{
|
||||
system ? "", # obsolete
|
||||
url,
|
||||
hash ? "", # an SRI hash
|
||||
|
||||
# Legacy hash specification
|
||||
md5 ? "",
|
||||
sha1 ? "",
|
||||
sha256 ? "",
|
||||
sha512 ? "",
|
||||
outputHash ?
|
||||
if hash != "" then
|
||||
hash
|
||||
else if sha512 != "" then
|
||||
sha512
|
||||
else if sha1 != "" then
|
||||
sha1
|
||||
else if md5 != "" then
|
||||
md5
|
||||
else
|
||||
sha256,
|
||||
outputHashAlgo ?
|
||||
if hash != "" then
|
||||
""
|
||||
else if sha512 != "" then
|
||||
"sha512"
|
||||
else if sha1 != "" then
|
||||
"sha1"
|
||||
else if md5 != "" then
|
||||
"md5"
|
||||
else
|
||||
"sha256",
|
||||
|
||||
executable ? false,
|
||||
unpack ? false,
|
||||
name ? baseNameOf (toString url),
|
||||
# still translates to __impure to trigger derivationStrict error checks.
|
||||
impure ? false,
|
||||
}:
|
||||
|
||||
derivation (
|
||||
{
|
||||
builder = "builtin:fetchurl";
|
||||
|
||||
# New-style output content requirements.
|
||||
outputHashMode = if unpack || executable then "recursive" else "flat";
|
||||
|
||||
inherit
|
||||
name
|
||||
url
|
||||
executable
|
||||
unpack
|
||||
;
|
||||
|
||||
system = "builtin";
|
||||
|
||||
# No need to double the amount of network traffic
|
||||
preferLocalBuild = true;
|
||||
|
||||
impureEnvVars = [
|
||||
# We borrow these environment variables from the caller to allow
|
||||
# easy proxy configuration. This is impure, but a fixed-output
|
||||
# derivation like fetchurl is allowed to do so since its result is
|
||||
# by definition pure.
|
||||
"http_proxy"
|
||||
"https_proxy"
|
||||
"ftp_proxy"
|
||||
"all_proxy"
|
||||
"no_proxy"
|
||||
];
|
||||
|
||||
# To make "nix-prefetch-url" work.
|
||||
urls = [ url ];
|
||||
}
|
||||
// (if impure then { __impure = true; } else { inherit outputHashAlgo outputHash; })
|
||||
)
|
||||
@@ -1,88 +0,0 @@
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
use gc_arena::Collect;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
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 = T::NEEDS_TRACE;
|
||||
fn trace<U: gc_arena::collect::Trace<'gc>>(&self, cc: &mut U) {
|
||||
for item in self.inner[..self.len].iter() {
|
||||
unsafe {
|
||||
item.assume_init_ref().trace(cc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T> Default for Stack<N, T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T> Stack<N, T> {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
inner: Box::new([const { MaybeUninit::uninit() }; N]),
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn push(&mut self, val: T) -> Result<(), T> {
|
||||
if self.len == N {
|
||||
return Err(val);
|
||||
}
|
||||
unsafe {
|
||||
self.inner.get_unchecked_mut(self.len).write(val);
|
||||
}
|
||||
self.len += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn pop(&mut self) -> Option<T> {
|
||||
if self.len == 0 {
|
||||
return None;
|
||||
}
|
||||
let ret = unsafe {
|
||||
self.inner
|
||||
.get_unchecked_mut(self.len - 1)
|
||||
.assume_init_read()
|
||||
};
|
||||
self.len -= 1;
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
pub(super) fn tos(&self) -> Option<&T> {
|
||||
if self.len == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { self.inner.get_unchecked(self.len - 1).assume_init_ref() })
|
||||
}
|
||||
|
||||
pub(super) fn tos_mut(&mut self) -> Option<&mut T> {
|
||||
if self.len == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { self.inner.get_unchecked_mut(self.len - 1).assume_init_mut() })
|
||||
}
|
||||
|
||||
pub(super) fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
pub(super) fn pop_n<const M: usize>(&mut self, n: usize) -> SmallVec<[T; M]> {
|
||||
assert!(n <= self.len, "pop_n: not enough items on stack");
|
||||
let mut result = SmallVec::new();
|
||||
let start = self.len - n;
|
||||
for i in start..self.len {
|
||||
result.push(unsafe { self.inner.get_unchecked(i).assume_init_read() });
|
||||
}
|
||||
self.len = start;
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -1,609 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::ops::Deref;
|
||||
|
||||
use gc_arena::collect::Trace;
|
||||
use gc_arena::{Collect, Gc, Mutation, RefLock};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use sealed::sealed;
|
||||
use smallvec::SmallVec;
|
||||
use string_interner::Symbol;
|
||||
use string_interner::symbol::SymbolU32;
|
||||
|
||||
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||
use crate::ir::StringId;
|
||||
use crate::runtime::builtins::BuiltinId;
|
||||
|
||||
#[sealed]
|
||||
/// # Safety
|
||||
/// 1. TAG must be unique among all implementors.
|
||||
/// 2. TAG must be within 1..=7
|
||||
pub(crate) unsafe trait Storable {
|
||||
const TAG: (bool, u8);
|
||||
}
|
||||
pub(crate) trait InlineStorable: Storable + RawStore {}
|
||||
pub(crate) trait GcStorable: Storable {}
|
||||
|
||||
macro_rules! define_value_types {
|
||||
(
|
||||
inline { $($itype:ty => $itag:expr, $iname:literal;)* }
|
||||
gc { $($gtype:ty => $gtag:expr, $gname:literal;)* }
|
||||
) => {
|
||||
$(
|
||||
#[sealed]
|
||||
unsafe impl Storable for $itype {
|
||||
const TAG: (bool, u8) = $itag;
|
||||
}
|
||||
impl InlineStorable for $itype {}
|
||||
)*
|
||||
$(
|
||||
#[sealed]
|
||||
unsafe impl Storable for $gtype {
|
||||
const TAG: (bool, u8) = $gtag;
|
||||
}
|
||||
impl GcStorable for $gtype {}
|
||||
)*
|
||||
|
||||
const _: () = assert!(size_of::<Value<'static>>() == 8);
|
||||
$(const _: () = assert!(size_of::<$itype>() <= 6);)*
|
||||
$(const _: () = { let (_, val) = $itag; assert!(val >= 1 && val <= 7); };)*
|
||||
$(const _: () = { let (_, val) = $gtag; assert!(val >= 1 && val <= 7); };)*
|
||||
|
||||
const _: () = {
|
||||
let tags: &[(bool, u8)] = &[$($itag),*, $($gtag),*];
|
||||
let mut mask_false: u8 = 0;
|
||||
let mut mask_true: u8 = 0;
|
||||
let mut i = 0;
|
||||
while i < tags.len() {
|
||||
let (neg, val) = tags[i];
|
||||
let bit = 1 << val;
|
||||
if neg {
|
||||
assert!(mask_true & bit == 0, "duplicate true tag id");
|
||||
mask_true |= bit;
|
||||
} else {
|
||||
assert!(mask_false & bit == 0, "duplicate false tag id");
|
||||
mask_false |= bit;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe impl<'gc> Collect<'gc> for Value<'gc> {
|
||||
const NEEDS_TRACE: bool = true;
|
||||
fn trace<T: Trace<'gc>>(&self, cc: &mut T) {
|
||||
let Some(tag) = self.raw.tag() else { return };
|
||||
match tag.neg_val() {
|
||||
$(<$gtype as Storable>::TAG => unsafe {
|
||||
self.load_gc::<$gtype>().trace(cc)
|
||||
},)*
|
||||
$(<$itype as Storable>::TAG => (),)*
|
||||
(neg, val) => unreachable!("invalid tag: neg={neg}, val={val}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Value<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.tag() {
|
||||
None => write!(f, "Float({:?})", unsafe {
|
||||
self.raw.float().unwrap_unchecked()
|
||||
}),
|
||||
$(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe {
|
||||
self.as_inline::<$itype>().unwrap_unchecked()
|
||||
}),)*
|
||||
$(Some(<$gtype as Storable>::TAG) =>
|
||||
write!(f, "{}(..)", $gname),)*
|
||||
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for StaticValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.tag() {
|
||||
None => write!(f, "Float({:?})", unsafe {
|
||||
self.raw.float().unwrap_unchecked()
|
||||
}),
|
||||
$(Some(<$itype as Storable>::TAG) => write!(f, "{}({:?})", $iname, unsafe {
|
||||
self.as_inline::<$itype>().unwrap_unchecked()
|
||||
}),)*
|
||||
Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_value_types! {
|
||||
inline {
|
||||
i32 => (false, 1), "SmallInt";
|
||||
bool => (false, 2), "Bool";
|
||||
Null => (false, 3), "Null";
|
||||
StringId => (false, 4), "SmallString";
|
||||
PrimOp => (false, 5), "PrimOp";
|
||||
}
|
||||
gc {
|
||||
i64 => (false, 6), "BigInt";
|
||||
NixString => (false, 7), "String";
|
||||
AttrSet<'_> => (true, 1), "AttrSet";
|
||||
List<'_> => (true, 2), "List";
|
||||
Thunk<'_> => (true, 3), "Thunk";
|
||||
Closure<'_> => (true, 4), "Closure";
|
||||
PrimOpApp<'_> => (true, 5), "PrimOpApp";
|
||||
}
|
||||
}
|
||||
|
||||
/// # 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 Default for Value<'_> {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::new_inline(Null)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline(always)]
|
||||
fn from_raw_value(rv: RawValue) -> Self {
|
||||
Self {
|
||||
raw: RawBox::from_value(rv),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a GC pointer from a value with a negative tag.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The value must actually store a `Gc<'gc, T>` with the matching type.
|
||||
#[inline(always)]
|
||||
unsafe fn load_gc<T: GcStorable>(&self) -> Gc<'gc, T> {
|
||||
unsafe {
|
||||
let rv = self.raw.value().unwrap_unchecked();
|
||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
||||
Gc::from_ptr(ptr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `(negative, val)` tag, or `None` for a float.
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> Option<(bool, u8)> {
|
||||
self.raw.tag().map(|t| t.neg_val())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn new_float(val: f64) -> Self {
|
||||
Self {
|
||||
raw: RawBox::from_float(val),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
|
||||
Self::from_raw_value(RawValue::store(
|
||||
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
|
||||
val,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_gc<T: GcStorable>(gc: Gc<'gc, T>) -> Self {
|
||||
let ptr = Gc::as_ptr(gc);
|
||||
Self::from_raw_value(RawValue::store(
|
||||
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
|
||||
ptr,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn make_int(val: i64, mc: &Mutation<'gc>) -> Self {
|
||||
if val >= i32::MIN as i64 && val <= i32::MAX as i64 {
|
||||
Value::new_inline(val as i32)
|
||||
} else {
|
||||
Value::new_gc(Gc::new(mc, val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn is_float(&self) -> bool {
|
||||
self.raw.is_float()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is<T: Storable>(&self) -> bool {
|
||||
self.tag() == Some(T::TAG)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn as_float(&self) -> Option<f64> {
|
||||
self.raw.float().copied()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_inline<T: InlineStorable>(&self) -> Option<T> {
|
||||
if self.is::<T>() {
|
||||
Some(unsafe {
|
||||
let rv = self.raw.value().unwrap_unchecked();
|
||||
T::from_val(rv)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_gc<T: GcStorable>(&self) -> Option<Gc<'gc, T>> {
|
||||
if self.is::<T>() {
|
||||
Some(unsafe {
|
||||
let rv = self.raw.value().unwrap_unchecked();
|
||||
let ptr: *const T = <*const T as RawStore>::from_val(rv);
|
||||
Gc::from_ptr(ptr)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn restrict(self) -> Option<StrictValue<'gc>> {
|
||||
if !self.is::<Thunk<'gc>>() {
|
||||
Some(StrictValue(self))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct StaticValue {
|
||||
raw: RawBox,
|
||||
}
|
||||
|
||||
impl Default for StaticValue {
|
||||
#[inline(always)]
|
||||
fn default() -> Self {
|
||||
Self::new_inline(Null)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticValue> for Value<'_> {
|
||||
#[inline]
|
||||
fn from(value: StaticValue) -> Self {
|
||||
Self {
|
||||
raw: value.raw,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticValue {
|
||||
#[inline(always)]
|
||||
fn from_raw_value(rv: RawValue) -> Self {
|
||||
Self {
|
||||
raw: RawBox::from_value(rv),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_bits(self) -> u64 {
|
||||
self.raw.to_bits()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_float(val: f64) -> Self {
|
||||
Self {
|
||||
raw: RawBox::from_float(val),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
|
||||
Self::from_raw_value(RawValue::store(
|
||||
unsafe { RawTag::new_unchecked(T::TAG.0, T::TAG.1) },
|
||||
val,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_float(&self) -> bool {
|
||||
self.raw.is_float()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is<T: InlineStorable>(&self) -> bool {
|
||||
self.tag() == Some(T::TAG)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_float(&self) -> Option<f64> {
|
||||
self.raw.float().copied()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_inline<T: InlineStorable>(&self) -> Option<T> {
|
||||
if self.is::<T>() {
|
||||
Some(unsafe {
|
||||
let rv = self.raw.value().unwrap_unchecked();
|
||||
T::from_val(rv)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `(negative, val)` tag, or `None` for a float.
|
||||
#[inline(always)]
|
||||
fn tag(&self) -> Option<(bool, u8)> {
|
||||
self.raw.tag().map(|t| t.neg_val())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct Null;
|
||||
impl RawStore for Null {
|
||||
fn to_val(self, value: &mut RawValue) {
|
||||
value.set_data([0; 6]);
|
||||
}
|
||||
fn from_val(_: &RawValue) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl RawStore for StringId {
|
||||
fn to_val(self, value: &mut RawValue) {
|
||||
(self.0.to_usize() as u32).to_val(value);
|
||||
}
|
||||
fn from_val(value: &RawValue) -> Self {
|
||||
Self(
|
||||
SymbolU32::try_from_usize(u32::from_val(value) as usize)
|
||||
.expect("failed to read StringId from Value"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Heap-allocated Nix string.
|
||||
///
|
||||
/// Stored on the GC heap via `Gc<'gc, NixString>`. The string data itself
|
||||
/// lives in a standard `Box<str>` owned by this struct; the GC only manages
|
||||
/// the outer allocation.
|
||||
#[derive(Collect)]
|
||||
#[collect(require_static)]
|
||||
pub(crate) struct NixString {
|
||||
data: Box<str>,
|
||||
// TODO: string context for derivation dependency tracking
|
||||
}
|
||||
|
||||
impl NixString {
|
||||
pub(crate) fn new(s: impl Into<Box<str>>) -> Self {
|
||||
Self { data: s.into() }
|
||||
}
|
||||
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for NixString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.data, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug, Default)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct AttrSet<'gc> {
|
||||
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) unsafe fn from_sorted_unchecked(
|
||||
entries: SmallVec<[(StringId, Value<'gc>); 4]>,
|
||||
) -> Self {
|
||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||
Self { entries }
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, key: StringId) -> Option<Value<'gc>> {
|
||||
self.entries
|
||||
.binary_search_by_key(&key, |(k, _)| *k)
|
||||
.ok()
|
||||
.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()
|
||||
}
|
||||
|
||||
pub(crate) fn merge(&self, other: &Self, mc: &Mutation<'gc>) -> Gc<'gc, Self> {
|
||||
use std::cmp::Ordering::*;
|
||||
|
||||
debug_assert!(self.entries.is_sorted_by_key(|(key, _)| *key));
|
||||
debug_assert!(other.entries.is_sorted_by_key(|(key, _)| *key));
|
||||
|
||||
let mut entries = SmallVec::new();
|
||||
let mut i = 0;
|
||||
let mut j = 0;
|
||||
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]);
|
||||
i += 1;
|
||||
}
|
||||
Greater => {
|
||||
entries.push(other.entries[j]);
|
||||
j += 1;
|
||||
}
|
||||
Equal => {
|
||||
entries.push(other.entries[j]);
|
||||
i += 1;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.extend(other.entries[j..].iter().cloned());
|
||||
entries.extend(self.entries[i..].iter().cloned());
|
||||
|
||||
debug_assert!(entries.is_sorted_by_key(|(key, _)| *key));
|
||||
|
||||
Gc::new(mc, AttrSet { entries })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug, Default)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct List<'gc> {
|
||||
pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
|
||||
}
|
||||
impl<'gc> Deref for List<'gc> {
|
||||
type Target = SmallVec<[Value<'gc>; 4]>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) enum ThunkState<'gc> {
|
||||
Pending {
|
||||
ip: usize,
|
||||
env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
},
|
||||
Apply {
|
||||
func: Value<'gc>,
|
||||
arg: Value<'gc>,
|
||||
},
|
||||
Blackhole,
|
||||
Evaluated(Value<'gc>),
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct Env<'gc> {
|
||||
pub(crate) locals: SmallVec<[Value<'gc>; 4]>,
|
||||
pub(crate) prev: Option<Gc<'gc, RefLock<Env<'gc>>>>,
|
||||
}
|
||||
|
||||
impl<'gc> Env<'gc> {
|
||||
pub(crate) fn empty() -> Self {
|
||||
Env {
|
||||
locals: SmallVec::new(),
|
||||
prev: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_arg(
|
||||
arg: Value<'gc>,
|
||||
n_locals: u32,
|
||||
prev: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
) -> Self {
|
||||
let mut locals = smallvec::smallvec![Value::default(); 1 + n_locals as usize];
|
||||
locals[0] = arg;
|
||||
Env {
|
||||
locals,
|
||||
prev: Some(prev),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct Closure<'gc> {
|
||||
pub(crate) ip: u32,
|
||||
pub(crate) n_locals: u32,
|
||||
pub(crate) env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
pub(crate) pattern: Option<Gc<'gc, PatternInfo>>,
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(require_static)]
|
||||
pub(crate) struct PatternInfo {
|
||||
pub(crate) required: SmallVec<[StringId; 4]>,
|
||||
pub(crate) optional: SmallVec<[StringId; 4]>,
|
||||
pub(crate) ellipsis: bool,
|
||||
pub(crate) param_spans: Box<[(StringId, u32)]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Collect)]
|
||||
#[collect(require_static)]
|
||||
pub(crate) struct PrimOp {
|
||||
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 as u8, self.arity]);
|
||||
}
|
||||
fn from_val(value: &RawValue) -> Self {
|
||||
let [.., id, arity] = *value.data();
|
||||
Self {
|
||||
id: BuiltinId::try_from_primitive(id).expect("invalid BuiltinId"),
|
||||
arity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(crate) struct PrimOpApp<'gc> {
|
||||
pub(crate) primop: PrimOp,
|
||||
pub(crate) args: SmallVec<[Value<'gc>; 2]>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct StrictValue<'gc>(Value<'gc>);
|
||||
|
||||
impl<'gc> StrictValue<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn relax(self) -> Value<'gc> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Deref for StrictValue<'gc> {
|
||||
type Target = Value<'gc>;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Value<'gc> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for StrictValue<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'gc> Collect<'gc> for StrictValue<'gc> {
|
||||
const NEEDS_TRACE: bool = true;
|
||||
fn trace<T: gc_arena::collect::Trace<'gc>>(&self, cc: &mut T) {
|
||||
self.0.trace(cc);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,381 +0,0 @@
|
||||
use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
||||
use core::hash::Hash;
|
||||
use core::ops::Deref;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use derive_more::{Constructor, IsVariant, Unwrap};
|
||||
|
||||
/// Represents a Nix symbol, which is used as a key in attribute sets.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
||||
pub struct Symbol<'a>(Cow<'a, str>);
|
||||
|
||||
pub type StaticSymbol = Symbol<'static>;
|
||||
|
||||
impl From<String> for Symbol<'_> {
|
||||
fn from(value: String) -> Self {
|
||||
Symbol(Cow::Owned(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Symbol<'a> {
|
||||
fn from(value: &'a str) -> Self {
|
||||
Symbol(Cow::Borrowed(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a string slice as a Nix symbol, quoting it if necessary.
|
||||
pub fn format_symbol<'a>(sym: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
|
||||
let sym = sym.into();
|
||||
if Symbol::NORMAL_REGEX.test(&sym) {
|
||||
sym
|
||||
} else {
|
||||
Cow::Owned(escape_quote_string(&sym))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Symbol<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
if self.normal() {
|
||||
write!(f, "{}", self.0)
|
||||
} else {
|
||||
write!(f, "{}", escape_quote_string(&self.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Symbol<'_> {
|
||||
const NORMAL_REGEX: ere::Regex<1> = ere::compile_regex!("^[a-zA-Z_][a-zA-Z0-9_'-]*$");
|
||||
/// Checks if the symbol is a "normal" identifier that doesn't require quotes.
|
||||
fn normal(&self) -> bool {
|
||||
Self::NORMAL_REGEX.test(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Symbol<'_> {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a Nix attribute set, which is a map from symbols to values.
|
||||
#[derive(Constructor, Default, Clone, PartialEq)]
|
||||
pub struct AttrSet {
|
||||
data: BTreeMap<StaticSymbol, Value>,
|
||||
}
|
||||
|
||||
impl AttrSet {
|
||||
/// Gets a value by key (string or Symbol).
|
||||
pub fn get<'a, 'sym: 'a>(&'a self, key: impl Into<Symbol<'sym>>) -> Option<&'a Value> {
|
||||
self.data.get(&key.into())
|
||||
}
|
||||
|
||||
/// Checks if a key exists in the attribute set.
|
||||
pub fn contains_key<'a, 'sym: 'a>(&'a self, key: impl Into<Symbol<'sym>>) -> bool {
|
||||
self.data.contains_key(&key.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AttrSet {
|
||||
type Target = BTreeMap<StaticSymbol, Value>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
impl DerefMut for AttrSet {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AttrSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
write!(f, "{{")?;
|
||||
for (k, v) in self.data.iter() {
|
||||
write!(f, " {k:?} = ")?;
|
||||
match v {
|
||||
List(_) => write!(f, "[ ... ];")?,
|
||||
AttrSet(_) => write!(f, "{{ ... }};")?,
|
||||
v => write!(f, "{v:?};")?,
|
||||
}
|
||||
}
|
||||
write!(f, " }}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AttrSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
if self.data.len() > 1 {
|
||||
writeln!(f, "{{")?;
|
||||
for (k, v) in self.data.iter() {
|
||||
write!(f, " {k} = ")?;
|
||||
match v {
|
||||
List(_) => writeln!(f, "[ ... ];")?,
|
||||
AttrSet(_) => writeln!(f, "{{ ... }};")?,
|
||||
v => writeln!(f, "{v};")?,
|
||||
}
|
||||
}
|
||||
write!(f, "}}")
|
||||
} else {
|
||||
write!(f, "{{")?;
|
||||
for (k, v) in self.data.iter() {
|
||||
write!(f, " {k} = ")?;
|
||||
match v {
|
||||
List(_) => write!(f, "[ ... ];")?,
|
||||
AttrSet(_) => write!(f, "{{ ... }};")?,
|
||||
v => write!(f, "{v};")?,
|
||||
}
|
||||
}
|
||||
write!(f, " }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AttrSet {
|
||||
pub fn display_compat(&self) -> AttrSetCompatDisplay<'_> {
|
||||
AttrSetCompatDisplay(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AttrSetCompatDisplay<'a>(&'a AttrSet);
|
||||
|
||||
impl Display for AttrSetCompatDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(f, "{{")?;
|
||||
for (k, v) in self.0.data.iter() {
|
||||
write!(f, " {k} = {};", v.display_compat())?;
|
||||
}
|
||||
write!(f, " }}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a Nix list, which is a vector of values.
|
||||
#[derive(Constructor, Default, Clone, Debug, PartialEq)]
|
||||
pub struct List {
|
||||
data: Vec<Value>,
|
||||
}
|
||||
|
||||
impl Deref for List {
|
||||
type Target = Vec<Value>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
impl DerefMut for List {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for List {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
if self.data.len() > 1 {
|
||||
writeln!(f, "[")?;
|
||||
for v in self.data.iter() {
|
||||
match v {
|
||||
List(_) => writeln!(f, " [ ... ]")?,
|
||||
AttrSet(_) => writeln!(f, " {{ ... }}")?,
|
||||
v => writeln!(f, " {v}")?,
|
||||
}
|
||||
}
|
||||
write!(f, "]")
|
||||
} else {
|
||||
write!(f, "[ ")?;
|
||||
for v in self.data.iter() {
|
||||
match v {
|
||||
List(_) => write!(f, "[ ... ] ")?,
|
||||
AttrSet(_) => write!(f, "{{ ... }} ")?,
|
||||
v => write!(f, "{v} ")?,
|
||||
}
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl List {
|
||||
pub fn display_compat(&self) -> ListCompatDisplay<'_> {
|
||||
ListCompatDisplay(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListCompatDisplay<'a>(&'a List);
|
||||
|
||||
impl Display for ListCompatDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
write!(f, "[ ")?;
|
||||
for v in self.0.data.iter() {
|
||||
write!(f, "{} ", v.display_compat())?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents any possible Nix value that can be returned from an evaluation.
|
||||
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
/// An integer value.
|
||||
Int(i64),
|
||||
/// An floating-point value.
|
||||
Float(f64),
|
||||
/// An boolean value.
|
||||
Bool(bool),
|
||||
/// An null value.
|
||||
Null,
|
||||
/// A string value.
|
||||
String(String),
|
||||
/// A path value (absolute path string).
|
||||
Path(String),
|
||||
/// An attribute set.
|
||||
AttrSet(AttrSet),
|
||||
/// A list.
|
||||
List(List),
|
||||
/// A thunk, representing a delayed computation.
|
||||
Thunk,
|
||||
/// A function (lambda).
|
||||
Func,
|
||||
/// A primitive (built-in) operation.
|
||||
PrimOp(String),
|
||||
/// A partially applied primitive operation.
|
||||
PrimOpApp(String),
|
||||
/// A marker for a value that has been seen before during serialization, to break cycles.
|
||||
/// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
|
||||
Repeated,
|
||||
}
|
||||
|
||||
fn escape_quote_string(s: &str) -> String {
|
||||
let mut ret = String::with_capacity(s.len() + 2);
|
||||
ret.push('"');
|
||||
let mut iter = s.chars().peekable();
|
||||
while let Some(c) = iter.next() {
|
||||
match c {
|
||||
'\\' => ret.push_str("\\\\"),
|
||||
'"' => ret.push_str("\\\""),
|
||||
'\n' => ret.push_str("\\n"),
|
||||
'\r' => ret.push_str("\\r"),
|
||||
'\t' => ret.push_str("\\t"),
|
||||
'$' if iter.peek() == Some(&'{') => ret.push_str("\\$"),
|
||||
c => ret.push(c),
|
||||
}
|
||||
}
|
||||
ret.push('"');
|
||||
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() {
|
||||
return write!(f, "{x}");
|
||||
}
|
||||
if x == 0.0 {
|
||||
return if x.is_sign_negative() {
|
||||
write!(f, "-0")
|
||||
} else {
|
||||
write!(f, "0")
|
||||
};
|
||||
}
|
||||
|
||||
let precision: i32 = 6;
|
||||
let exp = x.abs().log10().floor() as i32;
|
||||
|
||||
let formatted = if exp >= -4 && exp < precision {
|
||||
let decimal_places = (precision - 1 - exp) as usize;
|
||||
format!("{x:.decimal_places$}")
|
||||
} else {
|
||||
let sig_digits = (precision - 1) as usize;
|
||||
let s = format!("{x:.sig_digits$e}");
|
||||
let (mantissa, exp_part) = s
|
||||
.split_once('e')
|
||||
.expect("scientific notation must contain 'e'");
|
||||
let (sign, digits) = if let Some(d) = exp_part.strip_prefix('-') {
|
||||
("-", d)
|
||||
} else if let Some(d) = exp_part.strip_prefix('+') {
|
||||
("+", d)
|
||||
} else {
|
||||
("+", exp_part)
|
||||
};
|
||||
if digits.len() < 2 {
|
||||
format!("{mantissa}e{sign}0{digits}")
|
||||
} else {
|
||||
format!("{mantissa}e{sign}{digits}")
|
||||
}
|
||||
};
|
||||
|
||||
if formatted.contains('.') {
|
||||
if let Some(e_pos) = formatted.find('e') {
|
||||
let trimmed = formatted[..e_pos]
|
||||
.trim_end_matches('0')
|
||||
.trim_end_matches('.');
|
||||
write!(f, "{}{}", trimmed, &formatted[e_pos..])
|
||||
} else {
|
||||
let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
|
||||
write!(f, "{trimmed}")
|
||||
}
|
||||
} else {
|
||||
write!(f, "{formatted}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
match self {
|
||||
&Int(x) => write!(f, "{x}"),
|
||||
&Float(x) => fmt_nix_float(f, x),
|
||||
&Bool(x) => write!(f, "{x}"),
|
||||
Null => write!(f, "null"),
|
||||
String(x) => write!(f, "{}", escape_quote_string(x)),
|
||||
Path(x) => write!(f, "{x}"),
|
||||
AttrSet(x) => write!(f, "{x}"),
|
||||
List(x) => write!(f, "{x}"),
|
||||
Thunk => write!(f, "«code»"),
|
||||
Func => write!(f, "«lambda»"),
|
||||
PrimOp(name) => write!(f, "«primop {name}»"),
|
||||
PrimOpApp(name) => write!(f, "«partially applied primop {name}»"),
|
||||
Repeated => write!(f, "«repeated»"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn display_compat(&self) -> ValueCompatDisplay<'_> {
|
||||
ValueCompatDisplay(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ValueCompatDisplay<'a>(&'a Value);
|
||||
|
||||
impl Display for ValueCompatDisplay<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
match self.0 {
|
||||
&Int(x) => write!(f, "{x}"),
|
||||
&Float(x) => fmt_nix_float(f, x),
|
||||
&Bool(x) => write!(f, "{x}"),
|
||||
Null => write!(f, "null"),
|
||||
String(x) => write!(f, "{}", escape_quote_string(x)),
|
||||
Path(x) => write!(f, "{x}"),
|
||||
AttrSet(x) => write!(f, "{}", x.display_compat()),
|
||||
List(x) => write!(f, "{}", x.display_compat()),
|
||||
Thunk => write!(f, "«thunk»"),
|
||||
Func => write!(f, "<LAMBDA>"),
|
||||
PrimOp(_) => write!(f, "<PRIMOP>"),
|
||||
PrimOpApp(_) => write!(f, "<PRIMOP-APP>"),
|
||||
Repeated => write!(f, "«repeated»"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user