rewrite VM to support reentry (WIP)
This commit is contained in:
@@ -42,8 +42,6 @@ bumpalo = { version = "3.20", features = [
|
||||
|
||||
rust-embed = "8.11"
|
||||
|
||||
itertools = "0.14"
|
||||
|
||||
regex = "1.11"
|
||||
|
||||
nix-nar = "0.3"
|
||||
|
||||
@@ -459,6 +459,12 @@ impl RawBox {
|
||||
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 {
|
||||
|
||||
+149
-75
@@ -7,8 +7,10 @@ use rnix::TextRange;
|
||||
use string_interner::Symbol as _;
|
||||
|
||||
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
|
||||
use crate::runtime::{BUILTINS, BuiltinId};
|
||||
use crate::runtime::value::{Null, PrimOp, StaticValue};
|
||||
|
||||
pub(crate) struct InstructionPtr(pub usize);
|
||||
pub struct InstructionPtr(pub(crate) usize);
|
||||
|
||||
#[derive(Collect)]
|
||||
#[collect(require_static)]
|
||||
@@ -22,6 +24,7 @@ pub(crate) trait BytecodeContext {
|
||||
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)]
|
||||
@@ -49,13 +52,13 @@ pub enum Op {
|
||||
CallNoSpan,
|
||||
|
||||
MakeAttrs,
|
||||
MakeAttrsDyn,
|
||||
MakeEmptyAttrs,
|
||||
Select,
|
||||
SelectDefault,
|
||||
HasAttr,
|
||||
|
||||
MakeList,
|
||||
MakeEmptyList,
|
||||
|
||||
OpAdd,
|
||||
OpSub,
|
||||
@@ -73,7 +76,6 @@ pub enum Op {
|
||||
OpNeg,
|
||||
OpNot,
|
||||
|
||||
ForceBool,
|
||||
JumpIfFalse,
|
||||
JumpIfTrue,
|
||||
Jump,
|
||||
@@ -109,6 +111,21 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
||||
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);
|
||||
@@ -124,6 +141,61 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
}
|
||||
|
||||
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(id) => {
|
||||
let (layer, local) = self.resolve_arg(id);
|
||||
InlineOperand::Local { layer, local }
|
||||
}
|
||||
&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);
|
||||
@@ -382,11 +454,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir.deref() {
|
||||
Ir::TopLevel { body, thunks } => {
|
||||
let with_thunk_count = self.count_with_thunks(*body);
|
||||
&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 all_thunks = self.collect_all_thunks(thunks, body);
|
||||
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||
|
||||
self.push_scope(false, None, &thunk_ids);
|
||||
@@ -397,7 +469,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
|
||||
self.emit_scope_thunks(thunks);
|
||||
self.emit_expr(*body);
|
||||
self.emit_expr(body);
|
||||
self.emit_op(Op::Return);
|
||||
|
||||
self.pop_scope();
|
||||
@@ -459,7 +531,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
&Ir::If { cond, consq, alter } => {
|
||||
self.emit_expr(cond);
|
||||
self.emit_op(Op::ForceBool);
|
||||
|
||||
self.emit_op(Op::JumpIfFalse);
|
||||
let else_placeholder = self.emit_i32_placeholder();
|
||||
@@ -504,15 +575,20 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_attrset(stcs, dyns);
|
||||
}
|
||||
Ir::List { items } => {
|
||||
for &item in items.iter() {
|
||||
self.emit_expr(item);
|
||||
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);
|
||||
}
|
||||
}
|
||||
self.emit_op(Op::MakeList);
|
||||
self.emit_u32(items.len() as u32);
|
||||
}
|
||||
&Ir::Call { func, arg, span } => {
|
||||
self.emit_expr(func);
|
||||
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);
|
||||
@@ -539,20 +615,29 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
Ir::Builtins => {
|
||||
self.emit_op(Op::LoadBuiltins);
|
||||
}
|
||||
&Ir::Builtin(name) => {
|
||||
&Ir::Builtin(id) => {
|
||||
self.emit_op(Op::LoadBuiltin);
|
||||
self.emit_u32(name.0.to_usize() as u32);
|
||||
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,
|
||||
} => {
|
||||
for &part in parts.iter() {
|
||||
self.emit_expr(part);
|
||||
}
|
||||
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);
|
||||
@@ -603,13 +688,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
match kind {
|
||||
And => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_op(Op::ForceBool);
|
||||
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::ForceBool);
|
||||
self.emit_op(Op::Jump);
|
||||
let end_placeholder = self.emit_i32_placeholder();
|
||||
let after_jump = self.ctx.get_code_mut().len();
|
||||
@@ -624,13 +707,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
Or => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_op(Op::ForceBool);
|
||||
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::ForceBool);
|
||||
self.emit_op(Op::Jump);
|
||||
let end_placeholder = self.emit_i32_placeholder();
|
||||
let after_jump = self.ctx.get_code_mut().len();
|
||||
@@ -645,13 +726,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
Impl => {
|
||||
self.emit_expr(lhs);
|
||||
self.emit_op(Op::ForceBool);
|
||||
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::ForceBool);
|
||||
self.emit_op(Op::Jump);
|
||||
let end_placeholder = self.emit_i32_placeholder();
|
||||
let after_jump = self.ctx.get_code_mut().len();
|
||||
@@ -759,40 +838,26 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
return;
|
||||
}
|
||||
|
||||
if !dyns.is_empty() {
|
||||
for (&sym, &(val, _)) in stcs.iter() {
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_str_id(sym);
|
||||
self.emit_expr(val);
|
||||
}
|
||||
for (_, &(_, span)) in stcs.iter() {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
for &(key, val, span) in dyns.iter() {
|
||||
self.emit_expr(key);
|
||||
self.emit_expr(val);
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
self.emit_op(Op::MakeAttrsDyn);
|
||||
self.emit_u32(stcs.len() as u32);
|
||||
self.emit_u32(dyns.len() as u32);
|
||||
} else {
|
||||
for (&sym, &(val, _)) in stcs.iter() {
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_str_id(sym);
|
||||
self.emit_expr(val);
|
||||
}
|
||||
for (_, &(_, span)) in stcs.iter() {
|
||||
let span_id = self.ctx.register_span(span);
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_u32(span_id);
|
||||
}
|
||||
self.emit_op(Op::MakeAttrs);
|
||||
self.emit_u32(stcs.len() as u32);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,17 +870,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
) {
|
||||
self.emit_expr(expr);
|
||||
for attr in attrpath.iter() {
|
||||
match *attr {
|
||||
Attr::Str(sym, _) => {
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
Attr::Dynamic(expr, _) => {
|
||||
self.emit_expr(expr);
|
||||
}
|
||||
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);
|
||||
@@ -828,23 +886,39 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
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() {
|
||||
match *attr {
|
||||
Attr::Str(sym, _) => {
|
||||
self.emit_op(Op::PushString);
|
||||
self.emit_str_id(sym);
|
||||
}
|
||||
Attr::Dynamic(expr, _) => {
|
||||
self.emit_expr(expr);
|
||||
}
|
||||
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(
|
||||
|
||||
+66
-75
@@ -3,70 +3,71 @@ use std::fmt::Write;
|
||||
use colored::Colorize;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::codegen::{Bytecode, Op};
|
||||
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,
|
||||
pos: usize,
|
||||
pc: usize,
|
||||
}
|
||||
|
||||
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
pub fn new(bytecode: &'a Bytecode, ctx: &'a Ctx) -> Self {
|
||||
pub fn new(ip: InstructionPtr, ctx: &'a Ctx) -> Self {
|
||||
Self {
|
||||
code: &bytecode.code,
|
||||
code: ctx.get_code(),
|
||||
ctx,
|
||||
pos: 0,
|
||||
pc: ip.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u8(&mut self) -> u8 {
|
||||
let b = self.code[self.pos];
|
||||
self.pos += 1;
|
||||
let b = self.code[self.pc];
|
||||
self.pc += 1;
|
||||
b
|
||||
}
|
||||
|
||||
fn read_u16(&mut self) -> u16 {
|
||||
let bytes = self.code[self.pos..self.pos + 2]
|
||||
let bytes = self.code[self.pc..self.pc + 2]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pos += 2;
|
||||
self.pc += 2;
|
||||
u16::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> u32 {
|
||||
let bytes = self.code[self.pos..self.pos + 4]
|
||||
let bytes = self.code[self.pc..self.pc + 4]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pos += 4;
|
||||
self.pc += 4;
|
||||
u32::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_i32(&mut self) -> i32 {
|
||||
let bytes = self.code[self.pos..self.pos + 4]
|
||||
let bytes = self.code[self.pc..self.pc + 4]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pos += 4;
|
||||
self.pc += 4;
|
||||
i32::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_i64(&mut self) -> i64 {
|
||||
let bytes = self.code[self.pos..self.pos + 8]
|
||||
let bytes = self.code[self.pc..self.pc + 8]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pos += 8;
|
||||
self.pc += 8;
|
||||
i64::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_f64(&mut self) -> f64 {
|
||||
let bytes = self.code[self.pos..self.pos + 8]
|
||||
let bytes = self.code[self.pc..self.pc + 8]
|
||||
.try_into()
|
||||
.expect("no enough bytes");
|
||||
self.pos += 8;
|
||||
self.pc += 8;
|
||||
f64::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
@@ -93,62 +94,62 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
let _ = writeln!(out, "Length: {} bytes", self.code.len());
|
||||
}
|
||||
|
||||
while self.pos < self.code.len() {
|
||||
let start_pos = self.pos;
|
||||
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.pos];
|
||||
let bytes_slice = &self.code[start_pos + 1..self.pc];
|
||||
let mut chunks = bytes_slice.chunks(4);
|
||||
|
||||
for (i, chunk) in bytes_slice.chunks(4).enumerate() {
|
||||
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::new();
|
||||
if i == 0 {
|
||||
let _ = write!(&mut temp, "{:02x}", self.code[start_pos]);
|
||||
} else {
|
||||
let _ = write!(&mut temp, " ");
|
||||
}
|
||||
for b in chunk.iter() {
|
||||
let mut temp = String::from(" ");
|
||||
for b in chunk {
|
||||
let _ = write!(&mut temp, " {:02x}", b);
|
||||
}
|
||||
temp
|
||||
};
|
||||
|
||||
if i == 0 {
|
||||
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);
|
||||
}
|
||||
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 extra_width = start_pos.ilog2() >> 4;
|
||||
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);
|
||||
}
|
||||
let _ = write!(out, " ");
|
||||
for _ in 0..extra_width { let _ = write!(out, " "); }
|
||||
let _ = writeln!(out, " {:<14} |", bytes_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,14 +259,6 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
let count = self.read_u32();
|
||||
("MakeAttrs", format!("size={}", count))
|
||||
}
|
||||
Op::MakeAttrsDyn => {
|
||||
let static_count = self.read_u32();
|
||||
let dyn_count = self.read_u32();
|
||||
(
|
||||
"MakeAttrsDyn",
|
||||
format!("static={} dyn={}", static_count, dyn_count),
|
||||
)
|
||||
}
|
||||
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||
|
||||
Op::Select => {
|
||||
@@ -290,6 +283,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
let count = self.read_u32();
|
||||
("MakeList", format!("size={}", count))
|
||||
}
|
||||
Op::MakeEmptyList => ("MakeEmptyList", String::new()),
|
||||
|
||||
Op::OpAdd => ("OpAdd", String::new()),
|
||||
Op::OpSub => ("OpSub", String::new()),
|
||||
@@ -306,8 +300,6 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
Op::OpNeg => ("OpNeg", String::new()),
|
||||
Op::OpNot => ("OpNot", String::new()),
|
||||
|
||||
Op::ForceBool => ("ForceBool", String::new()),
|
||||
|
||||
Op::JumpIfFalse => {
|
||||
let offset = self.read_i32();
|
||||
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||
@@ -348,9 +340,8 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
|
||||
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||
Op::LoadBuiltin => {
|
||||
let idx = self.read_u32();
|
||||
let name = self.ctx.lookup_string(idx);
|
||||
("LoadBuiltin", format!("{:?}", name))
|
||||
let id = self.read_u8();
|
||||
("LoadBuiltin", format!("id={}", id))
|
||||
}
|
||||
Op::MkPos => {
|
||||
let span_id = self.read_u32();
|
||||
|
||||
@@ -8,6 +8,7 @@ use rowan::ast::AstNode;
|
||||
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::ir::*;
|
||||
use crate::runtime::BuiltinId;
|
||||
use crate::value::Symbol;
|
||||
|
||||
trait Require<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T> {
|
||||
@@ -170,10 +171,10 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
text[1..text.len() - 1].to_string().box_in(ctx.bump()),
|
||||
))
|
||||
};
|
||||
let sym = ctx.new_sym("findFile".into());
|
||||
let find_file = ctx.new_expr(Ir::Builtin(sym));
|
||||
// HACK: disgusting eww
|
||||
let find_file = ctx.new_expr(Ir::Builtin(BuiltinId::FindFile));
|
||||
let sym = ctx.new_sym("nixPath".into());
|
||||
let nix_path = ctx.new_expr(Ir::Builtin(sym));
|
||||
let nix_path = ctx.new_expr(Ir::BuiltinConst(sym));
|
||||
let call = ctx.new_expr(Ir::Call {
|
||||
func: find_file,
|
||||
arg: nix_path,
|
||||
|
||||
+13
-8
@@ -1,14 +1,16 @@
|
||||
use std::{
|
||||
hash::{Hash, Hasher},
|
||||
ops::Deref,
|
||||
};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
|
||||
use bumpalo::{Bump, boxed::Box, collections::Vec};
|
||||
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)]
|
||||
@@ -48,8 +50,8 @@ impl<'id, 'ir> IrRef<'id, 'ir> {
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
|
||||
#[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>>;
|
||||
@@ -59,6 +61,7 @@ impl<'ir> Deref for RawIrRef<'ir> {
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub enum Ir<'ir, Ref> {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
@@ -135,7 +138,8 @@ pub enum Ir<'ir, Ref> {
|
||||
|
||||
// Builtins
|
||||
Builtins,
|
||||
Builtin(StringId),
|
||||
Builtin(BuiltinId),
|
||||
BuiltinConst(StringId),
|
||||
|
||||
// Misc
|
||||
TopLevel {
|
||||
@@ -463,6 +467,7 @@ fn ir_content_hash<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>, state: &mut
|
||||
Ir::Thunk(x) => x.hash(state),
|
||||
Ir::Builtins => {}
|
||||
Ir::Builtin(x) => x.hash(state),
|
||||
Ir::BuiltinConst(x) => x.hash(state),
|
||||
Ir::CurPos(x) => x.hash(state),
|
||||
Ir::ReplBinding(x) => x.hash(state),
|
||||
Ir::ScopedImportBinding(x) => x.hash(state),
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
use std::env;
|
||||
use std::io::IsTerminal;
|
||||
|
||||
use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{EnvFilter, Layer, fmt};
|
||||
|
||||
pub fn init_logging() {
|
||||
let is_terminal = std::io::stderr().is_terminal();
|
||||
|
||||
+6
-7
@@ -40,19 +40,18 @@ struct ExprSource {
|
||||
file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result<()> {
|
||||
let _src = if let Some(expr) = src.expr {
|
||||
fn run_compile(runtime: &mut Runtime, src: ExprSource, silent: bool) -> Result<()> {
|
||||
let src = if let Some(expr) = src.expr {
|
||||
Source::new_eval(expr)?
|
||||
} else if let Some(file) = src.file {
|
||||
Source::new_file(file)?
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
todo!()
|
||||
/* match runtime.compile_bytecode(src) {
|
||||
Ok(compiled) => {
|
||||
match runtime.compile_bytecode(src) {
|
||||
Ok(ip) => {
|
||||
if !silent {
|
||||
println!("{}", runtime.disassemble_colored(&compiled));
|
||||
println!("{}", runtime.disassemble_colored(ip));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -60,7 +59,7 @@ fn run_compile(_runtime: &mut Runtime, src: ExprSource, _silent: bool) -> Result
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
Ok(()) */
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_eval(runtime: &mut Runtime, src: ExprSource) -> Result<()> {
|
||||
|
||||
+99
-57
@@ -1,75 +1,99 @@
|
||||
use std::hash::BuildHasher;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use gc_arena::{Arena, Rootable, arena::CollectionPhase};
|
||||
use gc_arena::{Arena, Rootable};
|
||||
use ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
|
||||
use rnix::TextRange;
|
||||
use string_interner::DefaultStringInterner;
|
||||
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::{ArgId, Ir, IrKey, IrRef, RawIrRef, StringId, ThunkId, ir_content_eq};
|
||||
use crate::runtime::builtins::new_builtins_env;
|
||||
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 primops;
|
||||
mod stack;
|
||||
mod value;
|
||||
pub(crate) mod value;
|
||||
mod vm;
|
||||
use vm::{Action, VM};
|
||||
pub(crate) use builtins::{BUILTINS, BuiltinId};
|
||||
use stack::Stack;
|
||||
use vm::{ErrorFrame, GcRoot};
|
||||
|
||||
pub struct Runtime {
|
||||
bytecode: Vec<u8>,
|
||||
global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
// global
|
||||
sources: Vec<Source>,
|
||||
store: DaemonStore,
|
||||
spans: Vec<(usize, TextRange)>,
|
||||
thunk_count: usize,
|
||||
store: DaemonStore,
|
||||
strings: DefaultStringInterner,
|
||||
arena: Arena<Rootable![VM<'_>]>,
|
||||
// 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<'_>]>,
|
||||
}
|
||||
|
||||
impl Runtime {
|
||||
const COLLECTOR_GRANULARITY: f64 = 1024.0;
|
||||
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 global_env = new_builtins_env(&mut strings);
|
||||
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 {
|
||||
arena: Arena::new(|mc| VM::new(mc, &mut strings)),
|
||||
global_env,
|
||||
sources: Vec::new(),
|
||||
spans: Vec::new(),
|
||||
store,
|
||||
strings,
|
||||
thunk_count: 0,
|
||||
bytecode: Vec::new(),
|
||||
sources: Vec::new(),
|
||||
spans: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
constant_dedup: HashMap::new(),
|
||||
|
||||
global_env,
|
||||
|
||||
pc: 0,
|
||||
fuel: Self::DEFAULT_FUEL_AMOUNT,
|
||||
error_contexts: Stack::new(),
|
||||
arena,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn eval(&mut self, source: Source) -> Result<crate::value::Value> {
|
||||
let root = self.downgrade(source, None)?;
|
||||
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
|
||||
self.run(ip)
|
||||
self.do_eval(source, None, ForceMode::AsIs)
|
||||
}
|
||||
|
||||
pub fn eval_shallow(&mut self, _source: Source) -> Result<crate::value::Value> {
|
||||
todo!()
|
||||
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> {
|
||||
// FIXME: deep
|
||||
let root = self.downgrade(source, None)?;
|
||||
let ip = crate::codegen::compile_bytecode(root.as_ref(), self);
|
||||
self.run(ip)
|
||||
self.do_eval(source, None, ForceMode::Deep)
|
||||
}
|
||||
|
||||
pub fn eval_repl(
|
||||
@@ -77,10 +101,18 @@ impl Runtime {
|
||||
source: Source,
|
||||
scope: &HashSet<StringId>,
|
||||
) -> Result<crate::value::Value> {
|
||||
// FIXME: shallow
|
||||
let root = self.downgrade(source, Some(Scope::Repl(scope)))?;
|
||||
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)
|
||||
self.run(ip, force_mode)
|
||||
}
|
||||
|
||||
pub fn add_binding(
|
||||
@@ -92,6 +124,20 @@ impl Runtime {
|
||||
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,
|
||||
@@ -147,34 +193,6 @@ impl Runtime {
|
||||
Ok(OwnedIr { _bump: bump, ir })
|
||||
})
|
||||
}
|
||||
|
||||
fn run(&mut self, ip: InstructionPtr) -> Result<crate::value::Value> {
|
||||
let mut pc = ip.0;
|
||||
loop {
|
||||
let Runtime {
|
||||
bytecode,
|
||||
strings,
|
||||
arena,
|
||||
..
|
||||
} = self;
|
||||
let action =
|
||||
arena.mutate_root(|mc, root| root.run_batch(bytecode, &mut pc, mc, strings));
|
||||
match action {
|
||||
Action::NeedGc => {
|
||||
if self.arena.collection_phase() == CollectionPhase::Sweeping {
|
||||
self.arena.collect_debt();
|
||||
} else if let Some(marked) = self.arena.mark_debt() {
|
||||
marked.start_sweeping();
|
||||
}
|
||||
}
|
||||
Action::Done(done) => {
|
||||
break done;
|
||||
}
|
||||
Action::Continue => (),
|
||||
Action::IoRequest(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_error_span(error: &rnix::ParseError) -> Option<rnix::TextRange> {
|
||||
@@ -536,4 +554,28 @@ impl BytecodeContext for Runtime {
|
||||
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:
|
||||
}
|
||||
|
||||
+290
-127
@@ -1,7 +1,9 @@
|
||||
use gc_arena::Collect;
|
||||
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};
|
||||
@@ -12,14 +14,14 @@ macro_rules! define_builtins {
|
||||
($(($name:literal, $variant:ident, $arity:expr)),* $(,)?) => {
|
||||
/// Builtin function registry.
|
||||
/// Array index IS the PrimOp id. (name, arity) pairs.
|
||||
pub(super) const BUILTINS: &[(&str, u8)] = &[
|
||||
pub(crate) const BUILTINS: &[(&str, u8)] = &[
|
||||
$(($name, $arity),)*
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, Collect)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, Collect)]
|
||||
#[repr(u8)]
|
||||
#[collect(require_static)]
|
||||
pub(super) enum BuiltinId {
|
||||
pub(crate) enum BuiltinId {
|
||||
$($variant,)*
|
||||
}
|
||||
};
|
||||
@@ -27,108 +29,108 @@ macro_rules! define_builtins {
|
||||
|
||||
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),
|
||||
("__add", Add, 2),
|
||||
("__addErrorContext", AddErrorContext, 2),
|
||||
("__all", All, 2),
|
||||
("__any", Any, 2),
|
||||
("__appendContext", AppendContext, 2),
|
||||
("__attrNames", AttrNames, 1),
|
||||
("__attrValues", AttrValues, 1),
|
||||
("baseNameOf", BaseNameOf, 1),
|
||||
("bitAnd", BitAnd, 2),
|
||||
("bitOr", BitOr, 2),
|
||||
("bitXor", BitXor, 2),
|
||||
("catAttrs", CatAttrs, 2),
|
||||
("ceil", Ceil, 1),
|
||||
("compareVersions", CompareVersions, 2),
|
||||
("concatLists", ConcatLists, 1),
|
||||
("concatMap", ConcatMap, 2),
|
||||
("concatStringsSep", ConcatStringsSep, 2),
|
||||
("convertHash", ConvertHash, 1),
|
||||
("deepSeq", DeepSeq, 2),
|
||||
("__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),
|
||||
("__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),
|
||||
("__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),
|
||||
("__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),
|
||||
("__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),
|
||||
("__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),
|
||||
("__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),
|
||||
("__parseDrvName", ParseDrvName, 1),
|
||||
("__partition", Partition, 2),
|
||||
("__path", Path, 1),
|
||||
("__pathExists", PathExists, 1),
|
||||
("placeholder", Placeholder, 1),
|
||||
("readDir", ReadDir, 1),
|
||||
("readFile", ReadFile, 1),
|
||||
("readFileType", ReadFileType, 1),
|
||||
("__readDir", ReadDir, 1),
|
||||
("__readFile", ReadFile, 1),
|
||||
("__readFileType", ReadFileType, 1),
|
||||
("removeAttrs", RemoveAttrs, 2),
|
||||
("replaceStrings", ReplaceStrings, 3),
|
||||
("__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),
|
||||
("__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),
|
||||
("__toFile", ToFile, 2),
|
||||
("__toJSON", ToJSON, 1),
|
||||
("__toPath", ToPath, 1),
|
||||
("toString", ToString, 1),
|
||||
("toXML", ToXML, 1),
|
||||
("trace", Trace, 2),
|
||||
("tryEval", TryEval, 1),
|
||||
("typeOf", TypeOf, 1),
|
||||
("unsafeDiscardStringContext", UnsafeDiscardStringContext, 1),
|
||||
("unsafeGetAttrPos", UnsafeGetAttrPos, 2),
|
||||
("warn", Warn, 2),
|
||||
("zipAttrsWith", ZipAttrsWith, 2),
|
||||
("break", Break, 1),
|
||||
("__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.
|
||||
@@ -194,54 +196,215 @@ fn intern_all_builtins(interner: &mut DefaultStringInterner) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn new_builtins_env(
|
||||
interner: &mut DefaultStringInterner,
|
||||
) -> HashMap<StringId, Ir<'static, RawIrRef<'static>>> {
|
||||
intern_all_builtins(interner);
|
||||
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());
|
||||
|
||||
let mut builtins = HashMap::new();
|
||||
let builtins_sym = StringId(interner.get_or_intern("builtins"));
|
||||
builtins.insert(builtins_sym, Ir::Builtins);
|
||||
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 free_globals = [
|
||||
"abort",
|
||||
"baseNameOf",
|
||||
"break",
|
||||
"dirOf",
|
||||
"derivation",
|
||||
"derivationStrict",
|
||||
"fetchGit",
|
||||
"fetchMercurial",
|
||||
"fetchTarball",
|
||||
"fetchTree",
|
||||
"fromTOML",
|
||||
"import",
|
||||
"isNull",
|
||||
"map",
|
||||
"placeholder",
|
||||
"removeAttrs",
|
||||
"scopedImport",
|
||||
"throw",
|
||||
"toString",
|
||||
];
|
||||
let consts = [
|
||||
("true", Ir::Bool(true)),
|
||||
("false", Ir::Bool(false)),
|
||||
("null", Ir::Null),
|
||||
(
|
||||
"__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 name in free_globals {
|
||||
let name = StringId(interner.get_or_intern(name));
|
||||
let value = Ir::Builtin(name);
|
||||
builtins.insert(name, value);
|
||||
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, value) in consts {
|
||||
let name = StringId(interner.get_or_intern(name));
|
||||
builtins.insert(name, value);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
builtins
|
||||
// Self-reference thunk for builtins.builtins
|
||||
let self_ref_thunk: Gc<'gc, Thunk<'gc>> = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
|
||||
let sym = strings.get_or_intern("builtins");
|
||||
entries.push((StringId(sym), Value::new_gc(self_ref_thunk)));
|
||||
|
||||
entries.sort_by_key(|(k, _)| *k);
|
||||
|
||||
let builtins_set = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
let builtins_val = Value::new_gc(builtins_set);
|
||||
|
||||
(global_env, builtins_val)
|
||||
}
|
||||
|
||||
pub(super) type PrimOpArgs<'gc> = [Value<'gc>; 3];
|
||||
pub(super) type PrimOpStrictArgs<'gc> = [StrictValue<'gc>; 3];
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub(super) struct WellKnownSymbols {
|
||||
abort: SymbolU32,
|
||||
add: SymbolU32,
|
||||
addErrorContext: SymbolU32,
|
||||
all: SymbolU32,
|
||||
any: SymbolU32,
|
||||
appendContext: SymbolU32,
|
||||
attrNames: SymbolU32,
|
||||
attrValues: SymbolU32,
|
||||
baseNameOf: SymbolU32,
|
||||
bitAnd: SymbolU32,
|
||||
bitOr: SymbolU32,
|
||||
bitXor: SymbolU32,
|
||||
catAttrs: SymbolU32,
|
||||
ceil: SymbolU32,
|
||||
compareVersions: SymbolU32,
|
||||
concatLists: SymbolU32,
|
||||
concatMap: SymbolU32,
|
||||
concatStringsSep: SymbolU32,
|
||||
convertHash: SymbolU32,
|
||||
deepSeq: SymbolU32,
|
||||
derivation: SymbolU32,
|
||||
derivationStrict: SymbolU32,
|
||||
dirOf: SymbolU32,
|
||||
div: SymbolU32,
|
||||
elem: SymbolU32,
|
||||
elemAt: SymbolU32,
|
||||
fetchGit: SymbolU32,
|
||||
fetchMercurial: SymbolU32,
|
||||
fetchTarball: SymbolU32,
|
||||
fetchTree: SymbolU32,
|
||||
fetchurl: SymbolU32,
|
||||
filter: SymbolU32,
|
||||
filterSource: SymbolU32,
|
||||
findFile: SymbolU32,
|
||||
floor: SymbolU32,
|
||||
foldl: SymbolU32,
|
||||
fromJSON: SymbolU32,
|
||||
fromTOML: SymbolU32,
|
||||
functionArgs: SymbolU32,
|
||||
genList: SymbolU32,
|
||||
genericClosure: SymbolU32,
|
||||
getAttr: SymbolU32,
|
||||
getContext: SymbolU32,
|
||||
getEnv: SymbolU32,
|
||||
groupBy: SymbolU32,
|
||||
hasAttr: SymbolU32,
|
||||
hasContext: SymbolU32,
|
||||
hashFile: SymbolU32,
|
||||
hashString: SymbolU32,
|
||||
head: SymbolU32,
|
||||
import: SymbolU32,
|
||||
intersectAttrs: SymbolU32,
|
||||
isAttrs: SymbolU32,
|
||||
isBool: SymbolU32,
|
||||
isFloat: SymbolU32,
|
||||
isFunction: SymbolU32,
|
||||
isInt: SymbolU32,
|
||||
isList: SymbolU32,
|
||||
isNull: SymbolU32,
|
||||
isPath: SymbolU32,
|
||||
isString: SymbolU32,
|
||||
length: SymbolU32,
|
||||
lessThan: SymbolU32,
|
||||
listToAttrs: SymbolU32,
|
||||
map: SymbolU32,
|
||||
mapAttrs: SymbolU32,
|
||||
match_: SymbolU32,
|
||||
mul: SymbolU32,
|
||||
null: SymbolU32,
|
||||
parseDrvName: SymbolU32,
|
||||
partition: SymbolU32,
|
||||
path: SymbolU32,
|
||||
pathExists: SymbolU32,
|
||||
placeholder: SymbolU32,
|
||||
readDir: SymbolU32,
|
||||
readFile: SymbolU32,
|
||||
readFileType: SymbolU32,
|
||||
removeAttrs: SymbolU32,
|
||||
replaceStrings: SymbolU32,
|
||||
scopedImport: SymbolU32,
|
||||
seq: SymbolU32,
|
||||
sort: SymbolU32,
|
||||
split: SymbolU32,
|
||||
splitVersion: SymbolU32,
|
||||
storePath: SymbolU32,
|
||||
stringLength: SymbolU32,
|
||||
sub: SymbolU32,
|
||||
substring: SymbolU32,
|
||||
tail: SymbolU32,
|
||||
throw: SymbolU32,
|
||||
toFile: SymbolU32,
|
||||
toJSON: SymbolU32,
|
||||
toPath: SymbolU32,
|
||||
toString: SymbolU32,
|
||||
toXML: SymbolU32,
|
||||
trace: SymbolU32,
|
||||
tryEval: SymbolU32,
|
||||
typeOf: SymbolU32,
|
||||
unsafeDiscardStringContext: SymbolU32,
|
||||
unsafeGetAttrPos: SymbolU32,
|
||||
warn: SymbolU32,
|
||||
zipAttrsWith: SymbolU32,
|
||||
break_: SymbolU32,
|
||||
}
|
||||
|
||||
@@ -1,929 +0,0 @@
|
||||
use gc_arena::{Collect, Gc, Mutation, RefLock};
|
||||
use smallvec::SmallVec;
|
||||
use string_interner::DefaultStringInterner;
|
||||
|
||||
use super::builtins::{BuiltinId, PrimOpArgs, PrimOpStrictArgs};
|
||||
use super::value::*;
|
||||
use super::vm::{ForceResult, VM, VmError};
|
||||
use crate::ir::StringId;
|
||||
|
||||
pub(super) enum BuiltinResult<'gc> {
|
||||
Done(Value<'gc>),
|
||||
Force(BuiltinState<'gc>, Value<'gc>),
|
||||
Call(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>),
|
||||
CallAndForce(BuiltinState<'gc>, StrictValue<'gc>, Value<'gc>),
|
||||
Error(VmError),
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(super) enum BuiltinState<'gc> {
|
||||
FoldlStrict(FoldlStrict<'gc>),
|
||||
// future: Filter, GenericClosure, Sort, All, Any, ConcatMap, ...
|
||||
}
|
||||
|
||||
impl<'gc> BuiltinState<'gc> {
|
||||
pub(super) fn resume(
|
||||
self,
|
||||
val: StrictValue<'gc>,
|
||||
ctx: &PrimOpCtx<'_, 'gc>,
|
||||
) -> BuiltinResult<'gc> {
|
||||
match self {
|
||||
BuiltinState::FoldlStrict(s) => s.resume(val, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct PrimOpCtx<'a, 'gc> {
|
||||
pub(super) vm: &'a VM<'gc>,
|
||||
pub(super) mc: &'a Mutation<'gc>,
|
||||
pub(super) strings: &'a DefaultStringInterner,
|
||||
}
|
||||
|
||||
macro_rules! force_inline_or_err {
|
||||
($ctx:expr, $val:expr) => {{
|
||||
let val = $val;
|
||||
match $ctx.vm.force_inline(val) {
|
||||
Ok(ForceResult::Ready(v)) => v,
|
||||
Ok(_) => {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"value requires evaluation in non-stateful builtin context",
|
||||
));
|
||||
}
|
||||
Err(e) => return BuiltinResult::Error(e),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! force {
|
||||
($ctx:expr, $state:expr, $val:expr) => {{
|
||||
let val = $val;
|
||||
match $ctx.vm.force_inline(val) {
|
||||
Ok(ForceResult::Ready(v)) => v,
|
||||
Ok(ForceResult::NeedEval { .. } | ForceResult::NeedApply(_)) => {
|
||||
return BuiltinResult::Force($state, val);
|
||||
}
|
||||
Err(e) => return BuiltinResult::Error(e),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! call {
|
||||
($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{
|
||||
let func = $func;
|
||||
let arg = $arg;
|
||||
return BuiltinResult::Call($state, func, arg);
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! call_and_force {
|
||||
($ctx:expr, $state:expr, $func:expr, $arg:expr) => {{
|
||||
let func = $func;
|
||||
let arg = $arg;
|
||||
return BuiltinResult::CallAndForce($state, func, arg);
|
||||
}};
|
||||
}
|
||||
|
||||
pub(super) fn dispatch_strict_builtin<'gc>(
|
||||
id: BuiltinId,
|
||||
args: PrimOpStrictArgs<'gc>,
|
||||
_arity: u8,
|
||||
ctx: &PrimOpCtx<'_, 'gc>,
|
||||
) -> BuiltinResult<'gc> {
|
||||
match id {
|
||||
BuiltinId::TypeOf => {
|
||||
let val = args[0];
|
||||
let name = if val.as_inline::<i32>().is_some() || val.as_gc::<i64>().is_some() {
|
||||
"int"
|
||||
} else if val.as_float().is_some() {
|
||||
"float"
|
||||
} else if val.as_inline::<bool>().is_some() {
|
||||
"bool"
|
||||
} else if VM::get_string(val, ctx.strings).is_some() {
|
||||
"string"
|
||||
} else if val.is::<Null>() {
|
||||
"null"
|
||||
} else if val.as_gc::<AttrSet<'gc>>().is_some() {
|
||||
"set"
|
||||
} else if val.as_gc::<List<'gc>>().is_some() {
|
||||
"list"
|
||||
} else if val.as_gc::<Closure<'gc>>().is_some()
|
||||
|| val.as_inline::<PrimOp>().is_some()
|
||||
|| val.as_gc::<PrimOpApp<'gc>>().is_some()
|
||||
{
|
||||
"lambda"
|
||||
} else {
|
||||
return BuiltinResult::Error(VM::err("typeOf: unknown type"));
|
||||
};
|
||||
let sid = ctx.strings.get(name).expect("typeOf string not interned");
|
||||
BuiltinResult::Done(Value::new_inline(StringId(sid)))
|
||||
}
|
||||
|
||||
BuiltinId::IsNull => BuiltinResult::Done(Value::new_inline(args[0].is::<Null>())),
|
||||
BuiltinId::IsAttrs => {
|
||||
BuiltinResult::Done(Value::new_inline(args[0].as_gc::<AttrSet<'gc>>().is_some()))
|
||||
}
|
||||
BuiltinId::IsBool => {
|
||||
BuiltinResult::Done(Value::new_inline(args[0].as_inline::<bool>().is_some()))
|
||||
}
|
||||
BuiltinId::IsFloat => BuiltinResult::Done(Value::new_inline(args[0].as_float().is_some())),
|
||||
BuiltinId::IsFunction => {
|
||||
let v = args[0];
|
||||
let is_func = v.as_gc::<Closure<'gc>>().is_some()
|
||||
|| v.as_inline::<PrimOp>().is_some()
|
||||
|| v.as_gc::<PrimOpApp<'gc>>().is_some();
|
||||
BuiltinResult::Done(Value::new_inline(is_func))
|
||||
}
|
||||
BuiltinId::IsInt => {
|
||||
let v = args[0];
|
||||
let is_int = v.as_inline::<i32>().is_some() || v.as_gc::<i64>().is_some();
|
||||
BuiltinResult::Done(Value::new_inline(is_int))
|
||||
}
|
||||
BuiltinId::IsList => {
|
||||
BuiltinResult::Done(Value::new_inline(args[0].as_gc::<List<'gc>>().is_some()))
|
||||
}
|
||||
BuiltinId::IsString => {
|
||||
let v = args[0];
|
||||
let is_str = v.as_inline::<StringId>().is_some() || v.as_gc::<NixString>().is_some();
|
||||
BuiltinResult::Done(Value::new_inline(is_str))
|
||||
}
|
||||
BuiltinId::IsPath => BuiltinResult::Done(Value::new_inline(false)),
|
||||
|
||||
BuiltinId::Length => {
|
||||
let Some(list) = args[0].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.length: not a list"));
|
||||
};
|
||||
BuiltinResult::Done(VM::make_int(list.inner.len() as i64, ctx.mc))
|
||||
}
|
||||
BuiltinId::Head => {
|
||||
let Some(list) = args[0].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.head: not a list"));
|
||||
};
|
||||
if list.inner.is_empty() {
|
||||
return BuiltinResult::Error(VM::err("builtins.head: empty list"));
|
||||
}
|
||||
BuiltinResult::Done(list.inner[0])
|
||||
}
|
||||
BuiltinId::Tail => {
|
||||
let Some(list) = args[0].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.tail: not a list"));
|
||||
};
|
||||
if list.inner.is_empty() {
|
||||
return BuiltinResult::Error(VM::err("builtins.tail: empty list"));
|
||||
}
|
||||
let tail = List {
|
||||
inner: SmallVec::from_slice(&list.inner[1..]),
|
||||
};
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, tail)))
|
||||
}
|
||||
|
||||
BuiltinId::AttrNames => {
|
||||
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.attrNames: not a set"));
|
||||
};
|
||||
let items: SmallVec<[Value<'gc>; 4]> =
|
||||
attrs.iter().map(|(k, _)| Value::new_inline(*k)).collect();
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
|
||||
}
|
||||
BuiltinId::AttrValues => {
|
||||
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.attrValues: not a set"));
|
||||
};
|
||||
let items: SmallVec<[Value<'gc>; 4]> = attrs.iter().map(|(_, v)| *v).collect();
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
|
||||
}
|
||||
|
||||
BuiltinId::Map => {
|
||||
let f = args[0];
|
||||
let Some(list) = args[1].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.map: second argument is not a list",
|
||||
));
|
||||
};
|
||||
if list.inner.is_empty() {
|
||||
return BuiltinResult::Done(Value::new_gc(Gc::new(
|
||||
ctx.mc,
|
||||
List {
|
||||
inner: SmallVec::new(),
|
||||
},
|
||||
)));
|
||||
}
|
||||
let new_elems: SmallVec<[Value<'gc>; 4]> = list
|
||||
.inner
|
||||
.iter()
|
||||
.map(|elem| {
|
||||
let thunk: Gc<'gc, Thunk<'gc>> = Gc::new(
|
||||
ctx.mc,
|
||||
RefLock::new(ThunkState::Apply {
|
||||
func: f.relax(),
|
||||
arg: *elem,
|
||||
}),
|
||||
);
|
||||
Value::new_gc(thunk)
|
||||
})
|
||||
.collect();
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: new_elems })))
|
||||
}
|
||||
|
||||
BuiltinId::GenList => {
|
||||
let f = args[0];
|
||||
let len_val = args[1];
|
||||
let Some(len) = VM::as_num(len_val) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.genList: second argument is not a number",
|
||||
));
|
||||
};
|
||||
let super::vm::NixNum::Int(len) = len else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.genList: second argument is not an integer",
|
||||
));
|
||||
};
|
||||
if len < 0 {
|
||||
return BuiltinResult::Error(VM::err("builtins.genList: negative length"));
|
||||
}
|
||||
let items: SmallVec<[Value<'gc>; 4]> = (0..len)
|
||||
.map(|i| {
|
||||
let arg = VM::make_int(i, ctx.mc);
|
||||
let thunk: Gc<'gc, Thunk<'gc>> = Gc::new(
|
||||
ctx.mc,
|
||||
RefLock::new(ThunkState::Apply {
|
||||
func: f.relax(),
|
||||
arg,
|
||||
}),
|
||||
);
|
||||
Value::new_gc(thunk)
|
||||
})
|
||||
.collect();
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: items })))
|
||||
}
|
||||
|
||||
BuiltinId::ElemAt => {
|
||||
let Some(list) = args[0].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.elemAt: not a list"));
|
||||
};
|
||||
let Some(idx) = VM::as_num(args[1]) else {
|
||||
return BuiltinResult::Error(VM::err("builtins.elemAt: index is not a number"));
|
||||
};
|
||||
let super::vm::NixNum::Int(idx) = idx else {
|
||||
return BuiltinResult::Error(VM::err("builtins.elemAt: index is not an integer"));
|
||||
};
|
||||
if idx < 0 || idx as usize >= list.inner.len() {
|
||||
return BuiltinResult::Error(VM::err(format!(
|
||||
"builtins.elemAt: index {} out of bounds for list of length {}",
|
||||
idx,
|
||||
list.inner.len()
|
||||
)));
|
||||
}
|
||||
BuiltinResult::Done(list.inner[idx as usize])
|
||||
}
|
||||
|
||||
BuiltinId::GetAttr => {
|
||||
let Some(name) = VM::get_string(args[0], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.getAttr: first argument is not a string",
|
||||
));
|
||||
};
|
||||
let Some(sid) = ctx.strings.get(name) else {
|
||||
return BuiltinResult::Error(VM::err(format!(
|
||||
"builtins.getAttr: attribute '{}' not found",
|
||||
name
|
||||
)));
|
||||
};
|
||||
let Some(attrs) = args[1].as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.getAttr: second argument is not a set",
|
||||
));
|
||||
};
|
||||
match attrs.lookup(StringId(sid)) {
|
||||
Some(v) => BuiltinResult::Done(v),
|
||||
None => BuiltinResult::Error(VM::err(format!("attribute '{}' missing", name))),
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinId::HasAttr => {
|
||||
let Some(name) = VM::get_string(args[0], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.hasAttr: first argument is not a string",
|
||||
));
|
||||
};
|
||||
let Some(attrs) = args[1].as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.hasAttr: second argument is not a set",
|
||||
));
|
||||
};
|
||||
let has = ctx
|
||||
.strings
|
||||
.get(name)
|
||||
.map(|sid| attrs.has(StringId(sid)))
|
||||
.unwrap_or(false);
|
||||
BuiltinResult::Done(Value::new_inline(has))
|
||||
}
|
||||
|
||||
BuiltinId::RemoveAttrs => {
|
||||
let Some(attrs) = args[0].as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.removeAttrs: first argument is not a set",
|
||||
));
|
||||
};
|
||||
let Some(remove_list) = args[1].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.removeAttrs: second argument is not a list",
|
||||
));
|
||||
};
|
||||
let mut to_remove = Vec::new();
|
||||
for item in remove_list.inner.iter() {
|
||||
let sv = force_inline_or_err!(ctx, *item);
|
||||
if let Some(s) = VM::get_string(sv, ctx.strings)
|
||||
&& let Some(sid) = ctx.strings.get(s)
|
||||
{
|
||||
to_remove.push(StringId(sid));
|
||||
}
|
||||
}
|
||||
let entries: SmallVec<[(StringId, Value<'gc>); 4]> = attrs
|
||||
.iter()
|
||||
.filter(|(k, _)| !to_remove.contains(k))
|
||||
.cloned()
|
||||
.collect();
|
||||
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
BuiltinResult::Done(Value::new_gc(new_attrs))
|
||||
}
|
||||
|
||||
BuiltinId::IntersectAttrs => {
|
||||
let Some(a) = args[0].as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.intersectAttrs: first argument is not a set",
|
||||
));
|
||||
};
|
||||
let Some(b) = args[1].as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.intersectAttrs: second argument is not a set",
|
||||
));
|
||||
};
|
||||
let entries: SmallVec<[(StringId, Value<'gc>); 4]> =
|
||||
b.iter().filter(|(k, _)| a.has(*k)).cloned().collect();
|
||||
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
BuiltinResult::Done(Value::new_gc(new_attrs))
|
||||
}
|
||||
|
||||
BuiltinId::ListToAttrs => {
|
||||
let Some(list) = args[0].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.listToAttrs: not a list"));
|
||||
};
|
||||
let name_sid = ctx.strings.get("name").expect("'name' not interned");
|
||||
let value_sid = ctx.strings.get("value").expect("'value' not interned");
|
||||
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
|
||||
for item in list.inner.iter() {
|
||||
let sv = force_inline_or_err!(ctx, *item);
|
||||
let Some(attr_set) = sv.as_gc::<AttrSet<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.listToAttrs: element is not a set",
|
||||
));
|
||||
};
|
||||
let Some(name_val) = attr_set.lookup(StringId(name_sid)) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.listToAttrs: element missing 'name'",
|
||||
));
|
||||
};
|
||||
let name_sv = force_inline_or_err!(ctx, name_val);
|
||||
let Some(name_str) = VM::get_string(name_sv, ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.listToAttrs: 'name' is not a string",
|
||||
));
|
||||
};
|
||||
let Some(value_val) = attr_set.lookup(StringId(value_sid)) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.listToAttrs: element missing 'value'",
|
||||
));
|
||||
};
|
||||
let Some(key_sym) = ctx.strings.get(name_str) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.listToAttrs: name not interned",
|
||||
));
|
||||
};
|
||||
entries.push((StringId(key_sym), value_val));
|
||||
}
|
||||
entries.sort_by_key(|(k, _)| *k);
|
||||
entries.dedup_by_key(|(k, _)| *k);
|
||||
let new_attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
BuiltinResult::Done(Value::new_gc(new_attrs))
|
||||
}
|
||||
|
||||
BuiltinId::ConcatLists => {
|
||||
let Some(list) = args[0].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.concatLists: not a list"));
|
||||
};
|
||||
let mut result = SmallVec::<[Value<'gc>; 4]>::new();
|
||||
for item in list.inner.iter() {
|
||||
let sv = force_inline_or_err!(ctx, *item);
|
||||
let Some(inner) = sv.as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.concatLists: element is not a list",
|
||||
));
|
||||
};
|
||||
result.extend(inner.inner.iter().cloned());
|
||||
}
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, List { inner: result })))
|
||||
}
|
||||
|
||||
BuiltinId::LessThan => {
|
||||
match ctx
|
||||
.vm
|
||||
.compare_values(args[0], args[1], ctx.strings, |o| o.is_lt())
|
||||
{
|
||||
Ok(v) => BuiltinResult::Done(v),
|
||||
Err(e) => BuiltinResult::Error(e),
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinId::Add => {
|
||||
match ctx.vm.compute_binop(
|
||||
super::vm::BinOpTag::Add,
|
||||
args[0],
|
||||
args[1],
|
||||
ctx.mc,
|
||||
ctx.strings,
|
||||
) {
|
||||
Ok(v) => BuiltinResult::Done(v),
|
||||
Err(e) => BuiltinResult::Error(e),
|
||||
}
|
||||
}
|
||||
BuiltinId::Sub => {
|
||||
match ctx
|
||||
.vm
|
||||
.numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_sub, |a, b| a - b)
|
||||
{
|
||||
Ok(v) => BuiltinResult::Done(v),
|
||||
Err(e) => BuiltinResult::Error(e),
|
||||
}
|
||||
}
|
||||
BuiltinId::Mul => {
|
||||
match ctx
|
||||
.vm
|
||||
.numeric_binop(args[0], args[1], ctx.mc, i64::wrapping_mul, |a, b| a * b)
|
||||
{
|
||||
Ok(v) => BuiltinResult::Done(v),
|
||||
Err(e) => BuiltinResult::Error(e),
|
||||
}
|
||||
}
|
||||
BuiltinId::Div => {
|
||||
match ctx.vm.compute_binop(
|
||||
super::vm::BinOpTag::Div,
|
||||
args[0],
|
||||
args[1],
|
||||
ctx.mc,
|
||||
ctx.strings,
|
||||
) {
|
||||
Ok(v) => BuiltinResult::Done(v),
|
||||
Err(e) => BuiltinResult::Error(e),
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinId::BitAnd => {
|
||||
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
|
||||
(VM::as_num(args[0]), VM::as_num(args[1]))
|
||||
else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.bitAnd: arguments must be integers",
|
||||
));
|
||||
};
|
||||
BuiltinResult::Done(VM::make_int(a & b, ctx.mc))
|
||||
}
|
||||
BuiltinId::BitOr => {
|
||||
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
|
||||
(VM::as_num(args[0]), VM::as_num(args[1]))
|
||||
else {
|
||||
return BuiltinResult::Error(VM::err("builtins.bitOr: arguments must be integers"));
|
||||
};
|
||||
BuiltinResult::Done(VM::make_int(a | b, ctx.mc))
|
||||
}
|
||||
BuiltinId::BitXor => {
|
||||
let (Some(super::vm::NixNum::Int(a)), Some(super::vm::NixNum::Int(b))) =
|
||||
(VM::as_num(args[0]), VM::as_num(args[1]))
|
||||
else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.bitXor: arguments must be integers",
|
||||
));
|
||||
};
|
||||
BuiltinResult::Done(VM::make_int(a ^ b, ctx.mc))
|
||||
}
|
||||
|
||||
BuiltinId::Ceil => {
|
||||
if let Some(f) = args[0].as_float() {
|
||||
BuiltinResult::Done(VM::make_int(f.ceil() as i64, ctx.mc))
|
||||
} else if VM::as_num(args[0]).is_some() {
|
||||
BuiltinResult::Done(args[0].relax())
|
||||
} else {
|
||||
BuiltinResult::Error(VM::err("builtins.ceil: not a number"))
|
||||
}
|
||||
}
|
||||
BuiltinId::Floor => {
|
||||
if let Some(f) = args[0].as_float() {
|
||||
BuiltinResult::Done(VM::make_int(f.floor() as i64, ctx.mc))
|
||||
} else if VM::as_num(args[0]).is_some() {
|
||||
BuiltinResult::Done(args[0].relax())
|
||||
} else {
|
||||
BuiltinResult::Error(VM::err("builtins.floor: not a number"))
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinId::StringLength => {
|
||||
let Some(s) = VM::get_string(args[0], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err("builtins.stringLength: not a string"));
|
||||
};
|
||||
BuiltinResult::Done(VM::make_int(s.len() as i64, ctx.mc))
|
||||
}
|
||||
|
||||
BuiltinId::Substring => {
|
||||
let Some(super::vm::NixNum::Int(start)) = VM::as_num(args[0]) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.substring: start is not an integer",
|
||||
));
|
||||
};
|
||||
let Some(super::vm::NixNum::Int(len)) = VM::as_num(args[1]) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.substring: length is not an integer",
|
||||
));
|
||||
};
|
||||
let Some(s) = VM::get_string(args[2], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.substring: third argument is not a string",
|
||||
));
|
||||
};
|
||||
let start = start.max(0) as usize;
|
||||
if start >= s.len() {
|
||||
let ns = Gc::new(ctx.mc, NixString::new(""));
|
||||
return BuiltinResult::Done(Value::new_gc(ns));
|
||||
}
|
||||
let end = if len < 0 {
|
||||
s.len()
|
||||
} else {
|
||||
(start + len as usize).min(s.len())
|
||||
};
|
||||
let result = &s[start..end];
|
||||
let ns = Gc::new(ctx.mc, NixString::new(result));
|
||||
BuiltinResult::Done(Value::new_gc(ns))
|
||||
}
|
||||
|
||||
BuiltinId::ToString => {
|
||||
let v = args[0];
|
||||
if let Some(s) = VM::get_string(v, ctx.strings) {
|
||||
let ns = Gc::new(ctx.mc, NixString::new(s));
|
||||
BuiltinResult::Done(Value::new_gc(ns))
|
||||
} else if let Some(b) = v.as_inline::<bool>() {
|
||||
let s = if b { "1" } else { "" };
|
||||
let ns = Gc::new(ctx.mc, NixString::new(s));
|
||||
BuiltinResult::Done(Value::new_gc(ns))
|
||||
} else if v.is::<Null>() {
|
||||
let ns = Gc::new(ctx.mc, NixString::new(""));
|
||||
BuiltinResult::Done(Value::new_gc(ns))
|
||||
} else if let Some(n) = VM::as_num(v) {
|
||||
let s = match n {
|
||||
super::vm::NixNum::Int(i) => i.to_string(),
|
||||
super::vm::NixNum::Float(f) => format!("{f}"),
|
||||
};
|
||||
let ns = Gc::new(ctx.mc, NixString::new(s));
|
||||
BuiltinResult::Done(Value::new_gc(ns))
|
||||
} else {
|
||||
BuiltinResult::Error(VM::err("builtins.toString: cannot coerce to string"))
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinId::Abort => {
|
||||
let Some(msg) = VM::get_string(args[0], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err("builtins.abort: argument is not a string"));
|
||||
};
|
||||
BuiltinResult::Error(VmError::Uncatchable(crate::error::Error::eval_error(
|
||||
format!("evaluation aborted with the following error message: '{msg}'"),
|
||||
)))
|
||||
}
|
||||
|
||||
BuiltinId::Throw => {
|
||||
let Some(msg) = VM::get_string(args[0], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err("builtins.throw: argument is not a string"));
|
||||
};
|
||||
BuiltinResult::Error(VmError::Catchable(msg.to_owned()))
|
||||
}
|
||||
|
||||
BuiltinId::FunctionArgs => {
|
||||
let v = args[0];
|
||||
if let Some(closure) = v.as_gc::<Closure<'gc>>() {
|
||||
if let Some(ref pattern) = closure.pattern {
|
||||
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
|
||||
for &name in &pattern.required {
|
||||
entries.push((name, Value::new_inline(false)));
|
||||
}
|
||||
for &name in &pattern.optional {
|
||||
entries.push((name, Value::new_inline(true)));
|
||||
}
|
||||
entries.sort_by_key(|(k, _)| *k);
|
||||
let attrs = Gc::new(ctx.mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
BuiltinResult::Done(Value::new_gc(attrs))
|
||||
} else {
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default())))
|
||||
}
|
||||
} else {
|
||||
BuiltinResult::Done(Value::new_gc(Gc::new(ctx.mc, AttrSet::default())))
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinId::FoldlStrict => FoldlStrict::call(args, ctx),
|
||||
|
||||
BuiltinId::Elem => {
|
||||
let Some(list) = args[1].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.elem: second argument is not a list",
|
||||
));
|
||||
};
|
||||
let needle = args[0];
|
||||
for item in list.inner.iter() {
|
||||
let sv = force_inline_or_err!(ctx, *item);
|
||||
if ctx.vm.values_equal(needle, sv, ctx.strings) {
|
||||
return BuiltinResult::Done(Value::new_inline(true));
|
||||
}
|
||||
}
|
||||
BuiltinResult::Done(Value::new_inline(false))
|
||||
}
|
||||
|
||||
BuiltinId::ReplaceStrings => {
|
||||
let Some(from_list) = args[0].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.replaceStrings: first argument is not a list",
|
||||
));
|
||||
};
|
||||
let Some(to_list) = args[1].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.replaceStrings: second argument is not a list",
|
||||
));
|
||||
};
|
||||
let Some(s) = VM::get_string(args[2], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.replaceStrings: third argument is not a string",
|
||||
));
|
||||
};
|
||||
if from_list.inner.len() != to_list.inner.len() {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.replaceStrings: lists must have same length",
|
||||
));
|
||||
}
|
||||
|
||||
let mut from_strs = Vec::new();
|
||||
let mut to_strs = Vec::new();
|
||||
for (f, t) in from_list.inner.iter().zip(to_list.inner.iter()) {
|
||||
let fv = force_inline_or_err!(ctx, *f);
|
||||
let tv = force_inline_or_err!(ctx, *t);
|
||||
let Some(fs) = VM::get_string(fv, ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.replaceStrings: from element is not a string",
|
||||
));
|
||||
};
|
||||
let Some(ts) = VM::get_string(tv, ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.replaceStrings: to element is not a string",
|
||||
));
|
||||
};
|
||||
from_strs.push(fs.to_owned());
|
||||
to_strs.push(ts.to_owned());
|
||||
}
|
||||
|
||||
let s = s.to_owned();
|
||||
let mut result = String::new();
|
||||
let mut i = 0;
|
||||
while i < s.len() {
|
||||
let mut found = false;
|
||||
for (j, from) in from_strs.iter().enumerate() {
|
||||
if from.is_empty() {
|
||||
result.push_str(&to_strs[j]);
|
||||
result.push(s.as_bytes()[i] as char);
|
||||
i += 1;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if s[i..].starts_with(from.as_str()) {
|
||||
result.push_str(&to_strs[j]);
|
||||
i += from.len();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result.push(s.as_bytes()[i] as char);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if from_strs.iter().any(|f| f.is_empty()) {
|
||||
let j = from_strs
|
||||
.iter()
|
||||
.position(|f| f.is_empty())
|
||||
.expect("just checked");
|
||||
result.push_str(&to_strs[j]);
|
||||
}
|
||||
|
||||
let ns = Gc::new(ctx.mc, NixString::new(result));
|
||||
BuiltinResult::Done(Value::new_gc(ns))
|
||||
}
|
||||
|
||||
BuiltinId::ConcatStringsSep => {
|
||||
let Some(sep) = VM::get_string(args[0], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.concatStringsSep: first argument is not a string",
|
||||
));
|
||||
};
|
||||
let sep = sep.to_owned();
|
||||
let Some(list) = args[1].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.concatStringsSep: second argument is not a list",
|
||||
));
|
||||
};
|
||||
let mut result = String::new();
|
||||
for (i, item) in list.inner.iter().enumerate() {
|
||||
if i > 0 {
|
||||
result.push_str(&sep);
|
||||
}
|
||||
let sv = force_inline_or_err!(ctx, *item);
|
||||
let Some(s) = VM::get_string(sv, ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err(
|
||||
"builtins.concatStringsSep: element is not a string",
|
||||
));
|
||||
};
|
||||
result.push_str(s);
|
||||
}
|
||||
let ns = Gc::new(ctx.mc, NixString::new(result));
|
||||
BuiltinResult::Done(Value::new_gc(ns))
|
||||
}
|
||||
|
||||
BuiltinId::FromJSON => {
|
||||
let Some(s) = VM::get_string(args[0], ctx.strings) else {
|
||||
return BuiltinResult::Error(VM::err("builtins.fromJSON: not a string"));
|
||||
};
|
||||
match serde_json::from_str::<serde_json::Value>(s) {
|
||||
Ok(json) => {
|
||||
let v = json_to_nix(&json, ctx.mc, ctx.strings);
|
||||
BuiltinResult::Done(v)
|
||||
}
|
||||
Err(e) => BuiltinResult::Error(VM::err(format!("builtins.fromJSON: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
BuiltinId::ToJSON => BuiltinResult::Error(VM::err("builtins.toJSON: not yet implemented")),
|
||||
|
||||
BuiltinId::Null => BuiltinResult::Done(Value::new_inline(Null)),
|
||||
|
||||
_ => BuiltinResult::Error(VM::err(format!("builtin {:?} not yet implemented", id))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn dispatch_lazy_builtin<'gc>(
|
||||
id: BuiltinId,
|
||||
args: &PrimOpArgs<'gc>,
|
||||
_arity: u8,
|
||||
ctx: &PrimOpCtx<'_, 'gc>,
|
||||
) -> BuiltinResult<'gc> {
|
||||
match id {
|
||||
BuiltinId::Seq => {
|
||||
let _ = force_inline_or_err!(ctx, args[0]);
|
||||
BuiltinResult::Done(args[1])
|
||||
}
|
||||
BuiltinId::DeepSeq => {
|
||||
// TODO: deep force
|
||||
let _ = force_inline_or_err!(ctx, args[0]);
|
||||
BuiltinResult::Done(args[1])
|
||||
}
|
||||
BuiltinId::Trace => {
|
||||
let sv = force_inline_or_err!(ctx, args[0]);
|
||||
if let Some(s) = VM::get_string(sv, ctx.strings) {
|
||||
eprintln!("trace: {s}");
|
||||
} else {
|
||||
eprintln!("trace: <non-string>");
|
||||
}
|
||||
BuiltinResult::Done(args[1])
|
||||
}
|
||||
BuiltinId::Warn => {
|
||||
let sv = force_inline_or_err!(ctx, args[0]);
|
||||
if let Some(s) = VM::get_string(sv, ctx.strings) {
|
||||
eprintln!("warning: {s}");
|
||||
} else {
|
||||
eprintln!("warning: <non-string>");
|
||||
}
|
||||
BuiltinResult::Done(args[1])
|
||||
}
|
||||
BuiltinId::TryEval => BuiltinResult::Error(VM::err(
|
||||
"builtins.tryEval: requires catch frame support (TODO)",
|
||||
)),
|
||||
BuiltinId::AddErrorContext => BuiltinResult::Done(args[1]),
|
||||
BuiltinId::Break => BuiltinResult::Done(args[0]),
|
||||
_ => BuiltinResult::Error(VM::err(format!(
|
||||
"lazy builtin {:?} not yet implemented",
|
||||
id
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn json_to_nix<'gc>(
|
||||
json: &serde_json::Value,
|
||||
mc: &Mutation<'gc>,
|
||||
strings: &DefaultStringInterner,
|
||||
) -> Value<'gc> {
|
||||
match json {
|
||||
serde_json::Value::Null => Value::new_inline(Null),
|
||||
serde_json::Value::Bool(b) => Value::new_inline(*b),
|
||||
serde_json::Value::Number(n) => {
|
||||
if let Some(i) = n.as_i64() {
|
||||
VM::make_int(i, mc)
|
||||
} else if let Some(f) = n.as_f64() {
|
||||
Value::new_float(f)
|
||||
} else {
|
||||
Value::new_inline(Null)
|
||||
}
|
||||
}
|
||||
serde_json::Value::String(s) => {
|
||||
let ns = Gc::new(mc, NixString::new(s.as_str()));
|
||||
Value::new_gc(ns)
|
||||
}
|
||||
serde_json::Value::Array(arr) => {
|
||||
let items: SmallVec<[Value<'gc>; 4]> =
|
||||
arr.iter().map(|v| json_to_nix(v, mc, strings)).collect();
|
||||
Value::new_gc(Gc::new(mc, List { inner: items }))
|
||||
}
|
||||
serde_json::Value::Object(obj) => {
|
||||
let mut entries = SmallVec::<[(StringId, Value<'gc>); 4]>::new();
|
||||
for (k, v) in obj {
|
||||
if let Some(sym) = strings.get(k.as_str()) {
|
||||
entries.push((StringId(sym), json_to_nix(v, mc, strings)));
|
||||
}
|
||||
}
|
||||
entries.sort_by_key(|(k, _)| *k);
|
||||
let attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
|
||||
Value::new_gc(attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub(super) struct FoldlStrict<'gc> {
|
||||
op: StrictValue<'gc>,
|
||||
list: Gc<'gc, List<'gc>>,
|
||||
acc: StrictValue<'gc>,
|
||||
index: usize,
|
||||
phase: FoldlPhase<'gc>,
|
||||
}
|
||||
|
||||
#[derive(Collect, Debug)]
|
||||
#[collect(no_drop)]
|
||||
enum FoldlPhase<'gc> {
|
||||
CallOp,
|
||||
CallPartial(StrictValue<'gc>),
|
||||
}
|
||||
|
||||
impl<'gc> FoldlStrict<'gc> {
|
||||
fn call(args: PrimOpStrictArgs<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
|
||||
let op = args[0];
|
||||
let nul = args[1];
|
||||
let Some(list) = args[2].as_gc::<List<'gc>>() else {
|
||||
return BuiltinResult::Error(VM::err("builtins.foldl': third argument is not a list"));
|
||||
};
|
||||
if list.inner.is_empty() {
|
||||
return BuiltinResult::Done(nul.relax());
|
||||
}
|
||||
let state = FoldlStrict {
|
||||
op,
|
||||
list,
|
||||
acc: nul,
|
||||
index: 0,
|
||||
phase: FoldlPhase::CallOp,
|
||||
};
|
||||
state.step(ctx)
|
||||
}
|
||||
|
||||
fn step(self, _ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
|
||||
let state = BuiltinState::FoldlStrict(FoldlStrict {
|
||||
op: self.op,
|
||||
list: self.list,
|
||||
acc: self.acc,
|
||||
index: self.index,
|
||||
phase: FoldlPhase::CallOp,
|
||||
});
|
||||
call!(ctx, state, self.op, self.acc.relax())
|
||||
}
|
||||
|
||||
fn resume(mut self, val: StrictValue<'gc>, ctx: &PrimOpCtx<'_, 'gc>) -> BuiltinResult<'gc> {
|
||||
match self.phase {
|
||||
FoldlPhase::CallOp => {
|
||||
let partial = val;
|
||||
let elem = self.list.inner[self.index];
|
||||
self.phase = FoldlPhase::CallPartial(partial);
|
||||
let state = BuiltinState::FoldlStrict(self);
|
||||
call_and_force!(ctx, state, partial, elem)
|
||||
}
|
||||
FoldlPhase::CallPartial(_) => {
|
||||
self.acc = val;
|
||||
self.index += 1;
|
||||
self.phase = FoldlPhase::CallOp;
|
||||
if self.index >= self.list.inner.len() {
|
||||
return BuiltinResult::Done(self.acc.relax());
|
||||
}
|
||||
self.step(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,6 @@ impl<const N: usize, T> Stack<N, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) unsafe fn push_unchecked(&mut self, val: T) {
|
||||
unsafe {
|
||||
self.inner.get_unchecked_mut(self.len).write(val);
|
||||
}
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
pub(super) fn push(&mut self, val: T) -> Result<(), T> {
|
||||
if self.len == N {
|
||||
return Err(val);
|
||||
|
||||
+135
-23
@@ -3,18 +3,22 @@ use std::marker::PhantomData;
|
||||
use std::mem::size_of;
|
||||
use std::ops::Deref;
|
||||
|
||||
use gc_arena::{Collect, Gc, Mutation, RefLock, collect::Trace};
|
||||
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, symbol::SymbolU32};
|
||||
use string_interner::Symbol;
|
||||
use string_interner::symbol::SymbolU32;
|
||||
|
||||
use crate::boxing::{RawBox, RawStore, RawTag, Value as RawValue};
|
||||
use crate::{ir::StringId, runtime::builtins::BuiltinId};
|
||||
use crate::ir::StringId;
|
||||
use crate::runtime::builtins::BuiltinId;
|
||||
|
||||
#[sealed]
|
||||
/// # Safety
|
||||
/// TAG must be unique among all implementors.
|
||||
/// 1. TAG must be unique among all implementors.
|
||||
/// 2. TAG must be within 1..=7
|
||||
pub(crate) unsafe trait Storable {
|
||||
const TAG: (bool, u8);
|
||||
}
|
||||
@@ -89,7 +93,21 @@ macro_rules! define_value_types {
|
||||
self.as_inline::<$itype>().unwrap_unchecked()
|
||||
}),)*
|
||||
$(Some(<$gtype as Storable>::TAG) =>
|
||||
write!(f, "{}({:?})", $gname, unsafe { self.as_gc::<$gtype>().unwrap_unchecked() }),)*
|
||||
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})"),
|
||||
}
|
||||
}
|
||||
@@ -134,12 +152,6 @@ impl Default for Value<'_> {
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
#[inline(always)]
|
||||
fn mk_tag(neg: bool, val: u8) -> RawTag {
|
||||
// Safety: val is asserted to be in 1..=7.
|
||||
unsafe { RawTag::new_unchecked(neg, val) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn from_raw_value(rv: RawValue) -> Self {
|
||||
Self {
|
||||
@@ -180,13 +192,28 @@ impl<'gc> Value<'gc> {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_inline<T: InlineStorable>(val: T) -> Self {
|
||||
Self::from_raw_value(RawValue::store(Self::mk_tag(T::TAG.0, T::TAG.1), val))
|
||||
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(Self::mk_tag(T::TAG.0, T::TAG.1), ptr))
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +259,100 @@ impl<'gc> Value<'gc> {
|
||||
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)]
|
||||
@@ -364,7 +485,7 @@ pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
|
||||
#[collect(no_drop)]
|
||||
pub(crate) enum ThunkState<'gc> {
|
||||
Pending {
|
||||
ip: u32,
|
||||
ip: usize,
|
||||
env: Gc<'gc, RefLock<Env<'gc>>>,
|
||||
},
|
||||
Apply {
|
||||
@@ -454,15 +575,6 @@ pub(crate) struct PrimOpApp<'gc> {
|
||||
pub(crate) struct StrictValue<'gc>(Value<'gc>);
|
||||
|
||||
impl<'gc> StrictValue<'gc> {
|
||||
#[inline]
|
||||
pub(crate) fn try_from_forced(val: Value<'gc>) -> Option<Self> {
|
||||
if !val.is::<Thunk<'gc>>() {
|
||||
Some(Self(val))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn relax(self) -> Value<'gc> {
|
||||
self.0
|
||||
|
||||
+943
-1977
File diff suppressed because it is too large
Load Diff
@@ -14,12 +14,11 @@ 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 expr = format!(r#"import "{}""#, nix_path.display());
|
||||
|
||||
let mut ctx = Runtime::new().map_err(|e| e.to_string())?;
|
||||
let content = std::fs::read_to_string(&nix_path).unwrap();
|
||||
let source = Source {
|
||||
ty: fix::error::SourceType::File(nix_path.into()),
|
||||
src: expr.into(),
|
||||
src: content.into(),
|
||||
};
|
||||
ctx.eval_deep(source.clone())
|
||||
.map(|val| (val, source))
|
||||
|
||||
Reference in New Issue
Block a user