diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 12b983dc..3c092eba 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -37,6 +37,7 @@ use crate::util::hash::FxHashMap as HashMap; pub use types::{Val, HeapObj, HeapPool, VmErr, Limits}; pub use methods::{ALL_METHODS, BuiltinMethodId, MethodDesc, MethodKind, lookup_method}; pub use globals::exc_parents; +pub use scope::{FnScope, Kind}; use types::*; use cache::CachePool; @@ -44,8 +45,8 @@ use alloc::{string::{String, ToString}, vec::Vec}; pub(crate) use types::ExceptionFrame; -#[derive(Clone, Copy, PartialEq)] -pub(crate) enum ParamKind { Normal, Star, DoubleStar, KwOnly } +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ParamKind { Normal, Star, DoubleStar, KwOnly } /* Side-channel state passed between opcodes in one dispatch frame, grouped for auditability. */ pub(crate) struct Pending { @@ -539,6 +540,15 @@ impl<'a> VM<'a> { } /* What the allocator holds for the program, garbage since the last collection included. */ + /* The index and scope of the function whose body is `body`, its names as this VM resolved them. */ + pub fn scope_of(&self, body: &SSAChunk) -> Option<(usize, &FnScope)> { + let fi = *self.body_to_fi.get(&(body as *const SSAChunk))?; + Some((fi, &self.fn_scope[fi])) + } + + /* The parameters of function `fi`, each with its kind and the slot it binds. */ + pub fn params_of(&self, fi: usize) -> &[(ParamKind, usize)] { &self.param_slots[fi] } + pub fn memory(&self) -> usize { self.heap.bytes() } /* The most the allocator held for the program at once, which a bench compares run to run. */ diff --git a/src/vm/scope.rs b/src/vm/scope.rs index 00e6d47f..4c10950f 100644 --- a/src/vm/scope.rs +++ b/src/vm/scope.rs @@ -79,7 +79,7 @@ impl Globals { /* What a frame slot holds, fixed for the chunk before it runs. */ #[derive(Clone, Copy, PartialEq, Debug)] -pub(crate) enum Kind { +pub enum Kind { Local, // A cell a nested function shares, read and written through it. Cell, @@ -89,7 +89,7 @@ pub(crate) enum Kind { /* How a function body's names resolve, computed once from the code. */ #[derive(Default, Clone)] -pub(crate) struct FnScope { +pub struct FnScope { pub kinds: alloc::rc::Rc<[Kind]>, /* Slots that start as a fresh cell holding what the call bound there. */ pub cellvars: Vec, diff --git a/tests/tables.rs b/tests/tables.rs index 2ac63b89..3ba3fc9e 100644 --- a/tests/tables.rs +++ b/tests/tables.rs @@ -60,4 +60,24 @@ mod test { assert_eq!(vm.output_text(), "True True True\n"); for (child, parent) in [("TimeoutError", "OSError"), ("KeyError", "LookupError")] { assert!(exc_parents(child).contains(&parent)); } } + + #[test] + fn a_function_scope_and_parameters_are_those_the_vm_resolved() { + use compiler::parser::Parser; + use compiler::vm::{Kind, ParamKind, VM}; + let src = "g = 1\ndef f(a, *r, k=2, **m):\n x = a\n def h():\n return x\n return g\n"; + let (tokens, _) = compiler::lexer::lex(src); + let (chunk, errors) = Parser::new(src, tokens.into_iter()).parse(); + assert!(errors.is_empty()); + let vm = VM::new(&chunk); + let body = &chunk.functions[0].1; + let (fi, scope) = vm.scope_of(body).expect("f is indexed"); + let kind = |name: &str| scope.kinds[body.names.iter().position(|n| n == name).unwrap_or_else(|| panic!("{name}"))]; + assert_eq!(kind("a_0"), Kind::Local); + assert!(matches!(kind("g_0"), Kind::Global(_))); + assert!(scope.cellvars.iter().any(|&s| body.names[s].starts_with('x'))); + let kinds: Vec = vm.params_of(fi).iter().map(|p| p.0).collect(); + assert_eq!(kinds, [ParamKind::Normal, ParamKind::Star, ParamKind::KwOnly, ParamKind::DoubleStar]); + assert!(vm.scope_of(&chunk).is_none()); + } }