Skip to content

Commit

Permalink
Add the abilty to run compilations against the AST (#1368)
Browse files Browse the repository at this point in the history
  • Loading branch information
idavis authored Apr 22, 2024
1 parent 6393180 commit 4d891c1
Show file tree
Hide file tree
Showing 6 changed files with 467 additions and 7 deletions.
34 changes: 33 additions & 1 deletion compiler/qsc/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ pub enum ErrorKind {
Lint(#[from] qsc_linter::Lint),
}

#[must_use]
#[allow(clippy::module_name_repetitions)]
pub fn compile_ast(
store: &PackageStore,
dependencies: &[PackageId],
ast_package: qsc_ast::ast::Package,
sources: SourceMap,
package_type: PackageType,
capabilities: TargetCapabilityFlags,
) -> (CompileUnit, Vec<Error>) {
let unit = qsc_frontend::compile::compile_ast(
store,
dependencies,
ast_package,
sources,
capabilities,
vec![],
);
process_compile_unit(store, package_type, capabilities, unit)
}

#[must_use]
pub fn compile(
store: &PackageStore,
Expand All @@ -42,13 +63,24 @@ pub fn compile(
capabilities: TargetCapabilityFlags,
language_features: LanguageFeatures,
) -> (CompileUnit, Vec<Error>) {
let mut unit = qsc_frontend::compile::compile(
let unit = qsc_frontend::compile::compile(
store,
dependencies,
sources,
capabilities,
language_features,
);
process_compile_unit(store, package_type, capabilities, unit)
}

#[must_use]
#[allow(clippy::module_name_repetitions)]
fn process_compile_unit(
store: &PackageStore,
package_type: PackageType,
capabilities: TargetCapabilityFlags,
mut unit: CompileUnit,
) -> (CompileUnit, Vec<Error>) {
let mut errors = Vec::new();
for error in unit.errors.drain(..) {
errors.push(WithSource::from_map(&unit.sources, error.into()));
Expand Down
85 changes: 85 additions & 0 deletions compiler/qsc/src/incremental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use crate::compile::{self, compile, core, std};
use miette::Diagnostic;
use qsc_ast::ast;
use qsc_data_structures::language_features::LanguageFeatures;
use qsc_frontend::{
compile::{OpenPackageStore, PackageStore, SourceMap, TargetCapabilityFlags},
Expand Down Expand Up @@ -80,6 +81,24 @@ impl Compiler {
})
}

pub fn from(
store: PackageStore,
source_package_id: PackageId,
capabilities: TargetCapabilityFlags,
language_features: LanguageFeatures,
) -> Result<Self, Errors> {
let frontend =
qsc_frontend::incremental::Compiler::new(&store, [], capabilities, language_features);
let store = store.open();

Ok(Self {
store,
source_package_id,
frontend,
passes: PassContext::new(capabilities),
})
}

/// Compiles Q# fragments. Fragments are Q# code that can contain
/// top-level statements as well as namespaces. A notebook cell
/// or an interpreter entry is an example of fragments.
Expand All @@ -99,6 +118,26 @@ impl Compiler {
self.compile_fragments(source_name, source_contents, fail_on_error)
}

/// Compiles Q# ast fragments. Fragments are Q# code that can contain
/// top-level statements as well as namespaces. A notebook cell
/// or an interpreter entry is an example of fragments.
///
/// This method returns the AST and HIR packages that were created as a result of
/// the compilation, however it does *not* update the current compilation.
///
/// The caller can use the returned packages to perform passes,
/// get information about the newly added items, or do other modifications.
/// It is then the caller's responsibility to merge
/// these packages into the current `CompileUnit` using the `update()` method.
pub fn compile_ast_fragments_fail_fast(
&mut self,
source_name: &str,
source_contents: &str,
package: ast::Package,
) -> Result<Increment, Errors> {
self.compile_ast_fragments(source_name, source_contents, package, fail_on_error)
}

/// Compiles Q# fragments. See [`compile_fragments_fail_fast`] for more details.
///
/// This method calls an accumulator function with any errors returned
Expand Down Expand Up @@ -140,6 +179,52 @@ impl Compiler {
Ok(increment)
}

/// Compiles Q# ast fragments. See [`compile_ast_fragments_fail_fast`] for more details.
///
/// This method calls an accumulator function with any errors returned
/// from each of the stages (parsing, lowering).
/// If the accumulator succeeds, compilation continues.
/// If the accumulator returns an error, compilation stops and the
/// error is returned to the caller.
pub fn compile_ast_fragments<F>(
&mut self,
source_name: &str,
source_contents: &str,
package: ast::Package,
mut accumulate_errors: F,
) -> Result<Increment, Errors>
where
F: FnMut(Errors) -> Result<(), Errors>,
{
let (core, unit) = self.store.get_open_mut();

let mut errors = false;
let mut increment = self.frontend.compile_ast_fragments(
unit,
source_name,
source_contents,
package,
|e| {
errors = errors || !e.is_empty();
accumulate_errors(into_errors(e))
},
)?;

// Even if we don't fail fast, skip passes if there were compilation errors.
if !errors {
let pass_errors = self.passes.run_default_passes(
&mut increment.hir,
&mut unit.assigner,
core,
PackageType::Lib,
);

accumulate_errors(into_errors_with_source(pass_errors, &unit.sources))?;
}

Ok(increment)
}

/// Compiles an entry expression.
///
/// This method returns the AST and HIR packages that were created as a result of
Expand Down
67 changes: 67 additions & 0 deletions compiler/qsc/src/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ use qsc_fir::{
use qsc_frontend::{
compile::{CompileUnit, PackageStore, Source, SourceMap, TargetCapabilityFlags},
error::WithSource,
incremental::Increment,
};
use qsc_passes::{PackageType, PassContext};
use rustc_hash::FxHashSet;
Expand Down Expand Up @@ -218,6 +219,42 @@ impl Interpreter {
})
}

pub fn from(
store: PackageStore,
source_package_id: qsc_hir::hir::PackageId,
capabilities: TargetCapabilityFlags,
language_features: LanguageFeatures,
) -> std::result::Result<Self, Vec<Error>> {
let compiler = Compiler::from(store, source_package_id, capabilities, language_features)
.map_err(into_errors)?;

let mut fir_store = fir::PackageStore::new();
for (id, unit) in compiler.package_store() {
let mut lowerer = qsc_lowerer::Lowerer::new();
fir_store.insert(
map_hir_package_to_fir(id),
lowerer.lower_package(&unit.package),
);
}

let source_package_id = compiler.source_package_id();
let package_id = compiler.package_id();

Ok(Self {
compiler,
lines: 0,
capabilities,
fir_store,
lowerer: qsc_lowerer::Lowerer::new(),
env: Env::default(),
sim: sim_circuit_backend(),
quantum_seed: None,
classical_seed: None,
package: map_hir_package_to_fir(package_id),
source_package: map_hir_package_to_fir(source_package_id),
})
}

pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
self.quantum_seed = seed;
self.sim.set_seed(seed);
Expand Down Expand Up @@ -293,6 +330,36 @@ impl Interpreter {
.compile_fragments_fail_fast(&label, fragments)
.map_err(into_errors)?;

self.eval_increment(receiver, increment)
}

/// It is assumed that if there were any parse errors on the fragments, the caller would have
/// already handled them. This function is intended to be used in cases where the caller wants
/// to handle the parse errors themselves.
/// # Errors
/// 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_ast_fragments(
&mut self,
receiver: &mut impl Receiver,
fragments: &str,
package: qsc_ast::ast::Package,
) -> InterpretResult {
let label = self.next_line_label();

let increment = self
.compiler
.compile_ast_fragments_fail_fast(&label, fragments, package)
.map_err(into_errors)?;

self.eval_increment(receiver, increment)
}

fn eval_increment(
&mut self,
receiver: &mut impl Receiver,
increment: Increment,
) -> InterpretResult {
let (graph, _) = self.lower(&increment)?;

// Updating the compiler state with the new AST/HIR nodes
Expand Down
Loading

0 comments on commit 4d891c1

Please sign in to comment.