implement import & scopedImport (WIP, ResolvePath resolves to string)
This commit is contained in:
@@ -122,8 +122,9 @@ impl<'a> BytecodeReader<'a> {
|
||||
OperandData::ReplBinding(id)
|
||||
}
|
||||
OperandType::ScopedImportBinding => {
|
||||
let id = self.read_string_id();
|
||||
OperandData::ScopedImportBinding(id)
|
||||
let slot_id = self.read_u32();
|
||||
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 {
|
||||
YieldFuel(u32),
|
||||
Done,
|
||||
LoadFile,
|
||||
}
|
||||
|
||||
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) => {{
|
||||
match $result {
|
||||
Step::Continue(()) | Step::Break(Break::Force) => {}
|
||||
Step::Break(Break::LoadFile) => return TailResult::LoadFile,
|
||||
Step::Break(Break::Done) => return TailResult::Done,
|
||||
}
|
||||
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_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));
|
||||
|
||||
@@ -193,7 +195,7 @@ tail_fn!(op_load_builtins, ());
|
||||
tail_fn!(op_load_builtin, (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 {
|
||||
($($variant:ident => $fn:ident),* $(,)?) => {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use std::path::{Component, PathBuf};
|
||||
|
||||
use fix_builtins::BuiltinId;
|
||||
use fix_common::StringId;
|
||||
use fix_error::Error;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
use crate::value::{NixString, StrictValue};
|
||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx};
|
||||
use crate::value::{AttrSet, NixString, StrictValue};
|
||||
use crate::{BytecodeReader, PrimOp, Step, Value, VmRuntimeCtx, VmRuntimeCtxExt};
|
||||
|
||||
impl<'gc> crate::Vm<'gc> {
|
||||
#[inline(always)]
|
||||
@@ -31,9 +34,37 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_load_scoped_binding(&mut self, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
let _name = reader.read_string_id();
|
||||
todo!("LoadScopedBinding");
|
||||
pub(crate) fn op_load_scoped_binding(
|
||||
&mut self,
|
||||
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)]
|
||||
@@ -58,8 +89,6 @@ impl<'gc> crate::Vm<'gc> {
|
||||
reader: &mut BytecodeReader<'_>,
|
||||
_mc: &gc_arena::Mutation<'gc>,
|
||||
) -> Step {
|
||||
use crate::VmRuntimeCtxExt;
|
||||
|
||||
let count = reader.read_u16() as usize;
|
||||
let _force_string = reader.read_u8() != 0;
|
||||
|
||||
@@ -85,7 +114,64 @@ impl<'gc> crate::Vm<'gc> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn op_resolve_path(&mut self, _ctx: &mut impl VmRuntimeCtx) -> Step {
|
||||
todo!("implement ResolvePath");
|
||||
pub(crate) fn op_resolve_path(
|
||||
&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())
|
||||
}
|
||||
|
||||
+77
-5
@@ -77,13 +77,25 @@ pub trait VmRuntimeCtx {
|
||||
|
||||
pub trait VmCode {
|
||||
fn bytecode(&self) -> &[u8];
|
||||
fn compile(
|
||||
fn compile_with_scope(
|
||||
&mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<ExtraScope>,
|
||||
ctx: &mut impl VmRuntimeCtx,
|
||||
) -> 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 {
|
||||
fn get_string<'a, 'gc: 'a>(&'a self, val: StrictValue<'gc>) -> Option<&'a str>;
|
||||
fn get_string_id<'a, 'gc: 'a>(
|
||||
@@ -197,6 +209,7 @@ impl<T: VmRuntimeCtx> ConvertValueWithSeen for T {
|
||||
enum Break {
|
||||
Force,
|
||||
Done,
|
||||
LoadFile,
|
||||
}
|
||||
|
||||
type Step = std::ops::ControlFlow<Break>;
|
||||
@@ -214,6 +227,7 @@ pub struct Vm<'gc> {
|
||||
env: GcEnv<'gc>,
|
||||
|
||||
import_cache: HashMap<PathBuf, Value<'gc>>,
|
||||
scope_slots: Vec<Value<'gc>>,
|
||||
|
||||
builtins: Value<'gc>,
|
||||
empty_list: Value<'gc>,
|
||||
@@ -224,9 +238,24 @@ pub struct Vm<'gc> {
|
||||
#[collect(require_static)]
|
||||
result: Option<Result<fix_common::Value>>,
|
||||
|
||||
#[collect(require_static)]
|
||||
pending_load: Option<PendingLoad>,
|
||||
|
||||
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 {
|
||||
Const(StaticValue),
|
||||
BigInt(i64),
|
||||
@@ -234,7 +263,7 @@ enum OperandData {
|
||||
BuiltinConst(StringId),
|
||||
Builtins,
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
ScopedImportBinding { slot_id: u32, name: StringId },
|
||||
}
|
||||
|
||||
impl OperandData {
|
||||
@@ -260,7 +289,17 @@ impl OperandData {
|
||||
.unwrap(),
|
||||
Builtins => root.builtins,
|
||||
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())),
|
||||
|
||||
import_cache: HashMap::new(),
|
||||
scope_slots: Vec::new(),
|
||||
|
||||
builtins,
|
||||
empty_list: Value::new_gc(Gc::new(mc, List::default())),
|
||||
@@ -342,6 +382,7 @@ impl<'gc> Vm<'gc> {
|
||||
force_mode,
|
||||
|
||||
result: None,
|
||||
pending_load: None,
|
||||
|
||||
functor_sym: ctx.intern_string("__functor"),
|
||||
}
|
||||
@@ -541,6 +582,7 @@ struct CallFrame<'gc> {
|
||||
enum Action {
|
||||
Continue { pc: usize },
|
||||
Done(Result<fix_common::Value>),
|
||||
LoadFile(PendingLoad),
|
||||
}
|
||||
|
||||
enum NixNum {
|
||||
@@ -573,6 +615,24 @@ 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;
|
||||
arena.mutate_root(|mc, root| {
|
||||
root.env = Gc::new(mc, RefLock::new(Env::empty()));
|
||||
});
|
||||
}
|
||||
Action::Done(done) => break done,
|
||||
}
|
||||
}
|
||||
@@ -604,6 +664,11 @@ impl<'gc> Vm<'gc> {
|
||||
TailResult::Done => {
|
||||
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 +754,7 @@ impl<'gc> Vm<'gc> {
|
||||
|
||||
ConcatStrings => self.op_concat_strings(ctx, &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),
|
||||
|
||||
@@ -699,7 +764,7 @@ impl<'gc> Vm<'gc> {
|
||||
LoadBuiltin => self.op_load_builtin(&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!(),
|
||||
};
|
||||
@@ -709,6 +774,13 @@ impl<'gc> Vm<'gc> {
|
||||
Step::Break(Break::Done) => {
|
||||
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),
|
||||
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:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn return_from_primop(&mut self, val: Value<'gc>, reader: &mut BytecodeReader<'_>) -> Step {
|
||||
self.push(val);
|
||||
let Some(CallFrame {
|
||||
|
||||
Reference in New Issue
Block a user