mirror of
https://github.com/duhanbalci/dexpr.git
synced 2026-08-30 16:01:41 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
use crate::ast::expr::Span;
|
||||
|
||||
/// Debug information that maps bytecode offsets to source locations.
|
||||
/// Uses a run-length encoded format: each entry covers instructions from
|
||||
/// its offset until the next entry's offset.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DebugInfo {
|
||||
/// Sorted list of (bytecode_offset, span) pairs
|
||||
entries: Vec<(u32, Span)>,
|
||||
}
|
||||
|
||||
impl DebugInfo {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a mapping from a bytecode offset to a source span.
|
||||
/// Entries must be added in increasing offset order.
|
||||
pub fn add_entry(&mut self, offset: u32, span: Span) {
|
||||
// Only add if different from the last entry's span
|
||||
if let Some((_, last_span)) = self.entries.last() {
|
||||
if *last_span == span {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.entries.push((offset, span));
|
||||
}
|
||||
|
||||
/// Look up the source span for a given bytecode offset.
|
||||
/// Returns None if no debug info is available.
|
||||
pub fn get_span(&self, offset: u32) -> Option<Span> {
|
||||
if self.entries.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Binary search for the largest offset <= target
|
||||
match self.entries.binary_search_by_key(&offset, |(off, _)| *off) {
|
||||
Ok(idx) => Some(self.entries[idx].1),
|
||||
Err(idx) => {
|
||||
if idx == 0 {
|
||||
// Before the first entry
|
||||
None
|
||||
} else {
|
||||
// Use the previous entry
|
||||
Some(self.entries[idx - 1].1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if debug info is available
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
/// Get the number of entries
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_debug_info_lookup() {
|
||||
let mut info = DebugInfo::new();
|
||||
info.add_entry(0, Span::new(1, 1));
|
||||
info.add_entry(10, Span::new(2, 5));
|
||||
info.add_entry(20, Span::new(3, 10));
|
||||
|
||||
// Exact matches
|
||||
assert_eq!(info.get_span(0), Some(Span::new(1, 1)));
|
||||
assert_eq!(info.get_span(10), Some(Span::new(2, 5)));
|
||||
assert_eq!(info.get_span(20), Some(Span::new(3, 10)));
|
||||
|
||||
// In-between values use previous entry
|
||||
assert_eq!(info.get_span(5), Some(Span::new(1, 1)));
|
||||
assert_eq!(info.get_span(15), Some(Span::new(2, 5)));
|
||||
assert_eq!(info.get_span(100), Some(Span::new(3, 10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_spans_not_added() {
|
||||
let mut info = DebugInfo::new();
|
||||
info.add_entry(0, Span::new(1, 1));
|
||||
info.add_entry(5, Span::new(1, 1)); // Same span, should not add
|
||||
info.add_entry(10, Span::new(2, 1));
|
||||
|
||||
assert_eq!(info.len(), 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use crate::ast::expr::Span;
|
||||
use smol_str::SmolStr;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur during VM execution
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VMError {
|
||||
/// Error when types don't match the operation's expectations
|
||||
#[error("Type error: expected {expected}, got {got}")]
|
||||
TypeMismatch { expected: String, got: String },
|
||||
|
||||
/// Error when a variable is not defined
|
||||
#[error("Undefined variable: {0}")]
|
||||
UndefinedVariable(SmolStr),
|
||||
|
||||
/// Error when dividing by zero
|
||||
#[error("Division by zero")]
|
||||
DivisionByZero,
|
||||
|
||||
/// Error in bytecode format or execution
|
||||
#[error("Bytecode error: {0}")]
|
||||
BytecodeError(String),
|
||||
|
||||
/// Error when a method is not found for a type
|
||||
#[error("Method '{method}' not found for type '{type_name}'")]
|
||||
MethodNotFound {
|
||||
type_name: &'static str,
|
||||
method: SmolStr,
|
||||
},
|
||||
|
||||
/// Generic runtime error
|
||||
#[error("Runtime error: {0}")]
|
||||
RuntimeError(String),
|
||||
|
||||
/// Error when an invalid operation is performed
|
||||
#[error("Type error: cannot {operation} {left_type} and {right_type}")]
|
||||
InvalidOperation {
|
||||
operation: &'static str,
|
||||
left_type: &'static str,
|
||||
right_type: &'static str,
|
||||
},
|
||||
|
||||
/// Error with source location information
|
||||
#[error("Error at {span}: {message}")]
|
||||
WithLocation { span: Span, message: String },
|
||||
}
|
||||
|
||||
impl VMError {
|
||||
/// Wrap this error with source location information
|
||||
pub fn with_span(self, span: Span) -> Self {
|
||||
// Don't double-wrap location errors
|
||||
if matches!(self, VMError::WithLocation { .. }) {
|
||||
return self;
|
||||
}
|
||||
// Only wrap if we have a valid span (non-zero)
|
||||
if span.line == 0 && span.column == 0 {
|
||||
return self;
|
||||
}
|
||||
VMError::WithLocation {
|
||||
span,
|
||||
message: self.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod debug_info;
|
||||
pub mod error;
|
||||
mod vm;
|
||||
|
||||
pub use debug_info::DebugInfo;
|
||||
pub use error::VMError;
|
||||
pub use vm::{ExternalFn, ExternalMethod, VM};
|
||||
+1417
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user