Add static variable resolution with scope tracking

- Add distance-based get_at on EnvironmentStack
- Add distance-based assign_at on EnvironmentStack
- Introduce ErrorSink to accumulate diagnostics across passes
- Implement Resolver with a ScopeStack and per-node distance map
- Extend interpreter to store locals distances for runtime lookup
- Add tests for resolution behavior and error accumulation
  Add static variable resolution with scope tracking
This commit is contained in:
Giulio Agostini
2026-06-30 15:05:34 +02:00
parent d40fe2a550
commit 9f15a00b98
8 changed files with 666 additions and 116 deletions
+87
View File
@@ -479,3 +479,90 @@ pub fn io_error<T>(message: impl Into<String>) -> LoxResult<T> {
message: message.into(),
})
}
/// Accumulates diagnostics during a pass instead of bailing on the first one.
///
/// Static analyses (e.g. the resolver) `report` problems and keep traversing so
/// the user sees every error at once, then surface them at the end.
#[derive(Debug, Default)]
pub struct ErrorSink {
errors: Vec<LoxError>,
}
impl ErrorSink {
pub fn new() -> Self {
Self::default()
}
/// Record a diagnostic and keep going.
pub fn report(&mut self, error: LoxError) {
self.errors.push(error);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn errors(&self) -> &[LoxError] {
&self.errors
}
/// Merge another sink's errors into this one (e.g. from a separate pass).
pub fn extend(&mut self, other: ErrorSink) {
self.errors.extend(other.errors);
}
pub fn into_errors(self) -> Vec<LoxError> {
self.errors
}
/// Bridge to the single-error [`LoxResult`] API: `Ok` if empty, otherwise
/// the first reported error.
pub fn into_result(self) -> LoxResult<()> {
match self.errors.into_iter().next() {
Some(error) => Err(error),
None => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::source_registry::SourceSlice;
fn parse_err(message: &str) -> LoxError {
LoxError::ParseError {
source_slice: SourceSlice::synthetic(),
message: message.to_string(),
}
}
#[test]
fn empty_sink_is_ok() {
let sink = ErrorSink::new();
assert!(!sink.has_errors());
assert!(sink.into_result().is_ok());
}
#[test]
fn accumulates_multiple_errors() {
let mut sink = ErrorSink::new();
sink.report(parse_err("first"));
sink.report(parse_err("second"));
assert!(sink.has_errors());
assert_eq!(sink.errors().len(), 2);
assert!(sink.into_result().is_err());
}
#[test]
fn extend_merges_sinks() {
let mut a = ErrorSink::new();
a.report(parse_err("a"));
let mut b = ErrorSink::new();
b.report(parse_err("b1"));
b.report(parse_err("b2"));
a.extend(b);
assert_eq!(a.into_errors().len(), 3);
}
}