diff --git a/Cargo.lock b/Cargo.lock index ad1e7a1105c..2c496d3b71d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2104,6 +2104,23 @@ dependencies = [ "stim_compiler", ] +[[package]] +name = "qdk_openqasm_parser" +version = "0.0.0" +dependencies = [ + "bitflags 2.11.0", + "criterion 0.8.2", + "enum-iterator", + "expect-test", + "indenter", + "miette", + "num-bigint", + "num-traits", + "qsc_data_structures", + "rustc-hash", + "thiserror", +] + [[package]] name = "qdk_simulators" version = "0.0.0" @@ -2153,6 +2170,7 @@ dependencies = [ "miette", "num-bigint", "num-complex", + "qdk_openqasm_parser", "qdk_simulators", "qsc_ast", "qsc_circuit", @@ -2168,7 +2186,6 @@ dependencies = [ "qsc_linter", "qsc_lowerer", "qsc_openqasm_compiler", - "qsc_openqasm_parser", "qsc_parse", "qsc_partial_eval", "qsc_passes", @@ -2404,33 +2421,17 @@ dependencies = [ "indoc", "miette", "num-bigint", + "qdk_openqasm_parser", "qsc", "qsc_ast", "qsc_data_structures", "qsc_frontend", "qsc_hir", - "qsc_openqasm_parser", "qsc_passes", "rustc-hash", "thiserror", ] -[[package]] -name = "qsc_openqasm_parser" -version = "0.0.0" -dependencies = [ - "bitflags 2.11.0", - "enum-iterator", - "expect-test", - "indenter", - "miette", - "num-bigint", - "num-traits", - "qsc_data_structures", - "rustc-hash", - "thiserror", -] - [[package]] name = "qsc_parse" version = "0.0.0" @@ -2497,11 +2498,11 @@ dependencies = [ "futures", "log", "miette", + "qdk_openqasm_parser", "qsc_circuit", "qsc_data_structures", "qsc_frontend", "qsc_linter", - "qsc_openqasm_parser", "qsc_project", "regex-lite", "rustc-hash", diff --git a/Cargo.toml b/Cargo.toml index 7101e39bb47..4b507f324d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ members = [ "source/compiler/qsc_frontend", "source/compiler/qsc_hir", "source/compiler/qsc_openqasm_compiler", - "source/compiler/qsc_openqasm_parser", "source/compiler/stim_compiler", "source/compiler/qsc_linter", "source/compiler/qsc_lowerer", @@ -29,6 +28,7 @@ members = [ "source/index_map", "source/language_service", "source/simulators", + "source/qdk_openqasm_parser", "source/qdk_package", "source/qre", "source/resource_estimator", diff --git a/clippy.toml b/clippy.toml index bedaaf269fb..748d9dd09ea 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,3 +2,7 @@ disallowed-types = [ { path = "std::collections::HashMap", reason = "use FxHashMap instead" }, { path = "std::collections::HashSet", reason = "use FxHashSet instead" }, ] + +# Proper nouns that should not be flagged by `clippy::doc_markdown` for missing +# backticks. `..` preserves clippy's built-in default identifier list. +doc-valid-idents = ["OpenQASM", "QASM", ".."] diff --git a/source/compiler/qsc/Cargo.toml b/source/compiler/qsc/Cargo.toml index 912651f26e0..977cc61b149 100644 --- a/source/compiler/qsc/Cargo.toml +++ b/source/compiler/qsc/Cargo.toml @@ -33,7 +33,7 @@ qsc_parse = { path = "../qsc_parse" } qsc_partial_eval = { path = "../qsc_partial_eval" } qsc_project = { path = "../qsc_project", features = ["fs"] } qsc_openqasm_compiler = { path = "../qsc_openqasm_compiler" } -qsc_openqasm_parser = { path = "../qsc_openqasm_parser" } +qdk_openqasm_parser = { path = "../../qdk_openqasm_parser", features = ["internal"] } qsc_rca = { path = "../qsc_rca" } qsc_circuit = { path = "../qsc_circuit" } rustc-hash = { workspace = true } diff --git a/source/compiler/qsc/src/openqasm.rs b/source/compiler/qsc/src/openqasm.rs index 1671316d47f..1d6ec6bfde3 100644 --- a/source/compiler/qsc/src/openqasm.rs +++ b/source/compiler/qsc/src/openqasm.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use std::vec; +use qdk_openqasm_parser::io::SourceResolver; use qsc_data_structures::error::WithSource; use qsc_data_structures::target::Profile; use qsc_frontend::compile::PackageStore; @@ -13,19 +14,18 @@ pub use qsc_openqasm_compiler::{ CompilerConfig, OperationSignature, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics, }; -use qsc_openqasm_parser::io::SourceResolver; use qsc_passes::PackageType; pub mod io { - pub use qsc_openqasm_parser::io::*; + pub use qdk_openqasm_parser::io::*; } pub mod parser { - pub use qsc_openqasm_parser::parser::*; + pub use qdk_openqasm_parser::parser::*; } pub mod semantic { - pub use qsc_openqasm_parser::semantic::*; + pub use qdk_openqasm_parser::semantic::*; } pub mod error { @@ -34,7 +34,7 @@ pub mod error { } pub mod completion { - pub use qsc_openqasm_parser::parser::completion::*; + pub use qdk_openqasm_parser::parser::completion::*; } pub mod compiler { @@ -42,7 +42,7 @@ pub mod compiler { } pub mod stdlib { - pub use qsc_openqasm_parser::stdlib::*; + pub use qdk_openqasm_parser::stdlib::*; } use crate::compile::package_store_with_stdlib; diff --git a/source/compiler/qsc_openqasm_compiler/Cargo.toml b/source/compiler/qsc_openqasm_compiler/Cargo.toml index 1d934dc95b1..83955512f9f 100644 --- a/source/compiler/qsc_openqasm_compiler/Cargo.toml +++ b/source/compiler/qsc_openqasm_compiler/Cargo.toml @@ -14,7 +14,7 @@ miette = { workspace = true } qsc_ast = { path = "../qsc_ast" } qsc_data_structures = { path = "../qsc_data_structures" } qsc_frontend = { path = "../qsc_frontend" } -qsc_openqasm_parser = { path = "../qsc_openqasm_parser" } +qdk_openqasm_parser = { path = "../../qdk_openqasm_parser", features = ["internal"] } qsc_hir = { path = "../qsc_hir" } rustc-hash = { workspace = true } thiserror = { workspace = true } diff --git a/source/compiler/qsc_openqasm_compiler/benches/rgqft_multiplier.rs b/source/compiler/qsc_openqasm_compiler/benches/rgqft_multiplier.rs index 7f7efe5e7f2..4e3256f9086 100644 --- a/source/compiler/qsc_openqasm_compiler/benches/rgqft_multiplier.rs +++ b/source/compiler/qsc_openqasm_compiler/benches/rgqft_multiplier.rs @@ -5,11 +5,11 @@ use std::hint::black_box; use std::sync::Arc; use criterion::{Criterion, criterion_group, criterion_main}; +use qdk_openqasm_parser::io::InMemorySourceResolver; use qsc_openqasm_compiler::{ CompilerConfig, OutputSemantics, ProgramType, QasmCompileUnit, QubitSemantics, compiler::parse_and_compile_to_qsharp_ast_with_config, }; -use qsc_openqasm_parser::io::InMemorySourceResolver; fn rgqft_multiplier>>(source: S) -> QasmCompileUnit { let config = CompilerConfig::new( diff --git a/source/compiler/qsc_openqasm_compiler/src/ast_builder.rs b/source/compiler/qsc_openqasm_compiler/src/ast_builder.rs index d26dc633e2b..0daa91440ab 100644 --- a/source/compiler/qsc_openqasm_compiler/src/ast_builder.rs +++ b/source/compiler/qsc_openqasm_compiler/src/ast_builder.rs @@ -13,14 +13,14 @@ use qsc_ast::ast::{ }; use qsc_data_structures::span::Span; -use qsc_openqasm_parser::{ - parser::ast::{List, list_from_iter}, - semantic::types::Type, - stdlib::angle::Angle, -}; +use qdk_openqasm_parser::{semantic::types::Type, stdlib::angle::Angle}; use crate::types::{ArrayDimensions, Complex}; +fn list_from_iter(iter: impl IntoIterator) -> Box<[Box]> { + iter.into_iter().map(Box::new).collect() +} + pub(crate) fn build_managed_qubit_alloc( name: S, stmt_span: Span, @@ -1464,7 +1464,7 @@ pub(crate) fn build_function_or_operation( return_type: Ty, kind: CallableKind, functors: Option, - attrs: List, + attrs: Box<[Box]>, ) -> Stmt { let args = cargs .into_iter() diff --git a/source/compiler/qsc_openqasm_compiler/src/compiler.rs b/source/compiler/qsc_openqasm_compiler/src/compiler.rs index 84a280e56f4..0cdaf57dc6f 100644 --- a/source/compiler/qsc_openqasm_compiler/src/compiler.rs +++ b/source/compiler/qsc_openqasm_compiler/src/compiler.rs @@ -39,9 +39,8 @@ use crate::{ }, get_semantic_errors_from_lowering_result, }; -use qsc_ast::ast::{self as qsast, NodeId, Package}; -use qsc_openqasm_parser::semantic::ast as semast; -use qsc_openqasm_parser::{ +use qdk_openqasm_parser::semantic::ast as semast; +use qdk_openqasm_parser::{ io::SourceResolver, parser::ast::{List, PathKind, list_from_iter}, semantic::{ @@ -56,6 +55,7 @@ use qsc_openqasm_parser::{ }, stdlib::complex::Complex, }; +use qsc_ast::ast::{self as qsast, NodeId, Package}; const QSHARP_QIR_INTRINSIC_ANNOTATION: &str = "SimulatableIntrinsic"; const QDK_QIR_INTRINSIC_ANNOTATION: &str = "qdk.qir.intrinsic"; @@ -88,6 +88,10 @@ fn err_expr(span: Span) -> qsast::Expr { } } +fn boxed_list_from_iter(iter: impl IntoIterator) -> Box<[Box]> { + iter.into_iter().map(Box::new).collect() +} + #[must_use] pub fn parse_and_compile_to_qsharp_ast_with_config< R: SourceResolver, @@ -100,9 +104,9 @@ pub fn parse_and_compile_to_qsharp_ast_with_config< config: CompilerConfig, ) -> QasmCompileUnit { let res = if let Some(resolver) = resolver { - qsc_openqasm_parser::semantic::parse_source(source, path, resolver) + qdk_openqasm_parser::semantic::parse_source(source, path, resolver) } else { - qsc_openqasm_parser::semantic::parse(source, path) + qdk_openqasm_parser::semantic::parse(source, path) }; compile_to_qsharp_ast_with_config(res, config) } @@ -583,7 +587,7 @@ impl QasmCompiler { .filter(|symbol| { matches!( symbol.ty, - qsc_openqasm_parser::semantic::types::Type::BitArray(..) + qdk_openqasm_parser::semantic::types::Type::BitArray(..) ) }) .map(|symbol| { @@ -623,7 +627,7 @@ impl QasmCompiler { .filter(|symbol| { matches!( symbol.ty, - qsc_openqasm_parser::semantic::types::Type::BitArray(..) + qdk_openqasm_parser::semantic::types::Type::BitArray(..) ) }) .map(|symbol| self.map_semantic_type_to_qsharp_type(&symbol.ty, symbol.ty_span)) @@ -670,9 +674,9 @@ impl QasmCompiler { } } - fn compile_stmts(&mut self, stmts: &[Box]) { + fn compile_stmts(&mut self, stmts: &[qdk_openqasm_parser::semantic::ast::Stmt]) { for stmt in stmts { - let compiled_stmt = self.compile_stmt(stmt.as_ref()); + let compiled_stmt = self.compile_stmt(stmt); if let Some(stmt) = compiled_stmt { self.stmts.push(stmt); } @@ -681,7 +685,7 @@ impl QasmCompiler { fn compile_stmt( &mut self, - stmt: &qsc_openqasm_parser::semantic::ast::Stmt, + stmt: &qdk_openqasm_parser::semantic::ast::Stmt, ) -> Option { if !stmt.annotations.is_empty() && !matches!( @@ -914,7 +918,7 @@ impl QasmCompiler { let block = qsast::Block { id: Default::default(), span: rhs_span, - stmts: list_from_iter(vec![temp_var_stmt, update_stmt, implicit_return]), + stmts: boxed_list_from_iter(vec![temp_var_stmt, update_stmt, implicit_return]), }; let rhs = qsast::Expr { @@ -962,7 +966,7 @@ impl QasmCompiler { let block = qsast::Block { id: qsast::NodeId::default(), - stmts: list_from_iter(stmts), + stmts: boxed_list_from_iter(stmts), span: stmt.span, }; @@ -977,7 +981,7 @@ impl QasmCompiler { .collect::>(); qsast::Block { id: qsast::NodeId::default(), - stmts: list_from_iter(stmts), + stmts: boxed_list_from_iter(stmts), span: block.span, } } @@ -1045,7 +1049,7 @@ impl QasmCompiler { // this can happen if the def statement shadows a non-def symbol // Since the symbol is not a function, we assume it returns an error type. // There is already an error reported for this case. - &Arc::from(qsc_openqasm_parser::semantic::types::Type::Err) + &Arc::from(qdk_openqasm_parser::semantic::types::Type::Err) } }; @@ -1093,9 +1097,7 @@ impl QasmCompiler { if let Some(annotation) = annotations .iter() .find(|annotation| Self::is_noise_intrinsic(annotation)) - && !annotations - .iter() - .any(|annotation| Self::is_simulatable_intrinsic(annotation)) + && !annotations.iter().any(Self::is_simulatable_intrinsic) { attrs.push(build_attr( QSHARP_QIR_INTRINSIC_ANNOTATION, @@ -1117,7 +1119,7 @@ impl QasmCompiler { return_type, kind, None, - list_from_iter(attrs), + boxed_list_from_iter(attrs), )) } @@ -1374,6 +1376,14 @@ impl QasmCompiler { } fn compile_pragma_stmt(&mut self, stmt: &semast::Pragma) { + fn is_parameterless_and_returns_void(args: &Arc<[Type]>, return_ty: &Arc) -> bool { + args.is_empty() + && matches!( + &**return_ty, + qdk_openqasm_parser::semantic::types::Type::Void + ) + } + let name_str = stmt .identifier .as_ref() @@ -1392,7 +1402,7 @@ impl QasmCompiler { match (pragma, stmt.value.as_ref()) { (PragmaKind::QdkBoxOpen, Some(value)) => { if let Ok(symbol) = self.symbols.get_symbol_by_name(value) - && let qsc_openqasm_parser::semantic::types::Type::Function(args, return_ty) = + && let qdk_openqasm_parser::semantic::types::Type::Function(args, return_ty) = &symbol.1.ty && is_parameterless_and_returns_void(args, return_ty) { @@ -1407,7 +1417,7 @@ impl QasmCompiler { } (PragmaKind::QdkBoxClose, Some(value)) => { if let Ok(symbol) = self.symbols.get_symbol_by_name(value) - && let qsc_openqasm_parser::semantic::types::Type::Function(args, return_ty) = + && let qdk_openqasm_parser::semantic::types::Type::Function(args, return_ty) = &symbol.1.ty && is_parameterless_and_returns_void(args, return_ty) { @@ -1511,9 +1521,7 @@ impl QasmCompiler { if let Some(annotation) = annotations .iter() .find(|annotation| Self::is_noise_intrinsic(annotation)) - && !annotations - .iter() - .any(|annotation| Self::is_simulatable_intrinsic(annotation)) + && !annotations.iter().any(Self::is_simulatable_intrinsic) { attrs.push(build_attr( QSHARP_QIR_INTRINSIC_ANNOTATION, @@ -1524,10 +1532,7 @@ impl QasmCompiler { // Determine which functors this gate needs based on how it's called. // Do not compile functors if we have the simulatable intrinsic annotation. - let functors = if annotations - .iter() - .any(|annotation| Self::is_simulatable_intrinsic(annotation)) - { + let functors = if annotations.iter().any(Self::is_simulatable_intrinsic) { None } else { // Use the constraint solver results to determine required functors. @@ -1553,7 +1558,7 @@ impl QasmCompiler { build_path_ident_ty("Unit"), qsast::CallableKind::Operation, functors, - list_from_iter(attrs), + boxed_list_from_iter(attrs), )) } @@ -1657,7 +1662,7 @@ impl QasmCompiler { } fn compile_reset_stmt(&mut self, stmt: &semast::ResetStmt) -> Option { - let is_register = matches!(stmt.operand.kind, qsc_openqasm_parser::semantic::ast::GateOperandKind::Expr(ref expr) if matches!(expr.ty, Type::QubitArray(..))); + let is_register = matches!(stmt.operand.kind, qdk_openqasm_parser::semantic::ast::GateOperandKind::Expr(ref expr) if matches!(expr.ty, Type::QubitArray(..))); let operand = self.compile_gate_operand(&stmt.operand); let operand_span = operand.span; @@ -1753,7 +1758,7 @@ impl QasmCompiler { let block_stmt = self.compile_stmt(&stmt.body)?; let block = qsast::Block { id: qsast::NodeId::default(), - stmts: list_from_iter([block_stmt]), + stmts: boxed_list_from_iter([block_stmt]), span: stmt.span, }; Some(build_while_stmt(condition, block, stmt.span)) @@ -1916,8 +1921,8 @@ impl QasmCompiler { op: qsast::BinOp, lhs: qsast::Expr, rhs: qsast::Expr, - lhs_ty: &qsc_openqasm_parser::semantic::types::Type, - rhs_ty: &qsc_openqasm_parser::semantic::types::Type, + lhs_ty: &qdk_openqasm_parser::semantic::types::Type, + rhs_ty: &qdk_openqasm_parser::semantic::types::Type, ) -> qsast::Expr { let span = Span { lo: lhs.span.lo, @@ -2035,29 +2040,29 @@ impl QasmCompiler { let expr = self.compile_expr(&cast.expr); let cast_expr = match cast.expr.ty { - qsc_openqasm_parser::semantic::types::Type::Bit(_) => { + qdk_openqasm_parser::semantic::types::Type::Bit(_) => { Self::cast_bit_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) } - qsc_openqasm_parser::semantic::types::Type::Bool(_) => { + qdk_openqasm_parser::semantic::types::Type::Bool(_) => { Self::cast_bool_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) } - qsc_openqasm_parser::semantic::types::Type::Duration(_) => { + qdk_openqasm_parser::semantic::types::Type::Duration(_) => { self.cast_duration_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) } - qsc_openqasm_parser::semantic::types::Type::Angle(_, _) => { + qdk_openqasm_parser::semantic::types::Type::Angle(_, _) => { Self::cast_angle_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) } - qsc_openqasm_parser::semantic::types::Type::Complex(_, _) => { + qdk_openqasm_parser::semantic::types::Type::Complex(_, _) => { self.cast_complex_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) } - qsc_openqasm_parser::semantic::types::Type::Float(_, _) => { + qdk_openqasm_parser::semantic::types::Type::Float(_, _) => { Self::cast_float_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) } - qsc_openqasm_parser::semantic::types::Type::Int(_, _) - | qsc_openqasm_parser::semantic::types::Type::UInt(_, _) => { + qdk_openqasm_parser::semantic::types::Type::Int(_, _) + | qdk_openqasm_parser::semantic::types::Type::UInt(_, _) => { Self::cast_int_expr_to_ty(expr, &cast.expr.ty, &cast.ty, cast.span) } - qsc_openqasm_parser::semantic::types::Type::BitArray(size, _) => { + qdk_openqasm_parser::semantic::types::Type::BitArray(size, _) => { Self::cast_bit_array_expr_to_ty(expr, &cast.expr.ty, &cast.ty, size, cast.span) } _ => err_expr(cast.span), @@ -2085,7 +2090,7 @@ impl QasmCompiler { fn compile_measure_expr( &mut self, expr: &MeasureExpr, - ty: &qsc_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, ) -> qsast::Expr { assert!(matches!(ty, Type::BitArray(..) | Type::Bit(..))); @@ -2248,8 +2253,8 @@ impl QasmCompiler { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ fn cast_angle_expr_to_ty( expr: qsast::Expr, - expr_ty: &qsc_openqasm_parser::semantic::types::Type, - ty: &qsc_openqasm_parser::semantic::types::Type, + expr_ty: &qdk_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Angle(..))); @@ -2290,8 +2295,8 @@ impl QasmCompiler { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ fn cast_bit_expr_to_ty( expr: qsast::Expr, - expr_ty: &qsc_openqasm_parser::semantic::types::Type, - ty: &qsc_openqasm_parser::semantic::types::Type, + expr_ty: &qdk_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Bit(..))); @@ -2338,8 +2343,8 @@ impl QasmCompiler { fn cast_bit_array_expr_to_ty( expr: qsast::Expr, - expr_ty: &qsc_openqasm_parser::semantic::types::Type, - ty: &qsc_openqasm_parser::semantic::types::Type, + expr_ty: &qdk_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, size: u32, span: Span, ) -> qsast::Expr { @@ -2381,8 +2386,8 @@ impl QasmCompiler { /// +----------------+-------+-----+------+-------+-------+-----+----------+-------+ fn cast_bool_expr_to_ty( expr: qsast::Expr, - expr_ty: &qsc_openqasm_parser::semantic::types::Type, - ty: &qsc_openqasm_parser::semantic::types::Type, + expr_ty: &qdk_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Bool(..))); @@ -2424,8 +2429,8 @@ impl QasmCompiler { fn cast_complex_expr_to_ty( &mut self, _expr: qsast::Expr, - _expr_ty: &qsc_openqasm_parser::semantic::types::Type, - _ty: &qsc_openqasm_parser::semantic::types::Type, + _expr_ty: &qdk_openqasm_parser::semantic::types::Type, + _ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> qsast::Expr { self.push_unimplemented_error_message("cast complex expressions", span); @@ -2435,8 +2440,8 @@ impl QasmCompiler { fn cast_duration_expr_to_ty( &mut self, _expr: qsast::Expr, - _expr_ty: &qsc_openqasm_parser::semantic::types::Type, - _ty: &qsc_openqasm_parser::semantic::types::Type, + _expr_ty: &qdk_openqasm_parser::semantic::types::Type, + _ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> qsast::Expr { self.push_unimplemented_error_message("cast duration expressions", span); @@ -2454,8 +2459,8 @@ impl QasmCompiler { /// Additional cast to complex fn cast_float_expr_to_ty( expr: qsast::Expr, - expr_ty: &qsc_openqasm_parser::semantic::types::Type, - ty: &qsc_openqasm_parser::semantic::types::Type, + expr_ty: &qdk_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Float(..))); @@ -2527,8 +2532,8 @@ impl QasmCompiler { #[allow(clippy::too_many_lines)] fn cast_int_expr_to_ty( expr: qsast::Expr, - expr_ty: &qsc_openqasm_parser::semantic::types::Type, - ty: &qsc_openqasm_parser::semantic::types::Type, + expr_ty: &qdk_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> qsast::Expr { assert!(matches!(expr_ty, Type::Int(..) | Type::UInt(..))); @@ -2624,7 +2629,7 @@ impl QasmCompiler { fn map_semantic_type_to_qsharp_type( &mut self, - ty: &qsc_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, ) -> crate::types::Type { let mut errors = Vec::new(); @@ -2638,15 +2643,15 @@ impl QasmCompiler { /// Mapping from an `OpenQASM` semantic type to its Q# equivalent. /// Returns the mapped type and any errors that would have been pushed. fn semantic_type_for_qsharp_type( - ty: &qsc_openqasm_parser::semantic::types::Type, + ty: &qdk_openqasm_parser::semantic::types::Type, span: Span, errs: &mut Vec, ) -> crate::types::Type { - use qsc_openqasm_parser::semantic::types::Type; + use qdk_openqasm_parser::semantic::types::Type; if ty.is_array() && matches!( ty.array_dims(), - Some(qsc_openqasm_parser::semantic::types::ArrayDimensions::Err) + Some(qdk_openqasm_parser::semantic::types::ArrayDimensions::Err) ) { errs.push(unsupported_err("arrays with more than 7 dimensions", span)); @@ -2692,7 +2697,7 @@ impl QasmCompiler { Type::Array(array) if !matches!( array.base_ty, - qsc_openqasm_parser::semantic::types::ArrayBaseType::Duration + qdk_openqasm_parser::semantic::types::ArrayBaseType::Duration ) => { let dims = (&array.dims).into(); @@ -2749,25 +2754,25 @@ impl QasmCompiler { } fn make_qsharp_array_ty( - base_ty: &qsc_openqasm_parser::semantic::types::ArrayBaseType, + base_ty: &qdk_openqasm_parser::semantic::types::ArrayBaseType, dims: crate::types::ArrayDimensions, ) -> crate::types::Type { match base_ty { - qsc_openqasm_parser::semantic::types::ArrayBaseType::Duration => unreachable!(), - qsc_openqasm_parser::semantic::types::ArrayBaseType::Bool => { + qdk_openqasm_parser::semantic::types::ArrayBaseType::Duration => unreachable!(), + qdk_openqasm_parser::semantic::types::ArrayBaseType::Bool => { crate::types::Type::BoolArray(dims) } - qsc_openqasm_parser::semantic::types::ArrayBaseType::Angle(_) => { + qdk_openqasm_parser::semantic::types::ArrayBaseType::Angle(_) => { crate::types::Type::AngleArray(dims) } - qsc_openqasm_parser::semantic::types::ArrayBaseType::Complex(_) => { + qdk_openqasm_parser::semantic::types::ArrayBaseType::Complex(_) => { crate::types::Type::ComplexArray(dims) } - qsc_openqasm_parser::semantic::types::ArrayBaseType::Float(_) => { + qdk_openqasm_parser::semantic::types::ArrayBaseType::Float(_) => { crate::types::Type::DoubleArray(dims) } - qsc_openqasm_parser::semantic::types::ArrayBaseType::Int(width) - | qsc_openqasm_parser::semantic::types::ArrayBaseType::UInt(width) => { + qdk_openqasm_parser::semantic::types::ArrayBaseType::Int(width) + | qdk_openqasm_parser::semantic::types::ArrayBaseType::UInt(width) => { if let Some(width) = width { if *width > 64 { crate::types::Type::BigIntArray(dims) @@ -2783,8 +2788,8 @@ impl QasmCompiler { /// Returns `true` if both `OpenQASM` types map to the same Q# type without errors. fn maps_to_same_qsharp_type( - a: &qsc_openqasm_parser::semantic::types::Type, - b: &qsc_openqasm_parser::semantic::types::Type, + a: &qdk_openqasm_parser::semantic::types::Type, + b: &qdk_openqasm_parser::semantic::types::Type, ) -> bool { let mut errs = Vec::new(); let ty_a = Self::semantic_type_for_qsharp_type(a, Span::default(), &mut errs); @@ -2816,7 +2821,7 @@ impl QasmCompiler { .map(|(name, ast_ty, pat, sym_type)| (name, ast_ty.clone(), pat.span, sym_type)) .collect::>(); let stmts = Self::get_argument_validation_stmts(&args); - let stmts = list_from_iter(stmts); + let stmts = boxed_list_from_iter(stmts); body.stmts = stmts.into_iter().chain(body.stmts).collect(); Some(body) diff --git a/source/compiler/qsc_openqasm_compiler/src/functor_constraints.rs b/source/compiler/qsc_openqasm_compiler/src/functor_constraints.rs index fedc9f75ef5..2987d871c43 100644 --- a/source/compiler/qsc_openqasm_compiler/src/functor_constraints.rs +++ b/source/compiler/qsc_openqasm_compiler/src/functor_constraints.rs @@ -14,7 +14,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use qsc_openqasm_parser::semantic::{ +use qdk_openqasm_parser::semantic::{ ast::{GateCall, GateModifierKind, Program, QuantumGateDefinition}, symbols::SymbolId, visit::{Visitor, walk_gate_call_stmt, walk_quantum_gate_definition}, diff --git a/source/compiler/qsc_openqasm_compiler/src/lib.rs b/source/compiler/qsc_openqasm_compiler/src/lib.rs index a0d7d9b4405..4f6cb25659e 100644 --- a/source/compiler/qsc_openqasm_compiler/src/lib.rs +++ b/source/compiler/qsc_openqasm_compiler/src/lib.rs @@ -27,9 +27,9 @@ pub use functor_constraints::{FunctorConstraintSolver, FunctorConstraints}; use std::{fmt::Write, sync::Arc}; use miette::Diagnostic; +use qdk_openqasm_parser::semantic::QasmSemanticParseResult; use qsc_ast::ast::Package; use qsc_data_structures::{error::WithSource, source::SourceMap, target::Profile}; -use qsc_openqasm_parser::semantic::QasmSemanticParseResult; use thiserror::Error; #[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] @@ -73,11 +73,11 @@ pub enum ErrorKind { Compiler(#[from] crate::compiler::error::Error), #[error(transparent)] #[diagnostic(transparent)] - Parser(#[from] qsc_openqasm_parser::error::Error), + Parser(#[from] qdk_openqasm_parser::error::Error), } -impl From for crate::Error { - fn from(error: qsc_openqasm_parser::error::Error) -> Self { +impl From for crate::Error { + fn from(error: qdk_openqasm_parser::error::Error) -> Self { Self(ErrorKind::Parser(error)) } } diff --git a/source/compiler/qsc_openqasm_compiler/src/tests.rs b/source/compiler/qsc_openqasm_compiler/src/tests.rs index fff89c56731..17d8a9e02c2 100644 --- a/source/compiler/qsc_openqasm_compiler/src/tests.rs +++ b/source/compiler/qsc_openqasm_compiler/src/tests.rs @@ -8,6 +8,8 @@ use crate::{ }; use expect_test::Expect; use miette::Report; +use qdk_openqasm_parser::io::{InMemorySourceResolver, SourceResolver}; +use qdk_openqasm_parser::semantic::{QasmSemanticParseResult, parse_source}; use qsc::compile::compile_ast; use qsc::compile::package_store_with_stdlib; use qsc::interpret::Error; @@ -17,8 +19,6 @@ use qsc::{ ast::{Package, Stmt, TopLevelNode, mut_visit::MutVisitor}, }; use qsc_hir::hir::PackageId; -use qsc_openqasm_parser::io::{InMemorySourceResolver, SourceResolver}; -use qsc_openqasm_parser::semantic::{QasmSemanticParseResult, parse_source}; use qsc_passes::PackageType; use rustc_hash::{FxHashMap, FxHashSet}; use std::sync::Arc; @@ -538,7 +538,7 @@ pub(crate) fn compare_qasm_and_qasharp_asts(source: &str) { None, None, ); - let mut resolver = qsc_openqasm_parser::io::InMemorySourceResolver::from_iter([]); + let mut resolver = qdk_openqasm_parser::io::InMemorySourceResolver::from_iter([]); let unit = parse_and_compile_to_qsharp_ast_with_config( source, "source.qasm", diff --git a/source/compiler/qsc_openqasm_compiler/src/types.rs b/source/compiler/qsc_openqasm_compiler/src/types.rs index 7411a75d9fa..37cd30069a5 100644 --- a/source/compiler/qsc_openqasm_compiler/src/types.rs +++ b/source/compiler/qsc_openqasm_compiler/src/types.rs @@ -96,34 +96,34 @@ impl From for ArrayDimensions { } } -impl From<&qsc_openqasm_parser::semantic::types::ArrayDimensions> for ArrayDimensions { - fn from(value: &qsc_openqasm_parser::semantic::types::ArrayDimensions) -> Self { +impl From<&qdk_openqasm_parser::semantic::types::ArrayDimensions> for ArrayDimensions { + fn from(value: &qdk_openqasm_parser::semantic::types::ArrayDimensions) -> Self { match value { - qsc_openqasm_parser::semantic::types::ArrayDimensions::One(..) => Self::One, - qsc_openqasm_parser::semantic::types::ArrayDimensions::Two(..) => Self::Two, - qsc_openqasm_parser::semantic::types::ArrayDimensions::Three(..) => Self::Three, - qsc_openqasm_parser::semantic::types::ArrayDimensions::Four(..) => Self::Four, - qsc_openqasm_parser::semantic::types::ArrayDimensions::Five(..) => Self::Five, - qsc_openqasm_parser::semantic::types::ArrayDimensions::Six(..) => Self::Six, - qsc_openqasm_parser::semantic::types::ArrayDimensions::Seven(..) => Self::Seven, - qsc_openqasm_parser::semantic::types::ArrayDimensions::Err => { + qdk_openqasm_parser::semantic::types::ArrayDimensions::One(..) => Self::One, + qdk_openqasm_parser::semantic::types::ArrayDimensions::Two(..) => Self::Two, + qdk_openqasm_parser::semantic::types::ArrayDimensions::Three(..) => Self::Three, + qdk_openqasm_parser::semantic::types::ArrayDimensions::Four(..) => Self::Four, + qdk_openqasm_parser::semantic::types::ArrayDimensions::Five(..) => Self::Five, + qdk_openqasm_parser::semantic::types::ArrayDimensions::Six(..) => Self::Six, + qdk_openqasm_parser::semantic::types::ArrayDimensions::Seven(..) => Self::Seven, + qdk_openqasm_parser::semantic::types::ArrayDimensions::Err => { unimplemented!("Array dimensions greater than seven are not supported.") } } } } -impl From for ArrayDimensions { - fn from(value: qsc_openqasm_parser::semantic::types::Dims) -> Self { +impl From for ArrayDimensions { + fn from(value: qdk_openqasm_parser::semantic::types::Dims) -> Self { match value { - qsc_openqasm_parser::semantic::types::Dims::One => Self::One, - qsc_openqasm_parser::semantic::types::Dims::Two => Self::Two, - qsc_openqasm_parser::semantic::types::Dims::Three => Self::Three, - qsc_openqasm_parser::semantic::types::Dims::Four => Self::Four, - qsc_openqasm_parser::semantic::types::Dims::Five => Self::Five, - qsc_openqasm_parser::semantic::types::Dims::Six => Self::Six, - qsc_openqasm_parser::semantic::types::Dims::Seven => Self::Seven, - qsc_openqasm_parser::semantic::types::Dims::Err => { + qdk_openqasm_parser::semantic::types::Dims::One => Self::One, + qdk_openqasm_parser::semantic::types::Dims::Two => Self::Two, + qdk_openqasm_parser::semantic::types::Dims::Three => Self::Three, + qdk_openqasm_parser::semantic::types::Dims::Four => Self::Four, + qdk_openqasm_parser::semantic::types::Dims::Five => Self::Five, + qdk_openqasm_parser::semantic::types::Dims::Six => Self::Six, + qdk_openqasm_parser::semantic::types::Dims::Seven => Self::Seven, + qdk_openqasm_parser::semantic::types::Dims::Err => { unimplemented!("Array dimensions greater than seven are not supported.") } } diff --git a/source/compiler/qsc_openqasm_parser/Cargo.toml b/source/compiler/qsc_openqasm_parser/Cargo.toml deleted file mode 100644 index e26941c0ae4..00000000000 --- a/source/compiler/qsc_openqasm_parser/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "qsc_openqasm_parser" -authors.workspace = true -homepage.workspace = true -repository.workspace = true -edition.workspace = true -license.workspace = true -version.workspace = true - -[dependencies] -bitflags = { workspace = true } -enum-iterator = { workspace = true } -indenter = { workspace = true } -num-bigint = { workspace = true } -num-traits = { workspace = true } -miette = { workspace = true } -qsc_data_structures = { path = "../qsc_data_structures" } -rustc-hash = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -expect-test = { workspace = true } -miette = { workspace = true, features = ["fancy-no-syscall"] } - -[lints] -workspace = true diff --git a/source/compiler/qsc_openqasm_parser/README.md b/source/compiler/qsc_openqasm_parser/README.md deleted file mode 100644 index bab3954b6fd..00000000000 --- a/source/compiler/qsc_openqasm_parser/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# OpenQASM 3 Parser - -A lexer, parser, and semantic analyzer for [OpenQASM 3](https://openqasm.com/) programs. - -## Overview - -This crate provides a complete front-end for processing OpenQASM 3 source code. It operates entirely in the OpenQASM domain and produces a typed semantic AST suitable for further compilation or analysis. - -The processing pipeline has two stages: - -1. **Lexing & Parsing** — Tokenizes and parses OpenQASM 3 source into a syntax tree (`QasmParseResult`) -2. **Semantic Analysis** — Lowers the syntax tree into a semantic AST with type checking, symbol resolution, and const evaluation (`QasmSemanticParseResult`) - -### Source Resolution - -OpenQASM programs can include other files via `include` statements. This crate abstracts filesystem access behind the `SourceResolver` trait, enabling: - -- In-memory source resolution for editors and notebooks -- Custom filesystem backends -- Include cycle detection - -### OpenQASM Standard Library Types - -The crate includes implementations of OpenQASM-native types: - -- `Angle` — Fixed-point angle representation -- `Complex` — Complex number type -- `Duration` — Timing duration type - -## Usage - -Parse and semantically analyze an OpenQASM 3 program: - -```rust -use qsc_openqasm_parser::semantic; - -let source = r#" - OPENQASM 3.0; - include "stdgates.inc"; - qubit[2] q; - h q[0]; - cx q[0], q[1]; - bit[2] c; - c = measure q; -"#; - -let result = semantic::parse(source, "bell.qasm"); -if result.has_errors() { - for error in result.all_errors() { - eprintln!("{error}"); - } -} else { - println!("parsed {} statements", result.program.statements.len()); -} -``` - -## License - -MIT diff --git a/source/compiler/qsc_openqasm_parser/src/lib.rs b/source/compiler/qsc_openqasm_parser/src/lib.rs deleted file mode 100644 index 4c7bf33aa56..00000000000 --- a/source/compiler/qsc_openqasm_parser/src/lib.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// When running `build.py` on the repo, clippy fails in this module with -// `clippy::large_stack_arrays`. Note that the `build.py` script runs the command -// `cargo clippy --all-targets --all-features -- -D warnings`. Just running -// `cargo clippy` won't trigger the failure. If you want to reproduce the failure -// with the minimal command possible, you can run `cargo clippy --test -- -D warnings`. -// -// We tried to track down the error, but it is non-deterministic. Our assumpution -// is that clippy is running out of stack memory because of how many and how large -// the static strings in the test modules are. -// -// Decision: Based on this, we decided to disable the `clippy::large_stack_arrays` lint. -#![allow(clippy::large_stack_arrays)] - -mod convert; -pub mod error; -pub mod io; -mod keyword; -mod lex; -pub mod parser; -pub mod semantic; -pub mod stdlib; - -#[cfg(test)] -pub(crate) mod tests; diff --git a/source/compiler/qsc_project/Cargo.toml b/source/compiler/qsc_project/Cargo.toml index bf705edf9c1..25b7e729945 100644 --- a/source/compiler/qsc_project/Cargo.toml +++ b/source/compiler/qsc_project/Cargo.toml @@ -19,7 +19,7 @@ async-trait = { workspace = true } qsc_linter = { path = "../qsc_linter" } qsc_circuit = { path = "../qsc_circuit" } qsc_data_structures = { path = "../qsc_data_structures" } -qsc_openqasm_parser = { path = "../qsc_openqasm_parser" } +qdk_openqasm_parser = { path = "../../qdk_openqasm_parser", features = ["internal"] } qsc_frontend = { path = "../qsc_frontend" } rustc-hash = { workspace = true } futures = { workspace = true } diff --git a/source/compiler/qsc_project/src/openqasm.rs b/source/compiler/qsc_project/src/openqasm.rs index c7b307fbee0..a3c894a5f01 100644 --- a/source/compiler/qsc_project/src/openqasm.rs +++ b/source/compiler/qsc_project/src/openqasm.rs @@ -5,8 +5,8 @@ mod integration_tests; use super::{FileSystemAsync, Project}; +use qdk_openqasm_parser::parser::ast::{PathKind, Program, StmtKind}; use qsc_data_structures::target::Profile; -use qsc_openqasm_parser::parser::ast::{PathKind, Program, StmtKind}; use rustc_hash::FxHashSet; use std::{path::Path, str::FromStr as _, sync::Arc}; @@ -27,7 +27,7 @@ where let path = Arc::from(path.as_ref().to_string_lossy().as_ref()); match source { Some(source) => { - let (program, _errors) = qsc_openqasm_parser::parser::parse(source.as_ref()); + let (program, _errors) = qdk_openqasm_parser::parser::parse(source.as_ref()); target_profile = get_first_profile_pragma(&program); let includes = get_includes(&program, &path); pending_includes.extend(includes); @@ -38,7 +38,7 @@ where match project_host.read_file(Path::new(path.as_ref())).await { Ok((file, source)) => { // load the root file - let (program, _errors) = qsc_openqasm_parser::parser::parse(source.as_ref()); + let (program, _errors) = qdk_openqasm_parser::parser::parse(source.as_ref()); target_profile = get_first_profile_pragma(&program); let includes = get_includes(&program, &file); pending_includes.extend(includes); @@ -92,7 +92,7 @@ where .read_file(Path::new(resolved_path.as_ref())) .await { - let (program, _errors) = qsc_openqasm_parser::parser::parse(source.as_ref()); + let (program, _errors) = qdk_openqasm_parser::parser::parse(source.as_ref()); let includes = get_includes(&program, &file); pending_includes.extend(includes); loaded_files.insert(file.clone()); diff --git a/source/compiler/qsc_project/src/openqasm/integration_tests.rs b/source/compiler/qsc_project/src/openqasm/integration_tests.rs index 676ca72a5c5..782b2784913 100644 --- a/source/compiler/qsc_project/src/openqasm/integration_tests.rs +++ b/source/compiler/qsc_project/src/openqasm/integration_tests.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use expect_test::expect; -use qsc_openqasm_parser::semantic::QasmSemanticParseResult; +use qdk_openqasm_parser::semantic::QasmSemanticParseResult; use crate::{FileSystem, ProjectType, StdFs}; use miette::Report; @@ -31,7 +31,7 @@ fn parse_file_with_contents>( let ProjectType::OpenQASM(sources) = project.project_type else { panic!("Expected OpenQASM project type"); }; - let result = qsc_openqasm_parser::semantic::parse_sources(&sources); + let result = qdk_openqasm_parser::semantic::parse_sources(&sources); ( test_file.as_ref().display().to_string().as_str().into(), result, diff --git a/source/qdk_openqasm_parser/Cargo.toml b/source/qdk_openqasm_parser/Cargo.toml new file mode 100644 index 00000000000..06a8e342e68 --- /dev/null +++ b/source/qdk_openqasm_parser/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "qdk_openqasm_parser" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +autobenches = false +edition.workspace = true +license.workspace = true +version.workspace = true +description = "A standalone lexer, parser, and semantic analyzer for OpenQASM 3." +readme = "README.md" +keywords = ["openqasm", "qasm", "quantum", "parser", "compiler"] +categories = ["parser-implementations", "compilers", "science"] + +[features] +default = [] +internal = ["dep:qsc_data_structures"] +fancy = ["miette/fancy-no-syscall"] + +[dependencies] +bitflags = { workspace = true } +enum-iterator = { workspace = true } +indenter = { workspace = true } +num-bigint = { workspace = true } +num-traits = { workspace = true } +miette = { workspace = true } +qsc_data_structures = { path = "../compiler/qsc_data_structures", optional = true } +rustc-hash = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +criterion = { workspace = true, features = ["cargo_bench_support"] } +expect-test = { workspace = true } +miette = { workspace = true, features = ["fancy-no-syscall"] } + +[[bin]] +name = "qasm_memtest" +path = "src/bin/qasm_memtest.rs" + +[[bench]] +name = "qasm_pipeline" +harness = false + +[lints] +workspace = true diff --git a/source/qdk_openqasm_parser/README.md b/source/qdk_openqasm_parser/README.md new file mode 100644 index 00000000000..24905caf355 --- /dev/null +++ b/source/qdk_openqasm_parser/README.md @@ -0,0 +1,154 @@ +# `qdk_openqasm_parser` + +A standalone lexer, parser, and semantic analyzer for [OpenQASM 3](https://openqasm.com/). + +## Overview + +This crate provides a complete front-end for processing OpenQASM 3 source code. It operates entirely in the OpenQASM domain and produces a typed semantic AST suitable for further compilation or analysis. + +The processing pipeline has two stages: + +1. Lexing and parsing tokenizes and parses OpenQASM 3 source into a syntax tree (`QasmParseResult`). +2. Semantic analysis lowers the syntax tree into a semantic AST with type checking, symbol resolution, and const evaluation (`QasmSemanticParseResult`). + +### Source Resolution + +OpenQASM programs can include other files via `include` statements. This crate abstracts filesystem access behind the `SourceResolver` trait, enabling: + +- In-memory source resolution for editors and notebooks +- Custom filesystem backends +- Include cycle detection + +### OpenQASM Standard Library Types + +The crate includes implementations of OpenQASM-native types: + +- `Angle` for fixed-point angle representation +- `Complex` for complex numbers +- `Duration` for timing durations + +## Installation + +Add the crate to your `Cargo.toml`: + +```toml +[dependencies] +qdk_openqasm_parser = "0.0.0" +``` + +The crate is developed in the [Microsoft Quantum Development Kit repository](https://github.com/microsoft/qdk). + +## Usage + +### Syntactic parsing + +Use `parse_source` for a fast, syntax-only parse. It takes the source text, a +logical path, and an optional `SourceResolver` for `include` directives. Pass +`None` when the program is self-contained: + +```rust +use qdk_openqasm_parser::{io::InMemorySourceResolver, parse_source}; + +let source = "OPENQASM 3.0; qubit q; h q;"; +let result = parse_source(source, "main.qasm", None::<&mut InMemorySourceResolver>); +assert!(!result.has_errors()); +``` + +Provide an in-memory resolver so `include` statements can be resolved: + +```rust +use qdk_openqasm_parser::{io::InMemorySourceResolver, parse_source}; + +let mut resolver = InMemorySourceResolver::from_iter([( + "gates.inc".into(), + "gate my_h q { h q; }".into(), +)]); +let source = "OPENQASM 3.0; include \"gates.inc\"; qubit q; my_h q;"; +let result = parse_source(source, "main.qasm", Some(&mut resolver)); +assert!(!result.has_errors()); +``` + +### Semantic analysis + +`analyze_source` performs the full lowering pipeline (type checking, symbol +resolution, and const evaluation). It shares the same `Option<&mut R>` resolver +argument as `parse_source`. The `stdgates.inc` standard library is resolved +internally, so a self-contained program can pass `None`: + +```rust +use qdk_openqasm_parser::{analyze_source, io::InMemorySourceResolver}; + +let source = "OPENQASM 3.0; include \"stdgates.inc\"; qubit q; h q;"; +let result = analyze_source(source, "main.qasm", None::<&mut InMemorySourceResolver>); +assert!(!result.has_errors()); +``` + +Provide an in-memory resolver so custom `include` statements can be resolved. The +built-in `InMemorySourceResolver` maps include paths to their contents, which is +handy for editors, notebooks, and tests: + +```rust +use qdk_openqasm_parser::{analyze_source, io::InMemorySourceResolver}; + +let mut resolver = InMemorySourceResolver::from_iter([( + "gates.inc".into(), + "gate my_h q { h q; }".into(), +)]); +let source = concat!( + "OPENQASM 3.0; ", + "include \"stdgates.inc\"; ", + "include \"gates.inc\"; ", + "qubit q; my_h q;", +); +let result = analyze_source(source, "main.qasm", Some(&mut resolver)); +assert!(!result.has_errors()); +``` + +### Walking the semantic program + +A successful `analyze_source` exposes the lowered program at `result.program`: + +```rust +use qdk_openqasm_parser::{analyze_source, io::InMemorySourceResolver}; + +let result = analyze_source( + "OPENQASM 3.0; qubit[2] q; U(0, 0, 0) q[0];", + "main.qasm", + None::<&mut InMemorySourceResolver>, +); + +for statement in &result.program.statements { + println!("statement at {:?}", statement.span); +} +``` + +### Rendering structured diagnostics + +Every error is a [`miette`](https://docs.rs/miette) diagnostic +(`WithSource`). Collect them with `result.all_errors()` and wrap each in a +`miette::Report` to render it. Enable the default-off `fancy` feature to get the +graphical report (source snippets, labels, and help text): + +```toml +[dependencies] +qdk_openqasm_parser = { version = "0.0.0", features = ["fancy"] } +``` + +```rust +use qdk_openqasm_parser::{analyze_source, io::InMemorySourceResolver}; + +// `h` requires `include "stdgates.inc";`, so this reports a diagnostic. +let result = analyze_source( + "OPENQASM 3.0; qubit q; h q;", + "main.qasm", + None::<&mut InMemorySourceResolver>, +); + +for error in result.all_errors() { + eprintln!("{:?}", miette::Report::new(error)); +} +``` + +## License + +MIT diff --git a/source/qdk_openqasm_parser/benches/corpus.rs b/source/qdk_openqasm_parser/benches/corpus.rs new file mode 100644 index 00000000000..cbb8b8742b2 --- /dev/null +++ b/source/qdk_openqasm_parser/benches/corpus.rs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::{fmt::Write as _, sync::Arc}; + +use qdk_openqasm_parser::io::InMemorySourceResolver; + +#[derive(Clone, Debug)] +pub struct Corpus { + pub name: &'static str, + pub source: Arc, + pub path: Arc, + pub statement_count: usize, + includes: Vec<(Arc, Arc)>, +} + +impl Corpus { + #[must_use] + pub fn resolver(&self) -> InMemorySourceResolver { + self.includes.iter().cloned().collect() + } +} + +#[must_use] +pub fn flat_gate(repetitions: usize) -> Corpus { + let mut source = String::new(); + source.push_str("OPENQASM 3.0;\n"); + source.push_str("include \"stdgates.inc\";\n"); + source.push_str("qubit q0;\n"); + source.push_str("qubit q1;\n"); + source.push_str("bit c0;\n"); + + for index in 0..repetitions { + source.push_str("h q0;\n"); + source.push_str("cx q0, q1;\n"); + source.push_str("rz(0.125) q1;\n"); + if index.is_multiple_of(8) { + source.push_str("c0 = measure q1;\n"); + source.push_str("reset q1;\n"); + } + } + + let measured_cycles = repetitions.div_ceil(8); + Corpus { + name: "flat_gate", + source: Arc::from(source), + path: Arc::from("flat_gate.qasm"), + statement_count: 5 + (3 * repetitions) + (2 * measured_cycles), + includes: Vec::new(), + } +} + +#[must_use] +pub fn broadcast_gate(repetitions: usize, register_width: usize) -> Corpus { + let mut source = String::new(); + source.push_str("OPENQASM 3.0;\n"); + source.push_str("include \"stdgates.inc\";\n"); + let _ = writeln!(source, "qubit[{register_width}] left;"); + let _ = writeln!(source, "qubit[{register_width}] right;"); + + for _ in 0..repetitions { + source.push_str("h left;\n"); + source.push_str("cx left, right;\n"); + source.push_str("rz(0.25) right;\n"); + } + + Corpus { + name: "broadcast_gate", + source: Arc::from(source), + path: Arc::from("broadcast_gate.qasm"), + statement_count: 4 + (3 * repetitions), + includes: Vec::new(), + } +} + +#[must_use] +pub fn include_heavy(include_count: usize, statements_per_include: usize) -> Corpus { + let mut source = String::new(); + source.push_str("OPENQASM 3.0;\n"); + source.push_str("include \"stdgates.inc\";\n"); + source.push_str("qubit q;\n"); + + let mut includes = Vec::with_capacity(include_count); + for include_index in 0..include_count { + let path = format!("bench/include_{include_index}.inc"); + let _ = writeln!(source, "include \"{path}\";"); + let _ = writeln!(source, "g{include_index} q;"); + + let mut include_source = String::new(); + let _ = writeln!(include_source, "gate g{include_index} target {{"); + for statement_index in 0..statements_per_include { + if statement_index.is_multiple_of(3) { + include_source.push_str(" h target;\n"); + } else if statement_index.is_multiple_of(3_usize.saturating_sub(1)) { + include_source.push_str(" rz(0.0625) target;\n"); + } else { + include_source.push_str(" x target;\n"); + } + } + include_source.push_str("}\n"); + includes.push((Arc::from(path), Arc::from(include_source))); + } + + Corpus { + name: "include_heavy", + source: Arc::from(source), + path: Arc::from("include_heavy.qasm"), + statement_count: 3 + (2 * include_count) + (include_count * statements_per_include), + includes, + } +} diff --git a/source/qdk_openqasm_parser/benches/qasm_pipeline.rs b/source/qdk_openqasm_parser/benches/qasm_pipeline.rs new file mode 100644 index 00000000000..e1441304d39 --- /dev/null +++ b/source/qdk_openqasm_parser/benches/qasm_pipeline.rs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::hint::black_box; + +use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; +use qdk_openqasm_parser::{analyze_source, parse_source, semantic::lower_parse_result}; + +mod corpus; + +use corpus::{Corpus, broadcast_gate, flat_gate, include_heavy}; + +fn assert_parse_success(corpus: &Corpus, result: &qdk_openqasm_parser::parser::QasmParseResult) { + assert!( + !result.has_errors(), + "{} parse corpus produced {} errors", + corpus.name, + result.all_errors().len() + ); +} + +fn assert_semantic_success( + corpus: &Corpus, + result: &qdk_openqasm_parser::semantic::QasmSemanticParseResult, +) { + assert!( + !result.has_errors(), + "{} semantic corpus produced {} errors", + corpus.name, + result.all_errors().len() + ); +} + +fn parse(corpus: &Corpus) -> qdk_openqasm_parser::parser::QasmParseResult { + let mut resolver = corpus.resolver(); + let result = parse_source( + corpus.source.clone(), + corpus.path.clone(), + Some(&mut resolver), + ); + assert_parse_success(corpus, &result); + result +} + +fn analyze(corpus: &Corpus) -> qdk_openqasm_parser::semantic::QasmSemanticParseResult { + let mut resolver = corpus.resolver(); + let result = analyze_source( + corpus.source.clone(), + corpus.path.clone(), + Some(&mut resolver), + ); + assert_semantic_success(corpus, &result); + result +} + +fn bench_corpus(c: &mut Criterion, corpus: &Corpus) { + let mut group = c.benchmark_group(corpus.name); + group.throughput(criterion::Throughput::Elements( + corpus.statement_count.try_into().unwrap_or(u64::MAX), + )); + + group.bench_function("parse", |b| { + b.iter(|| black_box(parse(black_box(corpus)))); + }); + + group.bench_function("semantic_lower", |b| { + b.iter_batched( + || parse(corpus), + |parse_result| { + let result = lower_parse_result(parse_result); + assert_semantic_success(corpus, &result); + black_box(result); + }, + BatchSize::SmallInput, + ); + }); + + group.bench_function("analyze", |b| { + b.iter(|| black_box(analyze(black_box(corpus)))); + }); + + group.finish(); +} + +pub fn qasm_pipeline(c: &mut Criterion) { + for corpus in [ + flat_gate(1_024), + broadcast_gate(256, 32), + include_heavy(64, 8), + ] { + bench_corpus(c, &corpus); + } +} + +criterion_group!(benches, qasm_pipeline); +criterion_main!(benches); diff --git a/source/qdk_openqasm_parser/src/bin/qasm_memtest.rs b/source/qdk_openqasm_parser/src/bin/qasm_memtest.rs new file mode 100644 index 00000000000..7a62cf9f9fe --- /dev/null +++ b/source/qdk_openqasm_parser/src/bin/qasm_memtest.rs @@ -0,0 +1,304 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::{ + alloc::{GlobalAlloc, Layout, System}, + env, + process::ExitCode, + sync::atomic::{AtomicU64, Ordering}, +}; + +use qdk_openqasm_parser::{analyze_source, parse_source, semantic::lower_parse_result}; + +#[path = "../../benches/corpus.rs"] +mod corpus; + +use corpus::{Corpus, broadcast_gate, flat_gate, include_heavy}; + +struct AllocationCounter { + allocator: A, + current_bytes: AtomicU64, + peak_bytes: AtomicU64, + allocated_bytes: AtomicU64, + deallocated_bytes: AtomicU64, + allocation_count: AtomicU64, + deallocation_count: AtomicU64, +} + +unsafe impl GlobalAlloc for AllocationCounter { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let pointer = unsafe { self.allocator.alloc(layout) }; + if !pointer.is_null() { + self.record_alloc(layout.size() as u64); + } + pointer + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { self.allocator.dealloc(ptr, layout) }; + self.record_dealloc(layout.size() as u64); + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let pointer = unsafe { self.allocator.realloc(ptr, layout, new_size) }; + if !pointer.is_null() { + let old_size = layout.size() as u64; + let new_size = new_size as u64; + if new_size >= old_size { + self.record_alloc(new_size - old_size); + } else { + self.record_dealloc(old_size - new_size); + } + } + pointer + } +} + +impl AllocationCounter { + const fn new(allocator: A) -> Self { + Self { + allocator, + current_bytes: AtomicU64::new(0), + peak_bytes: AtomicU64::new(0), + allocated_bytes: AtomicU64::new(0), + deallocated_bytes: AtomicU64::new(0), + allocation_count: AtomicU64::new(0), + deallocation_count: AtomicU64::new(0), + } + } + + fn reset(&self) { + self.current_bytes.store(0, Ordering::SeqCst); + self.peak_bytes.store(0, Ordering::SeqCst); + self.allocated_bytes.store(0, Ordering::SeqCst); + self.deallocated_bytes.store(0, Ordering::SeqCst); + self.allocation_count.store(0, Ordering::SeqCst); + self.deallocation_count.store(0, Ordering::SeqCst); + } + + fn snapshot(&self) -> MemoryStats { + MemoryStats { + peak_bytes: self.peak_bytes.load(Ordering::SeqCst), + net_live_bytes: self.current_bytes.load(Ordering::SeqCst), + allocated_bytes: self.allocated_bytes.load(Ordering::SeqCst), + deallocated_bytes: self.deallocated_bytes.load(Ordering::SeqCst), + allocation_count: self.allocation_count.load(Ordering::SeqCst), + deallocation_count: self.deallocation_count.load(Ordering::SeqCst), + } + } + + fn record_alloc(&self, bytes: u64) { + let current = self.current_bytes.fetch_add(bytes, Ordering::SeqCst) + bytes; + self.allocated_bytes.fetch_add(bytes, Ordering::SeqCst); + self.allocation_count.fetch_add(1, Ordering::SeqCst); + let mut peak = self.peak_bytes.load(Ordering::SeqCst); + while current > peak { + match self.peak_bytes.compare_exchange( + peak, + current, + Ordering::SeqCst, + Ordering::SeqCst, + ) { + Ok(_) => break, + Err(new_peak) => peak = new_peak, + } + } + } + + fn record_dealloc(&self, bytes: u64) { + self.current_bytes.fetch_sub(bytes, Ordering::SeqCst); + self.deallocated_bytes.fetch_add(bytes, Ordering::SeqCst); + self.deallocation_count.fetch_add(1, Ordering::SeqCst); + } +} + +#[derive(Clone, Copy, Debug)] +struct MemoryStats { + peak_bytes: u64, + net_live_bytes: u64, + allocated_bytes: u64, + deallocated_bytes: u64, + allocation_count: u64, + deallocation_count: u64, +} + +#[derive(Clone, Copy)] +enum Stage { + Parse, + SemanticLower, + Analyze, + SemanticLowerBroadcast, + AnalyzeBroadcast, + ParseInclude, + SemanticLowerInclude, + AnalyzeInclude, +} + +#[global_allocator] +static ALLOCATOR: AllocationCounter = AllocationCounter::new(System); + +fn main() -> ExitCode { + match try_main() { + Ok(()) => ExitCode::SUCCESS, + Err(message) => { + eprintln!("{message}"); + ExitCode::FAILURE + } + } +} + +fn try_main() -> Result<(), String> { + let mut args = env::args().skip(1); + let stage = args + .next() + .as_deref() + .map(parse_stage) + .transpose()? + .unwrap_or(Stage::Analyze); + let iterations = args + .next() + .map(|arg| { + arg.parse::() + .map_err(|error| format!("invalid iteration count '{arg}': {error}")) + }) + .transpose()? + .unwrap_or(1); + + if iterations == 0 { + return Err("iteration count must be greater than zero".into()); + } + + let corpus = stage.corpus(); + + ALLOCATOR.reset(); + for _ in 0..iterations { + run_stage(stage, &corpus)?; + } + let stats = ALLOCATOR.snapshot(); + print_stats(stage.name(), &corpus, iterations, stats); + Ok(()) +} + +fn parse_stage(stage: &str) -> Result { + match stage { + "parse" => Ok(Stage::Parse), + "semantic-lower" => Ok(Stage::SemanticLower), + "analyze" => Ok(Stage::Analyze), + "semantic-lower-broadcast" => Ok(Stage::SemanticLowerBroadcast), + "analyze-broadcast" => Ok(Stage::AnalyzeBroadcast), + "parse-include" => Ok(Stage::ParseInclude), + "semantic-lower-include" => Ok(Stage::SemanticLowerInclude), + "analyze-include" => Ok(Stage::AnalyzeInclude), + _ => Err(format!( + "unknown stage '{stage}'. expected parse, semantic-lower, analyze, semantic-lower-broadcast, analyze-broadcast, parse-include, semantic-lower-include, or analyze-include" + )), + } +} + +impl Stage { + const fn name(self) -> &'static str { + match self { + Self::Parse => "parse", + Self::SemanticLower => "semantic-lower", + Self::Analyze => "analyze", + Self::SemanticLowerBroadcast => "semantic-lower-broadcast", + Self::AnalyzeBroadcast => "analyze-broadcast", + Self::ParseInclude => "parse-include", + Self::SemanticLowerInclude => "semantic-lower-include", + Self::AnalyzeInclude => "analyze-include", + } + } + + fn corpus(self) -> Corpus { + match self { + Self::SemanticLowerBroadcast | Self::AnalyzeBroadcast => broadcast_gate(256, 32), + Self::ParseInclude | Self::SemanticLowerInclude | Self::AnalyzeInclude => { + include_heavy(64, 8) + } + Self::Parse | Self::SemanticLower | Self::Analyze => flat_gate(1_024), + } + } +} + +fn run_stage(stage: Stage, corpus: &Corpus) -> Result<(), String> { + match stage { + Stage::SemanticLower | Stage::SemanticLowerBroadcast | Stage::SemanticLowerInclude => { + let parse_result = parse(corpus)?; + let result = lower_parse_result(parse_result); + ensure_semantic_success(corpus, &result)?; + std::hint::black_box(result); + } + Stage::Analyze | Stage::AnalyzeBroadcast | Stage::AnalyzeInclude => { + analyze(corpus)?; + } + Stage::Parse | Stage::ParseInclude => { + parse(corpus)?; + } + } + Ok(()) +} + +fn parse(corpus: &Corpus) -> Result { + let mut resolver = corpus.resolver(); + let result = parse_source( + corpus.source.clone(), + corpus.path.clone(), + Some(&mut resolver), + ); + ensure_parse_success(corpus, &result)?; + Ok(std::hint::black_box(result)) +} + +fn analyze( + corpus: &Corpus, +) -> Result { + let mut resolver = corpus.resolver(); + let result = analyze_source( + corpus.source.clone(), + corpus.path.clone(), + Some(&mut resolver), + ); + ensure_semantic_success(corpus, &result)?; + Ok(std::hint::black_box(result)) +} + +fn ensure_parse_success( + corpus: &Corpus, + result: &qdk_openqasm_parser::parser::QasmParseResult, +) -> Result<(), String> { + if result.has_errors() { + return Err(format!( + "{} parse corpus produced {} errors", + corpus.name, + result.all_errors().len() + )); + } + Ok(()) +} + +fn ensure_semantic_success( + corpus: &Corpus, + result: &qdk_openqasm_parser::semantic::QasmSemanticParseResult, +) -> Result<(), String> { + if result.has_errors() { + return Err(format!( + "{} semantic corpus produced {} errors", + corpus.name, + result.all_errors().len() + )); + } + Ok(()) +} + +fn print_stats(stage: &str, corpus: &Corpus, iterations: usize, stats: MemoryStats) { + println!("stage: {stage}"); + println!("corpus: {}", corpus.name); + println!("statements: {}", corpus.statement_count); + println!("iterations: {iterations}"); + println!("peak_bytes: {}", stats.peak_bytes); + println!("net_live_bytes: {}", stats.net_live_bytes); + println!("allocated_bytes: {}", stats.allocated_bytes); + println!("deallocated_bytes: {}", stats.deallocated_bytes); + println!("allocation_count: {}", stats.allocation_count); + println!("deallocation_count: {}", stats.deallocation_count); +} diff --git a/source/compiler/qsc_openqasm_parser/src/convert.rs b/source/qdk_openqasm_parser/src/convert.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/convert.rs rename to source/qdk_openqasm_parser/src/convert.rs diff --git a/source/compiler/qsc_openqasm_parser/src/error.rs b/source/qdk_openqasm_parser/src/error.rs similarity index 77% rename from source/compiler/qsc_openqasm_parser/src/error.rs rename to source/qdk_openqasm_parser/src/error.rs index 179d1ebe9c3..05408c1bd59 100644 --- a/source/compiler/qsc_openqasm_parser/src/error.rs +++ b/source/qdk_openqasm_parser/src/error.rs @@ -4,6 +4,13 @@ use miette::Diagnostic; use thiserror::Error; +// In standalone (non-`internal`) builds the crate aliases itself as +// `qsc_data_structures`, so `qsc_data_structures::error::WithSource` resolves to +// `crate::error::WithSource`. Re-export the vendored type here to satisfy that +// path without changing the imports used throughout the crate. +#[cfg(not(feature = "internal"))] +pub(crate) use crate::vendor::error::WithSource; + #[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] #[diagnostic(transparent)] #[error(transparent)] diff --git a/source/compiler/qsc_openqasm_parser/src/io.rs b/source/qdk_openqasm_parser/src/io.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/io.rs rename to source/qdk_openqasm_parser/src/io.rs diff --git a/source/compiler/qsc_openqasm_parser/src/io/error.rs b/source/qdk_openqasm_parser/src/io/error.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/io/error.rs rename to source/qdk_openqasm_parser/src/io/error.rs diff --git a/source/compiler/qsc_openqasm_parser/src/keyword.rs b/source/qdk_openqasm_parser/src/keyword.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/keyword.rs rename to source/qdk_openqasm_parser/src/keyword.rs diff --git a/source/compiler/qsc_openqasm_parser/src/lex.rs b/source/qdk_openqasm_parser/src/lex.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/lex.rs rename to source/qdk_openqasm_parser/src/lex.rs diff --git a/source/compiler/qsc_openqasm_parser/src/lex/cooked.rs b/source/qdk_openqasm_parser/src/lex/cooked.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/lex/cooked.rs rename to source/qdk_openqasm_parser/src/lex/cooked.rs diff --git a/source/compiler/qsc_openqasm_parser/src/lex/cooked/tests.rs b/source/qdk_openqasm_parser/src/lex/cooked/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/lex/cooked/tests.rs rename to source/qdk_openqasm_parser/src/lex/cooked/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/lex/raw.rs b/source/qdk_openqasm_parser/src/lex/raw.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/lex/raw.rs rename to source/qdk_openqasm_parser/src/lex/raw.rs diff --git a/source/compiler/qsc_openqasm_parser/src/lex/raw/tests.rs b/source/qdk_openqasm_parser/src/lex/raw/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/lex/raw/tests.rs rename to source/qdk_openqasm_parser/src/lex/raw/tests.rs diff --git a/source/qdk_openqasm_parser/src/lib.rs b/source/qdk_openqasm_parser/src/lib.rs new file mode 100644 index 00000000000..7232371bb1e --- /dev/null +++ b/source/qdk_openqasm_parser/src/lib.rs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![doc = include_str!("../README.md")] + +mod convert; +pub mod error; +pub mod io; +mod keyword; +mod lex; +pub mod parser; +pub mod semantic; +pub mod stdlib; + +#[cfg(test)] +pub(crate) mod tests; + +// When the `internal` feature is disabled, this crate builds standalone by +// vendoring the shared data-structure types that are normally provided by the +// in-repo `qsc_data_structures` crate. Aliasing the crate to itself as +// `qsc_data_structures` lets the rest of the source keep its +// `use qsc_data_structures::...` imports unchanged in both configurations, and +// the crate-root re-exports below make the vendored modules reachable through +// that path. The `error` module is re-exported separately (see `error.rs`) +// because this crate already has its own top-level `error` module. +#[cfg(not(feature = "internal"))] +extern crate self as qsc_data_structures; + +// The vendored module tree holds minimal copies of the `qsc_data_structures` +// modules (plus the `index_map` crate). It is only compiled for standalone +// builds; the `internal` feature pulls in the real crates instead. +#[cfg(not(feature = "internal"))] +mod vendor; + +// Surface the vendored modules at the crate root so the `qsc_data_structures` +// self-alias above resolves `qsc_data_structures::span` to `crate::span`, +// `qsc_data_structures::index_map` to `crate::index_map`, and so on. `error` is +// deliberately omitted here and re-exported from `error.rs` instead, to avoid +// colliding with this crate's own top-level `error` module. +#[cfg(not(feature = "internal"))] +pub(crate) use vendor::{display, index_map, source, span}; + +use std::sync::Arc; + +use crate::{parser::QasmParseResult, semantic::QasmSemanticParseResult}; + +/// Parses `OpenQASM` source text into a syntax tree. +/// +/// This performs lexing and parsing only; it does not run semantic analysis. +/// Use [`analyze_source`] when symbol resolution and semantic checks are +/// required. +/// +/// # Arguments +/// +/// * `source` - The `OpenQASM` source text to parse. +/// * `path` - The logical path associated with `source`, used for diagnostics +/// and as the base for resolving `include` statements. +/// * `resolver` - An optional [`SourceResolver`](io::SourceResolver) used to +/// resolve `include` statements. When `None`, an empty +/// [`InMemorySourceResolver`](io::InMemorySourceResolver) is used, so any +/// `include` will fail to resolve. +/// +/// # Returns +/// +/// A [`QasmParseResult`](parser::QasmParseResult) containing the parsed source +/// and its source map. Parse errors are collected on the result rather than +/// returned as an `Err`; inspect them via +/// [`QasmParseResult::has_errors`](parser::QasmParseResult::has_errors) and +/// [`QasmParseResult::all_errors`](parser::QasmParseResult::all_errors). +/// +/// # Examples +/// +/// Parse a self-contained program without a custom resolver: +/// +/// ``` +/// use qdk_openqasm_parser::{io::InMemorySourceResolver, parse_source}; +/// +/// let source = "OPENQASM 3.0; qubit q; h q;"; +/// let result = parse_source(source, "main.qasm", None::<&mut InMemorySourceResolver>); +/// assert!(!result.has_errors()); +/// ``` +/// +/// Provide an in-memory resolver so `include` statements can be resolved: +/// +/// ``` +/// use qdk_openqasm_parser::{io::InMemorySourceResolver, parse_source}; +/// +/// let mut resolver = InMemorySourceResolver::from_iter([( +/// "gates.inc".into(), +/// "gate my_h q { h q; }".into(), +/// )]); +/// let source = "OPENQASM 3.0; include \"gates.inc\"; qubit q; my_h q;"; +/// let result = parse_source(source, "main.qasm", Some(&mut resolver)); +/// assert!(!result.has_errors()); +/// ``` +pub fn parse_source( + source: impl Into>, + path: impl Into>, + resolver: Option<&mut R>, +) -> QasmParseResult { + if let Some(resolver) = resolver { + parser::parse_source(source, path, resolver) + } else { + let mut default_resolver = io::InMemorySourceResolver::from_iter([]); + parser::parse_source(source, path, &mut default_resolver) + } +} + +/// Parses and semantically analyzes `OpenQASM` source text. +/// +/// In addition to lexing and parsing, this builds a symbol table and the +/// semantic AST, reporting both syntax and semantic diagnostics. Use +/// [`parse_source`] when only a syntax tree is needed. +/// +/// # Arguments +/// +/// * `source` - The `OpenQASM` source text to analyze. +/// * `path` - The logical path associated with `source`, used for diagnostics +/// and as the base for resolving `include` statements. +/// * `resolver` - An optional [`SourceResolver`](io::SourceResolver) used to +/// resolve `include` statements. When `None`, an empty +/// [`InMemorySourceResolver`](io::InMemorySourceResolver) is used, so any +/// `include` will fail to resolve. +/// +/// # Returns +/// +/// A [`QasmSemanticParseResult`](semantic::QasmSemanticParseResult) containing +/// the analyzed source, source map, symbol table, semantic program, and any +/// diagnostics. Errors are collected on the result rather than returned as an +/// `Err`; inspect them via +/// [`QasmSemanticParseResult::has_errors`](semantic::QasmSemanticParseResult::has_errors), +/// [`has_syntax_errors`](semantic::QasmSemanticParseResult::has_syntax_errors), +/// and [`has_semantic_errors`](semantic::QasmSemanticParseResult::has_semantic_errors). +/// +/// # Examples +/// +/// Analyze a self-contained program without a custom resolver. The +/// `stdgates.inc` standard library is resolved internally, so `h` is in scope: +/// +/// ``` +/// use qdk_openqasm_parser::{analyze_source, io::InMemorySourceResolver}; +/// +/// let source = "OPENQASM 3.0; include \"stdgates.inc\"; qubit q; h q;"; +/// let result = analyze_source(source, "main.qasm", None::<&mut InMemorySourceResolver>); +/// assert!(!result.has_errors()); +/// ``` +/// +/// Provide an in-memory resolver so custom `include` statements can be resolved: +/// +/// ``` +/// use qdk_openqasm_parser::{analyze_source, io::InMemorySourceResolver}; +/// +/// let mut resolver = InMemorySourceResolver::from_iter([( +/// "gates.inc".into(), +/// "gate my_h q { h q; }".into(), +/// )]); +/// let source = concat!( +/// "OPENQASM 3.0; ", +/// "include \"stdgates.inc\"; ", +/// "include \"gates.inc\"; ", +/// "qubit q; my_h q;", +/// ); +/// let result = analyze_source(source, "main.qasm", Some(&mut resolver)); +/// assert!(!result.has_errors()); +/// ``` +pub fn analyze_source( + source: impl Into>, + path: impl Into>, + resolver: Option<&mut R>, +) -> QasmSemanticParseResult { + if let Some(resolver) = resolver { + semantic::parse_source(source, path, resolver) + } else { + let mut default_resolver = io::InMemorySourceResolver::from_iter([]); + semantic::parse_source(source, path, &mut default_resolver) + } +} diff --git a/source/compiler/qsc_openqasm_parser/src/parser.rs b/source/qdk_openqasm_parser/src/parser.rs similarity index 76% rename from source/compiler/qsc_openqasm_parser/src/parser.rs rename to source/qdk_openqasm_parser/src/parser.rs index 6704d554a7f..c757619e3eb 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser.rs +++ b/source/qdk_openqasm_parser/src/parser.rs @@ -45,10 +45,8 @@ pub struct QasmParseResult { impl QasmParseResult { #[must_use] - pub fn new(source: QasmSource) -> QasmParseResult { - let source_map = create_source_map(&source); - let mut source = source; - update_offsets(&source_map, &mut source); + pub fn new(mut source: QasmSource) -> QasmParseResult { + let source_map = create_source_map_and_update_offsets(&mut source); QasmParseResult { source, source_map } } @@ -58,25 +56,25 @@ impl QasmParseResult { } pub fn all_errors(&self) -> Vec> { - let mut self_errors = self.errors(); - let include_errors = self - .source - .includes() - .iter() - .flat_map(QasmSource::all_errors) - .map(|e| self.map_error(e)) - .collect::>(); - - self_errors.extend(include_errors); - self_errors + let mut errors = self.errors(); + errors.extend( + self.source + .includes() + .iter() + .flat_map(QasmSource::all_errors) + .map(|e| self.map_error(e)), + ); + + errors } #[must_use] pub fn errors(&self) -> Vec> { self.source - .errors() + .errors .iter() - .map(|e| self.map_error(e.clone())) + .cloned() + .map(|e| self.map_error(e)) .collect::>() } @@ -88,13 +86,14 @@ impl QasmParseResult { } } -/// all spans and errors spans are relative to the start of the file -/// We need to update the spans based on the offset of the file in the source map. -/// We have to do this after a full parse as we don't know what files will be loaded -/// until we have parsed all the includes. -fn update_offsets(source_map: &SourceMap, source: &mut QasmSource) { - let source_file = source_map.find_by_name(&source.path()); - let offset = source_file.map_or(0, |source| source.offset); +/// All spans and error spans are relative to the start of their own file. Update +/// them to match the offsets `SourceMap` assigns while collecting files in the same +/// order `SourceMap` receives them. +fn collect_source_files_and_update_offsets( + source: &mut QasmSource, + files: &mut Vec<(Arc, Arc)>, + offset: u32, +) -> u32 { // Update the errors' offset source .errors @@ -102,12 +101,18 @@ fn update_offsets(source_map: &SourceMap, source: &mut QasmSource) { .for_each(|e| *e = e.clone().with_offset(offset)); // Update the program's spans with the offset let mut offsetter = Offsetter(offset); - offsetter.visit_program(&mut source.program); + if let Some(program) = &mut source.program { + offsetter.visit_program(program); + } + + files.push((source.path.clone(), source.source.clone())); - // Recursively update the includes, their programs, and errors - for include in source.includes_mut() { - update_offsets(source_map, include); + let mut next_offset = next_source_offset(offset, &source.source); + for include in &mut source.included { + next_offset = collect_source_files_and_update_offsets(include, files, next_offset); } + + next_offset } /// Parse a QASM file and return the parse result. @@ -128,35 +133,29 @@ pub fn parse_source>, P: Into>>( QasmParseResult::new(res) } -/// Creates a Q# source map from a QASM parse output. The `QasmSource` -/// has all of the recursive includes resolved with their own source -/// and parse results. -fn create_source_map(source: &QasmSource) -> SourceMap { +/// Creates a Q# source map from a QASM parse output while updating source and +/// include spans to match the offsets assigned by the source map. +fn create_source_map_and_update_offsets(source: &mut QasmSource) -> SourceMap { let mut files: Vec<(Arc, Arc)> = Vec::new(); - collect_source_files(source, &mut files); + collect_source_files_and_update_offsets(source, &mut files, 0); SourceMap::new(files, None) } -/// Recursively collect all source files from the includes -fn collect_source_files(source: &QasmSource, files: &mut Vec<(Arc, Arc)>) { - files.push((source.path(), source.source())); - // Collect all source files from the includes, this - // begins the recursive process of collecting all source files. - for include in source.includes() { - collect_source_files(include, files); - } +fn next_source_offset(offset: u32, source: &str) -> u32 { + 1 + offset + u32::try_from(source.len()).expect("contents length should fit into u32") } /// Represents a QASM source file that has been parsed. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct QasmSource { /// The path to the source file. This is used for error reporting. /// This path is just a name, it does not have to exist on disk. path: Arc, /// The source code of the file. source: Arc, - /// The parsed AST of the source file or any parse errors. - program: Program, + /// The parsed AST of the source file, or `None` once it has been dropped (for + /// example after semantic lowering, which no longer needs the syntax tree). + program: Option, /// Any parse errors that occurred. errors: Vec, /// Any included files that were resolved. @@ -176,7 +175,7 @@ impl QasmSource { QasmSource { path, source, - program, + program: Some(program), errors, included, } @@ -184,33 +183,51 @@ impl QasmSource { #[must_use] pub fn has_errors(&self) -> bool { - if !self.errors().is_empty() { + if !self.errors.is_empty() { return true; } - self.includes().iter().any(QasmSource::has_errors) + self.included.iter().any(QasmSource::has_errors) } #[must_use] pub fn all_errors(&self) -> Vec { - let mut self_errors = self.errors(); - let include_errors = self.includes().iter().flat_map(QasmSource::all_errors); - self_errors.extend(include_errors); - self_errors + let mut errors = Vec::new(); + self.collect_errors(&mut errors); + errors + } + + fn collect_errors(&self, errors: &mut Vec) { + errors.extend(self.errors.iter().cloned()); + for include in &self.included { + include.collect_errors(errors); + } } #[must_use] pub fn includes(&self) -> &Vec { - self.included.as_ref() + &self.included } #[must_use] pub fn includes_mut(&mut self) -> &mut Vec { - self.included.as_mut() + &mut self.included } #[must_use] - pub fn program(&self) -> &Program { - &self.program + pub fn program(&self) -> Option<&Program> { + self.program.as_ref() + } + + /// Drops the parsed syntax tree once it is no longer needed (for example after + /// semantic lowering, which produces its own program). Diagnostics rely on the + /// source text, the stored parse errors, and the source map rather than the + /// syntax tree, so dropping the program avoids retaining a full syntax AST + /// alongside the lowered semantic program. Applied recursively to includes. + pub(crate) fn drop_program(&mut self) { + self.program = None; + for include in &mut self.included { + include.drop_program(); + } } #[must_use] @@ -365,10 +382,7 @@ where let file_path = &include.filename; // Skip the standard gates include file. // Handling of this file is done by the compiler. - if matches!( - file_path.to_lowercase().as_ref(), - "stdgates.inc" | "qelib1.inc" | "qdk.inc" - ) { + if is_standard_include(file_path.as_ref()) { continue; } let source = match parse_qasm_file(file_path, resolver, stmt.span) { @@ -393,11 +407,11 @@ where QasmSource { path: file_path.clone(), source: Default::default(), - program: Program { + program: Some(Program { span: Span::default(), statements: vec![].into_boxed_slice(), version: None, - }, + }), errors: vec![], included: vec![], } @@ -410,6 +424,12 @@ where (includes, errors) } +fn is_standard_include(path: &str) -> bool { + path.eq_ignore_ascii_case("stdgates.inc") + || path.eq_ignore_ascii_case("qelib1.inc") + || path.eq_ignore_ascii_case("qdk.inc") +} + pub(crate) type Result = std::result::Result; pub(crate) trait Parser: FnMut(&mut ParserContext) -> Result {} diff --git a/source/compiler/qsc_openqasm_parser/src/parser/ast.rs b/source/qdk_openqasm_parser/src/parser/ast.rs similarity index 99% rename from source/compiler/qsc_openqasm_parser/src/parser/ast.rs rename to source/qdk_openqasm_parser/src/parser/ast.rs index 532fdd9f0d4..265f5c21e69 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/ast.rs +++ b/source/qdk_openqasm_parser/src/parser/ast.rs @@ -17,14 +17,16 @@ use std::{ use super::prim::SeqItem; -/// An alternative to `Vec` that uses less stack space. -pub type List = Box<[Box]>; +/// A boxed slice used in place of `Vec`: a thin (pointer + length) handle that +/// stores its elements inline, avoiding `Vec`'s capacity word and the per-element +/// heap allocation a `Box<[Box]>` would incur. +pub type List = Box<[T]>; pub fn list_from_iter(vals: impl IntoIterator) -> List { - vals.into_iter().map(Box::new).collect() + vals.into_iter().collect() } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Program { pub span: Span, pub statements: List, diff --git a/source/compiler/qsc_openqasm_parser/src/parser/completion.rs b/source/qdk_openqasm_parser/src/parser/completion.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/completion.rs rename to source/qdk_openqasm_parser/src/parser/completion.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/completion/collector.rs b/source/qdk_openqasm_parser/src/parser/completion/collector.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/completion/collector.rs rename to source/qdk_openqasm_parser/src/parser/completion/collector.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/completion/tests.rs b/source/qdk_openqasm_parser/src/parser/completion/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/completion/tests.rs rename to source/qdk_openqasm_parser/src/parser/completion/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/completion/word_kinds.rs b/source/qdk_openqasm_parser/src/parser/completion/word_kinds.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/completion/word_kinds.rs rename to source/qdk_openqasm_parser/src/parser/completion/word_kinds.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/error.rs b/source/qdk_openqasm_parser/src/parser/error.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/error.rs rename to source/qdk_openqasm_parser/src/parser/error.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/expr.rs b/source/qdk_openqasm_parser/src/parser/expr.rs similarity index 98% rename from source/compiler/qsc_openqasm_parser/src/parser/expr.rs rename to source/qdk_openqasm_parser/src/parser/expr.rs index 62dfe0e731e..a617d5ec74b 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/expr.rs +++ b/source/qdk_openqasm_parser/src/parser/expr.rs @@ -228,10 +228,12 @@ fn lit_token(lexeme: &str, token: Token) -> Result> { } } Literal::Float => { - let lexeme = lexeme.replace('_', ""); - let value: f64 = lexeme - .parse() - .map_err(|_| Error::new(ErrorKind::Lit("floating-point", token.span)))?; + let value: f64 = if lexeme.contains('_') { + lexeme.replace('_', "").parse() + } else { + lexeme.parse() + } + .map_err(|_| Error::new(ErrorKind::Lit("floating-point", token.span)))?; // Reject NaN, Infinity, and Neg-Infinity to ensure only finite floating-point literals are accepted. if !value.is_finite() { return Err(Error::new(ErrorKind::Lit("floating-point", token.span))); @@ -441,7 +443,7 @@ fn funcall(s: &mut ParserContext, ident: Ident) -> Result { Ok(ExprKind::FunctionCall(FunctionCall { span: s.span(lo), name: ident, - args: args.into_iter().map(Box::new).collect(), + args: args.into_iter().collect(), })) } diff --git a/source/compiler/qsc_openqasm_parser/src/parser/expr/tests.rs b/source/qdk_openqasm_parser/src/parser/expr/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/expr/tests.rs rename to source/qdk_openqasm_parser/src/parser/expr/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/mut_visit.rs b/source/qdk_openqasm_parser/src/parser/mut_visit.rs similarity index 99% rename from source/compiler/qsc_openqasm_parser/src/parser/mut_visit.rs rename to source/qdk_openqasm_parser/src/parser/mut_visit.rs index bd902d51756..9b3e6694fce 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/mut_visit.rs +++ b/source/qdk_openqasm_parser/src/parser/mut_visit.rs @@ -552,11 +552,11 @@ fn walk_pragma_stmt(vis: &mut impl MutVisitor, stmt: &mut Pragma) { fn walk_quantum_gate_definition_stmt(vis: &mut impl MutVisitor, stmt: &mut QuantumGateDefinition) { vis.visit_span(&mut stmt.span); vis.visit_ident(&mut stmt.ident); - stmt.params.iter_mut().for_each(|p| match &mut **p { + stmt.params.iter_mut().for_each(|p| match &mut *p { super::prim::SeqItem::Item(i) => vis.visit_ident(i), super::prim::SeqItem::Missing(span) => vis.visit_span(span), }); - stmt.qubits.iter_mut().for_each(|p| match &mut **p { + stmt.qubits.iter_mut().for_each(|p| match &mut *p { super::prim::SeqItem::Item(i) => vis.visit_ident(i), super::prim::SeqItem::Missing(span) => vis.visit_span(span), }); diff --git a/source/compiler/qsc_openqasm_parser/src/parser/prgm.rs b/source/qdk_openqasm_parser/src/parser/prgm.rs similarity index 93% rename from source/compiler/qsc_openqasm_parser/src/parser/prgm.rs rename to source/qdk_openqasm_parser/src/parser/prgm.rs index 68f5b0259a0..57fe1b8c84f 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/prgm.rs +++ b/source/qdk_openqasm_parser/src/parser/prgm.rs @@ -30,11 +30,7 @@ pub(super) fn parse(s: &mut ParserContext) -> Program { Program { span: s.span(lo), version, - statements: stmts - .into_iter() - .map(Box::new) - .collect::>() - .into_boxed_slice(), + statements: stmts.into_boxed_slice(), } } diff --git a/source/compiler/qsc_openqasm_parser/src/parser/prim.rs b/source/qdk_openqasm_parser/src/parser/prim.rs similarity index 93% rename from source/compiler/qsc_openqasm_parser/src/parser/prim.rs rename to source/qdk_openqasm_parser/src/parser/prim.rs index 9c24e521ec0..35a937129d9 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/prim.rs +++ b/source/qdk_openqasm_parser/src/parser/prim.rs @@ -43,7 +43,7 @@ pub(super) fn ident(s: &mut ParserContext) -> Result { s.expect(WordKinds::PathExpr); let peek = s.peek(); if peek.kind == TokenKind::Identifier { - let name = s.read().into(); + let name = s.intern(s.read()); s.advance(); Ok(Ident { span: peek.span, @@ -62,7 +62,7 @@ pub(super) fn ident_or_kw_as_ident(s: &mut ParserContext) -> Result { s.expect(WordKinds::PathExpr); let peek = s.peek(); if matches!(peek.kind, TokenKind::Identifier | TokenKind::Keyword(..)) { - let name = s.read().into(); + let name = s.intern(s.read()); s.advance(); Ok(Ident { span: peek.span, @@ -93,11 +93,18 @@ pub(super) fn opt(s: &mut ParserContext, mut p: impl Parser) -> Result(s: &mut ParserContext, mut p: impl Parser) -> Result> { let mut xs = Vec::new(); while let Some(x) = opt(s, &mut p)? { - xs.push(x); + push_with_initial_capacity(&mut xs, x); } Ok(xs) } +fn push_with_initial_capacity(xs: &mut Vec, value: T) { + if xs.capacity() == 0 { + xs.reserve_exact(1); + } + xs.push(value); +} + /// Parses a sequence of items separated by commas. /// Supports recovering on missing items. pub(super) fn seq(s: &mut ParserContext, mut p: impl Parser) -> Result<(Vec, FinalSep)> @@ -110,17 +117,17 @@ where let mut span = s.peek().span; span.hi = span.lo; s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); - xs.push(T::default().with_span(span)); + push_with_initial_capacity(&mut xs, T::default().with_span(span)); s.advance(); } while let Some(x) = opt(s, &mut p)? { - xs.push(x); + push_with_initial_capacity(&mut xs, x); if token(s, TokenKind::Comma).is_ok() { while s.peek().kind == TokenKind::Comma { let mut span = s.peek().span; span.hi = span.lo; s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); - xs.push(T::default().with_span(span)); + push_with_initial_capacity(&mut xs, T::default().with_span(span)); s.advance(); } final_sep = FinalSep::Present; @@ -179,17 +186,17 @@ pub(super) fn seq_item( let mut span = s.peek().span; span.hi = span.lo; s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); - xs.push(SeqItem::Missing(span)); + push_with_initial_capacity(&mut xs, SeqItem::Missing(span)); s.advance(); } while let Some(x) = opt(s, &mut p)? { - xs.push(SeqItem::Item(x)); + push_with_initial_capacity(&mut xs, SeqItem::Item(x)); if token(s, TokenKind::Comma).is_ok() { while s.peek().kind == TokenKind::Comma { let mut span = s.peek().span; span.hi = span.lo; s.push_error(Error::new(ErrorKind::MissingSeqEntry(span))); - xs.push(SeqItem::Missing(span)); + push_with_initial_capacity(&mut xs, SeqItem::Missing(span)); s.advance(); } final_sep = FinalSep::Present; diff --git a/source/compiler/qsc_openqasm_parser/src/parser/prim/tests.rs b/source/qdk_openqasm_parser/src/parser/prim/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/prim/tests.rs rename to source/qdk_openqasm_parser/src/parser/prim/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/scan.rs b/source/qdk_openqasm_parser/src/parser/scan.rs similarity index 92% rename from source/compiler/qsc_openqasm_parser/src/parser/scan.rs rename to source/qdk_openqasm_parser/src/parser/scan.rs index df265db1460..2e768621458 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/scan.rs +++ b/source/qdk_openqasm_parser/src/parser/scan.rs @@ -6,6 +6,8 @@ use crate::{ parser::completion::{collector::ValidWordCollector, word_kinds::WordKinds}, }; use qsc_data_structures::span::Span; +use rustc_hash::FxHashMap; +use std::sync::Arc; use super::Error; use super::error::ErrorKind; @@ -25,6 +27,7 @@ pub(crate) struct ParserContext<'a> { pub(super) struct Scanner<'a> { input: &'a str, tokens: Lexer<'a>, + interned_strings: InternedStrings<'a>, barriers: Vec<&'a [TokenKind]>, errors: Vec, recovered_eof: bool, @@ -32,6 +35,8 @@ pub(super) struct Scanner<'a> { offset: u32, } +type InternedStrings<'a> = FxHashMap<&'a str, Arc>; + impl<'a> ParserContext<'a> { pub fn new(input: &'a str) -> Self { Self { @@ -67,6 +72,10 @@ impl<'a> ParserContext<'a> { self.scanner.read_from(from) } + pub(super) fn intern(&mut self, value: &'a str) -> Arc { + self.scanner.intern(value) + } + /// Advances the scanner to start of the the next valid token. pub(super) fn advance(&mut self) { self.scanner.advance(); @@ -126,6 +135,7 @@ impl<'a> Scanner<'a> { Self { input, tokens, + interned_strings: InternedStrings::default(), barriers: Vec::new(), peek: peek.unwrap_or_else(|| eof(input.len())), errors: errors @@ -149,6 +159,16 @@ impl<'a> Scanner<'a> { &self.input[self.span(from)] } + pub(super) fn intern(&mut self, value: &'a str) -> Arc { + if let Some(interned) = self.interned_strings.get(value) { + return interned.clone(); + } + + let interned: Arc = Arc::from(value); + self.interned_strings.insert(value, interned.clone()); + interned + } + pub(super) fn span(&self, from: u32) -> Span { Span { lo: from, diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt.rs b/source/qdk_openqasm_parser/src/parser/stmt.rs similarity index 99% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt.rs rename to source/qdk_openqasm_parser/src/parser/stmt.rs index d940dca988e..94b3b329a07 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/stmt.rs +++ b/source/qdk_openqasm_parser/src/parser/stmt.rs @@ -261,7 +261,7 @@ fn disambiguate_ident( kind: Box::new(ExprKind::FunctionCall(FunctionCall { span: s.span(lo), name: ident, - args: args.into_iter().map(Box::new).collect(), + args: args.into_iter().collect(), })), }; @@ -290,7 +290,7 @@ fn disambiguate_ident( }, // Index expressions are not allowed to have multi-bracket indices. // i.e.: a[1][2] is disallowed in IndexExpr, instead you must do a[1, 2]. - index: *indexed_ident.indices[0].clone(), + index: indexed_ident.indices[0].clone(), }) } }; @@ -1693,7 +1693,7 @@ fn reinterpret_index_expr( if let Index::IndexList(set) = index && set.values.len() == 1 { - let first_elt: IndexListItem = (*set.values[0]).clone(); + let first_elt: IndexListItem = set.values[0].clone(); if let IndexListItem::Expr(expr) = first_elt && duration.is_none() { diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/alias.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/alias.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/alias.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/alias.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/annotation.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/annotation.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/annotation.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/annotation.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/barrier.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/barrier.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/barrier.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/barrier.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/block.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/block.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/block.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/block.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/box_stmt.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/box_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/box_stmt.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/box_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/cal.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/cal.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/cal.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/cal.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/cal_grammar.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/cal_grammar.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/cal_grammar.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/cal_grammar.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/classical_decl.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/classical_decl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/classical_decl.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/classical_decl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/def.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/def.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/def.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/def.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/defcal.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/defcal.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/defcal.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/defcal.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/delay.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/delay.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/delay.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/delay.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/expr_stmt.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/expr_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/expr_stmt.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/expr_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/extern_decl.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/extern_decl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/extern_decl.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/extern_decl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/for_loops.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/for_loops.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/for_loops.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/for_loops.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/gate_call.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/gate_call.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/gate_call.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/gate_call.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/gate_def.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/gate_def.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/gate_def.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/gate_def.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/gphase.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/gphase.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/gphase.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/gphase.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/if_stmt.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/if_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/if_stmt.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/if_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/include.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/include.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/include.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/include.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/branch.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/branch.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/branch.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/branch.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/cal.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/cal.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/cal.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/cal.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/constant.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/constant.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/constant.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/constant.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/decl.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/decl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/decl.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/decl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/gate_calls.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/gate_calls.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/gate_calls.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/gate_calls.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/headers.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/headers.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/headers.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/headers.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/io.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/io.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/io.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/io.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/loops.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/loops.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/loops.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/loops.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/measure.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/measure.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/measure.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/measure.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/switch.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/switch.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/switch.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/switch.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/tokens.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/tokens.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/invalid_stmts/tokens.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/invalid_stmts/tokens.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/io_decl.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/io_decl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/io_decl.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/io_decl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/measure.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/measure.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/measure.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/measure.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/old_style_decl.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/old_style_decl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/old_style_decl.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/old_style_decl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/pragma.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/pragma.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/pragma.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/pragma.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/quantum_decl.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/quantum_decl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/quantum_decl.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/quantum_decl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/reset.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/reset.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/reset.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/reset.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/switch_stmt.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/switch_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/switch_stmt.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/switch_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/while_loops.rs b/source/qdk_openqasm_parser/src/parser/stmt/tests/while_loops.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/parser/stmt/tests/while_loops.rs rename to source/qdk_openqasm_parser/src/parser/stmt/tests/while_loops.rs diff --git a/source/compiler/qsc_openqasm_parser/src/parser/tests.rs b/source/qdk_openqasm_parser/src/parser/tests.rs similarity index 91% rename from source/compiler/qsc_openqasm_parser/src/parser/tests.rs rename to source/qdk_openqasm_parser/src/parser/tests.rs index 738081dcf58..5696d89c46c 100644 --- a/source/compiler/qsc_openqasm_parser/src/parser/tests.rs +++ b/source/qdk_openqasm_parser/src/parser/tests.rs @@ -102,7 +102,14 @@ fn int_version_can_be_parsed() -> miette::Result<(), Vec> { let source = r#"OPENQASM 3;"#; let res = parse(source)?; assert_eq!( - Some(format!("{}", res.source.program.version.expect("version"))), + Some(format!( + "{}", + res.source + .program() + .expect("program") + .version + .expect("version") + )), Some("3".to_string()) ); Ok(()) @@ -113,7 +120,14 @@ fn dotted_version_can_be_parsed() -> miette::Result<(), Vec> { let source = r#"OPENQASM 3.0;"#; let res = parse(source)?; assert_eq!( - Some(format!("{}", res.source.program.version.expect("version"))), + Some(format!( + "{}", + res.source + .program() + .expect("program") + .version + .expect("version") + )), Some("3.0".to_string()) ); Ok(()) diff --git a/source/compiler/qsc_openqasm_parser/src/semantic.rs b/source/qdk_openqasm_parser/src/semantic.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic.rs rename to source/qdk_openqasm_parser/src/semantic.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/ast.rs b/source/qdk_openqasm_parser/src/semantic/ast.rs similarity index 98% rename from source/compiler/qsc_openqasm_parser/src/semantic/ast.rs rename to source/qdk_openqasm_parser/src/semantic/ast.rs index 0fe4d0eec39..f35f3f761ec 100644 --- a/source/compiler/qsc_openqasm_parser/src/semantic/ast.rs +++ b/source/qdk_openqasm_parser/src/semantic/ast.rs @@ -233,8 +233,8 @@ impl Display for AliasDeclStmt { #[derive(Clone, Debug)] pub struct AssignStmt { pub span: Span, - pub lhs: Expr, - pub rhs: Expr, + pub lhs: Box, + pub rhs: Box, } impl Display for AssignStmt { @@ -261,7 +261,7 @@ impl Display for BarrierStmt { #[derive(Clone, Debug)] pub struct BoxStmt { pub span: Span, - pub duration: Option, + pub duration: Option>, pub body: List, } @@ -410,7 +410,7 @@ impl Display for DefCalStmt { #[derive(Clone, Debug)] pub struct DelayStmt { pub span: Span, - pub duration: Expr, + pub duration: Box, pub qubits: List, } @@ -436,7 +436,7 @@ impl Display for EndStmt { #[derive(Clone, Debug)] pub struct ExprStmt { pub span: Span, - pub expr: Expr, + pub expr: Box, } impl Display for ExprStmt { @@ -490,7 +490,7 @@ pub struct GateCall { pub gate_name_span: Span, pub args: List, pub qubits: List, - pub duration: Option, + pub duration: Option>, pub classical_arity: u32, pub quantum_arity: u32, } @@ -512,7 +512,7 @@ impl Display for GateCall { #[derive(Clone, Debug)] pub struct IfStmt { pub span: Span, - pub condition: Expr, + pub condition: Box, pub if_body: Stmt, pub else_body: Option, } @@ -542,9 +542,9 @@ impl Display for IncludeStmt { #[derive(Clone, Debug)] pub struct IndexedClassicalTypeAssignStmt { pub span: Span, - pub lhs: Expr, + pub lhs: Box, pub indices: VecDeque, - pub rhs: Expr, + pub rhs: Box, } impl Display for IndexedClassicalTypeAssignStmt { @@ -669,7 +669,7 @@ pub struct QubitArrayDeclaration { pub symbol_id: SymbolId, /// This `Expr` is const, but we don't substitute by the `LiteralKind` yet /// to be able to provide Span and Type information to the Language Service. - pub size: Expr, + pub size: Box, pub size_span: Span, } @@ -713,7 +713,7 @@ impl Display for ReturnStmt { #[derive(Clone, Debug)] pub struct SwitchStmt { pub span: Span, - pub target: Expr, + pub target: Box, pub cases: List, /// Note that `None` is quite different to `[]` in this case; the latter is /// an explicitly empty body, whereas the absence of a default might mean @@ -748,7 +748,7 @@ impl Display for SwitchCase { #[derive(Clone, Debug)] pub struct WhileLoop { pub span: Span, - pub condition: Expr, + pub condition: Box, pub body: Stmt, } @@ -862,7 +862,11 @@ impl Expr { Self { span, - kind: Box::new(ExprKind::BinaryOp(BinaryOpExpr { op, lhs, rhs })), + kind: Box::new(ExprKind::BinaryOp(BinaryOpExpr { + op, + lhs: Box::new(lhs), + rhs: Box::new(rhs), + })), const_value: None, ty, } @@ -883,7 +887,7 @@ pub enum ExprKind { BuiltinFunctionCall(BuiltinFunctionCall), Cast(Cast), IndexedExpr(IndexedExpr), - Paren(Expr), + Paren(Box), Measure(MeasureExpr), SizeofCall(SizeofCallExpr), DurationofCall(DurationofCallExpr), @@ -1092,7 +1096,7 @@ impl Display for EnumerableSet { pub struct UnaryOpExpr { pub span: Span, pub op: UnaryOp, - pub expr: Expr, + pub expr: Box, } impl Display for UnaryOpExpr { @@ -1106,8 +1110,8 @@ impl Display for UnaryOpExpr { #[derive(Clone, Debug)] pub struct BinaryOpExpr { pub op: BinOp, - pub lhs: Expr, - pub rhs: Expr, + pub lhs: Box, + pub rhs: Box, } impl BinaryOpExpr { @@ -1151,9 +1155,9 @@ impl Display for FunctionCall { pub struct SizeofCallExpr { pub span: Span, pub fn_name_span: Span, - pub array: Expr, + pub array: Box, pub array_dims: u32, - pub dim: Expr, + pub dim: Box, } impl Display for SizeofCallExpr { @@ -1227,7 +1231,7 @@ pub struct Cast { pub span: Span, pub ty: Type, pub ty_exprs: List, - pub expr: Expr, + pub expr: Box, pub kind: CastKind, } diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/const_eval.rs b/source/qdk_openqasm_parser/src/semantic/const_eval.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/const_eval.rs rename to source/qdk_openqasm_parser/src/semantic/const_eval.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/error.rs b/source/qdk_openqasm_parser/src/semantic/error.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/error.rs rename to source/qdk_openqasm_parser/src/semantic/error.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/lowerer.rs b/source/qdk_openqasm_parser/src/semantic/lowerer.rs similarity index 97% rename from source/compiler/qsc_openqasm_parser/src/semantic/lowerer.rs rename to source/qdk_openqasm_parser/src/semantic/lowerer.rs index 80cf21b185e..0422707452a 100644 --- a/source/compiler/qsc_openqasm_parser/src/semantic/lowerer.rs +++ b/source/qdk_openqasm_parser/src/semantic/lowerer.rs @@ -120,7 +120,7 @@ impl Lowerer { pub fn new(source: QasmSource, source_map: SourceMap) -> Self { // do a quick check for the version to set up the symbol table // lowering and validation come later - let version = source.program().version; + let version = source.program().and_then(|p| p.version); let symbols = if let Some(version) = version { if version.major == 2 && version.minor == Some(0) { SymbolTable::new_qasm2() @@ -148,17 +148,27 @@ impl Lowerer { } pub fn lower(mut self) -> crate::semantic::QasmSemanticParseResult { + // Move the parsed source out of `self` so the immutable walk below does + // not borrow-conflict with the `&mut self` lowering methods. This avoids + // cloning the entire syntax AST just to read it; the source is moved back + // into the result at the end. + let mut source = std::mem::take(&mut self.source); // Should we fail if we see a version in included files? - let source = &self.source.clone(); - self.version = self.lower_version(source.program().version); + self.version = self.lower_version(source.program().and_then(|p| p.version)); - self.lower_source(source); + self.lower_source(&source); assert!( self.symbols.is_current_scope_global(), "scope stack was non popped correctly" ); + // Lowering has produced the semantic `program`; the syntax tree is no longer + // needed (diagnostics use the source text/errors and the source map, not the + // syntax tree). Drop it so the result does not retain a full syntax AST + // alongside the semantic program. + source.drop_program(); + let program = semantic::Program { version: self.version, statements: syntax::list_from_iter(self.stmts), @@ -166,7 +176,7 @@ impl Lowerer { }; super::QasmSemanticParseResult { - source: self.source, + source, source_map: self.source_map, symbols: self.symbols, program, @@ -210,7 +220,13 @@ impl Lowerer { // `source.includes()` let mut includes = source.includes().iter(); - for stmt in &source.program().statements { + // The syntax program is present during lowering; once a source's program has + // been dropped there is nothing to lower. + let Some(program) = source.program() else { + return; + }; + + for stmt in &program.statements { match &*stmt.kind { syntax::StmtKind::Include(include) => { // if we are not in the root we should not be able to include @@ -789,7 +805,11 @@ impl Lowerer { return semantic::StmtKind::Err; } - semantic::StmtKind::Assign(semantic::AssignStmt { span, lhs, rhs }) + semantic::StmtKind::Assign(semantic::AssignStmt { + span, + lhs: Box::new(lhs), + rhs: Box::new(rhs), + }) } fn lower_indexed_assign_stmt( @@ -843,7 +863,11 @@ impl Lowerer { } }; - semantic::StmtKind::Assign(semantic::AssignStmt { span, lhs, rhs }) + semantic::StmtKind::Assign(semantic::AssignStmt { + span, + lhs: Box::new(lhs), + rhs: Box::new(rhs), + }) } fn lower_indexed_classical_type_assign_stmt( @@ -865,9 +889,9 @@ impl Lowerer { // So, if return here, it is guaranteed that the assignment will succeed. semantic::StmtKind::IndexedClassicalTypeAssign(semantic::IndexedClassicalTypeAssignStmt { span, - lhs, + lhs: Box::new(lhs), indices, - rhs, + rhs: Box::new(rhs), }) } @@ -928,8 +952,8 @@ impl Lowerer { semantic::StmtKind::Assign(semantic::AssignStmt { span, - lhs, - rhs: binary_expr, + lhs: Box::new(lhs), + rhs: Box::new(binary_expr), }) } @@ -987,8 +1011,8 @@ impl Lowerer { semantic::StmtKind::Assign(semantic::AssignStmt { span, - lhs, - rhs: binary_expr, + lhs: Box::new(lhs), + rhs: Box::new(binary_expr), }) } @@ -1352,7 +1376,7 @@ impl Lowerer { fn lower_paren_expr(&mut self, expr: &syntax::Expr, span: Span) -> semantic::Expr { let expr = self.lower_expr(expr); let ty = expr.ty.clone(); - let kind = semantic::ExprKind::Paren(expr); + let kind = semantic::ExprKind::Paren(Box::new(expr)); semantic::Expr::new(span, kind, ty) } @@ -1373,7 +1397,7 @@ impl Lowerer { let unary = semantic::UnaryOpExpr { span, op: semantic::UnaryOp::Neg, - expr, + expr: Box::new(expr), }; semantic::Expr::new(span, semantic::ExprKind::UnaryOp(unary), ty) } @@ -1392,7 +1416,7 @@ impl Lowerer { let unary = semantic::UnaryOpExpr { span, op: semantic::UnaryOp::NotB, - expr, + expr: Box::new(expr), }; semantic::Expr::new(span, semantic::ExprKind::UnaryOp(unary), ty) } @@ -1414,7 +1438,7 @@ impl Lowerer { semantic::ExprKind::UnaryOp(semantic::UnaryOpExpr { span: expr.span, op: semantic::UnaryOp::NotL, - expr, + expr: Box::new(expr), }), ty, ) @@ -1437,10 +1461,10 @@ impl Lowerer { semantic::Expr::new(expr.span, kind, ty) } - fn lower_annotations(annotations: &[Box]) -> Vec { + fn lower_annotations(annotations: &[syntax::Annotation]) -> Vec { annotations .iter() - .map(|annotation| Self::lower_annotation(annotation)) + .map(Self::lower_annotation) .collect::>() } @@ -1496,7 +1520,7 @@ impl Lowerer { let duration = stmt .duration .as_ref() - .map(|d| self.lower_duration_designator(d)); + .map(|d| Box::new(self.lower_duration_designator(d))); semantic::StmtKind::Box(semantic::BoxStmt { span: stmt.span, @@ -1796,7 +1820,7 @@ impl Lowerer { } } - fn block_always_returns<'a>(stmts: impl IntoIterator>) -> bool { + fn block_always_returns<'a>(stmts: impl IntoIterator) -> bool { for stmt in stmts { if Self::stmt_always_returns(stmt) { return true; @@ -1894,7 +1918,7 @@ impl Lowerer { fn lower_delay(&mut self, stmt: &syntax::DelayStmt) -> semantic::StmtKind { let qubits = stmt.qubits.iter().map(|q| self.lower_gate_operand(q)); let qubits = list_from_iter(qubits); - let duration = self.lower_duration_designator(&stmt.duration); + let duration = Box::new(self.lower_duration_designator(&stmt.duration)); semantic::StmtKind::Delay(semantic::DelayStmt { span: stmt.span, @@ -1935,7 +1959,7 @@ impl Lowerer { } fn lower_expr_stmt(&mut self, stmt: &syntax::ExprStmt) -> semantic::StmtKind { - let expr = self.lower_expr(&stmt.expr); + let expr = Box::new(self.lower_expr(&stmt.expr)); match &*expr.kind { semantic::ExprKind::Err => semantic::StmtKind::Err, semantic::ExprKind::Ident(id) => { @@ -2125,7 +2149,7 @@ impl Lowerer { semantic::StmtKind::If(semantic::IfStmt { span: stmt.span, - condition, + condition: Box::new(condition), if_body, else_body, }) @@ -2222,9 +2246,9 @@ impl Lowerer { let kind = semantic::ExprKind::SizeofCall(semantic::SizeofCallExpr { span: expr.span, fn_name_span: expr.name.span, - array: first_arg, + array: Box::new(first_arg), array_dims: array_dims.into(), - dim: second_arg, + dim: Box::new(second_arg), }); Expr::new(expr.span, kind, Type::UInt(None, false)) @@ -2313,8 +2337,9 @@ impl Lowerer { stmt: &syntax::GateCall, annotations: &List, ) -> Vec { - let mut stmts = Vec::new(); - for kind in self.lower_gate_call_stmt(stmt) { + let kinds = self.lower_gate_call_stmt(stmt); + let mut stmts = Vec::with_capacity(kinds.len()); + for kind in kinds { let stmt = semantic::Stmt { span: stmt.span, annotations: annotations.clone(), @@ -2365,7 +2390,7 @@ impl Lowerer { let duration = stmt .duration .as_ref() - .map(|d| self.lower_duration_designator(d)); + .map(|d| Box::new(self.lower_duration_designator(d))); let name = stmt.name.name.to_string(); @@ -2482,16 +2507,16 @@ impl Lowerer { } // 6.2 Convert the broadcast call in a list of simple stmts. - let mut stmts = Vec::new(); - let Type::QubitArray(indexed_dim_size) = register_type else { unreachable!("we set register_type iff we find a QubitArray"); }; + let mut stmts = Vec::with_capacity(*indexed_dim_size as usize); + for index in 0..(*indexed_dim_size) { let qubits = qubits .iter() - .map(|qubit| Self::index_into_qubit_register((**qubit).clone(), index)); + .map(|qubit| Self::index_into_qubit_register(qubit, index)); let qubits = list_from_iter(qubits); @@ -2535,33 +2560,35 @@ impl Lowerer { // by all the QASM semantic analysis. } - fn index_into_qubit_register(op: semantic::GateOperand, index: u32) -> semantic::GateOperand { - let index = semantic::Index::Expr(semantic::Expr::new( - op.span, - semantic::ExprKind::Lit(semantic::LiteralKind::Int(index.into())), - Type::UInt(None, true), - )); - - match op.kind { + fn index_into_qubit_register(op: &semantic::GateOperand, index: u32) -> semantic::GateOperand { + match &op.kind { semantic::GateOperandKind::Expr(expr) => { // Single qubits are allowed in a broadcast call. match &expr.ty { Type::Qubit => semantic::GateOperand { span: op.span, - kind: semantic::GateOperandKind::Expr(expr), + kind: semantic::GateOperandKind::Expr(expr.clone()), }, - Type::QubitArray(..) => semantic::GateOperand { - span: op.span, - kind: semantic::GateOperandKind::Expr(Box::new(semantic::Expr::new( + Type::QubitArray(..) => { + let index = semantic::Index::Expr(semantic::Expr::new( op.span, - semantic::ExprKind::IndexedExpr(semantic::IndexedExpr { - span: op.span, - collection: expr, - index: Box::new(index), - }), - Type::Qubit, - ))), - }, + semantic::ExprKind::Lit(semantic::LiteralKind::Int(index.into())), + Type::UInt(None, true), + )); + + semantic::GateOperand { + span: op.span, + kind: semantic::GateOperandKind::Expr(Box::new(semantic::Expr::new( + op.span, + semantic::ExprKind::IndexedExpr(semantic::IndexedExpr { + span: op.span, + collection: expr.clone(), + index: Box::new(index), + }), + Type::Qubit, + ))), + } + } _ => unreachable!("we set register_type iff we find a QubitArray"), } } @@ -2742,7 +2769,7 @@ impl Lowerer { let measure = self.lower_measure_expr(&stmt.measurement); semantic::StmtKind::ExprStmt(semantic::ExprStmt { span: stmt.span, - expr: measure, + expr: Box::new(measure), }) } } @@ -2892,7 +2919,7 @@ impl Lowerer { semantic::StmtKind::QubitArrayDecl(semantic::QubitArrayDeclaration { span: stmt.span, symbol_id, - size, + size: Box::new(size), size_span, }) } else { @@ -3005,7 +3032,7 @@ impl Lowerer { semantic::StmtKind::Switch(semantic::SwitchStmt { span: stmt.span, - target, + target: Box::new(target), cases: list_from_iter(cases), default, }) @@ -3052,7 +3079,7 @@ impl Lowerer { semantic::StmtKind::WhileLoop(semantic::WhileLoop { span: stmt.span, - condition: while_condition, + condition: Box::new(while_condition), body, }) } @@ -4247,8 +4274,8 @@ impl Lowerer { } let bin_expr = semantic::BinaryOpExpr { op: op.into(), - lhs, - rhs, + lhs: Box::new(lhs), + rhs: Box::new(rhs), }; let kind = semantic::ExprKind::BinaryOp(bin_expr); let expr = semantic::Expr::new(span, kind, target_ty); @@ -4265,8 +4292,8 @@ impl Lowerer { }; let bin_expr = semantic::BinaryOpExpr { - lhs: new_lhs, - rhs: new_rhs, + lhs: Box::new(new_lhs), + rhs: Box::new(new_rhs), op: op.into(), }; let kind = semantic::ExprKind::BinaryOp(bin_expr); @@ -4384,8 +4411,8 @@ impl Lowerer { if is_complex_binop_supported(op) { let bin_expr = semantic::BinaryOpExpr { op: op.into(), - lhs, - rhs, + lhs: Box::new(lhs), + rhs: Box::new(rhs), }; let kind = semantic::ExprKind::BinaryOp(bin_expr); semantic::Expr::new(span, kind, ty.clone()) @@ -4398,8 +4425,8 @@ impl Lowerer { } else { let bin_expr = semantic::BinaryOpExpr { op: op.into(), - lhs, - rhs, + lhs: Box::new(lhs), + rhs: Box::new(rhs), }; let kind = semantic::ExprKind::BinaryOp(bin_expr); semantic::Expr::new(span, kind, ty.clone()) @@ -4503,8 +4530,8 @@ impl Lowerer { let bin_expr = semantic::BinaryOpExpr { op: op.into(), - lhs, - rhs, + lhs: Box::new(lhs), + rhs: Box::new(rhs), }; let kind = semantic::ExprKind::BinaryOp(bin_expr); let expr = semantic::Expr::new(span, kind, ty); @@ -4561,7 +4588,7 @@ impl Lowerer { let indices: Vec<_> = list .values .iter() - .filter_map(|index| match &**index { + .filter_map(|index| match index { syntax::IndexListItem::RangeDefinition(range) => self .lower_const_range(range) .map(|range| semantic::Index::Range(range.into())), @@ -4806,7 +4833,7 @@ impl Lowerer { != indexed_ident .indices .iter() - .map(|i| i.num_indices()) + .map(syntax::Index::num_indices) .sum::() { // Since we can't evaluate all the indices, we can't know the indexed type. @@ -5095,7 +5122,7 @@ fn wrap_expr_in_cast_expr(ty: Type, rhs: semantic::Expr) -> semantic::Expr { rhs.span, semantic::ExprKind::Cast(semantic::Cast { span: Span::default(), - expr: rhs, + expr: Box::new(rhs), ty: ty.clone(), kind: semantic::CastKind::Implicit, ty_exprs: list_from_iter(vec![]), diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/mut_visit.rs b/source/qdk_openqasm_parser/src/semantic/mut_visit.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/mut_visit.rs rename to source/qdk_openqasm_parser/src/semantic/mut_visit.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/passes.rs b/source/qdk_openqasm_parser/src/semantic/passes.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/passes.rs rename to source/qdk_openqasm_parser/src/semantic/passes.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/resources/openqasm_lowerer_errors_test.qasm b/source/qdk_openqasm_parser/src/semantic/resources/openqasm_lowerer_errors_test.qasm similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/resources/openqasm_lowerer_errors_test.qasm rename to source/qdk_openqasm_parser/src/semantic/resources/openqasm_lowerer_errors_test.qasm diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/symbols.rs b/source/qdk_openqasm_parser/src/semantic/symbols.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/symbols.rs rename to source/qdk_openqasm_parser/src/semantic/symbols.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests.rs b/source/qdk_openqasm_parser/src/semantic/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests.rs rename to source/qdk_openqasm_parser/src/semantic/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/assignment.rs b/source/qdk_openqasm_parser/src/semantic/tests/assignment.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/assignment.rs rename to source/qdk_openqasm_parser/src/semantic/tests/assignment.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/assignment/dyn_array_ref.rs b/source/qdk_openqasm_parser/src/semantic/tests/assignment/dyn_array_ref.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/assignment/dyn_array_ref.rs rename to source/qdk_openqasm_parser/src/semantic/tests/assignment/dyn_array_ref.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/assignment/static_array_ref.rs b/source/qdk_openqasm_parser/src/semantic/tests/assignment/static_array_ref.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/assignment/static_array_ref.rs rename to source/qdk_openqasm_parser/src/semantic/tests/assignment/static_array_ref.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/alias.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/alias.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/alias.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/alias.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/angle.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/angle.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/angle.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/angle.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/bit.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/bit.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/bit.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/bit.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/bool.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/bool.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/bool.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/bool.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/complex.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/complex.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/complex.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/complex.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/creg.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/creg.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/creg.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/creg.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/duration.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/duration.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/duration.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/duration.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/extern_decl.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/extern_decl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/extern_decl.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/extern_decl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/float.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/float.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/float.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/float.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/int.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/int.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/int.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/int.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/qreg.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/qreg.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/qreg.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/qreg.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/qubit.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/qubit.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/qubit.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/qubit.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/stretch.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/stretch.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/stretch.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/stretch.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/uint.rs b/source/qdk_openqasm_parser/src/semantic/tests/decls/uint.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/decls/uint.rs rename to source/qdk_openqasm_parser/src/semantic/tests/decls/uint.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation/array.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation/array.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation/array.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation/array.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation/dyn_array_ref.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation/dyn_array_ref.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation/dyn_array_ref.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation/dyn_array_ref.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation/static_array_ref.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation/static_array_ref.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/array_concatenation/static_array_ref.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/array_concatenation/static_array_ref.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/arithmetic_conversions.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/arithmetic_conversions.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/arithmetic_conversions.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/arithmetic_conversions.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/bit_to_bit.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/bool_to_bool.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/float_to_float.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/float_to_float.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/float_to_float.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/float_to_float.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/int_to_int.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/int_to_int.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/int_to_int.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/int_to_int.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/comparison/uint_to_uint.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/complex.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/complex.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/complex.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/complex.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/duration.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/duration.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/duration.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/duration.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/ident.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/binary/ident.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/binary/ident.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/binary/ident.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/arccos.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/arccos.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/arccos.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/arccos.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/arcsin.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/arcsin.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/arcsin.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/arcsin.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/arctan.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/arctan.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/arctan.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/arctan.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/ceiling.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/ceiling.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/ceiling.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/ceiling.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/cos.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/cos.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/cos.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/cos.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/exp.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/exp.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/exp.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/exp.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/floor.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/floor.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/floor.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/floor.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/log.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/log.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/log.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/log.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/mod_.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/mod_.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/mod_.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/mod_.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/popcount.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/popcount.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/popcount.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/popcount.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/pow.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/pow.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/pow.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/pow.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotl.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotl.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotl.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotl.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotr.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotr.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotr.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/rotr.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/sin.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/sin.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/sin.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/sin.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/sizeof.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/sizeof.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/sizeof.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/sizeof.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/sqrt.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/sqrt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/sqrt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/sqrt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/tan.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/tan.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/builtin_functions/tan.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/builtin_functions/tan.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_angle.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_angle.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_angle.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_angle.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bit.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bit.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bit.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bit.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bool.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bool.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bool.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_bool.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_complex.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_complex.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_complex.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_complex.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_duration.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_duration.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_duration.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_duration.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_float.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_float.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_float.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_float.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_int.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_int.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_int.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_int.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_stretch.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_stretch.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_stretch.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_stretch.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_uint.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_uint.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_uint.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/explicit_cast_from_uint.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_angle.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_angle.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_angle.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_angle.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bit.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bit.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bit.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bit.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bitarray.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bitarray.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bitarray.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bitarray.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bool.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bool.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bool.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_bool.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_float.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_float.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_float.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_float.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_int.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_int.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_int.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/implicit_cast_from_int.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/indexing.rs b/source/qdk_openqasm_parser/src/semantic/tests/expression/indexing.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/expression/indexing.rs rename to source/qdk_openqasm_parser/src/semantic/tests/expression/indexing.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/lowerer_errors.rs b/source/qdk_openqasm_parser/src/semantic/tests/lowerer_errors.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/lowerer_errors.rs rename to source/qdk_openqasm_parser/src/semantic/tests/lowerer_errors.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/box_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/box_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/box_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/box_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/break_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/break_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/break_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/break_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/continue_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/continue_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/continue_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/continue_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/delay_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/delay_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/delay_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/delay_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/for_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/for_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/for_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/for_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/if_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/if_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/if_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/if_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/reset_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/reset_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/reset_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/reset_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/switch_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/switch_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/switch_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/switch_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/while_stmt.rs b/source/qdk_openqasm_parser/src/semantic/tests/statements/while_stmt.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/tests/statements/while_stmt.rs rename to source/qdk_openqasm_parser/src/semantic/tests/statements/while_stmt.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/types.rs b/source/qdk_openqasm_parser/src/semantic/types.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/types.rs rename to source/qdk_openqasm_parser/src/semantic/types.rs diff --git a/source/compiler/qsc_openqasm_parser/src/semantic/visit.rs b/source/qdk_openqasm_parser/src/semantic/visit.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/semantic/visit.rs rename to source/qdk_openqasm_parser/src/semantic/visit.rs diff --git a/source/compiler/qsc_openqasm_parser/src/stdlib.rs b/source/qdk_openqasm_parser/src/stdlib.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/stdlib.rs rename to source/qdk_openqasm_parser/src/stdlib.rs diff --git a/source/compiler/qsc_openqasm_parser/src/stdlib/angle.rs b/source/qdk_openqasm_parser/src/stdlib/angle.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/stdlib/angle.rs rename to source/qdk_openqasm_parser/src/stdlib/angle.rs diff --git a/source/compiler/qsc_openqasm_parser/src/stdlib/angle/tests.rs b/source/qdk_openqasm_parser/src/stdlib/angle/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/stdlib/angle/tests.rs rename to source/qdk_openqasm_parser/src/stdlib/angle/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/stdlib/builtin_functions.rs b/source/qdk_openqasm_parser/src/stdlib/builtin_functions.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/stdlib/builtin_functions.rs rename to source/qdk_openqasm_parser/src/stdlib/builtin_functions.rs diff --git a/source/compiler/qsc_openqasm_parser/src/stdlib/complex.rs b/source/qdk_openqasm_parser/src/stdlib/complex.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/stdlib/complex.rs rename to source/qdk_openqasm_parser/src/stdlib/complex.rs diff --git a/source/compiler/qsc_openqasm_parser/src/stdlib/duration.rs b/source/qdk_openqasm_parser/src/stdlib/duration.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/stdlib/duration.rs rename to source/qdk_openqasm_parser/src/stdlib/duration.rs diff --git a/source/compiler/qsc_openqasm_parser/src/stdlib/duration/tests.rs b/source/qdk_openqasm_parser/src/stdlib/duration/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/stdlib/duration/tests.rs rename to source/qdk_openqasm_parser/src/stdlib/duration/tests.rs diff --git a/source/compiler/qsc_openqasm_parser/src/tests.rs b/source/qdk_openqasm_parser/src/tests.rs similarity index 100% rename from source/compiler/qsc_openqasm_parser/src/tests.rs rename to source/qdk_openqasm_parser/src/tests.rs diff --git a/source/qdk_openqasm_parser/src/vendor.rs b/source/qdk_openqasm_parser/src/vendor.rs new file mode 100644 index 00000000000..a997b07c43b --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor.rs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored copies of the shared data-structure types used by the parser. +//! +//! These are minimal copies of the corresponding items from the in-repo +//! `qsc_data_structures` (and `index_map`) crates. They are only compiled when +//! the `internal` feature is disabled, allowing the parser crate to build +//! standalone without the rest of the compiler workspace. + +pub mod display; +pub mod error; +pub mod index_map; +pub mod source; +pub mod span; diff --git a/source/qdk_openqasm_parser/src/vendor/display.rs b/source/qdk_openqasm_parser/src/vendor/display.rs new file mode 100644 index 00000000000..8e5067fa3db --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor/display.rs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored from `qsc_data_structures::display`. + +pub mod core; + +use crate::span::Span; +use core::{set_indentation, with_indentation, write_list}; +use std::fmt::{self, Display, Formatter, Write}; + +/// Displays values separated by the provided string. +pub fn join( + f: &mut Formatter, + mut vals: impl Iterator, + sep: &str, +) -> fmt::Result { + if let Some(v) = vals.next() { + v.fmt(f)?; + } + for v in vals { + write!(f, "{sep}")?; + v.fmt(f)?; + } + Ok(()) +} + +/// Writes a list of elements to the given buffer or stream +/// with an additional indentation level. +pub fn write_indented_list<'write, 'itemref, 'item, T, I>( + f: &'write mut impl Write, + vals: I, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + let mut iter = vals.into_iter().peekable(); + if iter.peek().is_none() { + write!(f, " ") + } else { + let mut indent = with_indentation(f); + for elt in iter { + write!(indent, "\n{elt}")?; + } + Ok(()) + } +} + +/// Writes the name and span of a structure to the given buffer or stream. +pub fn write_header_with_span(f: &mut impl Write, name: &str, span: Span) -> fmt::Result { + write!(f, "{name} {span}:") +} + +/// Writes the name and span of a structure to the given buffer or stream. +/// Inserts a newline afterwards. +pub fn writeln_header_with_span(f: &mut impl Write, name: &str, span: Span) -> fmt::Result { + writeln!(f, "{name} {span}:") +} + +/// Writes the name of a structure to the given buffer or stream. +pub fn write_header(f: &mut impl Write, name: &str) -> fmt::Result { + write!(f, "{name}:") +} + +/// Writes the name of a structure to the given buffer or stream. +/// Inserts a newline afterwards. +pub fn writeln_header(f: &mut impl Write, name: &str) -> fmt::Result { + writeln!(f, "{name}:") +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indentation level. +pub fn write_field(f: &mut impl Write, field_name: &str, val: &T) -> fmt::Result { + let mut indent = with_indentation(f); + write!(indent, "{field_name}: {val}") +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indentation level. +/// Inserts a newline afterwards. +pub fn writeln_field(f: &mut impl Write, field_name: &str, val: &T) -> fmt::Result { + write_field(f, field_name, val)?; + writeln!(f) +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indentation level. +pub fn write_opt_field( + f: &mut impl Write, + field_name: &str, + opt_val: Option<&T>, +) -> fmt::Result { + if let Some(val) = opt_val { + write_field(f, field_name, val) + } else { + write_field(f, field_name, &"") + } +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indentation level. +/// Inserts a newline afterwards. +pub fn writeln_opt_field( + f: &mut impl Write, + field_name: &str, + opt_val: Option<&T>, +) -> fmt::Result { + write_opt_field(f, field_name, opt_val)?; + writeln!(f) +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indentation level. +/// The field must be an iterable. +pub fn write_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + vals: I, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + let mut indent = with_indentation(f); + write!(indent, "{field_name}:")?; + let mut indent = set_indentation(indent, 2); + write_list(&mut indent, vals) +} + +/// Writes a field of a structure to the given buffer +/// or stream with an additional indentation level. +/// The field must be an iterable. +/// Inserts a newline afterwards. +pub fn writeln_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + vals: I, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + write_list_field(f, field_name, vals)?; + writeln!(f) +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indentation level. +/// The field must be an iterable. +pub fn write_opt_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + opt_vals: Option, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + if let Some(vals) = opt_vals { + write_list_field(f, field_name, vals) + } else { + let mut indent = with_indentation(f); + write!(indent, "{field_name}: ") + } +} + +/// Writes an optional field of a structure to the given buffer +/// or stream with an additional indentation level. +/// The field must be an iterable. +/// Inserts a newline afterwards. +pub fn writeln_opt_list_field<'write, 'itemref, 'item, T, I>( + f: &mut impl Write, + field_name: &str, + opt_vals: Option, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + write_opt_list_field(f, field_name, opt_vals)?; + writeln!(f) +} diff --git a/source/qdk_openqasm_parser/src/vendor/display/core.rs b/source/qdk_openqasm_parser/src/vendor/display/core.rs new file mode 100644 index 00000000000..716ca52d927 --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor/display/core.rs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored from `qsc_data_structures::display::core`. +//! +//! Low level printing primitives used by the vendored `display` module. + +use std::fmt::{self, Display, Write}; + +/// Takes a unicode buffer or stream and wraps it with +/// `indenter::Indented`. Which applies an indentation of 1 +/// each time you insert a new line. +pub(super) fn with_indentation(f: &mut T) -> indenter::Indented<'_, T> +where + T: fmt::Write, +{ + let indent = indenter::indented(f); + set_indentation(indent, 1) +} + +/// Takes an `indenter::Indented` and changes its indentation level. +/// +/// Note: This function is a very low level primitive. It's only +/// public to mantain backwards compatibility with existing code. +/// Prefer using the functions in [`crate::display`] instead. +#[must_use] +pub fn set_indentation( + indent: indenter::Indented<'_, T>, + level: usize, +) -> indenter::Indented<'_, T> +where + T: fmt::Write, +{ + match level { + 0 => indent.with_str(""), + 1 => indent.with_str(" "), + 2 => indent.with_str(" "), + 3 => indent.with_str(" "), + _ => unimplemented!("indentation level not supported"), + } +} + +/// Writes a list of elements to the given buffer or stream. +pub(super) fn write_list<'write, 'itemref, 'item, T, I>( + f: &'write mut impl Write, + vals: I, +) -> fmt::Result +where + 'item: 'itemref, + T: Display + 'item, + I: IntoIterator, +{ + let mut iter = vals.into_iter().peekable(); + if iter.peek().is_none() { + write!(f, " ") + } else { + for elt in iter { + write!(f, "\n{elt}")?; + } + Ok(()) + } +} diff --git a/source/qdk_openqasm_parser/src/vendor/error.rs b/source/qdk_openqasm_parser/src/vendor/error.rs new file mode 100644 index 00000000000..e9f9e32b9d3 --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor/error.rs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored from `qsc_data_structures::error`. + +#[cfg(test)] +mod tests; + +use crate::vendor::source::{Source, SourceMap}; +use miette::{Diagnostic, MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents}; +use std::{ + error::Error, + fmt::{self, Debug, Display, Formatter}, +}; + +#[derive(Clone, Debug)] +pub struct WithSource { + sources: Vec, + error: E, +} + +impl WithSource { + pub fn error(&self) -> &E { + &self.error + } + + pub fn into_error(self) -> E { + self.error + } + + /// Construct a diagnostic with source information from a source map. + /// Since errors may contain labeled spans from any source file in the + /// compilation, the entire source map is needed to resolve offsets. + pub fn from_map(sources: &SourceMap, error: E) -> Self { + // Filter the source map to the relevant sources + // to avoid cloning all of them. + let mut filtered = Vec::::new(); + + for offset in error + .labels() + .into_iter() + .flatten() + .map(|label| u32::try_from(label.offset()).expect("offset should fit into u32")) + { + let source = sources + .find_by_offset(offset) + .expect("expected to find source at offset"); + + // Keep the vector sorted by source offsets + match filtered.binary_search_by_key(&source.offset, |s| s.offset) { + Ok(_) => {} // source already in vector + Err(pos) => filtered.insert(pos, source.clone()), + } + } + + Self { + sources: filtered, + error, + } + } + + pub fn into_with_source(self) -> WithSource + where + T: From, + { + WithSource { + sources: self.sources, + error: self.error.into(), + } + } + + /// Takes a span that uses `SourceMap` offsets, and returns + /// a span that is relative to the `Source` that the span falls into, + /// along with a reference to the `Source`. + pub fn resolve_span(&self, span: &SourceSpan) -> (&Source, SourceSpan) { + let offset = u32::try_from(span.offset()).expect("expected the offset to fit into u32"); + let source = self + .sources + .iter() + .rev() + .find(|source| offset >= source.offset) + .expect("expected to find source at span"); + (source, with_offset(span, |o| o - (source.offset as usize))) + } + + /// Like [`resolve_span`](Self::resolve_span), but returns `None` when no + /// source in the map contains the span's offset, instead of panicking. + #[must_use] + pub fn try_resolve_span(&self, span: &SourceSpan) -> Option<(&Source, SourceSpan)> { + let offset = u32::try_from(span.offset()).ok()?; + let source = self + .sources + .iter() + .rev() + .find(|source| offset >= source.offset)?; + Some((source, with_offset(span, |o| o - (source.offset as usize)))) + } +} + +impl Error for WithSource { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.error.source() + } +} + +impl Diagnostic for WithSource { + fn code<'a>(&'a self) -> Option> { + self.error.code() + } + + fn severity(&self) -> Option { + self.error.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.error.help() + } + + fn url<'a>(&'a self) -> Option> { + self.error.url() + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(self) + } + + fn labels(&self) -> Option + '_>> { + self.error.labels() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.error.related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.error.diagnostic_source() + } +} + +impl Display for WithSource { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + std::fmt::Display::fmt(&self.error, f) + } +} + +impl SourceCode for WithSource { + fn read_span<'a>( + &'a self, + span: &SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> Result + 'a>, MietteError> { + let (source, source_relative_span) = self.resolve_span(span); + + let contents = source.contents.read_span( + &source_relative_span, + context_lines_before, + context_lines_after, + )?; + + Ok(Box::new(MietteSpanContents::new_named( + source.name.to_string(), + contents.data(), + with_offset(contents.span(), |o| o + (source.offset as usize)), + contents.line(), + contents.column(), + contents.line_count(), + ))) + } +} + +fn with_offset(span: &SourceSpan, f: impl FnOnce(usize) -> usize) -> SourceSpan { + SourceSpan::new(f(span.offset()).into(), span.len()) +} diff --git a/source/qdk_openqasm_parser/src/vendor/error/tests.rs b/source/qdk_openqasm_parser/src/vendor/error/tests.rs new file mode 100644 index 00000000000..b7279b49b7e --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor/error/tests.rs @@ -0,0 +1,198 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored from `qsc_data_structures::error::tests`. + +use super::WithSource; +use crate::vendor::source::SourceMap; +use crate::vendor::span::Span; +use expect_test::expect; +use miette::Diagnostic; +use std::{error::Error, fmt::Write, iter, str::from_utf8}; +use thiserror::Error; + +#[derive(Clone, Debug, Diagnostic, Error)] +enum TestError { + #[error("Error: {0}")] + #[diagnostic(code("Qsc.Test.Error.NoSpans"))] + NoSpans(String), + #[error("Error: {0}")] + #[diagnostic(code("Qsc.Test.Error.TwoSpans"))] + TwoSpans( + String, + #[label("first label")] Span, + #[label("second label")] Span, + ), +} + +#[test] +fn no_files() { + let sources = SourceMap::default(); + let error = TestError::NoSpans("value".into()); + let formatted_error = format_error(&WithSource::from_map(&sources, error)); + + expect![[r#" + Error: value + "#]] + .assert_eq(&formatted_error); +} + +#[test] +fn error_spans_two_files() { + let test1_contents = "namespace Foo {}"; + let test2_contents = "namespace Bar {}"; + let mut sources = SourceMap::default(); + let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); + let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); + + let error = TestError::TwoSpans( + "value".into(), + span_with_offset(test1_offset, 10, 13), + span_with_offset(test2_offset, 10, 13), + ); + + let formatted_error = format_error(&WithSource::from_map(&sources, error)); + + expect![[r#" + Error: value + [first label] [test1.qs] [Foo] + [second label] [test2.qs] [Bar] + "#]] + .assert_eq(&formatted_error); +} + +#[test] +fn error_spans_begin() { + let test1_contents = "namespace Foo {}"; + let test2_contents = "namespace Bar {}"; + let mut sources = SourceMap::default(); + let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); + let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); + + let error = TestError::TwoSpans( + "value".into(), + span_with_offset(test1_offset, 0, 13), + span_with_offset(test2_offset, 0, 13), + ); + + let formatted_error = format_error(&WithSource::from_map(&sources, error)); + + expect![[r#" + Error: value + [first label] [test1.qs] [namespace Foo] + [second label] [test2.qs] [namespace Bar] + "#]] + .assert_eq(&formatted_error); +} + +#[allow(clippy::cast_possible_truncation)] +#[test] +fn error_spans_eof() { + let test1_contents = "namespace Foo {}"; + let test2_contents = "namespace Bar {}"; + let mut sources = SourceMap::default(); + let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); + let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); + + let error = TestError::TwoSpans( + "value".into(), + span_with_offset( + test1_offset, + test1_contents.len() as u32, + test1_contents.len() as u32, + ), + span_with_offset( + test2_offset, + test2_contents.len() as u32, + test2_contents.len() as u32, + ), + ); + + let formatted_error = format_error(&WithSource::from_map(&sources, error)); + + expect![[r#" + Error: value + [first label] [test1.qs] [] + [second label] [test2.qs] [] + "#]] + .assert_eq(&formatted_error); +} + +#[test] +fn resolve_spans() { + let test1_contents = "namespace Foo {}"; + let test2_contents = "namespace Bar {}"; + let mut sources = SourceMap::default(); + let test1_offset = sources.push("test1.qs".into(), test1_contents.into()); + let test2_offset = sources.push("test2.qs".into(), test2_contents.into()); + + let error = TestError::TwoSpans( + "value".into(), + span_with_offset(test1_offset, 10, 13), + span_with_offset(test2_offset, 10, 13), + ); + + let with_source = WithSource::from_map(&sources, error); + + let resolved_spans = with_source + .labels() + .expect("expected labels to exist") + .map(|l| { + let resolved = with_source.resolve_span(l.inner()); + ( + resolved.0.name.to_string(), + resolved.1.offset(), + resolved.1.len(), + ) + }) + .collect::>(); + + expect![[r#" + [ + ( + "test1.qs", + 10, + 3, + ), + ( + "test2.qs", + 10, + 3, + ), + ] + "#]] + .assert_debug_eq(&resolved_spans); +} + +fn span_with_offset(offset: u32, lo: u32, hi: u32) -> Span { + Span { + lo: lo + offset, + hi: hi + offset, + } +} + +fn format_error(error: &WithSource) -> String { + let mut s = String::new(); + write!(s, "{error}").expect("writing should succeed"); + for e in iter::successors(error.source(), |&e| e.source()) { + write!(s, ": {e}").expect("writing should succeed"); + } + for label in error.labels().into_iter().flatten() { + let span = error + .source_code() + .expect("expected valid source code") + .read_span(label.inner(), 0, 0) + .expect("expected to be able to read span"); + + write!( + s, + "\n [{}] [{}] [{}]", + label.label().unwrap_or(""), + span.name().expect("expected source file name"), + from_utf8(span.data()).expect("expected valid utf-8 string"), + ) + .expect("writing should succeed"); + } + writeln!(s).expect("writing should succeed"); + s +} diff --git a/source/qdk_openqasm_parser/src/vendor/index_map.rs b/source/qdk_openqasm_parser/src/vendor/index_map.rs new file mode 100644 index 00000000000..4a0eaecb5fc --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor/index_map.rs @@ -0,0 +1,343 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored from the `index_map` crate. + +use std::{ + fmt::{self, Debug, Formatter}, + iter::Enumerate, + marker::PhantomData, + ops::{Index, IndexMut}, + option::Option, + slice, vec, +}; + +pub struct IndexMap { + _keys: PhantomData, + values: Vec>, +} + +impl IndexMap +where + K: Into, + V: Default, +{ + pub fn get_mut_or_default(&mut self, key: K) -> &mut V { + let index: usize = key.into(); + if index >= self.values.len() { + self.values.resize_with(index + 1, Option::default); + } + self.values + .get_mut(index) + .expect("IndexMap::get_mut_or_default: index out of bounds") + .get_or_insert_with(Default::default) + } +} + +impl IndexMap { + #[must_use] + pub fn new() -> Self { + Self::default() + } + + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + _keys: PhantomData, + values: Vec::with_capacity(capacity), + } + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.values.iter().all(Option::is_none) + } + + // `Iter` does implement `Iterator`, but it has an additional bound on `K`. + #[allow(clippy::iter_not_returning_iterator)] + #[must_use] + pub fn iter(&self) -> Iter<'_, K, V> { + Iter { + _keys: PhantomData, + base: self.values.iter().enumerate(), + } + } + + // `Iter` does implement `Iterator`, but it has an additional bound on `K`. + #[allow(clippy::iter_not_returning_iterator)] + pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { + IterMut { + _keys: PhantomData, + base: self.values.iter_mut().enumerate(), + } + } + + pub fn drain(&mut self) -> Drain<'_, K, V> { + Drain { + _keys: PhantomData, + base: self.values.drain(..).enumerate(), + } + } + + #[must_use] + pub fn values(&self) -> Values<'_, V> { + Values { + base: self.values.iter(), + } + } + + pub fn values_mut(&mut self) -> ValuesMut<'_, V> { + ValuesMut { + base: self.values.iter_mut(), + } + } + + pub fn retain(&mut self, mut f: F) + where + F: FnMut(K, &V) -> bool, + K: From, + { + for (k, v) in self.values.iter_mut().enumerate() { + let remove = if let Some(value) = v { + !f(K::from(k), value) + } else { + false + }; + if remove { + *v = None; + } + } + } + + pub fn clear(&mut self) { + self.values.clear(); + } +} + +impl, V> IndexMap { + pub fn insert(&mut self, key: K, value: V) { + let index = key.into(); + if index >= self.values.len() { + self.values.resize_with(index + 1, || None); + } + self.values[index] = Some(value); + } + + pub fn contains_key(&self, key: K) -> bool { + let index: usize = key.into(); + self.values.get(index).is_some_and(Option::is_some) + } + + pub fn get(&self, key: K) -> Option<&V> { + let index: usize = key.into(); + self.values.get(index).and_then(Option::as_ref) + } + + pub fn get_mut(&mut self, key: K) -> Option<&mut V> { + let index: usize = key.into(); + self.values.get_mut(index).and_then(Option::as_mut) + } + + pub fn remove(&mut self, key: K) { + let index: usize = key.into(); + if index < self.values.len() { + self.values[index] = None; + } + } +} + +impl Clone for IndexMap { + fn clone(&self) -> Self { + Self { + _keys: PhantomData, + values: self.values.clone(), + } + } +} + +impl Debug for IndexMap { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.debug_struct("IndexMap") + .field( + "values", + &self + .values + .iter() + .enumerate() + .filter_map(|(k, v)| v.as_ref().map(|val| format!("{k:?}: {val:?}"))) + .collect::>(), + ) + .finish() + } +} + +impl Default for IndexMap { + fn default() -> Self { + Self { + _keys: PhantomData, + values: Vec::default(), + } + } +} + +impl, V> IntoIterator for IndexMap { + type Item = (K, V); + + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + _keys: PhantomData, + base: self.values.into_iter().enumerate(), + } + } +} + +impl<'a, K: From, V> IntoIterator for &'a IndexMap { + type Item = (K, &'a V); + + type IntoIter = Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl, V> FromIterator<(K, V)> for IndexMap { + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter(); + let mut map = Self::new(); + let (lo, hi) = iter.size_hint(); + map.values.reserve(hi.unwrap_or(lo)); + for (key, value) in iter { + map.insert(key, value); + } + map + } +} + +pub struct Iter<'a, K, V> { + _keys: PhantomData, + base: Enumerate>>, +} + +impl<'a, K: From, V> Iterator for Iter<'a, K, V> { + type Item = (K, &'a V); + + fn next(&mut self) -> Option { + loop { + if let (index, Some(value)) = self.base.next()? { + break Some((index.into(), value)); + } + } + } +} + +pub struct IterMut<'a, K, V> { + _keys: PhantomData, + base: Enumerate>>, +} + +impl, V> DoubleEndedIterator for Iter<'_, K, V> { + fn next_back(&mut self) -> Option { + loop { + if let (index, Some(value)) = self.base.next_back()? { + break Some((index.into(), value)); + } + } + } +} + +impl<'a, K: From, V> Iterator for IterMut<'a, K, V> { + type Item = (K, &'a mut V); + + fn next(&mut self) -> Option { + loop { + if let (index, Some(value)) = self.base.next()? { + break Some((index.into(), value)); + } + } + } +} + +pub struct IntoIter { + _keys: PhantomData, + base: Enumerate>>, +} + +impl, V> Iterator for IntoIter { + type Item = (K, V); + + fn next(&mut self) -> Option { + loop { + if let (index, Some(value)) = self.base.next()? { + break Some((index.into(), value)); + } + } + } +} + +pub struct Drain<'a, K, V> { + _keys: PhantomData, + base: Enumerate>>, +} + +impl, V> Iterator for Drain<'_, K, V> { + type Item = (K, V); + + fn next(&mut self) -> Option { + loop { + if let (index, Some(value)) = self.base.next()? { + break Some((index.into(), value)); + } + } + } +} + +pub struct Values<'a, V> { + base: slice::Iter<'a, Option>, +} + +impl<'a, V> Iterator for Values<'a, V> { + type Item = &'a V; + + fn next(&mut self) -> Option { + loop { + if let Some(value) = self.base.next()? { + break Some(value); + } + } + } +} + +pub struct ValuesMut<'a, V> { + base: slice::IterMut<'a, Option>, +} + +impl<'a, V> Iterator for ValuesMut<'a, V> { + type Item = &'a mut V; + + fn next(&mut self) -> Option { + loop { + if let Some(value) = self.base.next()? { + break Some(value); + } + } + } +} + +impl Index for IndexMap { + type Output = usize; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index) + .expect("IndexMap::index: index out of bounds") + } +} + +impl IndexMut for IndexMap { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index) + .expect("IndexMap::index_mut: index out of bounds") + } +} diff --git a/source/qdk_openqasm_parser/src/vendor/source.rs b/source/qdk_openqasm_parser/src/vendor/source.rs new file mode 100644 index 00000000000..c960c07b8ac --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor/source.rs @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored from `qsc_data_structures::source`. + +use std::sync::Arc; + +#[derive(Clone, Debug, Default)] +pub struct SourceMap { + sources: Vec, + /// The common prefix of the sources + /// e.g. if the sources all start with `/Users/microsoft/code/qsharp/src`, then this value is + /// `/Users/microsoft/code/qsharp/src`. + common_prefix: Option>, + entry: Option, +} + +impl SourceMap { + pub fn new( + sources: impl IntoIterator, + entry: Option>, + ) -> Self { + let mut offset_sources = Vec::new(); + + let entry_source = entry.map(|contents| Source { + name: "".into(), + contents, + offset: 0, + }); + + let mut offset = next_offset(entry_source.as_ref()); + for (name, contents) in sources { + let source = Source { + name, + contents, + offset, + }; + offset = next_offset(Some(&source)); + offset_sources.push(source); + } + + // Each source has a name, which is a string. The project root dir is calculated as the + // common prefix of all of the sources. + // Calculate the common prefix. + let common_prefix: String = longest_common_prefix( + &offset_sources + .iter() + .map(|source| source.name.as_ref()) + .collect::>(), + ) + .to_string(); + + let common_prefix: Arc = Arc::from(common_prefix); + + Self { + sources: offset_sources, + common_prefix: if common_prefix.is_empty() { + None + } else { + Some(common_prefix) + }, + entry: entry_source, + } + } + + #[must_use] + pub fn entry(&self) -> Option<&Source> { + self.entry.as_ref() + } + + pub fn push(&mut self, name: SourceName, contents: SourceContents) -> u32 { + let offset = next_offset(self.sources.last()); + + self.sources.push(Source { + name, + contents, + offset, + }); + + offset + } + + #[must_use] + pub fn find_by_offset(&self, offset: u32) -> Option<&Source> { + self.sources + .iter() + .rev() + .chain(&self.entry) + .find(|source| offset >= source.offset) + } + + #[must_use] + pub fn find_by_name(&self, name: &str) -> Option<&Source> { + self.sources.iter().find(|s| s.name.as_ref() == name) + } + + pub fn iter(&self) -> impl Iterator { + self.sources.iter() + } + + /// Returns the sources as an iter, but with the project root directory subtracted + /// from the individual source names. + pub fn relative_sources(&self) -> impl Iterator + '_ { + self.sources.iter().map(move |source| { + let name = source.name.as_ref(); + let relative_name = self.relative_name(name); + + Source { + name: relative_name.into(), + contents: source.contents.clone(), + offset: source.offset, + } + }) + } + + #[must_use] + pub fn relative_name<'a>(&'a self, name: &'a str) -> &'a str { + if let Some(common_prefix) = &self.common_prefix { + name.strip_prefix(common_prefix.as_ref()).unwrap_or(name) + } else { + name + } + } +} + +#[derive(Clone, Debug)] +pub struct Source { + pub name: SourceName, + pub contents: SourceContents, + pub offset: u32, +} + +pub type SourceName = Arc; + +pub type SourceContents = Arc; + +#[must_use] +pub fn longest_common_prefix<'a>(strs: &'a [&'a str]) -> &'a str { + if strs.len() == 1 { + return truncate_to_path_separator(strs[0]); + } + + let Some(common_prefix_so_far) = strs.first() else { + return ""; + }; + + for (i, character) in common_prefix_so_far.char_indices() { + for string in strs { + if string.chars().nth(i) != Some(character) { + let prefix = &common_prefix_so_far[0..i]; + // Find the last occurrence of the path separator in the prefix + return truncate_to_path_separator(prefix); + } + } + } + common_prefix_so_far +} + +fn truncate_to_path_separator(prefix: &str) -> &str { + let last_separator_index = prefix + .rfind('/') + .or_else(|| prefix.rfind('\\')) + .or_else(|| prefix.rfind(':')); + if let Some(last_separator_index) = last_separator_index { + // Return the prefix up to and including the last path separator + return &prefix[0..=last_separator_index]; + } + // If there's no path separator in the prefix, return an empty string + "" +} + +fn next_offset(last_source: Option<&Source>) -> u32 { + // Leave a gap of 1 between each source so that offsets at EOF + // get mapped to the correct source + last_source.map_or(0, |s| { + 1 + s.offset + u32::try_from(s.contents.len()).expect("contents length should fit into u32") + }) +} diff --git a/source/qdk_openqasm_parser/src/vendor/span.rs b/source/qdk_openqasm_parser/src/vendor/span.rs new file mode 100644 index 00000000000..8f6cf7dcee4 --- /dev/null +++ b/source/qdk_openqasm_parser/src/vendor/span.rs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Vendored from `qsc_data_structures::span`. + +use miette::SourceSpan; +use std::{ + fmt::{self, Display, Formatter}, + ops::{Add, Index, Sub}, +}; + +/// A region between two offsets in an array. Spans are the half-open interval `[lo, hi)`. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Span { + /// The smallest offset contained in the span. + pub lo: u32, + /// The next offset after the largest offset contained in the span. + pub hi: u32, +} + +impl Span { + /// Returns true if the position is within the span. Meaning it is in the + /// right open interval `[self.lo, self.hi)`. + #[must_use] + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn contains(&self, offset: u32) -> bool { + (self.lo..self.hi).contains(&offset) + } + + /// Returns true if the position is in the closed interval `[self.lo, self.hi]`. + #[must_use] + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn touches(&self, offset: u32) -> bool { + (self.lo..=self.hi).contains(&offset) + } + + /// Intersect `other` with `self` and returns a new `Span` or `None` + /// if the spans have no overlap. + #[must_use] + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn intersection(&self, other: &Self) -> Option { + let lo = self.lo.max(other.lo); + let hi = self.hi.min(other.hi); + + if lo <= hi { + Some(Self { lo, hi }) + } else { + None + } + } +} + +impl Add for Span { + type Output = Self; + + fn add(self, rhs: u32) -> Self::Output { + Self { + lo: self.lo + rhs, + hi: self.hi + rhs, + } + } +} + +impl Sub for Span { + type Output = Self; + + fn sub(self, rhs: u32) -> Self::Output { + Self { + lo: self.lo - rhs, + hi: self.hi - rhs, + } + } +} + +impl Display for Span { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "[{}-{}]", self.lo, self.hi)?; + Ok(()) + } +} + +impl Index for str { + type Output = str; + + fn index(&self, index: Span) -> &Self::Output { + &self[(index.lo as usize)..(index.hi as usize)] + } +} + +impl Index<&Span> for str { + type Output = str; + + fn index(&self, index: &Span) -> &Self::Output { + &self[(index.lo as usize)..(index.hi as usize)] + } +} + +impl Index for String { + type Output = str; + + fn index(&self, index: Span) -> &Self::Output { + &self[(index.lo as usize)..(index.hi as usize)] + } +} + +impl From for SourceSpan { + fn from(value: Span) -> Self { + Self::from((value.lo as usize)..(value.hi as usize)) + } +} + +pub trait WithSpan { + #[must_use] + fn with_span(self, span: Span) -> Self; +} + +impl WithSpan for Box +where + T: WithSpan, +{ + fn with_span(mut self, span: Span) -> Self { + *self = (*self).with_span(span); + self + } +}