Skip to content

Commit

Permalink
Separate Debugger into a distinct struct from Interpreter (#1039)
Browse files Browse the repository at this point in the history
The immediate, selfish motivation for this change is that I want to add
a parameter to the `Interpreter` constructor (in #1038) that is really
only relevant to debugger scenarios, and I didn't want to update every
single call to `Interpreter::new`.

But I think this is a good change regardless. Some methods and fields in
`Interpreter` are only applicable to debugging, and the rest are only
applicable to not-debugging. It's really two separate use cases and two
separate sets of data. I wanted to make this distinction clearer.

Very open to feedback and opinions, and suggestions for further cleanup.
  • Loading branch information
minestarks authored Jan 20, 2024
1 parent 9b12bf8 commit 66a8ea7
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 185 deletions.
131 changes: 77 additions & 54 deletions compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod debug;
mod tests;

#[cfg(test)]
mod stepping_tests;
mod debugger_tests;

pub use qsc_eval::{
debug::Frame,
Expand Down Expand Up @@ -106,8 +106,6 @@ pub struct Interpreter {
classical_seed: Option<u64>,
/// The evaluator environment.
env: Env,
/// The current state of the evaluator.
state: State,
}

#[allow(clippy::module_name_repetitions)]
Expand Down Expand Up @@ -149,7 +147,6 @@ impl Interpreter {
sim: SparseSim::new(),
quantum_seed: None,
classical_seed: None,
state: State::new(map_hir_package_to_fir(source_package_id), None),
package: map_hir_package_to_fir(package_id),
source_package: map_hir_package_to_fir(source_package_id),
})
Expand All @@ -163,46 +160,6 @@ impl Interpreter {
pub fn set_classical_seed(&mut self, seed: Option<u64>) {
self.classical_seed = seed;
}

/// Loads the entry expression to the top of the evaluation stack.
/// This is needed for debugging so that when begging to debug with
/// a step action the system is already in the correct state.
/// # Errors
/// Returns a vector of errors if loading the entry point fails.
pub fn set_entry(&mut self) -> Result<(), Vec<Error>> {
let expr = self.get_entry_expr()?;
qsc_eval::eval_push_expr(&mut self.state, expr);
Ok(())
}

/// Resumes execution with specified `StepAction`.
/// # Errors
/// Returns a vector of errors if evaluating the entry point fails.
pub fn eval_step(
&mut self,
receiver: &mut impl Receiver,
breakpoints: &[StmtId],
step: StepAction,
) -> Result<StepResult, Vec<Error>> {
self.state
.eval(
&self.fir_store,
&mut self.env,
&mut self.sim,
receiver,
breakpoints,
step,
)
.map_err(|(error, call_stack)| {
eval_error(
self.compiler.package_store(),
&self.fir_store,
call_stack,
error,
)
})
}

/// Executes the entry expression until the end of execution.
/// # Errors
/// Returns a vector of errors if evaluating the entry point fails.
Expand Down Expand Up @@ -382,18 +339,72 @@ impl Interpreter {
.lower_and_update_package(fir_package, &unit_addition.hir)
}

fn source_package(&self) -> &CompileUnit {
self.compiler
.package_store()
.get(map_fir_package_to_hir(self.source_package))
.expect("Could not load package")
}

fn next_line_label(&mut self) -> String {
let label = format!("line_{}", self.lines);
self.lines += 1;
label
}
}

/// A debugger that enables step-by-step evaluation of code
/// and inspecting state in the interpreter.
pub struct Debugger {
interpreter: Interpreter,
/// The current state of the evaluator.
state: State,
}

impl Debugger {
pub fn new(
sources: SourceMap,
capabilities: RuntimeCapabilityFlags,
) -> Result<Self, Vec<Error>> {
let interpreter = Interpreter::new(true, sources, PackageType::Exe, capabilities)?;
let source_package_id = interpreter.source_package;
Ok(Self {
interpreter,
state: State::new(source_package_id, None),
})
}

/// Loads the entry expression to the top of the evaluation stack.
/// This is needed for debugging so that when begging to debug with
/// a step action the system is already in the correct state.
/// # Errors
/// Returns a vector of errors if loading the entry point fails.
pub fn set_entry(&mut self) -> Result<(), Vec<Error>> {
let expr = self.interpreter.get_entry_expr()?;
qsc_eval::eval_push_expr(&mut self.state, expr);
Ok(())
}

/// Resumes execution with specified `StepAction`.
/// # Errors
/// Returns a vector of errors if evaluating the entry point fails.
pub fn eval_step(
&mut self,
receiver: &mut impl Receiver,
breakpoints: &[StmtId],
step: StepAction,
) -> Result<StepResult, Vec<Error>> {
self.state
.eval(
&self.interpreter.fir_store,
&mut self.interpreter.env,
&mut self.interpreter.sim,
receiver,
breakpoints,
step,
)
.map_err(|(error, call_stack)| {
eval_error(
self.interpreter.compiler.package_store(),
&self.interpreter.fir_store,
call_stack,
error,
)
})
}

#[must_use]
pub fn get_stack_frames(&self) -> Vec<StackFrame> {
Expand All @@ -402,6 +413,7 @@ impl Interpreter {
.iter()
.map(|frame| {
let callable = self
.interpreter
.fir_store
.get_global(frame.id)
.expect("frame should exist");
Expand All @@ -412,6 +424,7 @@ impl Interpreter {
};

let hir_package = self
.interpreter
.compiler
.package_store()
.get(map_fir_package_to_hir(frame.id.package))
Expand All @@ -434,7 +447,7 @@ impl Interpreter {
}

pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
self.sim.capture_quantum_state()
self.interpreter.sim.capture_quantum_state()
}

#[must_use]
Expand All @@ -443,8 +456,9 @@ impl Interpreter {

if let Some(source) = unit.sources.find_by_name(path) {
let package = self
.interpreter
.fir_store
.get(self.source_package)
.get(self.interpreter.source_package)
.expect("package should have been lowered");
let mut collector = BreakpointCollector::new(&unit.sources, source.offset, package);
collector.visit_package(package);
Expand All @@ -466,12 +480,21 @@ impl Interpreter {

#[must_use]
pub fn get_locals(&self) -> Vec<VariableInfo> {
self.env
self.interpreter
.env
.get_variables_in_top_frame()
.into_iter()
.filter(|v| !v.name.starts_with('@'))
.collect()
}

fn source_package(&self) -> &CompileUnit {
self.interpreter
.compiler
.package_store()
.get(map_fir_package_to_hir(self.interpreter.source_package))
.expect("Could not load package")
}
}

/// Wrapper function for `qsc_eval::eval` that handles error conversion.
Expand Down
Loading

0 comments on commit 66a8ea7

Please sign in to comment.