implement import & scopedImport (WIP, ResolvePath resolves to string)
This commit is contained in:
-70
@@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
|
||||||
"vcs": {
|
|
||||||
"enabled": true,
|
|
||||||
"clientKind": "git",
|
|
||||||
"useIgnoreFile": true
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"includes": ["**", "!!**/dist"]
|
|
||||||
},
|
|
||||||
"formatter": {
|
|
||||||
"enabled": true,
|
|
||||||
"formatWithErrors": true,
|
|
||||||
"attributePosition": "auto",
|
|
||||||
"indentStyle": "space",
|
|
||||||
"indentWidth": 2,
|
|
||||||
"lineWidth": 110,
|
|
||||||
"lineEnding": "lf"
|
|
||||||
},
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"style": {
|
|
||||||
"useNamingConvention": {
|
|
||||||
"level": "warn",
|
|
||||||
"options": {
|
|
||||||
"strictCase": false,
|
|
||||||
"conventions": [
|
|
||||||
{
|
|
||||||
"selector": { "kind": "objectLiteralProperty" },
|
|
||||||
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"selector": { "kind": "typeProperty" },
|
|
||||||
"formats": ["camelCase", "snake_case"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"includes": ["**/global.d.ts"],
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"style": {
|
|
||||||
"useNamingConvention": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"javascript": {
|
|
||||||
"formatter": {
|
|
||||||
"arrowParentheses": "always",
|
|
||||||
"bracketSameLine": false,
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"jsxQuoteStyle": "double",
|
|
||||||
"quoteProperties": "asNeeded",
|
|
||||||
"semicolons": "always",
|
|
||||||
"trailingCommas": "all"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json": {
|
|
||||||
"formatter": {
|
|
||||||
"trailingCommas": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -247,6 +247,9 @@ pub enum PrimOpPhase {
|
|||||||
CallFunctor1,
|
CallFunctor1,
|
||||||
CallFunctor2,
|
CallFunctor2,
|
||||||
|
|
||||||
|
ImportFinalize,
|
||||||
|
ScopedImportFinalize,
|
||||||
|
|
||||||
Illegal,
|
Illegal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,11 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
self.read_u32();
|
self.read_u32();
|
||||||
}
|
}
|
||||||
Builtins => {}
|
Builtins => {}
|
||||||
ReplBinding | ScopedImportBinding => {
|
ReplBinding => {
|
||||||
|
self.read_u32();
|
||||||
|
}
|
||||||
|
ScopedImportBinding => {
|
||||||
|
self.read_u32();
|
||||||
self.read_u32();
|
self.read_u32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,7 +424,11 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
("ConcatStrings", format!("count={} force={}", count, force))
|
("ConcatStrings", format!("count={} force={}", count, force))
|
||||||
}
|
}
|
||||||
Op::CoerceToString => ("CoerceToString", String::new()),
|
Op::CoerceToString => ("CoerceToString", String::new()),
|
||||||
Op::ResolvePath => ("ResolvePath", String::new()),
|
Op::ResolvePath => {
|
||||||
|
let dir_id = self.read_u32();
|
||||||
|
let dir = self.ctx.resolve_string(dir_id);
|
||||||
|
("ResolvePath", format!("dir={:?}", dir))
|
||||||
|
}
|
||||||
Op::Assert => {
|
Op::Assert => {
|
||||||
let raw_idx = self.read_u32();
|
let raw_idx = self.read_u32();
|
||||||
let span_id = self.read_u32();
|
let span_id = self.read_u32();
|
||||||
@@ -447,9 +455,10 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
|||||||
("LoadReplBinding", format!("{:?}", name))
|
("LoadReplBinding", format!("{:?}", name))
|
||||||
}
|
}
|
||||||
Op::LoadScopedBinding => {
|
Op::LoadScopedBinding => {
|
||||||
|
let slot = self.read_u32();
|
||||||
let idx = self.read_u32();
|
let idx = self.read_u32();
|
||||||
let name = self.ctx.resolve_string(idx);
|
let name = self.ctx.resolve_string(idx);
|
||||||
("LoadScopedBinding", format!("{:?}", name))
|
("LoadScopedBinding", format!("slot={} {:?}", slot, name))
|
||||||
}
|
}
|
||||||
Op::Return => ("Return", String::new()),
|
Op::Return => ("Return", String::new()),
|
||||||
Op::Illegal => ("Illegal", String::new()),
|
Op::Illegal => ("Illegal", String::new()),
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ pub trait BytecodeContext {
|
|||||||
fn get_code(&self) -> &[u8];
|
fn get_code(&self) -> &[u8];
|
||||||
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
fn get_code_mut(&mut self) -> &mut Vec<u8>;
|
||||||
fn add_constant(&mut self, val: Const) -> u32;
|
fn add_constant(&mut self, val: Const) -> u32;
|
||||||
|
fn current_source_dir(&mut self) -> StringId;
|
||||||
|
fn current_scope_slot(&self) -> Option<u32>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -232,6 +234,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
ScopedImportBinding(id) => {
|
ScopedImportBinding(id) => {
|
||||||
self.emit_u8(OperandType::ScopedImportBinding as u8);
|
self.emit_u8(OperandType::ScopedImportBinding as u8);
|
||||||
|
let slot = self
|
||||||
|
.ctx
|
||||||
|
.current_scope_slot()
|
||||||
|
.expect("ScopedImportBinding outside scoped compilation");
|
||||||
|
self.emit_u32(slot);
|
||||||
self.emit_str_id(id);
|
self.emit_str_id(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,6 +430,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
&Ir::Path(p) => {
|
&Ir::Path(p) => {
|
||||||
self.emit_expr(p);
|
self.emit_expr(p);
|
||||||
self.emit_op(Op::ResolvePath);
|
self.emit_op(Op::ResolvePath);
|
||||||
|
let dir_id = self.ctx.current_source_dir();
|
||||||
|
self.emit_str_id(dir_id);
|
||||||
}
|
}
|
||||||
&Ir::If { cond, consq, alter } => {
|
&Ir::If { cond, consq, alter } => {
|
||||||
self.emit_expr(cond);
|
self.emit_expr(cond);
|
||||||
@@ -548,6 +557,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
&Ir::ScopedImportBinding(name) => {
|
&Ir::ScopedImportBinding(name) => {
|
||||||
self.emit_op(Op::LoadScopedBinding);
|
self.emit_op(Op::LoadScopedBinding);
|
||||||
|
let slot = self
|
||||||
|
.ctx
|
||||||
|
.current_scope_slot()
|
||||||
|
.expect("ScopedImportBinding outside scoped compilation");
|
||||||
|
self.emit_u32(slot);
|
||||||
self.emit_str_id(name);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
Ir::WithLookup { sym, namespaces } => {
|
Ir::WithLookup { sym, namespaces } => {
|
||||||
@@ -592,6 +606,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
self.emit_op(Op::PushString);
|
self.emit_op(Op::PushString);
|
||||||
self.emit_str_id(id);
|
self.emit_str_id(id);
|
||||||
self.emit_op(Op::ResolvePath);
|
self.emit_op(Op::ResolvePath);
|
||||||
|
let dir_id = self.ctx.current_source_dir();
|
||||||
|
self.emit_str_id(dir_id);
|
||||||
}
|
}
|
||||||
Thunk(id) => {
|
Thunk(id) => {
|
||||||
let (layer, local) = self.resolve_thunk(id);
|
let (layer, local) = self.resolve_thunk(id);
|
||||||
@@ -615,6 +631,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
ScopedImportBinding(name) => {
|
ScopedImportBinding(name) => {
|
||||||
self.emit_op(Op::LoadScopedBinding);
|
self.emit_op(Op::LoadScopedBinding);
|
||||||
|
let slot = self
|
||||||
|
.ctx
|
||||||
|
.current_scope_slot()
|
||||||
|
.expect("ScopedImportBinding outside scoped compilation");
|
||||||
|
self.emit_u32(slot);
|
||||||
self.emit_str_id(name);
|
self.emit_str_id(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,8 +122,9 @@ impl<'a> BytecodeReader<'a> {
|
|||||||
OperandData::ReplBinding(id)
|
OperandData::ReplBinding(id)
|
||||||
}
|
}
|
||||||
OperandType::ScopedImportBinding => {
|
OperandType::ScopedImportBinding => {
|
||||||
let id = self.read_string_id();
|
let slot_id = self.read_u32();
|
||||||
OperandData::ScopedImportBinding(id)
|
let name = self.read_string_id();
|
||||||
|
OperandData::ScopedImportBinding { slot_id, name }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::{Break, BytecodeReader, Step, Vm, VmRuntimeCtx};
|
|||||||
pub(crate) enum TailResult {
|
pub(crate) enum TailResult {
|
||||||
YieldFuel(u32),
|
YieldFuel(u32),
|
||||||
Done,
|
Done,
|
||||||
|
LoadFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
pub(crate) type OpFn<'gc, C> = extern "rust-preserve-none" fn(
|
||||||
@@ -37,6 +38,7 @@ macro_rules! tail_dispatch_after {
|
|||||||
($result:expr, $new_pc:expr, $vm:ident, $mc:ident, $ctx:ident, $bc:ident, $table:ident, $fuel:ident) => {{
|
($result:expr, $new_pc:expr, $vm:ident, $mc:ident, $ctx:ident, $bc:ident, $table:ident, $fuel:ident) => {{
|
||||||
match $result {
|
match $result {
|
||||||
Step::Continue(()) | Step::Break(Break::Force) => {}
|
Step::Continue(()) | Step::Break(Break::Force) => {}
|
||||||
|
Step::Break(Break::LoadFile) => return TailResult::LoadFile,
|
||||||
Step::Break(Break::Done) => return TailResult::Done,
|
Step::Break(Break::Done) => return TailResult::Done,
|
||||||
}
|
}
|
||||||
let new_pc: u32 = $new_pc;
|
let new_pc: u32 = $new_pc;
|
||||||
@@ -183,7 +185,7 @@ tail_fn!(op_jump, (reader));
|
|||||||
tail_fn!(op_coerce_to_string, (reader, mc));
|
tail_fn!(op_coerce_to_string, (reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_concat_strings, (ctx, reader, mc));
|
tail_fn!(op_concat_strings, (ctx, reader, mc));
|
||||||
tail_fn!(op_resolve_path, (ctx));
|
tail_fn!(op_resolve_path, (ctx, reader, mc));
|
||||||
|
|
||||||
tail_fn!(op_assert, (ctx, reader, mc));
|
tail_fn!(op_assert, (ctx, reader, mc));
|
||||||
|
|
||||||
@@ -193,7 +195,7 @@ tail_fn!(op_load_builtins, ());
|
|||||||
tail_fn!(op_load_builtin, (reader));
|
tail_fn!(op_load_builtin, (reader));
|
||||||
|
|
||||||
tail_fn!(op_load_repl_binding, (reader));
|
tail_fn!(op_load_repl_binding, (reader));
|
||||||
tail_fn!(op_load_scoped_binding, (reader));
|
tail_fn!(op_load_scoped_binding, (ctx, reader, mc));
|
||||||
|
|
||||||
macro_rules! table {
|
macro_rules! table {
|
||||||
($($variant:ident => $fn:ident),* $(,)?) => {
|
($($variant:ident => $fn:ident),* $(,)?) => {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
use std::path::{Component, PathBuf};
|
||||||
|
|
||||||
use fix_builtins::BuiltinId;
|
use fix_builtins::BuiltinId;
|
||||||
use fix_common::StringId;
|
use fix_common::StringId;
|
||||||
|
use fix_error::Error;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
use crate::value::{NixString, StrictValue};
|
use crate::value::{AttrSet, NixString, StrictValue};
|
||||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx};
|
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||||
|
|
||||||
impl<'gc> crate::Vm<'gc> {
|
impl<'gc> crate::Vm<'gc> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -31,9 +34,37 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
pub(crate) fn op_load_scoped_binding(
|
||||||
let _name = reader.read_string_id();
|
&mut self,
|
||||||
todo!("LoadScopedBinding");
|
ctx: &impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let slot_id = reader.read_u32();
|
||||||
|
let name = reader.read_string_id();
|
||||||
|
let scope = match self.scope_slots.get(slot_id as usize).copied() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"internal: invalid scope slot {slot_id}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Some(attrs) = scope.as_gc::<AttrSet>() else {
|
||||||
|
return self.finish_err(Error::eval_error(
|
||||||
|
"internal: scope slot is not an attrset",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
match attrs.lookup(name) {
|
||||||
|
Some(val) => {
|
||||||
|
self.push(val);
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
None => self.finish_err(Error::eval_error(format!(
|
||||||
|
"scoped binding '{}' not found",
|
||||||
|
ctx.resolve_string(name)
|
||||||
|
))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -58,8 +89,6 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
reader: &mut BytecodeReader<'_>,
|
reader: &mut BytecodeReader<'_>,
|
||||||
_mc: &gc_arena::Mutation<'gc>,
|
_mc: &gc_arena::Mutation<'gc>,
|
||||||
) -> Step {
|
) -> Step {
|
||||||
use crate::VmRuntimeCtxExt;
|
|
||||||
|
|
||||||
let count = reader.read_u16() as usize;
|
let count = reader.read_u16() as usize;
|
||||||
let _force_string = reader.read_u8() != 0;
|
let _force_string = reader.read_u8() != 0;
|
||||||
|
|
||||||
@@ -85,7 +114,64 @@ impl<'gc> crate::Vm<'gc> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl VmRuntimeCtx) -> Step {
|
pub(crate) fn op_resolve_path(
|
||||||
todo!("implement ResolvePath");
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &gc_arena::Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let dir_id = reader.read_string_id();
|
||||||
|
let path = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a string for path, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let resolved = match resolve_path_str(ctx.resolve_string(dir_id), &path) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => return self.finish_err(e),
|
||||||
|
};
|
||||||
|
let sid = ctx.intern_string(resolved);
|
||||||
|
self.push(Value::new_inline(sid));
|
||||||
|
Step::Continue(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a Nix path literal against `current_dir`.
|
||||||
|
///
|
||||||
|
/// Mirrors nix-js's `op_resolve_path`: absolute paths returned as-is, `~/X`
|
||||||
|
/// expanded against `$HOME`, otherwise joined onto `current_dir`. The result
|
||||||
|
/// is normalized by removing `.` components and resolving `..` lexically
|
||||||
|
/// (no symlink resolution).
|
||||||
|
fn resolve_path_str(current_dir: &str, path: &str) -> Result<String, Box<Error>> {
|
||||||
|
let raw = if path.starts_with('/') {
|
||||||
|
return Ok(path.to_owned());
|
||||||
|
} else if let Some(rest) = path.strip_prefix("~/") {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let mut dir = std::env::home_dir()
|
||||||
|
.ok_or_else(|| Error::eval_error("home dir not defined"))?;
|
||||||
|
dir.push(rest);
|
||||||
|
dir
|
||||||
|
} else {
|
||||||
|
let mut dir = PathBuf::from(current_dir);
|
||||||
|
dir.push(path);
|
||||||
|
dir
|
||||||
|
};
|
||||||
|
let mut normalized = PathBuf::new();
|
||||||
|
for component in raw.components() {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(p) => normalized.push(p.as_os_str()),
|
||||||
|
Component::RootDir => normalized.push("/"),
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
Component::Normal(c) => normalized.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(normalized.to_string_lossy().into_owned())
|
||||||
|
}
|
||||||
|
|||||||
+74
-5
@@ -77,13 +77,25 @@ pub trait VmRuntimeCtx {
|
|||||||
|
|
||||||
pub trait VmCode {
|
pub trait VmCode {
|
||||||
fn bytecode(&self) -> &[u8];
|
fn bytecode(&self) -> &[u8];
|
||||||
fn compile(
|
fn compile_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
extra_scope: Option<ExtraScope>,
|
||||||
ctx: &mut impl VmRuntimeCtx,
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
) -> fix_error::Result<InstructionPtr>;
|
) -> fix_error::Result<InstructionPtr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extra scope passed to a re-entrant compile from inside a running VM.
|
||||||
|
///
|
||||||
|
/// Currently only `ScopedImport` is produced (by the `scopedImport` builtin),
|
||||||
|
/// but the variant is kept open so REPL bindings could later land here too.
|
||||||
|
pub enum ExtraScope {
|
||||||
|
ScopedImport {
|
||||||
|
keys: HashSet<StringId>,
|
||||||
|
slot_id: u32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
trait VmRuntimeCtxExt: VmRuntimeCtx {
|
trait VmRuntimeCtxExt: VmRuntimeCtx {
|
||||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
||||||
fn get_string_id<'a, 'gc: 'a>(
|
fn get_string_id<'a, 'gc: 'a>(
|
||||||
@@ -197,6 +209,7 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
|||||||
enum Break {
|
enum Break {
|
||||||
Force,
|
Force,
|
||||||
Done,
|
Done,
|
||||||
|
LoadFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Step = std::ops::ControlFlow<Break>;
|
type Step = std::ops::ControlFlow<Break>;
|
||||||
@@ -214,6 +227,7 @@ pub struct Vm<'gc> {
|
|||||||
env: GcEnv<'gc>,
|
env: GcEnv<'gc>,
|
||||||
|
|
||||||
import_cache: HashMap<PathBuf, Value<'gc>>,
|
import_cache: HashMap<PathBuf, Value<'gc>>,
|
||||||
|
scope_slots: Vec<Value<'gc>>,
|
||||||
|
|
||||||
builtins: Value<'gc>,
|
builtins: Value<'gc>,
|
||||||
empty_list: Value<'gc>,
|
empty_list: Value<'gc>,
|
||||||
@@ -224,9 +238,24 @@ pub struct Vm<'gc> {
|
|||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
result: Option<Result<fix_common::Value>>,
|
result: Option<Result<fix_common::Value>>,
|
||||||
|
|
||||||
|
#[collect(require_static)]
|
||||||
|
pending_load: Option<PendingLoad>,
|
||||||
|
|
||||||
functor_sym: StringId,
|
functor_sym: StringId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PendingLoad {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub scope: Option<PendingScope>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PendingScope {
|
||||||
|
pub keys: HashSet<StringId>,
|
||||||
|
pub slot_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
enum OperandData {
|
enum OperandData {
|
||||||
Const(StaticValue),
|
Const(StaticValue),
|
||||||
BigInt(i64),
|
BigInt(i64),
|
||||||
@@ -234,7 +263,7 @@ enum OperandData {
|
|||||||
BuiltinConst(StringId),
|
BuiltinConst(StringId),
|
||||||
Builtins,
|
Builtins,
|
||||||
ReplBinding(StringId),
|
ReplBinding(StringId),
|
||||||
ScopedImportBinding(StringId),
|
ScopedImportBinding { slot_id: u32, name: StringId },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperandData {
|
impl OperandData {
|
||||||
@@ -260,7 +289,17 @@ impl OperandData {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
Builtins => root.builtins,
|
Builtins => root.builtins,
|
||||||
ReplBinding(_id) => todo!(),
|
ReplBinding(_id) => todo!(),
|
||||||
ScopedImportBinding(_id) => todo!(),
|
ScopedImportBinding { slot_id, name } => {
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let scope = root
|
||||||
|
.scope_slots
|
||||||
|
.get(slot_id as usize)
|
||||||
|
.expect("invalid scope slot");
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let attrs = scope.as_gc::<AttrSet>().expect("scope must be attrset");
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
attrs.lookup(name).expect("scoped binding not found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,6 +373,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
env: Gc::new(mc, RefLock::new(Env::empty())),
|
env: Gc::new(mc, RefLock::new(Env::empty())),
|
||||||
|
|
||||||
import_cache: HashMap::new(),
|
import_cache: HashMap::new(),
|
||||||
|
scope_slots: Vec::new(),
|
||||||
|
|
||||||
builtins,
|
builtins,
|
||||||
empty_list: Value::new_gc(Gc::new(mc, List::default())),
|
empty_list: Value::new_gc(Gc::new(mc, List::default())),
|
||||||
@@ -342,6 +382,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
force_mode,
|
force_mode,
|
||||||
|
|
||||||
result: None,
|
result: None,
|
||||||
|
pending_load: None,
|
||||||
|
|
||||||
functor_sym: ctx.intern_string("__functor"),
|
functor_sym: ctx.intern_string("__functor"),
|
||||||
}
|
}
|
||||||
@@ -541,6 +582,7 @@ struct CallFrame<'gc> {
|
|||||||
enum Action {
|
enum Action {
|
||||||
Continue { pc: usize },
|
Continue { pc: usize },
|
||||||
Done(Result<fix_common::Value>),
|
Done(Result<fix_common::Value>),
|
||||||
|
LoadFile(PendingLoad),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NixNum {
|
enum NixNum {
|
||||||
@@ -573,6 +615,21 @@ impl Vm<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::LoadFile(load) => {
|
||||||
|
let source = match Source::new_file(load.path) {
|
||||||
|
Ok(src) => src,
|
||||||
|
Err(err) => break Err(Error::eval_error(format!("import failed: {err}"))),
|
||||||
|
};
|
||||||
|
let extra_scope = load.scope.map(|s| ExtraScope::ScopedImport {
|
||||||
|
keys: s.keys,
|
||||||
|
slot_id: s.slot_id,
|
||||||
|
});
|
||||||
|
let new_ip = match code.compile_with_scope(source, extra_scope, runtime) {
|
||||||
|
Ok(ip) => ip,
|
||||||
|
Err(err) => break Err(err),
|
||||||
|
};
|
||||||
|
pc = new_ip.0;
|
||||||
|
}
|
||||||
Action::Done(done) => break done,
|
Action::Done(done) => break done,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -604,6 +661,11 @@ impl<'gc> Vm<'gc> {
|
|||||||
TailResult::Done => {
|
TailResult::Done => {
|
||||||
Action::Done(self.result.take().expect("TailResult::Done without result"))
|
Action::Done(self.result.take().expect("TailResult::Done without result"))
|
||||||
}
|
}
|
||||||
|
TailResult::LoadFile => Action::LoadFile(
|
||||||
|
self.pending_load
|
||||||
|
.take()
|
||||||
|
.expect("TailResult::LoadFile without pending_load"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -689,7 +751,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
|
|
||||||
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc),
|
ConcatStrings => self.op_concat_strings(ctx, &mut reader, mc),
|
||||||
CoerceToString => self.op_coerce_to_string(&mut reader, mc),
|
CoerceToString => self.op_coerce_to_string(&mut reader, mc),
|
||||||
ResolvePath => self.op_resolve_path(ctx),
|
ResolvePath => self.op_resolve_path(ctx, &mut reader, mc),
|
||||||
|
|
||||||
Assert => self.op_assert(ctx, &mut reader, mc),
|
Assert => self.op_assert(ctx, &mut reader, mc),
|
||||||
|
|
||||||
@@ -699,7 +761,7 @@ impl<'gc> Vm<'gc> {
|
|||||||
LoadBuiltin => self.op_load_builtin(&mut reader),
|
LoadBuiltin => self.op_load_builtin(&mut reader),
|
||||||
|
|
||||||
LoadReplBinding => self.op_load_repl_binding(&mut reader),
|
LoadReplBinding => self.op_load_repl_binding(&mut reader),
|
||||||
LoadScopedBinding => self.op_load_scoped_binding(&mut reader),
|
LoadScopedBinding => self.op_load_scoped_binding(ctx, &mut reader, mc),
|
||||||
|
|
||||||
Illegal => unreachable!(),
|
Illegal => unreachable!(),
|
||||||
};
|
};
|
||||||
@@ -709,6 +771,13 @@ impl<'gc> Vm<'gc> {
|
|||||||
Step::Break(Break::Done) => {
|
Step::Break(Break::Done) => {
|
||||||
return Action::Done(self.result.take().expect("Break::Done without result"));
|
return Action::Done(self.result.take().expect("Break::Done without result"));
|
||||||
}
|
}
|
||||||
|
Step::Break(Break::LoadFile) => {
|
||||||
|
return Action::LoadFile(
|
||||||
|
self.pending_load
|
||||||
|
.take()
|
||||||
|
.expect("Break::LoadFile without pending_load"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,181 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use fix_builtins::PrimOpPhase;
|
||||||
|
use fix_common::StringId;
|
||||||
|
use fix_error::Error;
|
||||||
|
use gc_arena::{Gc, Mutation};
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
|
||||||
|
use crate::bytecode_reader::BytecodeReader;
|
||||||
|
use crate::value::*;
|
||||||
|
use crate::{Break, CallFrame, PendingLoad, PendingScope, Step, Vm, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||||
|
|
||||||
|
impl<'gc> Vm<'gc> {
|
||||||
|
pub(crate) fn primop_import(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [path]
|
||||||
|
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let path_str = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let abs = match resolve_import_target(&path_str) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return self.finish_err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(&cached) = self.import_cache.get(&abs) {
|
||||||
|
return self.return_from_primop(cached, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stash the resolved path on the stack as a string-id so the
|
||||||
|
// finalizer can use it as the cache key. The slot we pop here was
|
||||||
|
// freed by `force_and_retry`, so we simply push.
|
||||||
|
let path_sid = ctx.intern_string(abs.to_string_lossy());
|
||||||
|
self.push(Value::new_inline(path_sid));
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: PrimOpPhase::ImportFinalize.ip() as usize,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.pending_load = Some(PendingLoad {
|
||||||
|
path: abs,
|
||||||
|
scope: None,
|
||||||
|
});
|
||||||
|
Step::Break(Break::LoadFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_import_finalize(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [path_sid, return_value]
|
||||||
|
let val = self.pop();
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let path_sid = self.pop().as_inline::<StringId>().unwrap();
|
||||||
|
// The cache key is keyed by the absolute path string we interned in
|
||||||
|
// `primop_import`. Resolve it back to the host PathBuf.
|
||||||
|
let path_str = ctx.resolve_string(path_sid).to_owned();
|
||||||
|
self.import_cache.insert(PathBuf::from(path_str), val);
|
||||||
|
self.push(val);
|
||||||
|
let Some(CallFrame {
|
||||||
|
pc: ret_pc,
|
||||||
|
thunk: _,
|
||||||
|
env,
|
||||||
|
}) = self.call_stack.pop()
|
||||||
|
else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
reader.set_pc(ret_pc);
|
||||||
|
// FIXME:
|
||||||
|
// self.call_depth -= 1;
|
||||||
|
self.env = env;
|
||||||
|
Step::Continue(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_scoped_import(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [scope, path]
|
||||||
|
let (scope_attrs, path_val) =
|
||||||
|
self.force_and_retry::<(Gc<AttrSet>, StrictValue)>(reader, mc)?;
|
||||||
|
let path_str = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let abs = match resolve_import_target(&path_str) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return self.finish_err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let keys: HashSet<StringId> = scope_attrs.entries.iter().map(|&(k, _)| k).collect();
|
||||||
|
let slot_id = self.scope_slots.len() as u32;
|
||||||
|
self.scope_slots.push(Value::new_gc(scope_attrs));
|
||||||
|
|
||||||
|
self.call_stack.push(CallFrame {
|
||||||
|
pc: PrimOpPhase::ScopedImportFinalize.ip() as usize,
|
||||||
|
thunk: None,
|
||||||
|
env: self.env,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.pending_load = Some(PendingLoad {
|
||||||
|
path: abs,
|
||||||
|
scope: Some(PendingScope { keys, slot_id }),
|
||||||
|
});
|
||||||
|
Step::Break(Break::LoadFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_scoped_import_finalize(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
_mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
// stack: [return_value]
|
||||||
|
// We intentionally do NOT pop the slot from `scope_slots` so that
|
||||||
|
// closures or thunks created inside the imported file can still
|
||||||
|
// resolve their scope after `scopedImport` returns.
|
||||||
|
let val = self.pop();
|
||||||
|
self.return_from_primop(val, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn primop_path_exists(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut impl VmRuntimeCtx,
|
||||||
|
reader: &mut BytecodeReader<'_>,
|
||||||
|
mc: &Mutation<'gc>,
|
||||||
|
) -> Step {
|
||||||
|
let path_val = self.force_and_retry::<StrictValue>(reader, mc)?;
|
||||||
|
let path = match ctx.get_string(path_val) {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => {
|
||||||
|
return self.finish_err(Error::eval_error(format!(
|
||||||
|
"expected a path string, got {}",
|
||||||
|
path_val.ty()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
||||||
|
let p = std::path::Path::new(&path);
|
||||||
|
let exists = if must_be_dir {
|
||||||
|
std::fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
std::fs::symlink_metadata(p).is_ok()
|
||||||
|
};
|
||||||
|
self.return_from_primop(Value::new_inline(exists), reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the user-supplied path string into an absolute, dotted-segment
|
||||||
|
/// resolved `PathBuf` and append `default.nix` if the target is a directory.
|
||||||
|
fn resolve_import_target(path: &str) -> Result<PathBuf, Box<Error>> {
|
||||||
|
let mut abs = PathBuf::from(path);
|
||||||
|
if !abs.is_absolute() {
|
||||||
|
return Err(Error::eval_error(format!(
|
||||||
|
"import: expected an absolute path, got '{path}'"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if abs.is_dir() {
|
||||||
|
abs.push("default.nix");
|
||||||
|
}
|
||||||
|
Ok(abs)
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,10 +50,18 @@ impl<'gc> Vm<'gc> {
|
|||||||
CallFunctor1 => self.primop_call_functor_1(reader, mc),
|
CallFunctor1 => self.primop_call_functor_1(reader, mc),
|
||||||
CallFunctor2 => self.primop_call_functor_2(reader, mc),
|
CallFunctor2 => self.primop_call_functor_2(reader, mc),
|
||||||
|
|
||||||
|
Import => self.primop_import(ctx, reader, mc),
|
||||||
|
ImportFinalize => self.primop_import_finalize(ctx, reader),
|
||||||
|
ScopedImport => self.primop_scoped_import(ctx, reader, mc),
|
||||||
|
ScopedImportFinalize => self.primop_scoped_import_finalize(ctx, reader, mc),
|
||||||
|
|
||||||
|
PathExists => self.primop_path_exists(ctx, reader, mc),
|
||||||
|
|
||||||
phase => todo!("primop phase {phase:?}"),
|
phase => todo!("primop phase {phase:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
||||||
self.push(val);
|
self.push(val);
|
||||||
let Some(CallFrame {
|
let Some(CallFrame {
|
||||||
|
|||||||
+52
-11
@@ -34,6 +34,9 @@ pub struct CodeState {
|
|||||||
pub spans: Vec<(usize, rnix::TextRange)>,
|
pub spans: Vec<(usize, rnix::TextRange)>,
|
||||||
pub thunk_count: usize,
|
pub thunk_count: usize,
|
||||||
pub global_env: HashMap<StringId, MaybeThunk>,
|
pub global_env: HashMap<StringId, MaybeThunk>,
|
||||||
|
/// Set during a compilation pass when the code is being compiled under a
|
||||||
|
/// `scopedImport` scope. Read by [`CompilerCtx::current_scope_slot`].
|
||||||
|
pub current_scope_slot: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Evaluator {
|
pub struct Evaluator {
|
||||||
@@ -67,6 +70,7 @@ impl Evaluator {
|
|||||||
thunk_count: 0,
|
thunk_count: 0,
|
||||||
bytecode,
|
bytecode,
|
||||||
global_env,
|
global_env,
|
||||||
|
current_scope_slot: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,16 +154,22 @@ impl VmCode for CodeState {
|
|||||||
fn bytecode(&self) -> &[u8] {
|
fn bytecode(&self) -> &[u8] {
|
||||||
&self.bytecode
|
&self.bytecode
|
||||||
}
|
}
|
||||||
fn compile(
|
fn compile_with_scope(
|
||||||
&mut self,
|
&mut self,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
extra_scope: Option<fix_vm::ExtraScope>,
|
||||||
runtime: &mut impl VmRuntimeCtx,
|
runtime: &mut impl VmRuntimeCtx,
|
||||||
) -> Result<InstructionPtr> {
|
) -> Result<InstructionPtr> {
|
||||||
let mut compiler = CompilerCtx {
|
let mut compiler = CompilerCtx {
|
||||||
code: self,
|
code: self,
|
||||||
runtime,
|
runtime,
|
||||||
};
|
};
|
||||||
compiler.compile_bytecode(source, None)
|
let extra = extra_scope.map(|s| match s {
|
||||||
|
fix_vm::ExtraScope::ScopedImport { keys, slot_id } => {
|
||||||
|
ExtraScope::ScopedImport { keys, slot_id }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
compiler.compile_bytecode(source, extra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,9 +190,18 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
|||||||
source: Source,
|
source: Source,
|
||||||
extra_scope: Option<ExtraScope>,
|
extra_scope: Option<ExtraScope>,
|
||||||
) -> Result<InstructionPtr> {
|
) -> Result<InstructionPtr> {
|
||||||
let root = self.downgrade(source, extra_scope)?;
|
let prev_scope_slot = self.code.current_scope_slot;
|
||||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
self.code.current_scope_slot = match &extra_scope {
|
||||||
Ok(ip)
|
Some(ExtraScope::ScopedImport { slot_id, .. }) => Some(*slot_id),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let result = (|| -> Result<InstructionPtr> {
|
||||||
|
let root = self.downgrade(source, extra_scope)?;
|
||||||
|
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||||
|
Ok(ip)
|
||||||
|
})();
|
||||||
|
self.code.current_scope_slot = prev_scope_slot;
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
|
fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
|
||||||
@@ -258,6 +277,22 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
|||||||
};
|
};
|
||||||
self.runtime.add_const(val)
|
self.runtime.add_const(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn current_source_dir(&mut self) -> StringId {
|
||||||
|
let dir = self
|
||||||
|
.code
|
||||||
|
.sources
|
||||||
|
.last()
|
||||||
|
.expect("current_source not set")
|
||||||
|
.get_dir()
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned();
|
||||||
|
self.runtime.intern_string(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_scope_slot(&self) -> Option<u32> {
|
||||||
|
self.code.current_scope_slot
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -410,8 +445,8 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
|||||||
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into()));
|
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Scope::ScopedImport(scoped_bindings) => {
|
Scope::ScopedImport { keys, .. } => {
|
||||||
if scoped_bindings.contains(&sym) {
|
if keys.contains(&sym) {
|
||||||
return Ok(self
|
return Ok(self
|
||||||
.bump
|
.bump
|
||||||
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into()));
|
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into()));
|
||||||
@@ -598,21 +633,27 @@ impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
|||||||
enum Scope<'ctx, 'id, 'ir> {
|
enum Scope<'ctx, 'id, 'ir> {
|
||||||
Global(&'ctx HashMap<StringId, MaybeThunk>),
|
Global(&'ctx HashMap<StringId, MaybeThunk>),
|
||||||
Repl(&'ctx HashSet<StringId>),
|
Repl(&'ctx HashSet<StringId>),
|
||||||
ScopedImport(HashSet<StringId>),
|
ScopedImport {
|
||||||
|
keys: HashSet<StringId>,
|
||||||
|
slot_id: u32,
|
||||||
|
},
|
||||||
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
||||||
Param { sym: StringId, abs_layer: u8 },
|
Param { sym: StringId, abs_layer: u8 },
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ExtraScope<'ctx> {
|
pub enum ExtraScope<'ctx> {
|
||||||
Repl(&'ctx HashSet<StringId>),
|
Repl(&'ctx HashSet<StringId>),
|
||||||
ScopedImport(HashSet<StringId>),
|
ScopedImport {
|
||||||
|
keys: HashSet<StringId>,
|
||||||
|
slot_id: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
|
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
|
||||||
fn from(value: ExtraScope<'ctx>) -> Self {
|
fn from(value: ExtraScope<'ctx>) -> Self {
|
||||||
use ExtraScope::*;
|
use ExtraScope::*;
|
||||||
match value {
|
match value {
|
||||||
ScopedImport(scope) => Scope::ScopedImport(scope),
|
ScopedImport { keys, slot_id } => Scope::ScopedImport { keys, slot_id },
|
||||||
Repl(scope) => Scope::Repl(scope),
|
Repl(scope) => Scope::Repl(scope),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user