Repository: microsoft/qsharp
Pull request #756 (2023-09-25 23:02:49)

Refactor to eliminate stateless interpreter
This change eliminates the stateless interpreter and refactors the internals of the stateful interpreter to clean up some code repetition. For now this leaves the stateful interpreter as a separate `stateful` module to make other in-flight refactoring a little easier to merge, but a follow up can and should move the code from `stateful` into `interpret` since now there is only one interpreter implementation.

Performance benchmarks compared against main show no negative impact:
```
Teleport evaluation
                        time: [167.24 µs 167.90 µs 168.63 µs]
                        change: [-1.7025% -0.6191% +0.3523%] (p = 0.25 > 0.05)
                        No change in performance detected.
Found 4 outliers among 100 measurements (4.00%)
  1 (1.00%) high mild
  3 (3.00%) high severe

Deutsch-Jozsa evaluation
                        time: [35.189 ms 35.275 ms 35.374 ms]
                        change: [-1.4248% -0.9964% -0.5813%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe

Large file parity evaluation
                        time: [37.790 ms 37.896 ms 38.014 ms]
                        change: [-1.2062% -0.6223% -0.0448%] (p = 0.04 < 0.05)
                        Change within noise threshold.
Found 8 outliers among 100 measurements (8.00%)
  5 (5.00%) high mild
  3 (3.00%) high severe

Large input file
                        time: [32.822 ms 33.113 ms 33.481 ms]
                        change: [-1.5965% -0.2764% +1.1202%] (p = 0.70 > 0.05)
                        No change in performance detected.
Found 13 outliers among 100 measurements (13.00%)
  9 (9.00%) high mild
  4 (4.00%) high severe

Standard library
                        time: [12.261 ms 12.371 ms 12.500 ms]
                        change: [-5.1162% -3.3155% -1.5653%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  3 (3.00%) high mild
  2 (2.00%) high severe
```


Patch:
diff --git a/.ado/publish.yml b/.ado/publish.yml
index dca19f9a..48f6c08c 100644
--- a/.ado/publish.yml
+++ b/.ado/publish.yml
@@ -213,7 +213,7 @@ jobs:
   # for linux aarch64 cross build we want to skip tests as we can't run the code.
   # and we can't run the samples as qsc is currently cross compiled.
   - script: |
-      env CARGO_BUILD_TARGET=aarch64-unknown-linux-gnu python build.py --pip --no-check-prereqs --no-check --no-test --no-samples
+      env CARGO_BUILD_TARGET=aarch64-unknown-linux-gnu python build.py --pip --no-check-prereqs --no-check --no-test
     displayName: Cross Build Linux aarch64 Py Packages
     condition: and(eq(variables['Agent.OS'], 'Linux'), eq(variables['arch'], 'aarch64'))
 
@@ -232,7 +232,7 @@ jobs:
     condition: and(eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['arch'], 'aarch64'))
 
   - script: |
-      python build.py --pip --no-check-prereqs --no-check --no-test --no-samples
+      python build.py --pip --no-check-prereqs --no-check --no-test
     displayName: Cross Build Windows aarch64 Py Packages
     condition: and(eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['arch'], 'aarch64'))
 
diff --git a/build.py b/build.py
index 0e80907e..909ac0ef 100755
--- a/build.py
+++ b/build.py
@@ -29,12 +29,7 @@ parser.add_argument("--pip", action="store_true", help="Build the pip wheel")
 parser.add_argument("--wasm", action="store_true", help="Build the WebAssembly files")
 parser.add_argument("--npm", action="store_true", help="Build the npm package")
 parser.add_argument("--play", action="store_true", help="Build the web playground")
-parser.add_argument(
-    "--samples",
-    action=argparse.BooleanOptionalAction,
-    default=None,
-    help="Compile the Q# samples (default is --no-samples)",
-)
+parser.add_argument("--samples", action="store_true", help="Compile the Q# samples")
 parser.add_argument("--vscode", action="store_true", help="Build the VS Code extension")
 parser.add_argument(
     "--jupyterlab", action="store_true", help="Build the JupyterLab extension"
@@ -75,16 +70,16 @@ build_all = (
     not args.cli
     and not args.pip
     and not args.wasm
+    and not args.samples
     and not args.npm
     and not args.play
     and not args.vscode
     and not args.jupyterlab
-    # allow --no-samples to be passed and still build all
-    and not args.samples is True
 )
 build_cli = build_all or args.cli
 build_pip = build_all or args.pip
 build_wasm = build_all or args.wasm
+build_samples = build_all or args.samples
 build_npm = build_all or args.npm
 build_play = build_all or args.play
 build_vscode = build_all or args.vscode
@@ -100,9 +95,6 @@ npm_cmd = "npm.cmd" if platform.system() == "Windows" else "npm"
 build_type = "debug" if args.debug else "release"
 run_tests = args.test
 
-# build samples by default, unless --no-samples is passed
-build_samples = args.samples is not False
-
 # TODO: This requires that both targets are installed on macOS to build Python packages. Add to prereqs checks.
 pip_archflags = "-arch x86_64 -arch arm64" if platform.system() == "Darwin" else None
 
diff --git a/compiler/qsc/benches/eval.rs b/compiler/qsc/benches/eval.rs
index c6e89f4a..39830672 100644
--- a/compiler/qsc/benches/eval.rs
+++ b/compiler/qsc/benches/eval.rs
@@ -2,7 +2,7 @@
 // Licensed under the MIT License.
 
 use criterion::{criterion_group, criterion_main, Criterion};
-use qsc::interpret::stateless;
+use qsc::{interpret::stateful, PackageType, TargetProfile};
 use qsc_eval::output::GenericReceiver;
 use qsc_frontend::compile::SourceMap;
 
@@ -13,12 +13,13 @@ const LARGE: &str = include_str!("./large.qs");
 pub fn teleport(c: &mut Criterion) {
     c.bench_function("Teleport evaluation", |b| {
         let sources = SourceMap::new([("Teleportation.qs".into(), TELEPORT.into())], None);
-        let evaluator = stateless::Interpreter::new(true, sources).expect("code should compile");
+        let mut evaluator =
+            stateful::Interpreter::new(true, sources, PackageType::Exe, TargetProfile::Full)
+                .expect("code should compile");
         b.iter(move || {
             let mut out = Vec::new();
             let mut rec = GenericReceiver::new(&mut out);
-            let mut eval_ctx = evaluator.new_eval_context();
-            assert!(eval_ctx.eval_entry(&mut rec).is_ok());
+            assert!(evaluator.eval_entry(&mut rec).is_ok());
         })
     });
 }
@@ -26,12 +27,13 @@ pub fn teleport(c: &mut Criterion) {
 pub fn deutsch_jozsa(c: &mut Criterion) {
     c.bench_function("Deutsch-Jozsa evaluation", |b| {
         let sources = SourceMap::new([("DeutschJozsa.qs".into(), DEUTSCHJOZSA.into())], None);
-        let evaluator = stateless::Interpreter::new(true, sources).expect("code should compile");
+        let mut evaluator =
+            stateful::Interpreter::new(true, sources, PackageType::Exe, TargetProfile::Full)
+                .expect("code should compile");
         b.iter(move || {
             let mut out = Vec::new();
             let mut rec = GenericReceiver::new(&mut out);
-            let mut eval_ctx = evaluator.new_eval_context();
-            assert!(eval_ctx.eval_entry(&mut rec).is_ok());
+            assert!(evaluator.eval_entry(&mut rec).is_ok());
         })
     });
 }
@@ -39,12 +41,13 @@ pub fn deutsch_jozsa(c: &mut Criterion) {
 pub fn large_file(c: &mut Criterion) {
     c.bench_function("Large file parity evaluation", |b| {
         let sources = SourceMap::new([("large.qs".into(), LARGE.into())], None);
-        let evaluator = stateless::Interpreter::new(true, sources).expect("code should compile");
+        let mut evaluator =
+            stateful::Interpreter::new(true, sources, PackageType::Exe, TargetProfile::Full)
+                .expect("code should compile");
         b.iter(move || {
             let mut out = Vec::new();
             let mut rec = GenericReceiver::new(&mut out);
-            let mut eval_ctx = evaluator.new_eval_context();
-            assert!(eval_ctx.eval_entry(&mut rec).is_ok());
+            assert!(evaluator.eval_entry(&mut rec).is_ok());
         })
     });
 }
diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs
index 67c38699..11509462 100644
--- a/compiler/qsc/src/bin/qsi.rs
+++ b/compiler/qsc/src/bin/qsi.rs
@@ -9,10 +9,7 @@ use miette::{Context, IntoDiagnostic, Report, Result};
 use num_bigint::BigUint;
 use num_complex::Complex64;
 use qsc::{
-    interpret::{
-        stateful::{Interpreter, LineResult},
-        stateless,
-    },
+    interpret::stateful::{self, InterpretResult, Interpreter},
     TargetProfile,
 };
 use qsc_eval::{
@@ -82,9 +79,11 @@ fn main() -> miette::Result<ExitCode> {
         .collect::<miette::Result<Vec<_>>>()?;
 
     if cli.exec {
-        let interpreter = match stateless::Interpreter::new(
+        let mut interpreter = match Interpreter::new(
             !cli.nostdlib,
             SourceMap::new(sources, cli.entry.map(std::convert::Into::into)),
+            PackageType::Exe,
+            TargetProfile::Full,
         ) {
             Ok(interpreter) => interpreter,
             Err(errors) => {
@@ -94,9 +93,8 @@ fn main() -> miette::Result<ExitCode> {
                 return Ok(ExitCode::FAILURE);
             }
         };
-        let mut eval_ctx = interpreter.new_eval_context();
         return Ok(print_exec_result(
-            eval_ctx.eval_entry(&mut TerminalReceiver),
+            interpreter.eval_entry(&mut TerminalReceiver),
         ));
     }
 
@@ -116,7 +114,7 @@ fn main() -> miette::Result<ExitCode> {
     };
 
     if let Some(entry) = cli.entry {
-        print_interpret_result(interpreter.interpret_line(&mut TerminalReceiver, &entry));
+        print_interpret_result(interpreter.eval_fragments(&mut TerminalReceiver, &entry));
     }
 
     repl(&mut interpreter, &mut TerminalReceiver).into_diagnostic()?;
@@ -143,7 +141,7 @@ fn repl(interpreter: &mut Interpreter, receiver: &mut impl Receiver) -> io::Resu
         }
 
         if !line.trim().is_empty() {
-            print_interpret_result(interpreter.interpret_line(receiver, &line));
+            print_interpret_result(interpreter.eval_fragments(receiver, &line));
         }
 
         print_prompt(false);
@@ -172,7 +170,7 @@ fn print_prompt(continuation: bool) {
     io::stdout().flush().expect("standard out should flush");
 }
 
-fn print_interpret_result(result: LineResult) {
+fn print_interpret_result(result: InterpretResult) {
     match result {
         Ok(Value::Tuple(items)) if items.is_empty() => {}
         Ok(value) => println!("{value}"),
@@ -188,7 +186,7 @@ fn print_interpret_result(result: LineResult) {
     }
 }
 
-fn print_exec_result(result: Result<Value, Vec<stateless::Error>>) -> ExitCode {
+fn print_exec_result(result: Result<Value, Vec<stateful::Error>>) -> ExitCode {
     match result {
         Ok(value) => {
             println!("{value}");
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index fa91549b..d3894f69 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -3,7 +3,6 @@
 
 mod debug;
 pub mod stateful;
-pub mod stateless;
 
 pub use qsc_eval::{
     debug::Frame,
diff --git a/compiler/qsc/src/interpret/stateful.rs b/compiler/qsc/src/interpret/stateful.rs
index 7e8db99d..ff193bb6 100644
--- a/compiler/qsc/src/interpret/stateful.rs
+++ b/compiler/qsc/src/interpret/stateful.rs
@@ -7,7 +7,7 @@ mod tests;
 #[cfg(test)]
 mod stepping_tests;
 
-use super::{debug::format_call_stack, stateless};
+use super::debug::format_call_stack;
 use crate::{
     compile::{self, compile},
     error::{self, WithStack},
@@ -16,20 +16,20 @@ use crate::{
 use miette::Diagnostic;
 use num_bigint::BigUint;
 use num_complex::Complex;
-use qsc_codegen::qir_base::generate_qir_for_stmt;
+use qsc_codegen::qir_base::BaseProfSim;
 use qsc_data_structures::{index_map::IndexMap, span::Span};
 use qsc_eval::{
     backend::{Backend, SparseSim},
     debug::{map_fir_package_to_hir, map_hir_package_to_fir, Frame},
-    eval_stmt,
-    output::Receiver,
-    val::{GlobalId, Value},
+    eval_expr, eval_stmt,
+    output::{GenericReceiver, Receiver},
+    val::{self, GlobalId, Value},
     Env, Global, NodeLookup, State, StepAction, StepResult, VariableInfo,
 };
 use qsc_fir::{
     fir::{
-        Block, BlockId, CallableDecl, Expr, ExprId, LocalItemId, Package, PackageId, Pat, PatId,
-        Stmt, StmtId,
+        Block, BlockId, CallableDecl, Expr, ExprId, ItemKind, LocalItemId, Package, PackageId, Pat,
+        PatId, Stmt, StmtId,
     },
     visit::{self, Visitor},
 };
@@ -145,7 +145,7 @@ pub struct Interpreter {
     state: State,
 }
 
-pub type LineResult = Result<Value, Vec<Error>>;
+pub type InterpretResult = Result<Value, Vec<Error>>;
 
 impl Interpreter {
     /// # Errors
@@ -278,7 +278,7 @@ impl Interpreter {
             callables: &self.callables,
         };
 
-        qsc_eval::eval_expr(
+        eval_expr(
             &mut self.state,
             expr,
             &globals,
@@ -311,16 +311,20 @@ impl Interpreter {
     }
 
     /// # Errors
-    /// If the parsing of the line fails, an error is returned.
-    /// If the compilation of the line fails, an error is returned.
-    /// If there is a runtime error when interpreting the line, an error is returned.
-    pub fn interpret_line(&mut self, receiver: &mut impl Receiver, line: &str) -> LineResult {
+    /// If the parsing of the fragments fails, an error is returned.
+    /// If the compilation of the fragments fails, an error is returned.
+    /// If there is a runtime error when interpreting the fragments, an error is returned.
+    pub fn eval_fragments(
+        &mut self,
+        receiver: &mut impl Receiver,
+        fragments: &str,
+    ) -> InterpretResult {
         let mut result = Value::unit();
 
         let label = &self.next_line_label();
 
         let (items, stmts) =
-            self.compile_incremental(|c, p| c.compile_fragments(p, label, line))?;
+            self.compile_incremental(|c, p| c.compile_fragments(p, label, fragments))?;
 
         for item in items {
             match item.kind {
@@ -340,8 +344,21 @@ impl Interpreter {
 
         for stmt in stmts {
             let stmt_id = self.lower_stmt(&stmt);
+            let globals = Lookup {
+                fir_store: &self.fir_store,
+                package: self.package,
+                udts: &self.udts,
+                callables: &self.callables,
+            };
 
-            match self.eval_stmt(receiver, stmt_id) {
+            match eval_stmt(
+                stmt_id,
+                &globals,
+                &mut self.env,
+                &mut self.sim,
+                self.package,
+                receiver,
+            ) {
                 Ok(value) => result = value,
                 Err((error, call_stack)) => {
                     let stack_trace = if call_stack.is_empty() {
@@ -406,22 +423,57 @@ impl Interpreter {
 
     /// Runs the given entry expression on a new instance of the environment and simulator,
     /// but using the current compilation.
-    /// # Errors
-    /// If the parsing of the expr fails, an error is returned.
-    /// If the compilation of the expr fails, an error is returned.
-    /// If there is a runtime error when generating code for the expr, an error is returned.
-    /// # Panics
-    /// If internal compiler state is inconsistent, a panic may occur.
     pub fn run(
         &mut self,
         receiver: &mut impl Receiver,
         expr: &str,
         shots: u32,
-    ) -> Result<Vec<LineResult>, Vec<Error>> {
+    ) -> Result<Vec<InterpretResult>, Vec<Error>> {
+        self.run_with_sim(&mut SparseSim::new(), receiver, expr, shots)
+    }
+
+    /// Performs QIR codegen using the given entry expression on a new instance of the environment
+    /// and simulator but using the current compilation.
+    pub fn qirgen(&mut self, expr: &str) -> Result<String, Vec<Error>> {
+        if self.target != TargetProfile::Base {
+            return Err(vec![Error(ErrorKind::TargetMismatch)]);
+        }
+
+        let mut sim = BaseProfSim::new();
+        let mut stdout = std::io::sink();
+        let mut out = GenericReceiver::new(&mut stdout);
+
+        let val = self
+            .run_with_sim(&mut sim, &mut out, expr, 1)?
+            .into_iter()
+            .last()
+            .expect("execution should have at least one result")?;
+
+        Ok(sim.finish(&val))
+    }
+
+    /// Runs the given entry expression on the given simulator with a new instance of the environment
+    /// but using the current compilation.
+    pub fn run_with_sim(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+        expr: &str,
+        shots: u32,
+    ) -> Result<Vec<InterpretResult>, Vec<Error>> {
         let (items, stmts) = self.compile_incremental(|c, p| c.compile_expr(p, "<entry>", expr))?;
 
-        let mut stmt_id = None;
+        Ok(self.run_internal(sim, receiver, items, stmts, shots))
+    }
 
+    fn run_internal(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+        items: Vec<hir::Item>,
+        stmts: Vec<hir::Stmt>,
+        shots: u32,
+    ) -> Vec<InterpretResult> {
         for item in items {
             match item.kind {
                 qsc_hir::hir::ItemKind::Callable(callable) => {
@@ -438,17 +490,11 @@ impl Interpreter {
             }
         }
 
+        let mut stmt_ids = Vec::new();
         for stmt in stmts {
-            assert!(
-                stmt_id.replace(self.lower_stmt(&stmt)).is_none(),
-                "expression should yield exactly one statement"
-            );
+            stmt_ids.push(self.lower_stmt(&stmt));
         }
 
-        let Some(stmt_id) = stmt_id else {
-            panic!("expression should yield exactly one statement");
-        };
-
         let globals = Lookup {
             fir_store: &self.fir_store,
             package: self.package,
@@ -456,93 +502,42 @@ impl Interpreter {
             callables: &self.callables,
         };
 
-        let mut results: Vec<LineResult> = Vec::new();
-        for _i in 0..shots {
-            results.push(
-                match eval_stmt(
-                    stmt_id,
-                    &globals,
-                    &mut Env::with_empty_scope(),
-                    &mut SparseSim::new(),
-                    self.package,
-                    receiver,
-                ) {
-                    Ok(value) => Ok(value),
-                    Err((error, call_stack)) => {
-                        let stack_trace = if call_stack.is_empty() {
-                            None
-                        } else {
-                            Some(self.render_call_stack(call_stack, &error))
-                        };
-
-                        Err(vec![Error(
-                            error::from_eval(error, &self.store, stack_trace).into(),
-                        )])
-                    }
-                },
-            );
-        }
-
-        Ok(results)
-    }
-
-    /// # Errors
-    /// If the currently configured target profile is not `TargetProfile::Base`, an error is returned.
-    /// If the parsing of the expr fails, an error is returned.
-    /// If the compilation of the expr fails, an error is returned.
-    /// If there is a runtime error when generating code for the expr, an error is returned.
-    /// # Panics
-    /// If internal compiler state is inconsistent, a panic may occur.
-    pub fn qirgen(&mut self, expr: &str) -> Result<String, Vec<Error>> {
-        if self.target != TargetProfile::Base {
-            return Err(vec![Error(ErrorKind::TargetMismatch)]);
-        }
-
-        let (items, stmts) = self.compile_incremental(|c, p| c.compile_expr(p, "<entry>", expr))?;
-
-        let mut ret = Ok(String::new());
-
-        for item in items {
-            match item.kind {
-                qsc_hir::hir::ItemKind::Callable(callable) => {
-                    let callable = self.lower_callable_decl(&callable);
-
-                    self.callables
-                        .insert(qsc_eval::lower::lower_local_item_id(item.id), callable);
-                }
-                qsc_hir::hir::ItemKind::Namespace(..) => {}
-                qsc_hir::hir::ItemKind::Ty(..) => {
-                    self.udts
-                        .insert(qsc_eval::lower::lower_local_item_id(item.id));
-                }
+        let mut results: Vec<InterpretResult> = Vec::new();
+        for i in 0..shots {
+            for stmt_id in &stmt_ids {
+                results.push(
+                    match eval_stmt(
+                        *stmt_id,
+                        &globals,
+                        &mut Env::with_empty_scope(),
+                        sim,
+                        self.package,
+                        receiver,
+                    ) {
+                        Ok(value) => Ok(value),
+                        Err((error, call_stack)) => {
+                            let stack_trace = if call_stack.is_empty() {
+                                None
+                            } else {
+                                Some(self.render_call_stack(call_stack, &error))
+                            };
+
+                            Err(vec![Error(
+                                error::from_eval(error, &self.store, stack_trace).into(),
+                            )])
+                        }
+                    },
+                );
             }
-        }
-
-        for stmt in stmts {
-            let stmt_id = self.lower_stmt(&stmt);
-            let globals = Lookup {
-                fir_store: &self.fir_store,
-                package: self.package,
-                udts: &self.udts,
-                callables: &self.callables,
-            };
 
-            ret = generate_qir_for_stmt(stmt_id, &globals, &mut self.env, self.package).map_err(
-                |(error, call_stack)| {
-                    let stack_trace = if call_stack.is_empty() {
-                        None
-                    } else {
-                        Some(self.render_call_stack(call_stack, &error))
-                    };
-
-                    vec![Error(
-                        error::from_eval(error, &self.store, stack_trace).into(),
-                    )]
-                },
-            );
+            if i != 0 {
+                // If running more than one shot, re-initialize the simulator to start the next shot
+                // from a clean state.
+                sim.reinit();
+            }
         }
 
-        ret
+        results
     }
 
     fn package(&self) -> &CompileUnit {
@@ -578,28 +573,6 @@ impl Interpreter {
         self.lowerer.update_package(package);
     }
 
-    fn eval_stmt(
-        &mut self,
-        receiver: &mut impl Receiver,
-        stmt: StmtId,
-    ) -> Result<Value, (qsc_eval::Error, Vec<Frame>)> {
-        let globals = Lookup {
-            fir_store: &self.fir_store,
-            package: self.package,
-            udts: &self.udts,
-            callables: &self.callables,
-        };
-
-        eval_stmt(
-            stmt,
-            &globals,
-            &mut self.env,
-            &mut self.sim,
-            self.package,
-            receiver,
-        )
-    }
-
     fn render_call_stack(&self, call_stack: Vec<Frame>, error: &dyn std::error::Error) -> String {
         let globals = Lookup {
             fir_store: &self.fir_store,
@@ -723,7 +696,13 @@ fn get_global<'a>(
             .then_some(Global::Udt)
             .or_else(|| callables.get(id.item).map(Global::Callable))
     } else {
-        stateless::get_global(fir_store, id)
+        fir_store
+            .get(id.package)
+            .and_then(|package| match &package.items.get(id.item)?.kind {
+                ItemKind::Callable(callable) => Some(Global::Callable(callable)),
+                ItemKind::Namespace(..) => None,
+                ItemKind::Ty(..) => Some(Global::Udt),
+            })
     }
 }
 
diff --git a/compiler/qsc/src/interpret/stateless.rs b/compiler/qsc/src/interpret/stateless.rs
deleted file mode 100644
index aa6c6b32..00000000
--- a/compiler/qsc/src/interpret/stateless.rs
+++ /dev/null
@@ -1,251 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-use super::debug::format_call_stack;
-use crate::compile::{self, compile};
-use crate::error::{self, WithStack};
-use miette::Diagnostic;
-use qsc_data_structures::index_map::IndexMap;
-use qsc_eval::{
-    backend::{Backend, SparseSim},
-    debug::{map_hir_package_to_fir, Frame},
-    eval_expr,
-    output::Receiver,
-    val::{self, GlobalId, Value},
-    Env, Global, NodeLookup, State,
-};
-use qsc_fir::fir::{BlockId, ExprId, PatId, StmtId};
-use qsc_fir::fir::{ItemKind, PackageId};
-use qsc_frontend::{
-    compile::{PackageStore, SourceMap, TargetProfile},
-    error::WithSource,
-};
-use qsc_passes::PackageType;
-use thiserror::Error;
-
-#[derive(Clone, Debug, Diagnostic, Error)]
-#[diagnostic(transparent)]
-#[error(transparent)]
-pub struct Error(#[from] ErrorKind);
-
-impl Error {
-    #[must_use]
-    pub fn stack_trace(&self) -> &Option<String> {
-        match &self.0 {
-            ErrorKind::Eval(err) => err.stack_trace(),
-            _ => &None,
-        }
-    }
-}
-
-#[derive(Clone, Debug, Diagnostic, Error)]
-enum ErrorKind {
-    #[error(transparent)]
-    #[diagnostic(transparent)]
-    Compile(#[from] WithSource<compile::Error>),
-    #[error(transparent)]
-    #[diagnostic(transparent)]
-    Pass(#[from] WithSource<qsc_passes::Error>),
-    #[error("runtime error")]
-    #[diagnostic(transparent)]
-    Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
-    #[error("entry point not found")]
-    #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
-    NoEntryPoint,
-}
-
-pub struct Interpreter {
-    store: PackageStore,
-    fir_store: IndexMap<PackageId, qsc_fir::fir::Package>,
-    package: PackageId,
-}
-
-pub struct EvalContext<'a, Sim> {
-    interpreter: &'a Interpreter,
-    env: Env,
-    sim: Sim,
-    lookup: Lookup<'a>,
-    state: State,
-}
-
-struct Lookup<'a> {
-    fir_store: &'a IndexMap<PackageId, qsc_fir::fir::Package>,
-}
-
-impl<'a> Lookup<'a> {
-    fn get_package(&self, package: PackageId) -> &qsc_fir::fir::Package {
-        self.fir_store
-            .get(package)
-            .expect("Package should be in FIR store")
-    }
-}
-
-impl<'a> NodeLookup for Lookup<'a> {
-    fn get(&self, id: GlobalId) -> Option<Global<'a>> {
-        get_global(self.fir_store, id)
-    }
-
-    fn get_block(&self, package: PackageId, id: BlockId) -> &qsc_fir::fir::Block {
-        self.get_package(package)
-            .blocks
-            .get(id)
-            .expect("BlockId should have been lowered")
-    }
-    fn get_expr(&self, package: PackageId, id: ExprId) -> &qsc_fir::fir::Expr {
-        self.get_package(package)
-            .exprs
-            .get(id)
-            .expect("ExprId should have been lowered")
-    }
-    fn get_pat(&self, package: PackageId, id: PatId) -> &qsc_fir::fir::Pat {
-        self.get_package(package)
-            .pats
-            .get(id)
-            .expect("PatId should have been lowered")
-    }
-    fn get_stmt(&self, package: PackageId, id: StmtId) -> &qsc_fir::fir::Stmt {
-        self.get_package(package)
-            .stmts
-            .get(id)
-            .expect("StmtId should have been lowered")
-    }
-}
-
-impl Interpreter {
-    /// # Errors
-    ///
-    /// Returns a vector of errors if compiling the given sources fails.
-    pub fn new(std: bool, sources: SourceMap) -> Result<Self, Vec<Error>> {
-        let mut fir_store = IndexMap::new();
-        let mut fir_lowerer = qsc_eval::lower::Lowerer::new();
-        let core = compile::core();
-
-        let core_fir = fir_lowerer.lower_package(&core.package);
-        fir_store.insert(
-            map_hir_package_to_fir(qsc_hir::hir::PackageId::CORE),
-            core_fir,
-        );
-        let mut store = PackageStore::new(core);
-        let mut dependencies = Vec::new();
-
-        if std {
-            let std = compile::std(&store, TargetProfile::Full);
-            let std_fir = fir_lowerer.lower_package(&std.package);
-            let id = store.insert(std);
-            fir_store.insert(map_hir_package_to_fir(id), std_fir);
-            dependencies.push(id);
-        }
-
-        let (unit, errors) = compile(
-            &store,
-            &dependencies,
-            sources,
-            PackageType::Exe,
-            TargetProfile::Full,
-        );
-        if errors.is_empty() {
-            let user_fir = fir_lowerer.lower_package(&unit.package);
-            let package = store.insert(unit);
-            fir_store.insert(map_hir_package_to_fir(package), user_fir);
-            Ok(Self {
-                store,
-                fir_store,
-                package: map_hir_package_to_fir(package),
-            })
-        } else {
-            Err(errors
-                .into_iter()
-                .map(|error| Error(WithSource::from_map(&unit.sources, error).into()))
-                .collect())
-        }
-    }
-
-    #[must_use]
-    pub fn new_eval_context(&self) -> EvalContext<SparseSim> {
-        EvalContext {
-            interpreter: self,
-            env: Env::with_empty_scope(),
-            sim: SparseSim::new(),
-            lookup: Lookup {
-                fir_store: &self.fir_store,
-            },
-            state: State::new(self.package),
-        }
-    }
-}
-
-impl<'a, Sim, ResultType> EvalContext<'a, Sim>
-where
-    Sim: Backend<ResultType = ResultType>,
-    ResultType: PartialEq + Into<val::Result>,
-{
-    /// # Errors
-    ///
-    /// Returns a vector of errors if evaluating the entry point fails.
-    /// # Panics
-    ///
-    /// This function will panic if compiler state is invalid or in out-of-memory conditions.
-    pub fn eval_entry(&mut self, receiver: &mut impl Receiver) -> Result<Value, Vec<Error>> {
-        let expr = self.get_entry_expr()?;
-        eval_expr(
-            &mut self.state,
-            expr,
-            &self.lookup,
-            &mut self.env,
-            &mut self.sim,
-            receiver,
-        )
-        .map_err(|(error, call_stack)| {
-            let stack_trace = if call_stack.is_empty() {
-                None
-            } else {
-                Some(render_call_stack(
-                    &self.interpreter.store,
-                    &Lookup {
-                        fir_store: &self.interpreter.fir_store,
-                    },
-                    call_stack,
-                    &error,
-                ))
-            };
-
-            vec![Error(
-                error::from_eval(error, &self.interpreter.store, stack_trace).into(),
-            )]
-        })
-    }
-
-    fn get_entry_expr(&self) -> Result<ExprId, Vec<Error>> {
-        let unit = self
-            .interpreter
-            .fir_store
-            .get(self.interpreter.package)
-            .expect("store should have package");
-        if let Some(entry) = unit.entry {
-            return Ok(entry);
-        };
-        Err(vec![Error(ErrorKind::NoEntryPoint)])
-    }
-}
-
-fn render_call_stack(
-    store: &PackageStore,
-    globals: &impl NodeLookup,
-    call_stack: Vec<Frame>,
-    error: &dyn std::error::Error,
-) -> String {
-    format_call_stack(store, globals, call_stack, error)
-}
-
-pub(super) fn get_global(
-    fir_store: &IndexMap<PackageId, qsc_fir::fir::Package>,
-    id: GlobalId,
-) -> Option<Global> {
-    fir_store
-        .get(id.package)
-        .and_then(|package| match &package.items.get(id.item)?.kind {
-            ItemKind::Callable(callable) => Some(Global::Callable(callable)),
-            ItemKind::Namespace(..) => None,
-            ItemKind::Ty(..) => Some(Global::Udt),
-        })
-}
diff --git a/compiler/qsc_codegen/src/qir_base.rs b/compiler/qsc_codegen/src/qir_base.rs
index 9bb7fa99..7ba21f99 100644
--- a/compiler/qsc_codegen/src/qir_base.rs
+++ b/compiler/qsc_codegen/src/qir_base.rs
@@ -10,7 +10,7 @@ use qsc_data_structures::index_map::IndexMap;
 use qsc_eval::{
     backend::Backend,
     debug::{map_hir_package_to_fir, Frame},
-    eval_expr, eval_stmt,
+    eval_expr,
     output::GenericReceiver,
     val::{GlobalId, Value},
     Env, Error, Global, NodeLookup, State,
@@ -38,7 +38,6 @@ pub fn generate_qir(
     let mut fir_store = IndexMap::new();
     let package = map_hir_package_to_fir(package);
     let mut sim = BaseProfSim::default();
-    sim.instrs.push_str(PREFIX);
 
     for (id, unit) in store.iter() {
         fir_store.insert(
@@ -63,38 +62,7 @@ pub fn generate_qir(
         &mut out,
     );
     match result {
-        Ok(val) => {
-            sim.write_output_recording(&val)
-                .expect("writing to string should succeed");
-            sim.instrs.push_str(POSTFIX);
-            Ok(sim.instrs)
-        }
-        Err((err, stack)) => Err((err, stack)),
-    }
-}
-
-/// # Errors
-/// This function will return an error if execution was unable to complete.
-/// # Panics
-///
-/// This function will panic if compiler state is invalid or in out-of-memory conditions.
-pub fn generate_qir_for_stmt(
-    stmt: StmtId,
-    globals: &impl NodeLookup,
-    env: &mut Env,
-    package: PackageId,
-) -> std::result::Result<String, (Error, Vec<Frame>)> {
-    let mut sim = BaseProfSim::default();
-    sim.instrs.push_str(PREFIX);
-    let mut stdout = std::io::sink();
-    let mut out = GenericReceiver::new(&mut stdout);
-    match eval_stmt(stmt, globals, env, &mut sim, package, &mut out) {
-        Ok(val) => {
-            sim.write_output_recording(&val)
-                .expect("writing to string should succeed");
-            sim.instrs.push_str(POSTFIX);
-            Ok(sim.instrs)
-        }
+        Ok(val) => Ok(sim.finish(&val)),
         Err((err, stack)) => Err((err, stack)),
     }
 }
@@ -154,14 +122,37 @@ pub(super) fn get_global(
         })
 }
 
-#[derive(Default)]
-struct BaseProfSim {
+pub struct BaseProfSim {
     next_meas_id: usize,
     sim: QuantumSim,
     instrs: String,
 }
 
+impl Default for BaseProfSim {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl BaseProfSim {
+    #[must_use]
+    pub fn new() -> Self {
+        let mut sim = BaseProfSim {
+            next_meas_id: 0,
+            sim: QuantumSim::new(),
+            instrs: String::new(),
+        };
+        sim.instrs.push_str(PREFIX);
+        sim
+    }
+
+    pub fn finish(mut self, val: &Value) -> String {
+        self.write_output_recording(val)
+            .expect("writing to string should succeed");
+        self.instrs.push_str(POSTFIX);
+        self.instrs
+    }
+
     #[must_use]
     fn get_meas_id(&mut self) -> usize {
         let id = self.next_meas_id;
@@ -454,6 +445,10 @@ impl Backend for BaseProfSim {
         // true to avoid a panic.
         true
     }
+
+    fn reinit(&mut self) {
+        *self = Self::default();
+    }
 }
 
 struct Qubit(usize);
diff --git a/compiler/qsc_eval/src/backend.rs b/compiler/qsc_eval/src/backend.rs
index 8326afe9..c7cdd88f 100644
--- a/compiler/qsc_eval/src/backend.rs
+++ b/compiler/qsc_eval/src/backend.rs
@@ -36,6 +36,7 @@ pub trait Backend {
     fn qubit_release(&mut self, q: usize);
     fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize);
     fn qubit_is_zero(&mut self, q: usize) -> bool;
+    fn reinit(&mut self);
 }
 
 /// Default backend used when targeting sparse simulation.
@@ -186,4 +187,8 @@ impl Backend for SparseSim {
     fn qubit_is_zero(&mut self, q: usize) -> bool {
         self.sim.qubit_is_zero(q)
     }
+
+    fn reinit(&mut self) {
+        *self = Self::new();
+    }
 }
diff --git a/compiler/qsc_parse/src/item.rs b/compiler/qsc_parse/src/item.rs
index 2e98356b..76937ab8 100644
--- a/compiler/qsc_parse/src/item.rs
+++ b/compiler/qsc_parse/src/item.rs
@@ -43,14 +43,19 @@ pub(super) fn parse(s: &mut Scanner) -> Result<Box<Item>> {
         ty
     } else if let Some(callable) = opt(s, parse_callable_decl)? {
         Box::new(ItemKind::Callable(callable))
+    } else if doc.is_some() {
+        let err_item = default(s.span(lo));
+        s.push_error(Error(ErrorKind::FloatingDocComment(err_item.span)));
+        return Ok(err_item);
     } else {
-        return Err(Error(ErrorKind::Rule("item", s.peek().kind, s.peek().span)));
+        let p = s.peek();
+        return Err(Error(ErrorKind::Rule("item", p.kind, p.span)));
     };
 
     Ok(Box::new(Item {
         id: NodeId::default(),
         span: s.span(lo),
-        doc: doc.into(),
+        doc: doc.unwrap_or_default().into(),
         attrs: attrs.into_boxed_slice(),
         visibility,
         kind,
@@ -103,7 +108,7 @@ fn parse_fragment(s: &mut Scanner) -> Result<Fragment> {
     // to avoid problems with error reporting. Specifically, if `parse_namespace` consumes the
     // doc comment and then fails to find a namespace, that becomes an unrecoverable error even with
     // opt. This pattern can be dropped along with namespaces once we have a module-based design.
-    let doc = parse_doc(s);
+    let doc = parse_doc(s).unwrap_or_default();
     if let Some(mut namespace) = opt(s, parse_namespace)? {
         namespace.doc = doc.into();
         Ok(Fragment::Namespace(namespace))
@@ -122,7 +127,7 @@ fn parse_fragment(s: &mut Scanner) -> Result<Fragment> {
 
 fn parse_namespace(s: &mut Scanner) -> Result<Namespace> {
     let lo = s.peek().span.lo;
-    let doc = parse_doc(s);
+    let doc = parse_doc(s).unwrap_or_default();
     token(s, TokenKind::Keyword(Keyword::Namespace))?;
     let name = dot_ident(s)?;
     token(s, TokenKind::Open(Delim::Brace))?;
@@ -137,7 +142,7 @@ fn parse_namespace(s: &mut Scanner) -> Result<Namespace> {
     })
 }
 
-fn parse_doc(s: &mut Scanner) -> String {
+fn parse_doc(s: &mut Scanner) -> Option<String> {
     let mut content = String::new();
     while s.peek().kind == TokenKind::DocComment {
         if !content.is_empty() {
@@ -150,7 +155,7 @@ fn parse_doc(s: &mut Scanner) -> String {
         s.advance();
     }
 
-    content
+    (!content.is_empty()).then_some(content)
 }
 
 fn parse_attr(s: &mut Scanner) -> Result<Box<Attr>> {
diff --git a/compiler/qsc_parse/src/lib.rs b/compiler/qsc_parse/src/lib.rs
index 28c07541..aaf3adef 100644
--- a/compiler/qsc_parse/src/lib.rs
+++ b/compiler/qsc_parse/src/lib.rs
@@ -51,6 +51,9 @@ enum ErrorKind {
     #[error("expected {0}, found {1}")]
     #[diagnostic(code("Qsc.Parse.Token"))]
     Token(TokenKind, TokenKind, #[label] Span),
+    #[error("expected item after doc comment")]
+    #[diagnostic(code("Qsc.Parse.FloatingDocComment"))]
+    FloatingDocComment(#[label] Span),
     #[error("expected {0}, found {1}")]
     #[diagnostic(code("Qsc.Parse.Rule"))]
     Rule(&'static str, TokenKind, #[label] Span),
@@ -76,6 +79,7 @@ impl ErrorKind {
             Self::Convert(expected, actual, span) => Self::Convert(expected, actual, span + offset),
             Self::MissingSemi(span) => Self::MissingSemi(span + offset),
             Self::MissingParens(span) => Self::MissingParens(span + offset),
+            Self::FloatingDocComment(span) => Self::FloatingDocComment(span + offset),
         }
     }
 }
diff --git a/katas/src/lib.rs b/katas/src/lib.rs
index 9bcb80a4..6c16fd69 100644
--- a/katas/src/lib.rs
+++ b/katas/src/lib.rs
@@ -10,10 +10,10 @@ mod tests;
 use qsc::{
     interpret::{
         output::Receiver,
-        stateless::{self, Interpreter},
+        stateful::{self, Interpreter},
         Value,
     },
-    SourceContents, SourceMap, SourceName,
+    PackageType, SourceContents, SourceMap, SourceName, TargetProfile,
 };
 
 pub const EXAMPLE_ENTRY: &str = "Kata.RunExample()";
@@ -30,11 +30,11 @@ pub const EXERCISE_ENTRY: &str = "Kata.Verification.CheckSolution()";
 pub fn check_solution(
     exercise_sources: Vec<(SourceName, SourceContents)>,
     receiver: &mut impl Receiver,
-) -> Result<bool, Vec<stateless::Error>> {
+) -> Result<bool, Vec<stateful::Error>> {
     let source_map = SourceMap::new(exercise_sources, Some(EXERCISE_ENTRY.into()));
-    let interpreter: Interpreter = Interpreter::new(true, source_map)?;
-    let mut eval_ctx = interpreter.new_eval_context();
-    eval_ctx.eval_entry(receiver).map(|value| {
+    let mut interpreter: Interpreter =
+        Interpreter::new(true, source_map, PackageType::Exe, TargetProfile::Full)?;
+    interpreter.eval_entry(receiver).map(|value| {
         if let Value::Bool(success) = value {
             success
         } else {
diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs
index e48cda73..899f9076 100644
--- a/pip/src/interpreter.rs
+++ b/pip/src/interpreter.rs
@@ -91,7 +91,7 @@ impl Interpreter {
         callback: Option<PyObject>,
     ) -> PyResult<PyObject> {
         let mut receiver = OptionalCallbackReceiver { callback, py };
-        match self.interpreter.interpret_line(&mut receiver, input) {
+        match self.interpreter.eval_fragments(&mut receiver, input) {
             Ok(value) => Ok(ValueWrapper(value).into_py(py)),
             Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
         }
diff --git a/samples/algorithms/Shor.qs b/samples/algorithms/Shor.qs
index 7770a72a..109d96a9 100644
--- a/samples/algorithms/Shor.qs
+++ b/samples/algorithms/Shor.qs
@@ -133,16 +133,17 @@ namespace Sample {
                     GreatestCommonDivisorI(halfPower + 1, modulus)
                 );
 
-                // Add a flag that we found the factors, and return computed
-                // non-trivial factors.
-                Message($"Found factor={factor}");
-                return (true, (factor, modulus / factor));
-            } else {
-                // Return a flag indicating we hit a trivial case and didn't get
-                // any factors.
-                Message($"Found trivial factors.");
-                return (false, (1, 1));
-            }
+                // Add a flag that we found the factors, and return only if computed
+                // non-trivial factors (not like 1:n or n:1)
+                if (factor != 1) and (factor != modulus) {
+                    Message($"Found factor={factor}");
+                    return (true, (factor, modulus / factor));
+                }
+            } 
+            // Return a flag indicating we hit a trivial case and didn't get
+            // any factors.
+            Message($"Found trivial factors.");
+            return (false, (1, 1));
         } else {
             // When period is odd we have to pick another generator to estimate
             // period of and start over.
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index d02d8bd2..1e6ba38c 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -10,7 +10,7 @@ use qsc::{
     hir::PackageId,
     interpret::{
         output::{self, Receiver},
-        stateless,
+        stateful,
     },
     PackageStore, PackageType, SourceContents, SourceMap, SourceName, TargetProfile,
 };
@@ -158,13 +158,14 @@ where
     }
 }
 
-fn run_internal<F>(code: &str, expr: &str, event_cb: F, shots: u32) -> Result<(), stateless::Error>
+fn run_internal<F>(code: &str, expr: &str, event_cb: F, shots: u32) -> Result<(), stateful::Error>
 where
     F: Fn(&str),
 {
     let mut out = CallbackReceiver { event_cb };
     let sources = SourceMap::new([("code".into(), code.into())], Some(expr.into()));
-    let interpreter = stateless::Interpreter::new(true, sources);
+    let interpreter =
+        stateful::Interpreter::new(true, sources, PackageType::Exe, TargetProfile::Full);
     if let Err(err) = interpreter {
         // TODO: handle multiple errors
         // https://github.com/microsoft/qsharp/issues/149
@@ -175,10 +176,9 @@ where
         (out.event_cb)(&msg.to_string());
         return Err(e);
     }
-    let interpreter = interpreter.expect("context should be valid");
+    let mut interpreter = interpreter.expect("context should be valid");
     for _ in 0..shots {
-        let mut eval_ctx = interpreter.new_eval_context();
-        let result = eval_ctx.eval_entry(&mut out);
+        let result = interpreter.eval_entry(&mut out);
         let mut success = true;
         let msg: serde_json::Value = match result {
             Ok(value) => serde_json::Value::String(value.to_string()),


Test patch:
diff --git a/compiler/qsc/src/interpret/debug/tests.rs b/compiler/qsc/src/interpret/debug/tests.rs
index 7651c330..b512d801 100644
--- a/compiler/qsc/src/interpret/debug/tests.rs
+++ b/compiler/qsc/src/interpret/debug/tests.rs
@@ -8,25 +8,21 @@ use qsc_frontend::compile::{SourceMap, TargetProfile};
 use qsc_passes::PackageType;
 use std::io::Cursor;
 
-use crate::interpret::{
-    stateful::{Interpreter, LineResult},
-    stateless::{self},
-};
+use crate::interpret::stateful::{self, InterpretResult, Interpreter};
 
-fn line(interpreter: &mut Interpreter, line: impl AsRef<str>) -> (LineResult, String) {
+fn line(interpreter: &mut Interpreter, line: impl AsRef<str>) -> (InterpretResult, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     (
-        interpreter.interpret_line(&mut receiver, line.as_ref()),
+        interpreter.eval_fragments(&mut receiver, line.as_ref()),
         receiver.dump(),
     )
 }
 
-fn eval(interpreter: &stateless::Interpreter) -> (Result<Value, Vec<stateless::Error>>, String) {
+fn eval(interpreter: &mut stateful::Interpreter) -> (Result<Value, Vec<stateful::Error>>, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
-    let mut eval_ctx = interpreter.new_eval_context();
-    (eval_ctx.eval_entry(&mut receiver), receiver.dump())
+    (interpreter.eval_entry(&mut receiver), receiver.dump())
 }
 
 #[test]
@@ -136,10 +132,10 @@ fn stack_traces_can_cross_file_and_entry_boundaries() {
         ],
         Some("Adjoint Test2.A(0)".into()),
     );
-    let interpreter =
-        stateless::Interpreter::new(true, source_map).expect("Failed to compile base environment.");
+    let mut interpreter = Interpreter::new(true, source_map, PackageType::Exe, TargetProfile::Full)
+        .expect("Failed to compile base environment.");
 
-    let (result, _) = eval(&interpreter);
+    let (result, _) = eval(&mut interpreter);
 
     match result {
         Ok(_) => panic!("Expected error"),
diff --git a/compiler/qsc/src/interpret/stateful/tests.rs b/compiler/qsc/src/interpret/stateful/tests.rs
index 6925c15d..8ec4d97c 100644
--- a/compiler/qsc/src/interpret/stateful/tests.rs
+++ b/compiler/qsc/src/interpret/stateful/tests.rs
@@ -2,7 +2,7 @@
 // Licensed under the MIT License.
 
 mod given_interpreter {
-    use crate::interpret::stateful::{Error, Interpreter, LineResult};
+    use crate::interpret::stateful::{Error, InterpretResult, Interpreter};
     use expect_test::Expect;
     use miette::Diagnostic;
     use qsc_eval::{output::CursorReceiver, val::Value};
@@ -10,11 +10,11 @@ mod given_interpreter {
     use qsc_passes::PackageType;
     use std::{fmt::Write, io::Cursor, iter, str::from_utf8};
 
-    fn line(interpreter: &mut Interpreter, line: &str) -> (LineResult, String) {
+    fn line(interpreter: &mut Interpreter, line: &str) -> (InterpretResult, String) {
         let mut cursor = Cursor::new(Vec::<u8>::new());
         let mut receiver = CursorReceiver::new(&mut cursor);
         (
-            interpreter.interpret_line(&mut receiver, line),
+            interpreter.eval_fragments(&mut receiver, line),
             receiver.dump(),
         )
     }
@@ -23,7 +23,7 @@ mod given_interpreter {
         interpreter: &mut Interpreter,
         expr: &str,
         shots: u32,
-    ) -> (Result<Vec<LineResult>, Vec<Error>>, String) {
+    ) -> (Result<Vec<InterpretResult>, Vec<Error>>, String) {
         let mut cursor = Cursor::new(Vec::<u8>::new());
         let mut receiver = CursorReceiver::new(&mut cursor);
         (interpreter.run(&mut receiver, expr, shots), receiver.dump())
@@ -1157,7 +1157,7 @@ mod given_interpreter {
         .expect("interpreter should be created")
     }
 
-    fn is_only_value(result: &LineResult, output: &str, value: &Value) {
+    fn is_only_value(result: &InterpretResult, output: &str, value: &Value) {
         assert_eq!("", output);
 
         match result {
@@ -1179,7 +1179,7 @@ mod given_interpreter {
         }
     }
 
-    fn is_unit_with_output(result: &LineResult, output: &str, expected_output: &str) {
+    fn is_unit_with_output(result: &InterpretResult, output: &str, expected_output: &str) {
         assert_eq!(expected_output, output);
 
         match result {
diff --git a/compiler/qsc_parse/src/item/tests.rs b/compiler/qsc_parse/src/item/tests.rs
index 0c21492b..2d82c5e3 100644
--- a/compiler/qsc_parse/src/item/tests.rs
+++ b/compiler/qsc_parse/src/item/tests.rs
@@ -854,6 +854,43 @@ fn namespace_doc() {
     );
 }
 
+#[test]
+fn floating_doc_comments_in_namespace() {
+    check_vec(
+        parse_namespaces,
+        "namespace MyQuantumProgram {
+    @EntryPoint()
+    function Main() : Unit {}
+    /// hi
+    /// another doc comment
+}
+",
+        &expect![[r#"
+            Namespace _id_ [0-117] (Ident _id_ [10-26] "MyQuantumProgram"):
+                Item _id_ [33-76]:
+                    Attr _id_ [33-46] (Ident _id_ [34-44] "EntryPoint"):
+                        Expr _id_ [44-46]: Unit
+                    Callable _id_ [51-76] (Function):
+                        name: Ident _id_ [60-64] "Main"
+                        input: Pat _id_ [64-66]: Unit
+                        output: Type _id_ [69-73]: Path: Path _id_ [69-73] (Ident _id_ [69-73] "Unit")
+                        body: Block: Block _id_ [74-76]: <empty>
+                Item _id_ [81-115]:
+                    Err
+
+            [
+                Error(
+                    FloatingDocComment(
+                        Span {
+                            lo: 81,
+                            hi: 115,
+                        },
+                    ),
+                ),
+            ]"#]],
+    );
+}
+
 #[test]
 fn two_namespaces() {
     check_vec(
@@ -975,31 +1012,15 @@ fn doc_without_item() {
         }",
         &expect![[r#"
             Namespace _id_ [0-62] (Ident _id_ [10-11] "A"):
-                Item _id_ [26-62]:
+                Item _id_ [26-52]:
                     Err
 
             [
                 Error(
-                    Rule(
-                        "item",
-                        Close(
-                            Brace,
-                        ),
-                        Span {
-                            lo: 61,
-                            hi: 62,
-                        },
-                    ),
-                ),
-                Error(
-                    Token(
-                        Close(
-                            Brace,
-                        ),
-                        Eof,
+                    FloatingDocComment(
                         Span {
-                            lo: 62,
-                            hi: 62,
+                            lo: 26,
+                            hi: 52,
                         },
                     ),
                 ),
diff --git a/compiler/qsc_parse/src/stmt/tests.rs b/compiler/qsc_parse/src/stmt/tests.rs
index dc406880..bee9f08b 100644
--- a/compiler/qsc_parse/src/stmt/tests.rs
+++ b/compiler/qsc_parse/src/stmt/tests.rs
@@ -574,18 +574,15 @@ fn doc_at_end_of_block() {
         }",
         &expect![[r#"
             Block _id_ [0-50]:
-                Stmt _id_ [14-40]: Err
+                Stmt _id_ [14-40]: Item: Item _id_ [14-40]:
+                    Err
 
             [
                 Error(
-                    Rule(
-                        "item",
-                        Close(
-                            Brace,
-                        ),
+                    FloatingDocComment(
                         Span {
-                            lo: 49,
-                            hi: 50,
+                            lo: 14,
+                            hi: 40,
                         },
                     ),
                 ),
@@ -603,18 +600,19 @@ fn doc_followed_by_non_item() {
         }",
         &expect![[r#"
             Block _id_ [0-72]:
-                Stmt _id_ [14-62]: Err
+                Stmt _id_ [14-39]: Item: Item _id_ [14-39]:
+                    Err
+                Stmt _id_ [52-62]: Local (Immutable):
+                    Pat _id_ [56-57]: Bind:
+                        Ident _id_ [56-57] "x"
+                    Expr _id_ [60-61]: Lit: Int(2)
 
             [
                 Error(
-                    Rule(
-                        "item",
-                        Keyword(
-                            Let,
-                        ),
+                    FloatingDocComment(
                         Span {
-                            lo: 52,
-                            hi: 55,
+                            lo: 14,
+                            hi: 39,
                         },
                     ),
                 ),
diff --git a/katas/src/tests.rs b/katas/src/tests.rs
index dd75d88b..a35c9e61 100644
--- a/katas/src/tests.rs
+++ b/katas/src/tests.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use qsc::interpret::{output::CursorReceiver, stateless};
+use qsc::interpret::{output::CursorReceiver, stateful};
 use std::{
     env, fs,
     io::Cursor,
@@ -14,7 +14,7 @@ fn test_cases_dir() -> PathBuf {
         .join("test_cases")
 }
 
-fn run_check_solution(solution: &str, verification: &str) -> Result<bool, Vec<stateless::Error>> {
+fn run_check_solution(solution: &str, verification: &str) -> Result<bool, Vec<stateful::Error>> {
     let mut cursor = Cursor::new(Vec::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     let result = crate::check_solution(
diff --git a/library/tests/src/lib.rs b/library/tests/src/lib.rs
index b45ba442..1b825656 100644
--- a/library/tests/src/lib.rs
+++ b/library/tests/src/lib.rs
@@ -14,8 +14,8 @@ mod test_measurement;
 mod tests;
 
 use qsc::{
-    interpret::{stateless, GenericReceiver, Value},
-    SourceMap,
+    interpret::{stateful, GenericReceiver, Value},
+    PackageType, SourceMap, TargetProfile,
 };
 
 /// # Panics
@@ -27,9 +27,10 @@ pub fn test_expression(expr: &str, expected: &Value) {
 
     let sources = SourceMap::new([("test".into(), "".into())], Some(expr.into()));
 
-    let interpreter = stateless::Interpreter::new(true, sources).expect("test should compile");
-    let mut eval_ctx = interpreter.new_eval_context();
-    let result = eval_ctx
+    let mut interpreter =
+        stateful::Interpreter::new(true, sources, PackageType::Exe, TargetProfile::Full)
+            .expect("test should compile");
+    let result = interpreter
         .eval_entry(&mut out)
         .expect("test should run successfully");