diff --git a/crates/oq3_semantics/src/context.rs b/crates/oq3_semantics/src/context.rs index c766c0f..96bb5d9 100644 --- a/crates/oq3_semantics/src/context.rs +++ b/crates/oq3_semantics/src/context.rs @@ -43,6 +43,26 @@ impl Context { &self.symbol_table } + // `SymbolTable::standard_library_gates()` returns a vector of + // all names that were already bound. We record a redeclaration error + // for each of these. The caller of the present method should pass + // the node corresponding to `include "stdgates.qasm"`. This is the + // best we can do since no real file has been included. + /// Define gates in the standard library. + pub fn standard_library_gates(&mut self, node: &T) + where + T: AstNode, + { + self.symbol_table + .standard_library_gates() + .into_iter() + .map(|name| { + self.semantic_errors + .insert(RedeclarationError(name.to_string()), node); + }) + .for_each(drop); + } + pub fn as_tuple(self) -> (asg::Program, SemanticErrorList, SymbolTable) { (self.program, self.semantic_errors, self.symbol_table) } @@ -108,7 +128,8 @@ impl Context { // let symbol_id_result = self.symbol_table.new_binding(name, typ, node.syntax()); let symbol_id_result = self.symbol_table.new_binding(name, typ); if symbol_id_result.is_err() { - self.semantic_errors.insert(RedeclarationError, node); + self.semantic_errors + .insert(RedeclarationError(name.to_string()), node); } symbol_id_result } diff --git a/crates/oq3_semantics/src/semantic_error.rs b/crates/oq3_semantics/src/semantic_error.rs index e44c47c..7d8e3b1 100644 --- a/crates/oq3_semantics/src/semantic_error.rs +++ b/crates/oq3_semantics/src/semantic_error.rs @@ -16,7 +16,7 @@ use crate::TextRange; pub enum SemanticErrorKind { UndefVarError, UndefGateError, - RedeclarationError, + RedeclarationError(String), ConstIntegerError, // need a better way to organize this kind of type error IncompatibleTypesError, MutateConstError, diff --git a/crates/oq3_semantics/src/symbols.rs b/crates/oq3_semantics/src/symbols.rs index c0cd3c3..a03ca1e 100644 --- a/crates/oq3_semantics/src/symbols.rs +++ b/crates/oq3_semantics/src/symbols.rs @@ -221,6 +221,46 @@ pub struct SymbolTable { symbol_id_counter: SymbolId, } +impl SymbolTable { + // This will be called if `include "stdgates.qasm"` is encountered. At present we don't have any include guard. + // FIXME: This function allocates a vector. The caller iterates over the vector. + // Would be nice to return the `FlatMap` instead. I tried doing this, but it was super compilcated. + // The compiler helps with which trait to use as the return type. But then tons of bugs occur within + // the body. + /// Define gates in standard library "as if" a file of definitions (or declarations) had been read. + pub(crate) fn standard_library_gates(&mut self) -> Vec<&str> { + let g1q0p = ( + vec![ + "x", "y", "z", "h", "s", "sdg", "t", "tdg", "sx", /* 2.0 */ "id", + ], + [0, 1], + ); + let g1q1p = (vec!["p", "rx", "ry", "rz", /* 2.0 */ "phase", "u1"], [1, 1]); + let g1q2p = (vec![/* 2.0 */ "u2"], [2, 1]); + let g1q3p = (vec![/* 2.0 */ "u3"], [3, 1]); + let g2q0p = (vec!["cx", "cy", "cz", "ch", "swap", /* 2.0 */ "CX"], [0, 2]); + let g2q1p = (vec!["cp", "crx", "cry", "crz", /* 2.0 */ "cphase"], [1, 2]); + let g2q4p = (vec!["cu"], [4, 2]); + let g3q0p = (vec!["ccx", "cswap"], [0, 3]); + let all_gates = vec![g1q0p, g1q1p, g1q2p, g1q3p, g2q0p, g2q1p, g2q4p, g3q0p]; + // If `new_binding` returns `Err`, we push `name` onto a vector which will be + // used by the caller to record errors. Here flat_map and filter are used to + // select filter the names. + all_gates + .into_iter() + .flat_map(|(names, [n_cl, n_qu])| { + names + .into_iter() + .filter(|name| { + // The side effect of the test is important! + self.new_binding(name, &Type::Gate(n_cl, n_qu)).is_err() + }) + .collect::>() + }) + .collect::>() + } +} + #[allow(dead_code)] impl SymbolTable { /// Create a new `SymbolTable` and initialize with the global scope. @@ -231,7 +271,7 @@ impl SymbolTable { all_symbols: Vec::::new(), }; symbol_table.enter_scope(ScopeType::Global); - // Define global, built-in constants + // Define global, built-in constants, and the single built-in gate for const_name in ["pi", "π", "euler", "ℇ", "tau", "τ"] { let _ = symbol_table.new_binding(const_name, &Type::Float(Some(64), types::IsConst::True)); diff --git a/crates/oq3_semantics/src/syntax_to_semantics.rs b/crates/oq3_semantics/src/syntax_to_semantics.rs index 418d396..536373f 100644 --- a/crates/oq3_semantics/src/syntax_to_semantics.rs +++ b/crates/oq3_semantics/src/syntax_to_semantics.rs @@ -152,25 +152,32 @@ pub fn syntax_to_semantic( // It is probably possible to encapsulate the manipulations of (context, errors). // But I have not made much of an attempt to do so. synast::Stmt::Include(include) => { - // Get SourceFile object with syntax AST for the next included file. - let included_parsed_source = included_iter.next().unwrap(); - // Empty list for possible semantic errors in the included file. - let mut errors_in_included = - SemanticErrorList::new(included_parsed_source.file_path().clone()); - // The following path is likely never taken - if context.symbol_table().current_scope_type() != ScopeType::Global { - context.insert_error(IncludeNotInGlobalScopeError, &include); + let file: synast::FilePath = include.file().unwrap(); + let file_path = file.to_string().unwrap(); + if file_path == "stdgates.qasm" { + // We do not use a file for standard library, but rather create the symbols. + context.standard_library_gates(&include); + } else { + // Get SourceFile object with syntax AST for the next included file. + let included_parsed_source = included_iter.next().unwrap(); + // Empty list for possible semantic errors in the included file. + let mut errors_in_included = + SemanticErrorList::new(included_parsed_source.file_path().clone()); + // The following path is likely never taken + if context.symbol_table().current_scope_type() != ScopeType::Global { + context.insert_error(IncludeNotInGlobalScopeError, &include); + } + // Call this function recursively passing the new, empty, storage for errors. + // Note that `errors_in_included` will be swapped into `context` upon entering `syntax_to_semantic`. + (context, errors_in_included) = + syntax_to_semantic(included_parsed_source, context, errors_in_included); + // Just before exiting the previous call, `errors_in_included` and `errors` are swapped again in `context`. + // Push the newly-populated list of errors onto the list of included errors in `context`, which now + // holds `errors`, the list passed in the current call to this `syntax_to_semantic`. And `errors` + // corresponds to the source in which `include` was encountered. + context.push_included(errors_in_included); + // Return `None` because have evaluated (and removed)the `include` statement. } - // Call this function recursively passing the new, empty, storage for errors. - // Note that `errors_in_included` will be swapped into `context` upon entering `syntax_to_semantic`. - (context, errors_in_included) = - syntax_to_semantic(included_parsed_source, context, errors_in_included); - // Just before exiting the previous call, `errors_in_included` and `errors` are swapped again in `context`. - // Push the newly-populated list of errors onto the list of included errors in `context`, which now - // holds `errors`, the list passed in the current call to this `syntax_to_semantic`. And `errors` - // corresponds to the source in which `include` was encountered. - context.push_included(errors_in_included); - // Return `None` because have evaluated (and removed)the `include` statement. None } diff --git a/crates/oq3_semantics/tests/from_string_tests.rs b/crates/oq3_semantics/tests/from_string_tests.rs index 266b0d6..a299822 100644 --- a/crates/oq3_semantics/tests/from_string_tests.rs +++ b/crates/oq3_semantics/tests/from_string_tests.rs @@ -664,3 +664,26 @@ def xcheck(qubit[4] d, qubit a) -> bit { assert_eq!(errors.len(), 0); assert_eq!(program.len(), 2); } + +#[test] +fn test_from_string_stdgates() { + let code = r##" +include "stdgates.qasm"; +qubit q; +h q; +"##; + let (program, errors, _symbol_table) = parse_string(code); + assert_eq!(errors.len(), 0); + assert_eq!(program.len(), 2); +} + +#[test] +fn test_from_string_stdgates_2() { + let code = r##" +gate h q {} +include "stdgates.qasm"; +"##; + let (program, errors, _symbol_table) = parse_string(code); + assert_eq!(errors.len(), 1); + assert_eq!(program.len(), 1); +} diff --git a/crates/oq3_source_file/src/source_file.rs b/crates/oq3_source_file/src/source_file.rs index 3f3f842..b4b70ee 100644 --- a/crates/oq3_source_file/src/source_file.rs +++ b/crates/oq3_source_file/src/source_file.rs @@ -169,7 +169,12 @@ pub(crate) fn parse_included_files>( synast::Stmt::Include(include) => { let file: synast::FilePath = include.file().unwrap(); let file_path = file.to_string().unwrap(); - Some(parse_source_file(file_path, search_path_list)) + // stdgates.qasm will be handled "as if" it really existed. + if file_path == "stdgates.qasm" { + None + } else { + Some(parse_source_file(file_path, search_path_list)) + } } _ => None, })