This commit is contained in:
2026-04-04 11:34:54 +08:00
parent ee54ab8895
commit b1b886229b
34 changed files with 1811 additions and 4222 deletions
+17 -63
View File
@@ -6,20 +6,9 @@ edition = "2024"
[dependencies]
mimalloc = "0.1"
tokio = { version = "1.41", features = [
"rt-multi-thread",
"sync",
"net",
"io-util",
] }
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = [
"wire",
"async",
] }
# REPL
anyhow = "1.0"
rustyline = "17.0"
rustyline = "18.0"
# CLI
clap = { version = "4", features = ["derive"] }
@@ -28,67 +17,32 @@ clap = { version = "4", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
derive_more = { version = "2", features = ["full"] }
# Error Reporting
thiserror = "2"
miette = { version = "7.4", features = ["fancy"] }
hashbrown = "0.16"
string-interner = "0.19"
bumpalo = { version = "3.20", features = [
"allocator-api2",
"boxed",
"collections",
] }
# Data Structure
hashbrown = { workspace = true }
smallvec = { workspace = true }
string-interner = { workspace = true }
rust-embed = "8.11"
# Memory Management
bumpalo = { workspace = true }
regex = "1.11"
rnix = { workspace = true }
nix-nar = "0.3"
sha2 = "0.10"
sha1 = "0.10"
md5 = "0.8"
hex = "0.4"
ere = { workspace = true }
ghost-cell = { workspace = true }
base64 = "0.22"
reqwest = { version = "0.13", features = [
"blocking",
"rustls",
], default-features = false }
tar = "0.4"
flate2 = "1.0"
xz2 = "0.1"
bzip2 = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# spec 1.0.0
toml = "=0.9.9"
dirs = "6.0"
tempfile = "3.24"
rusqlite = { version = "0.38", features = ["bundled"] }
rnix = "0.14"
rowan = "0.16"
ere = "0.2.4"
num_enum = "0.7.5"
tap = "1.0.1"
ghost-cell = "0.2"
colored = "3.1"
sptr = "0.3"
sealed = "0.6"
small-map = "0.1"
smallvec = "1.15"
[dependencies.gc-arena]
git = "https://github.com/kyren/gc-arena"
rev = "75671ae03f53718357b741ed4027560f14e90836"
features = ["allocator-api2", "hashbrown", "smallvec"]
fix-common = { path = "../fix-common" }
fix-codegen = { path = "../fix-codegen" }
fix-error = { path = "../fix-error" }
fix-ir = { path = "../fix-ir" }
fix-vm = { path = "../fix-vm" }
[dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] }
tempfile = "3.24"
test-log = { version = "0.2", features = ["trace"] }
[[bench]]
+5 -8
View File
@@ -1,18 +1,15 @@
#![allow(dead_code)]
use fix::error::{Result, Source};
use fix::runtime::Runtime;
use fix::value::Value;
use fix::Evaluator;
use fix_common::Value;
use fix_error::{Result, Source};
pub fn eval(expr: &str) -> Value {
Runtime::new()
.unwrap()
Evaluator::new()
.eval(Source::new_eval(expr.into()).unwrap())
.unwrap()
}
pub fn eval_result(expr: &str) -> Result<Value> {
Runtime::new()
.unwrap()
.eval(Source::new_eval(expr.into()).unwrap())
Evaluator::new().eval(Source::new_eval(expr.into()).unwrap())
}
-484
View File
@@ -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()
}
}
}
}
-919
View File
@@ -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);
}
}
-369
View File
@@ -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()),
}
}
}
-1243
View File
File diff suppressed because it is too large Load Diff
-260
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
-559
View File
@@ -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:
}
-410
View File
@@ -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,
}
-31
View File
@@ -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
-76
View File
@@ -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; })
)
-88
View File
@@ -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
}
}
-609
View File
@@ -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
-381
View File
@@ -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»"),
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
use fix::value::Value;
use fix_common::Value;
use crate::utils::{eval_deep, eval_deep_result};
+6 -6
View File
@@ -1,6 +1,6 @@
use fix::error::Source;
use fix::runtime::Runtime;
use fix::value::Value;
use fix::Evaluator;
use fix_common::Value;
use fix_error::Source;
use crate::utils::{eval, eval_result};
@@ -97,7 +97,7 @@ fn import_with_complex_dependency_graph() {
#[test_log::test]
fn path_with_file() {
let mut ctx = Runtime::new().unwrap();
let mut ctx = Evaluator::new();
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("test.txt");
std::fs::write(&test_file, "Hello, World!").unwrap();
@@ -136,7 +136,7 @@ fn path_with_custom_name() {
#[test_log::test]
fn path_with_directory_recursive() {
let mut ctx = Runtime::new().unwrap();
let mut ctx = Evaluator::new();
let temp_dir = tempfile::tempdir().unwrap();
let test_dir = temp_dir.path().join("mydir");
std::fs::create_dir_all(&test_dir).unwrap();
@@ -159,7 +159,7 @@ fn path_with_directory_recursive() {
#[test_log::test]
fn path_flat_with_file() {
let mut ctx = Runtime::new().unwrap();
let mut ctx = Evaluator::new();
let temp_dir = tempfile::tempdir().unwrap();
let test_file = temp_dir.path().join("flat.txt");
std::fs::write(&test_file, "Flat content").unwrap();
+5 -5
View File
@@ -2,9 +2,9 @@
use std::path::PathBuf;
use fix::error::Source;
use fix::runtime::Runtime;
use fix::value::Value;
use fix::Evaluator;
use fix_common::Value;
use fix_error::{Source, SourceType};
fn get_lang_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/tests/lang")
@@ -14,10 +14,10 @@ fn eval_file(name: &str) -> Result<(Value, Source), String> {
let lang_dir = get_lang_dir();
let nix_path = lang_dir.join(format!("{name}.nix"));
let mut ctx = Runtime::new().map_err(|e| e.to_string())?;
let mut ctx = Evaluator::new();
let content = std::fs::read_to_string(&nix_path).unwrap();
let source = Source {
ty: fix::error::SourceType::File(nix_path.into()),
ty: SourceType::File(nix_path.into()),
src: content.into(),
};
ctx.eval_deep(source.clone())
+5 -5
View File
@@ -1,5 +1,5 @@
use fix::runtime::Runtime;
use fix::value::Value;
use fix::Evaluator;
use fix_common::Value;
use crate::utils::eval_result;
@@ -153,7 +153,7 @@ fn string_add_merges_context() {
#[test_log::test]
fn context_in_derivation_args() {
let mut rt = Runtime::new().unwrap();
let mut rt = Evaluator::new();
let result = rt
.eval(
r#"
@@ -182,7 +182,7 @@ fn context_in_derivation_args() {
#[test_log::test]
fn context_in_derivation_env() {
let mut rt = Runtime::new().unwrap();
let mut rt = Evaluator::new();
let result = rt
.eval(
r#"
@@ -224,7 +224,7 @@ fn tostring_preserves_context() {
#[test_log::test]
fn interpolation_derivation_returns_outpath() {
let mut rt = Runtime::new().unwrap();
let mut rt = Evaluator::new();
let result = rt
.eval(
r#"
+8 -15
View File
@@ -1,38 +1,31 @@
#![allow(dead_code)]
use fix::error::{Result, Source};
use fix::runtime::Runtime;
use fix::value::Value;
use fix::Evaluator;
use fix_common::Value;
use fix_error::{Result, Source};
pub fn eval(expr: &str) -> Value {
Runtime::new()
.unwrap()
Evaluator::new()
.eval(Source::new_eval(expr.into()).unwrap())
.unwrap()
}
pub fn eval_shallow(expr: &str) -> Value {
Runtime::new()
.unwrap()
Evaluator::new()
.eval_shallow(Source::new_eval(expr.into()).unwrap())
.unwrap()
}
pub fn eval_deep(expr: &str) -> Value {
Runtime::new()
.unwrap()
Evaluator::new()
.eval_deep(Source::new_eval(expr.into()).unwrap())
.unwrap()
}
pub fn eval_deep_result(expr: &str) -> Result<Value> {
Runtime::new()
.unwrap()
.eval_deep(Source::new_eval(expr.into()).unwrap())
Evaluator::new().eval_deep(Source::new_eval(expr.into()).unwrap())
}
pub fn eval_result(expr: &str) -> Result<Value> {
Runtime::new()
.unwrap()
.eval(Source::new_eval(expr.into()).unwrap())
Evaluator::new().eval(Source::new_eval(expr.into()).unwrap())
}