diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml new file mode 100644 index 00000000..56dee02a --- /dev/null +++ b/.github/workflows/msrv.yml @@ -0,0 +1,102 @@ +# Based on https://github.com/actions-rs/meta/blob/master/recipes/msrv.md + +on: [ push, pull_request ] + +name: MSRV + +jobs: + check: + name: Check + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + with: + command: check + + test: + name: Test Suite + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + + - name: Install rustfmt + run: rustup component add rustfmt + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + + - name: Install clippy + run: rustup component add clippy + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..536b5c2c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + push: + branches: + - main + - rc + +jobs: + release: + name: Semantic Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v1 + with: + node-version: 12 + - name: Install Semantic Release Dependencies + run: npm install + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release + # - uses: actions-rs/toolchain@v1 + # with: + # toolchain: stable + # - uses: actions-rs/cargo@v1 + # with: + # command: build + # args: --release --all-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9fe100e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +node_modules \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..ae55667a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "quil" +version = "0.1.0" +authors = ["kalzoo "] +edition = "2018" +license = "Apache-2.0" + +[dependencies] +indexmap = "1.6.1" +lexical = "5.2.0" +nom = "6.1.0" +num-complex = "0.3.0" +petgraph = "0.5.1" +serde = { version = "1.0.125", features = ["derive"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Readme.md b/Readme.md new file mode 100644 index 00000000..8d8dabb6 --- /dev/null +++ b/Readme.md @@ -0,0 +1,11 @@ +# Quil Parser & Program Builder + +This library is the implementation of the [Quil spec](https://github.com/quil-lang/quil) in Rust. + +It serves three purposes: + +1. Parse Quil programs from strings, and output programs to strings +2. Manipulate Quil programs within Rust +3. Construct a dependency graph among program instructions + +It should be considered unstable until the release of v1.0. \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..36e7bc5d --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "quil-rust-semantic-release", + "version": "1.0.0", + "description": "Encapsulate dependencies needed to use semantic-release", + "dependencies": { + "@semantic-release/exec": "^5.0.0", + "@semantic-release/git": "^9.0.0", + "@semantic-release/gitlab": "^6.0.4", + "conventional-changelog-eslint": "^3.0.8", + "semantic-release": "^17.1.1" + }, + "release": { + "branches": ["main", {"name": "rc", "prerelease": true}], + "plugins": [ + ["@semantic-release/commit-analyzer",{ + "preset": "eslint", + "releaseRules": [ + {"tag": "Breaking", "release": "major"}, + {"tag": "Update", "release": "minor"}, + {"tag": "Fix", "release": "patch"}, + {"tag": "New", "release": "patch"}, + {"tag": "Upgrade", "release": "patch"} + ] + }], + ["@semantic-release/release-notes-generator", { + "preset": "eslint" + }], + ["@semantic-release/exec",{ + "prepareCmd": "sh prepare_release.sh ${nextRelease.version}" + }], + "@semantic-release/github", + ["@semantic-release/git", { + "assets": ["Cargo.toml"], + "message": "Release v${nextRelease.version} [skip ci]" + }] + ], + "repositoryUrl": "ssh://git@github.com/rigetti/quil-rust.git" + } +} diff --git a/prepare_release.sh b/prepare_release.sh new file mode 100755 index 00000000..62fdd99d --- /dev/null +++ b/prepare_release.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +### This script is called by Semantic Release after determining a new version +### for the package and prior to pushing it back to GitHub + +set -ex + +[[ $1 == "" ]] && echo "usage: $0 " && exit 1 + +# We use sed to bump the version to avoid having to install or use any other tools +sed -i.bak -E "s/^version = \".+\"$/version = \"$1\"/" Cargo.toml +rm Cargo.toml.bak diff --git a/src/expression.rs b/src/expression.rs new file mode 100644 index 00000000..3c69e287 --- /dev/null +++ b/src/expression.rs @@ -0,0 +1,393 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use crate::{imag, instruction::MemoryReference, real}; +use std::collections::HashMap; +use std::f64::consts::PI; +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum EvaluationError { + Incomplete, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Expression { + Address(MemoryReference), + FunctionCall { + function: ExpressionFunction, + expression: Box, + }, + Infix { + left: Box, + operator: InfixOperator, + right: Box, + }, + Number(num_complex::Complex64), + PiConstant, + Prefix { + operator: PrefixOperator, + expression: Box, + }, + Variable(String), +} + +/// Compute the result of an infix expression where both operands are complex. +fn calculate_infix( + left: &num_complex::Complex64, + operator: &InfixOperator, + right: &num_complex::Complex64, +) -> num_complex::Complex64 { + use InfixOperator::*; + match operator { + Caret => left.powc(*right), + Plus => left + right, + Minus => left - right, + Slash => left / right, + Star => left * right, + } +} + +/// Compute the result of a Quil-defined expression function where the operand is complex. +fn calculate_function( + function: &ExpressionFunction, + argument: &num_complex::Complex64, +) -> num_complex::Complex64 { + use ExpressionFunction::*; + match function { + Sine => argument.sin(), + Cis => argument.cos() + imag!(1f64) * argument.sin(), + Cosine => argument.cos(), + Exponent => argument.exp(), + SquareRoot => argument.sqrt(), + } +} + +pub type EvaluationEnvironment = HashMap; + +impl Expression { + /// Consume the expression, simplifying it as much as possible using the values provided in the environment. + /// If variables are used in the expression which are not present in the environment, evaluation stops there, + /// returning the possibly-simplified expression. + pub fn evaluate(self, environment: &EvaluationEnvironment) -> Self { + use Expression::*; + match self { + FunctionCall { + function, + expression, + } => { + let evaluated = (*expression).evaluate(environment); + match &evaluated { + Number(value) => Number(calculate_function(&function, value)), + PiConstant => Number(calculate_function(&function, &real!(PI))), + _ => FunctionCall { + function, + expression: Box::new(evaluated), + }, + } + } + Infix { + left, + operator, + right, + } => { + let left_evaluated = (*left).evaluate(environment); + let right_evaluated = (*right).evaluate(environment); + + match (&left_evaluated, &right_evaluated) { + (Number(value_left), Number(value_right)) => { + Number(calculate_infix(&value_left, &operator, &value_right)) + } + (PiConstant, Number(value)) => { + Number(calculate_infix(&real!(PI), &operator, &value)) + } + (Number(value), PiConstant) => { + Number(calculate_infix(&value, &operator, &real!(PI))) + } + _ => Infix { + left: Box::new(left_evaluated), + operator, + right: Box::new(right_evaluated), + }, + } + } + Prefix { + operator, + expression, + } => { + use PrefixOperator::*; + let prefixed_expression = *expression; + match (&operator, prefixed_expression) { + (Minus, Number(value)) => Number(-value), + (Minus, PiConstant) => Number(real!(-PI)), + (Minus, expr) => Prefix { + operator, + expression: Box::new(expr), + }, + (Plus, expr) => expr, + } + } + Variable(identifier) => match environment.get(&identifier) { + Some(value) => Number(*value), + None => Variable(identifier), + }, + _ => self, + } + } + + /// Evaluate an expression, expecting that it may be fully reduced to a single complex number. + /// If it cannot be reduced to a complex number, return an error. + pub fn evaluate_to_complex( + self, + environment: &EvaluationEnvironment, + ) -> Result { + use Expression::*; + + let result = self.evaluate(environment); + match result { + Number(value) => Ok(value), + PiConstant => Ok(real!(PI)), + _ => Err(EvaluationError::Incomplete), + } + } +} + +/// Format a num_complex::Complex64 value in a way that omits the real or imaginary part when +/// reasonable. That is: +/// +/// - When imaginary is set but real is 0, show only imaginary +/// - When imaginary is 0, show real only +/// - When both are non-zero, show with the correct operator in between +macro_rules! format_complex { + ($value:expr) => {{ + let mut operator = String::new(); + let mut imaginary_component = String::new(); + + if $value.im > 0f64 { + operator = "+".to_owned(); + imaginary_component = format!("{}i", pretty_float!($value.im)) + } else if $value.im < 0f64 { + imaginary_component = format!("-{}i", pretty_float!($value.im)) + } + + if imaginary_component == "" { + pretty_float!($value.re) + } else if $value.re == 0f64 { + format!("{}", imaginary_component) + } else { + format!( + "{}{}{}", + pretty_float!($value.re), + operator, + imaginary_component + ) + } + }}; +} + +// Display floats in a human-friendly way using scientific notation. +// See https://internals.rust-lang.org/t/pre-rfc-draft-g-or-floating-points-for-humans/9110/8 +#[macro_export] +macro_rules! pretty_float { + ($value: expr) => {{ + let mut attempt = format!("{:.2e}", $value); + if attempt.ends_with("e0") { + attempt = format!("{:.2}", $value) + } + if attempt.ends_with(".00") { + attempt = format!("{:.0}", $value) + } + attempt + }}; +} + +impl fmt::Display for Expression { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Expression::*; + match self { + Address(memory_reference) => write!(f, "{}", memory_reference), + FunctionCall { + function, + expression, + } => write!(f, "{}({})", function, expression), + Infix { + left, + operator, + right, + } => write!(f, "({}{}{})", left, operator, right), + Number(value) => write!(f, "{}", format_complex!(value)), + PiConstant => write!(f, "pi"), + Prefix { + operator, + expression, + } => write!(f, "({}{})", operator, expression), + Variable(identifier) => write!(f, "%{}", identifier), + } + } +} + +/// A function defined within Quil syntax. +#[derive(Clone, Debug, PartialEq)] +pub enum ExpressionFunction { + Cis, + Cosine, + Exponent, + Sine, + SquareRoot, +} + +impl fmt::Display for ExpressionFunction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ExpressionFunction::*; + write!( + f, + "{}", + match self { + Cis => "cis", + Cosine => "cos", + Exponent => "exp", + Sine => "sin", + SquareRoot => "sqrt", + } + ) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum PrefixOperator { + Plus, + Minus, +} + +impl fmt::Display for PrefixOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use PrefixOperator::*; + write!( + f, + "{}", + match self { + Plus => "+", + Minus => "-", + } + ) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum InfixOperator { + Caret, + Plus, + Minus, + Slash, + Star, +} + +impl fmt::Display for InfixOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use InfixOperator::*; + write!( + f, + "{}", + match self { + Caret => "^", + Plus => "+", + Minus => "-", + Slash => "/", + Star => "*", + } + ) + } +} + +#[cfg(test)] +mod tests { + use super::PrefixOperator; + use crate::{ + expression::{EvaluationError, Expression, ExpressionFunction, InfixOperator}, + real, + }; + use num_complex::Complex64; + use std::{collections::HashMap, f64::consts::PI}; + + #[test] + fn evaluate() { + use Expression::*; + + let one = Complex64::new(1f64, 0f64); + let empty_environment = HashMap::new(); + + let mut environment = HashMap::new(); + environment.insert("foo".to_owned(), real!(10f64)); + environment.insert("bar".to_owned(), real!(100f64)); + + struct TestCase<'a> { + expression: Expression, + environment: &'a HashMap, + evaluated_expression: Expression, + evaluated_complex: Result, + } + + let cases: Vec = vec![ + TestCase { + expression: Number(one), + environment: &empty_environment, + evaluated_expression: Number(one), + evaluated_complex: Ok(one), + }, + TestCase { + expression: Expression::Prefix { + operator: PrefixOperator::Minus, + expression: Box::new(Number(real!(1f64))), + }, + environment: &empty_environment, + evaluated_expression: Number(real!(-1f64)), + evaluated_complex: Ok(real!(-1f64)), + }, + TestCase { + expression: Expression::Variable("foo".to_owned()), + environment: &environment, + evaluated_expression: Number(real!(10f64)), + evaluated_complex: Ok(real!(10f64)), + }, + TestCase { + expression: Expression::Infix { + left: Box::new(Expression::Variable("foo".to_owned())), + operator: InfixOperator::Plus, + right: Box::new(Expression::Variable("bar".to_owned())), + }, + environment: &environment, + evaluated_expression: Number(real!(110f64)), + evaluated_complex: Ok(real!(110f64)), + }, + TestCase { + expression: Expression::FunctionCall { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Number(real!(PI / 2f64))), + }, + environment: &environment, + evaluated_expression: Number(real!(1f64)), + evaluated_complex: Ok(real!(1f64)), + }, + ]; + + for case in cases { + let evaluated = case.expression.evaluate(&case.environment); + assert_eq!(evaluated, case.evaluated_expression); + + let evaluated_complex = evaluated.evaluate_to_complex(&case.environment); + assert_eq!(evaluated_complex, case.evaluated_complex) + } + } +} diff --git a/src/instruction.rs b/src/instruction.rs new file mode 100644 index 00000000..a660f434 --- /dev/null +++ b/src/instruction.rs @@ -0,0 +1,691 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fmt}; + +use crate::expression::Expression; + +#[derive(Clone, Debug, PartialEq)] +pub enum ArithmeticOperand { + LiteralInteger(i64), + LiteralReal(f64), + MemoryReference(MemoryReference), +} + +impl fmt::Display for ArithmeticOperand { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self { + ArithmeticOperand::LiteralInteger(value) => write!(f, "{}", value), + ArithmeticOperand::LiteralReal(value) => write!(f, "{}", value), + ArithmeticOperand::MemoryReference(value) => write!(f, "{}", value), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ArithmeticOperator { + Add, + Subtract, + Divide, + Multiply, +} + +impl fmt::Display for ArithmeticOperator { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self { + ArithmeticOperator::Add => write!(f, "ADD"), + ArithmeticOperator::Divide => write!(f, "DIV"), + ArithmeticOperator::Multiply => write!(f, "MUL"), + ArithmeticOperator::Subtract => write!(f, "SUB"), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum AttributeValue { + String(String), + Expression(Expression), +} + +impl fmt::Display for AttributeValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use AttributeValue::*; + match self { + String(value) => write!(f, "{}", value), + Expression(value) => write!(f, "{}", value), + } + } +} + +pub type FrameAttributes = HashMap; + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Calibration { + pub instructions: Vec, + pub modifiers: Vec, + pub name: String, + pub parameters: Vec, + pub qubits: Vec, +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct FrameIdentifier { + pub name: String, + pub qubits: Vec, +} + +impl fmt::Display for FrameIdentifier { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} \"{}\"", format_qubits(&self.qubits), self.name) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum GateModifier { + Controlled, + Dagger, + Forked, +} + +impl fmt::Display for GateModifier { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GateModifier::*; + write!( + f, + "{}", + match self { + Controlled => "CONTROLLED", + Dagger => "DAGGER", + Forked => "FORKED", + } + ) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum GateType { + Matrix, + Permutation, +} + +impl fmt::Display for GateType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GateType::*; + write!( + f, + "{}", + match self { + Matrix => "MATRIX", + Permutation => "PERMUTATION", + } + ) + } +} + +#[derive(Clone, Debug, Hash, PartialEq)] +pub enum ScalarType { + Bit, + Integer, + Octet, + Real, +} + +impl fmt::Display for ScalarType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ScalarType::*; + write!( + f, + "{}", + match self { + Bit => "BIT", + Integer => "INTEGER", + Octet => "OCTET", + Real => "REAL", + } + ) + } +} + +#[derive(Clone, Debug, Hash, PartialEq)] +pub struct Vector { + pub data_type: ScalarType, + pub length: u64, +} + +impl fmt::Display for Vector { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}[{}]", self.data_type, self.length) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct WaveformInvocation { + pub name: String, + pub parameters: HashMap, +} + +impl fmt::Display for WaveformInvocation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut key_value_pairs = self + .parameters + .iter() + .collect::>(); + + key_value_pairs.sort_by(|(k1, _), (k2, _)| k1.cmp(&k2)); + + write!( + f, + "{}({})", + self.name, + key_value_pairs + .iter() + .map(|(k, v)| format!("{}: {}", k, v)) + .collect::>() + .join(", ") + ) + } +} + +#[derive(Clone, Debug, Hash, PartialEq)] +pub struct MemoryReference { + pub name: String, + pub index: u64, +} + +impl Eq for MemoryReference {} + +impl fmt::Display for MemoryReference { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}[{}]", self.name, self.index) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Instruction { + Gate { + name: String, + parameters: Vec, + qubits: Vec, + modifiers: Vec, + }, + CircuitDefinition { + name: String, + parameters: Vec, + // These cannot be fixed qubits and thus are not typed as `Qubit` + qubit_variables: Vec, + instructions: Vec, + }, + GateDefinition { + name: String, + parameters: Vec, + matrix: Vec>, + r#type: GateType, + }, + Declaration { + name: String, + size: Vector, + sharing: Option, + }, + Measurement { + qubit: Qubit, + target: Option, + }, + Reset { + qubit: Option, + }, + CalibrationDefinition(Box), + Capture { + frame: FrameIdentifier, + memory_reference: MemoryReference, + waveform: Box, + }, + Delay { + duration: Expression, + frame_names: Vec, + qubits: Vec, + }, + Fence { + qubits: Vec, + }, + FrameDefinition { + identifier: FrameIdentifier, + attributes: HashMap, + }, + MeasureCalibrationDefinition { + qubit: Option, + parameter: String, + instructions: Vec, + }, + Pragma { + name: String, + arguments: Vec, + data: Option, + }, + Pulse { + blocking: bool, + frame: FrameIdentifier, + waveform: Box, + }, + RawCapture { + frame: FrameIdentifier, + duration: Expression, + memory_reference: MemoryReference, + }, + SetFrequency { + frame: FrameIdentifier, + frequency: f64, + }, + SetPhase { + frame: FrameIdentifier, + phase: Expression, + }, + SetScale { + frame: FrameIdentifier, + scale: Expression, + }, + ShiftFrequency { + frame: FrameIdentifier, + frequency: f64, + }, + ShiftPhase { + frame: FrameIdentifier, + phase: Expression, + }, + SwapPhases { + frame_1: FrameIdentifier, + frame_2: FrameIdentifier, + }, + WaveformDefinition { + name: String, + definition: Waveform, + }, + Arithmetic { + operator: ArithmeticOperator, + destination: ArithmeticOperand, + source: ArithmeticOperand, + }, + Halt, + Label(String), + Move { + destination: ArithmeticOperand, + source: ArithmeticOperand, + }, + Exchange { + left: ArithmeticOperand, + right: ArithmeticOperand, + }, + Load { + destination: MemoryReference, + source: String, + offset: MemoryReference, + }, + Store { + destination: String, + offset: MemoryReference, + source: ArithmeticOperand, + }, + Jump { + target: String, + }, + JumpWhen { + target: String, + condition: MemoryReference, + }, + JumpUnless { + target: String, + condition: MemoryReference, + }, +} + +#[derive(Clone, Debug)] +pub enum InstructionRole { + ClassicalCompute, + ControlFlow, + ProgramComposition, + RFControl, +} + +impl From<&Instruction> for InstructionRole { + fn from(instruction: &Instruction) -> Self { + match instruction { + Instruction::CalibrationDefinition(_) + | Instruction::CircuitDefinition { .. } + | Instruction::Declaration { .. } + | Instruction::FrameDefinition { .. } + | Instruction::Gate { .. } + | Instruction::GateDefinition { .. } + | Instruction::Label(_) + | Instruction::MeasureCalibrationDefinition { .. } + | Instruction::Measurement { .. } + | Instruction::Pragma { .. } + | Instruction::WaveformDefinition { .. } => InstructionRole::ProgramComposition, + Instruction::Reset { .. } + | Instruction::Capture { .. } + | Instruction::Delay { .. } + | Instruction::Fence { .. } + | Instruction::Pulse { .. } + | Instruction::RawCapture { .. } + | Instruction::SetFrequency { .. } + | Instruction::SetPhase { .. } + | Instruction::SetScale { .. } + | Instruction::ShiftFrequency { .. } + | Instruction::ShiftPhase { .. } + | Instruction::SwapPhases { .. } => InstructionRole::RFControl, + Instruction::Arithmetic { .. } + | Instruction::Move { .. } + | Instruction::Exchange { .. } + | Instruction::Load { .. } + | Instruction::Store { .. } => InstructionRole::ClassicalCompute, + Instruction::Halt + | Instruction::Jump { .. } + | Instruction::JumpWhen { .. } + | Instruction::JumpUnless { .. } => InstructionRole::ControlFlow, + } + } +} + +pub fn format_instructions(values: &[Instruction]) -> String { + values + .iter() + .map(|i| format!("{}", i)) + .collect::>() + .join("\n\t") +} + +pub fn format_integer_vector(values: &[u64]) -> String { + values + .iter() + .map(|q| format!("{}", q)) + .collect::>() + .join(" ") +} + +pub fn format_matrix(matrix: &[Vec]) -> String { + matrix + .iter() + .map(|row| { + row.iter() + .map(|cell| format!("{}", cell)) + .collect::>() + .join(", ") + }) + .collect::>() + .join("\n\t") +} + +pub fn format_qubits(qubits: &[Qubit]) -> String { + qubits + .iter() + .map(|q| format!("{}", q)) + .collect::>() + .join(" ") +} + +pub fn get_parameter_string(parameters: &[Expression]) -> String { + if parameters.is_empty() { + return String::from(""); + } + + let parameter_str: String = parameters.iter().map(|e| format!("{}", e)).collect(); + format!("({})", parameter_str) +} + +impl fmt::Display for Instruction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Instruction::*; + match self { + Arithmetic { + operator, + destination, + source, + } => write!(f, "{} {} {}", operator, destination, source), + CalibrationDefinition(calibration) => { + let parameter_str = get_parameter_string(&calibration.parameters); + write!( + f, + "DEFCAL {}{} {}:", + calibration.name, + parameter_str, + format_qubits(&calibration.qubits) + )?; + for instruction in &calibration.instructions { + write!(f, "\n\t{}", instruction)?; + } + writeln!(f) + } + Capture { + frame, + waveform, + memory_reference, + } => write!(f, "CAPTURE {} {} {}", frame, waveform, memory_reference), + CircuitDefinition { + name, + parameters, + qubit_variables, + instructions, + } => { + let mut parameter_str: String = parameters + .iter() + .map(|p| p.to_string()) + .collect::>() + .join(", "); + if !parameter_str.is_empty() { + parameter_str = format!("({})", parameter_str) + } + write!(f, "DEFCIRCUIT {}{}", name, parameter_str)?; + for qubit_variable in qubit_variables { + write!(f, " {}", qubit_variable)?; + } + writeln!(f, ":")?; + for instruction in &**instructions { + writeln!(f, "\t{}", instruction)?; + } + Ok(()) + } + Declaration { + name, + size, + sharing, + } => { + write!(f, "DECLARE {} {}", name, size)?; + match sharing { + Some(shared) => write!(f, "SHARING {}", shared)?, + None => {} + } + Ok(()) + } + Delay { + qubits, + frame_names, + duration, + } => { + write!(f, "DELAY {}", format_qubits(&qubits))?; + for frame_name in frame_names { + write!(f, " \"{}\"", frame_name)?; + } + write!(f, " {}", duration) + } + Fence { qubits } => write!(f, "FENCE {}", format_qubits(&qubits)), + FrameDefinition { + identifier, + attributes, + } => write!( + f, + "DEFFRAME {}:\n{}", + identifier, + attributes + .iter() + .map(|(k, v)| format!("\n\t{}: {}", k, v)) + .collect::() + ), + Gate { + name, + parameters, + qubits, + modifiers, + } => { + let parameter_str = get_parameter_string(parameters); + + let qubit_str = format_qubits(&qubits); + let modifier_str = modifiers + .iter() + .map(|m| format!("{} ", m)) + .collect::>() + .join(""); + write!(f, "{}{}{} {}", modifier_str, name, parameter_str, qubit_str) + } + GateDefinition { + name, + parameters, + matrix, + r#type, + } => { + let parameter_str: String = parameters.iter().map(|p| p.to_string()).collect(); + writeln!(f, "DEFGATE {}{} AS {}:", name, parameter_str, r#type)?; + for row in matrix { + writeln!( + f, + "\t{}", + row.iter() + .map(|cell| format!("{}", cell)) + .collect::>() + .join(",") + )?; + } + Ok(()) + } + MeasureCalibrationDefinition { + qubit, + parameter, + instructions, + } => { + write!(f, "DEFCAL MEASURE")?; + match qubit { + Some(qubit) => { + write!(f, " {}", qubit)?; + } + None => {} + } + + writeln!( + f, + " %{}:\n\t{}", + parameter, + format_instructions(instructions) + ) + } + Measurement { qubit, target } => match target { + Some(reference) => write!(f, "MEASURE {} {}", qubit, reference), + None => write!(f, "MEASURE {}", qubit), + }, + Move { + destination, + source, + } => write!(f, "MOVE {} {}", destination, source), + Exchange { left, right } => write!(f, "EXCHANGE {} {}", left, right), + Load { + destination, + source, + offset, + } => { + write!(f, "LOAD {} {} {}", destination, source, offset) + } + Store { + destination, + offset, + source, + } => { + write!(f, "STORE {} {} {}", destination, offset, source) + } + Pulse { + blocking, + frame, + waveform, + } => { + if !blocking { + write!(f, "NONBLOCKING ")?; + } + write!(f, "PULSE {} {}", frame, waveform) + } + Pragma { + name, + arguments, + data, + } => match data { + // FIXME: Handle empty argument lists + Some(data) => write!(f, "PRAGMA {} {} {}", name, arguments.join(" "), data), + None => write!(f, "PRAGMA {} {}", name, arguments.join(" ")), + }, + RawCapture { + frame, + duration, + memory_reference, + } => write!(f, "RAW-CAPTURE {} {} {}", frame, duration, memory_reference), + Reset { qubit } => match qubit { + Some(qubit) => write!(f, "RESET {}", qubit), + None => write!(f, "RESET"), + }, + SetFrequency { frame, frequency } => write!(f, "SET-FREQUENCY {} {}", frame, frequency), + SetPhase { frame, phase } => write!(f, "SET-PHASE {} {}", frame, phase), + SetScale { frame, scale } => write!(f, "SET-SCALE {} {}", frame, scale), + ShiftFrequency { frame, frequency } => { + write!(f, "SHIFT-FREQUENCY {} {}", frame, frequency) + } + ShiftPhase { frame, phase } => write!(f, "SHIFT-PHASE {} {}", frame, phase), + SwapPhases { frame_1, frame_2 } => write!(f, "SWAP-PHASES {} {}", frame_1, frame_2), + WaveformDefinition { name, definition } => write!( + f, + "DEFWAVEFORM {} {} {}:{}", + name, + definition.parameters.join(","), + definition.sample_rate, + definition + .matrix + .iter() + .map(|e| format!("{}", e)) + .collect::() + ), + Halt => write!(f, "HALT"), + Jump { target } => write!(f, "JUMP @{}", target), + JumpUnless { condition, target } => write!(f, "JUMP-UNLESS @{} {}", target, condition), + JumpWhen { condition, target } => write!(f, "JUMP-WHEN @{} {}", target, condition), + Label(label) => write!(f, "LABEL @{}", label), + } + } +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub enum Qubit { + Fixed(u64), + Variable(String), +} + +impl fmt::Display for Qubit { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Qubit::*; + match self { + Fixed(value) => write!(f, "{}", value), + Variable(value) => write!(f, "{}", value), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Waveform { + pub matrix: Vec, + pub parameters: Vec, + pub sample_rate: f64, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..7496eac3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +pub mod expression; +pub mod instruction; +mod macros; +pub mod parser; +pub mod program; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..fa6340da --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,33 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +/// Construct a complex number with the provided real component. +#[macro_export] +macro_rules! real { + ($value:expr) => {{ + use num_complex::Complex64; + Complex64::new($value, 0f64) + }}; +} + +/// Construct a complex number with the provided imaginary component. +#[macro_export] +macro_rules! imag { + ($value:expr) => {{ + use num_complex::Complex64; + Complex64::new(0f64, $value) + }}; +} diff --git a/src/parser/command.rs b/src/parser/command.rs new file mode 100644 index 00000000..7cdf94c7 --- /dev/null +++ b/src/parser/command.rs @@ -0,0 +1,417 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use nom::{ + combinator::opt, + multi::{many0, many1, separated_list0, separated_list1}, + sequence::{delimited, tuple}, +}; + +use super::{ + common::{ + self, parse_frame_attribute, parse_frame_identifier, parse_gate_modifier, + parse_memory_reference, parse_qubits, parse_waveform_invocation, + }, + expression::parse_expression, + instruction, ParserInput, ParserResult, +}; +use crate::{ + instruction::{ + ArithmeticOperand, ArithmeticOperator, Calibration, FrameIdentifier, Instruction, Waveform, + }, + parser::common::parse_qubit, + token, +}; + +/// Parse an arithmetic instruction of the form `destination source`. +/// Called using the arithmetic operator itself (such as `ADD`) which should be previously parsed. +pub fn parse_arithmetic( + operator: ArithmeticOperator, + input: ParserInput, +) -> ParserResult { + let (input, destination_memory_reference) = common::parse_memory_reference(input)?; + let destination = ArithmeticOperand::MemoryReference(destination_memory_reference); + let (input, source) = common::parse_arithmetic_operand(input)?; + Ok(( + input, + Instruction::Arithmetic { + operator, + destination, + source, + }, + )) +} + +/// Parse the contents of a `DECLARE` instruction. +pub fn parse_declare<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, name) = token!(Identifier(v))(input)?; + let (input, size) = common::parse_vector(input)?; + Ok(( + input, + Instruction::Declaration { + name, + sharing: None, + size, + }, + )) +} + +/// Parse the contents of a `CAPTURE` instruction. +pub fn parse_capture(input: ParserInput) -> ParserResult { + let (input, frame) = common::parse_frame_identifier(input)?; + let (input, waveform) = common::parse_waveform_invocation(input)?; + let (input, memory_reference) = common::parse_memory_reference(input)?; + + Ok(( + input, + Instruction::Capture { + frame, + waveform: Box::new(waveform), + memory_reference, + }, + )) +} + +/// Parse the contents of a `DEFCAL` instruction. +pub fn parse_defcal<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, modifiers) = many0(parse_gate_modifier)(input)?; + let (input, name) = token!(Identifier(v))(input)?; + let (input, parameters) = opt(delimited( + token!(LParenthesis), + separated_list0(token!(Comma), parse_expression), + token!(RParenthesis), + ))(input)?; + let parameters = parameters.unwrap_or_default(); + let (input, qubits) = parse_qubits(input)?; + let (input, _) = token!(Colon)(input)?; + let (input, instructions) = instruction::parse_block(input)?; + Ok(( + input, + Instruction::CalibrationDefinition(Box::new(Calibration { + instructions, + modifiers, + name, + parameters, + qubits, + })), + )) +} + +/// Parse the contents of a `DEFFRAME` instruction. +pub fn parse_defframe<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, identifier) = parse_frame_identifier(input)?; + let (input, _) = token!(Colon)(input)?; + let (input, attribute_pairs) = many1(parse_frame_attribute)(input)?; + let attributes = attribute_pairs.iter().cloned().collect(); + + Ok(( + input, + Instruction::FrameDefinition { + identifier, + attributes, + }, + )) +} + +/// Parse the contents of a `DEFWAVEFORM` instruction. +pub fn parse_defwaveform<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, name) = token!(Identifier(v))(input)?; + let (input, parameters) = opt(delimited( + token!(LParenthesis), + separated_list0(token!(Comma), token!(Variable(v))), + token!(RParenthesis), + ))(input)?; + let parameters = parameters.unwrap_or_default(); + let (input, sample_rate) = token!(Float(v))(input)?; + let (input, _) = tuple((token!(Colon), token!(NewLine), token!(Indentation)))(input)?; + let (input, matrix) = separated_list1(token!(Comma), parse_expression)(input)?; + + Ok(( + input, + Instruction::WaveformDefinition { + name, + definition: Waveform { + matrix, + parameters, + sample_rate, + }, + }, + )) +} + +/// Parse the contents of a `DELAY` instruction. +pub fn parse_delay<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, qubits) = parse_qubits(input)?; + let (input, frame_names) = many0(token!(String(v)))(input)?; + let (input, duration) = parse_expression(input)?; + + Ok(( + input, + Instruction::Delay { + frame_names, + qubits, + duration, + }, + )) +} + +/// Parse the contents of an `EXCHANGE` instruction. +pub fn parse_exchange(input: ParserInput) -> ParserResult { + let (input, left) = common::parse_memory_reference(input)?; + let (input, right) = common::parse_memory_reference(input)?; + + Ok(( + input, + Instruction::Exchange { + left: ArithmeticOperand::MemoryReference(left), + right: ArithmeticOperand::MemoryReference(right), + }, + )) +} + +/// Parse the contents of a `JUMP` instruction. +pub fn parse_jump<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, target) = token!(Label(v))(input)?; + Ok((input, Instruction::Jump { target })) +} + +/// Parse the contents of a `JUMP-WHEN` instruction. +pub fn parse_jump_when<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, target) = token!(Label(v))(input)?; + let (input, condition) = common::parse_memory_reference(input)?; + Ok((input, Instruction::JumpWhen { condition, target })) +} + +/// Parse the contents of a `JUMP-UNLESS` instruction. +pub fn parse_jump_unless<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, target) = token!(Label(v))(input)?; + let (input, condition) = common::parse_memory_reference(input)?; + Ok((input, Instruction::JumpUnless { condition, target })) +} + +/// Parse the contents of a `DECLARE` instruction. +pub fn parse_label<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, name) = token!(Label(v))(input)?; + Ok((input, Instruction::Label(name))) +} + +/// Parse the contents of a `MOVE` instruction. +pub fn parse_move(input: ParserInput) -> ParserResult { + let (input, destination) = common::parse_arithmetic_operand(input)?; + let (input, source) = common::parse_arithmetic_operand(input)?; + Ok(( + input, + Instruction::Move { + destination, + source, + }, + )) +} + +/// Parse the contents of a `LOAD` instruction. +pub fn parse_load<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, destination) = common::parse_memory_reference(input)?; + let (input, source) = token!(Identifier(v))(input)?; + let (input, offset) = common::parse_memory_reference(input)?; + + Ok(( + input, + Instruction::Load { + destination, + source, + offset, + }, + )) +} + +/// Parse the contents of a `STORE` instruction. +pub fn parse_store<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, destination) = token!(Identifier(v))(input)?; + let (input, offset) = common::parse_memory_reference(input)?; + let (input, source) = common::parse_arithmetic_operand(input)?; + + Ok(( + input, + Instruction::Store { + destination, + source, + offset, + }, + )) +} + +/// Parse the contents of a `PRAGMA` instruction. +pub fn parse_pragma<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, pragma_type) = token!(Identifier(v))(input)?; + // FIXME: Also allow Int (not just Identifier) + let (input, arguments) = many0(token!(Identifier(v)))(input)?; + let (input, data) = opt(token!(String(v)))(input)?; + Ok(( + input, + Instruction::Pragma { + name: pragma_type, + arguments, + data, + }, + )) +} + +/// Parse a pulse instruction, **including the PULSE keyword**. +/// +/// Unlike most other instructions, this can be _prefixed_ with the NONBLOCKING keyword, +/// and thus it expects and parses the PULSE token itself. +pub fn parse_pulse<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, blocking) = match token!(NonBlocking)(input) { + Ok((input, _)) => (input, false), + Err(_) => (input, true), + }; + // TODO (Kalan): Actually check that this is a pulse + let (input, _) = token!(Command(pulse))(input)?; + let (input, qubits) = parse_qubits(input)?; + let (input, name) = token!(String(v))(input)?; + let (input, waveform) = parse_waveform_invocation(input)?; + + Ok(( + input, + Instruction::Pulse { + blocking, + frame: FrameIdentifier { name, qubits }, + waveform: Box::new(waveform), + }, + )) +} + +/// Parse the contents of a `RAW-CAPTURE` instruction. +pub fn parse_raw_capture(input: ParserInput) -> ParserResult { + let (input, frame) = parse_frame_identifier(input)?; + let (input, duration) = parse_expression(input)?; + let (input, memory_reference) = parse_memory_reference(input)?; + + Ok(( + input, + Instruction::RawCapture { + frame, + duration, + memory_reference, + }, + )) +} + +/// Parse the contents of a `MEASURE` instruction. +pub fn parse_measurement(input: ParserInput) -> ParserResult { + let (input, qubit) = parse_qubit(input)?; + let (input, target) = match parse_memory_reference(input) { + Ok((input, target)) => (input, Some(target)), + Err(_) => (input, None), + }; + + Ok((input, Instruction::Measurement { qubit, target })) +} + +#[cfg(test)] +mod tests { + use crate::parser::lexer::lex; + use crate::{ + instruction::{Instruction, MemoryReference, Qubit, ScalarType, Vector}, + make_test, + }; + + use super::{parse_declare, parse_measurement, parse_pragma}; + + make_test!( + declare_instruction_length_1, + parse_declare, + "ro BIT", + Instruction::Declaration { + name: "ro".to_owned(), + sharing: None, + size: Vector { + data_type: ScalarType::Bit, + length: 1 + } + } + ); + + make_test!( + declare_instruction_length_n, + parse_declare, + "ro INTEGER[5]", + Instruction::Declaration { + name: "ro".to_owned(), + sharing: None, + size: Vector { + data_type: ScalarType::Integer, + length: 5 + } + } + ); + + make_test!( + measure_into_register, + parse_measurement, + "0 ro[0]", + Instruction::Measurement { + qubit: Qubit::Fixed(0), + target: Some(MemoryReference { + name: String::from("ro"), + index: 0 + }) + } + ); + + make_test!( + measure_discard, + parse_measurement, + "0", + Instruction::Measurement { + qubit: Qubit::Fixed(0), + target: None + } + ); + + make_test!( + measure_named_qubit, + parse_measurement, + "q0 ro[0]", + Instruction::Measurement { + qubit: Qubit::Variable(String::from("q0")), + target: Some(MemoryReference { + name: String::from("ro"), + index: 0 + }) + } + ); + + make_test!( + measure_named_qubit_discard, + parse_measurement, + "q0", + Instruction::Measurement { + qubit: Qubit::Variable(String::from("q0")), + target: None + } + ); + + make_test!( + pragma_inline_json, + parse_pragma, + "FILTER-NODE q35_unclassified \"{'module':'lodgepole.filters.io','filter_type':'DataBuffer','source':'q35_ro_rx/filter','publish':true,'params':{},'_type':'FilterNode'}\"", + Instruction::Pragma { + name: "FILTER-NODE".to_owned(), + arguments: vec!["q35_unclassified".to_owned()], + data: Some("{'module':'lodgepole.filters.io','filter_type':'DataBuffer','source':'q35_ro_rx/filter','publish':true,'params':{},'_type':'FilterNode'}".to_owned()) + } + ); +} diff --git a/src/parser/common.rs b/src/parser/common.rs new file mode 100644 index 00000000..42b460e0 --- /dev/null +++ b/src/parser/common.rs @@ -0,0 +1,208 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use std::collections::HashMap; + +use nom::{ + branch::alt, + combinator::{map, opt, value}, + multi::many0, + multi::{many1, separated_list0}, + sequence::{delimited, tuple}, +}; + +use crate::{ + expected_token, + expression::Expression, + instruction::{ + ArithmeticOperand, AttributeValue, FrameIdentifier, GateModifier, MemoryReference, Qubit, + ScalarType, Vector, WaveformInvocation, + }, + parser::lexer::Operator, + token, +}; + +use super::{ + error::{Error, ErrorKind}, + expression::parse_expression, + lexer::{DataType, Modifier, Token}, + ParserInput, ParserResult, +}; + +/// Parse the operand of an arithmetic instruction, which may be a literal integer, literal real number, or memory reference. +pub fn parse_arithmetic_operand<'a>(input: ParserInput<'a>) -> ParserResult<'a, ArithmeticOperand> { + alt(( + map( + tuple((opt(token!(Operator(o))), token!(Float(v)))), + |(op, v)| { + let sign = match op { + None => 1f64, + Some(Operator::Minus) => -1f64, + _ => panic!("Implement this error"), // TODO + }; + ArithmeticOperand::LiteralReal(sign * v) + }, + ), + map( + tuple((opt(token!(Operator(o))), token!(Integer(v)))), + |(op, v)| { + let sign = match op { + None => 1, + Some(Operator::Minus) => -1, + _ => panic!("Implement this error"), // TODO + }; + ArithmeticOperand::LiteralInteger(sign * (v as i64)) + }, + ), + map(parse_memory_reference, |f| { + ArithmeticOperand::MemoryReference(f) + }), + ))(input) +} + +/// Parse a single attribute key-value pair of a frame. The value may be either a frame or an expression. +pub fn parse_frame_attribute<'a>( + input: ParserInput<'a>, +) -> ParserResult<'a, (String, AttributeValue)> { + let (input, _) = token!(NewLine)(input)?; + let (input, _) = token!(Indentation)(input)?; + let (input, key) = token!(Identifier(v))(input)?; + let (input, _) = token!(Colon)(input)?; + let (input, value) = alt(( + map(token!(String(v)), AttributeValue::String), + map(parse_expression, |expression| { + AttributeValue::Expression(expression) + }), + ))(input)?; + Ok((input, (key, value))) +} + +/// Parse a frame identifier, such as `0 "rf"`. +pub fn parse_frame_identifier<'a>(input: ParserInput<'a>) -> ParserResult<'a, FrameIdentifier> { + let (input, qubits) = many1(parse_qubit)(input)?; + let (input, name) = token!(String(v))(input)?; + + Ok((input, FrameIdentifier { name, qubits })) +} + +/// Parse a gate modifier prefix, such as `CONTROLLED`. +pub fn parse_gate_modifier<'a>(input: ParserInput<'a>) -> ParserResult<'a, GateModifier> { + let (input, token) = token!(Modifier(v))(input)?; + Ok(( + input, + match token { + Modifier::Controlled => GateModifier::Controlled, + Modifier::Dagger => GateModifier::Dagger, + Modifier::Forked => GateModifier::Forked, + }, + )) +} + +/// Parse a reference to a memory location, such as `ro[5]`. +pub fn parse_memory_reference<'a>(input: ParserInput<'a>) -> ParserResult<'a, MemoryReference> { + let (input, name) = token!(Identifier(v))(input)?; + let (input, index) = opt(delimited( + token!(LBracket), + token!(Integer(v)), + token!(RBracket), + ))(input)?; + let index = index.unwrap_or(0); + Ok((input, MemoryReference { name, index })) +} + +/// Parse a named argument key-value pair, such as `foo: 42`. +pub fn parse_named_argument<'a>(input: ParserInput<'a>) -> ParserResult<'a, (String, Expression)> { + let (input, (name, _, value)) = + tuple((token!(Identifier(v)), token!(Colon), parse_expression))(input)?; + Ok((input, (name, value))) +} + +/// Parse the invocation of a waveform, such as `flat(iq: 1)`. +pub fn parse_waveform_invocation<'a>( + input: ParserInput<'a>, +) -> ParserResult<'a, WaveformInvocation> { + let (input, name) = token!(Identifier(v))(input)?; + let (input, parameter_tuples) = opt(delimited( + token!(LParenthesis), + separated_list0(token!(Comma), parse_named_argument), + token!(RParenthesis), + ))(input)?; + let parameter_tuples = parameter_tuples.unwrap_or_default(); + let parameters: HashMap<_, _> = parameter_tuples.into_iter().collect(); + + Ok((input, WaveformInvocation { name, parameters })) +} + +/// Parse a single qubit, which may be an integer (`1`), variable (`%q1`), or identifier (`q1`). +/// Per the specification, variable-named and identifier-named are valid in different locations, +/// but this parser is tolerant and accepts both as equivalent. +pub fn parse_qubit(input: ParserInput) -> ParserResult { + match input.split_first() { + None => Err(nom::Err::Error(Error { + input, + error: ErrorKind::UnexpectedEOF("something else".to_owned()), + })), + Some((Token::Integer(value), remainder)) => Ok((remainder, Qubit::Fixed(*value))), + Some((Token::Variable(name), remainder)) => Ok((remainder, Qubit::Variable(name.clone()))), + Some((Token::Identifier(name), remainder)) => { + Ok((remainder, Qubit::Variable(name.clone()))) + } + Some((other_token, _)) => { + expected_token!(input, other_token, stringify!($expected_variant).to_owned()) + } + } +} + +/// Parse zero or more qubits in sequence. +pub fn parse_qubits(input: ParserInput) -> ParserResult> { + many0(parse_qubit)(input) +} + +/// Parse a "vector" which is an integer index, such as `[0]` +pub fn parse_vector<'a>(input: ParserInput<'a>) -> ParserResult<'a, Vector> { + let (input, data_type_token) = token!(DataType(v))(input)?; + + let data_type = match data_type_token { + DataType::Bit => ScalarType::Bit, + DataType::Integer => ScalarType::Integer, + DataType::Real => ScalarType::Real, + DataType::Octet => ScalarType::Octet, + }; + + let (input, length) = opt(delimited( + token!(LBracket), + token!(Integer(v)), + token!(RBracket), + ))(input)?; + let length = length.unwrap_or(1); + + Ok((input, Vector { data_type, length })) +} + +/// Parse ahead past any sequence of newlines, comments, and semicolons, returning +/// once the first other token is encountered. +pub fn skip_newlines_and_comments<'a>(input: ParserInput<'a>) -> ParserResult<'a, ()> { + let (input, _) = many0(alt(( + value((), token!(Comment(v))), + token!(NewLine), + token!(Semicolon), + )))(input)?; + Ok((input, ())) +} + +/// Parse ahead to the next non-newline token. +pub fn skip_newlines<'a>(input: ParserInput<'a>) -> ParserResult<'a, ()> { + many0(token!(NewLine))(input).map(|(input, _)| (input, ())) +} diff --git a/src/parser/error.rs b/src/parser/error.rs new file mode 100644 index 00000000..301cdd8e --- /dev/null +++ b/src/parser/error.rs @@ -0,0 +1,98 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use std::fmt; + +use super::lexer::{Command, Token}; + +/// This is a superset of `(I, nom::ErrorKind)` that includes the additional errors specified by +/// [`ErrorKind`]. +pub struct Error { + /// The remainder of the input stream at the time of the error. + pub input: I, + /// The error that occurred. + pub error: ErrorKind, +} + +impl fmt::Debug for Error<&[Token]> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.input.first() { + Some(token) => write!(f, "{:?} (at {:?})", self.error, token), + None => write!(f, "{:?} (at EOF)", self.error), + } + } +} + +/// Parsing errors specific to Quil parsing +#[derive(Debug)] +pub enum ErrorKind { + UnexpectedEOF(String), + ExpectedToken { + actual: Token, + expected: String, + }, + /// An unknown identifier was encountered + UnknownIdentifier, + + /// An invalid literal was encountered. + /// + /// When encountered, this generally means a bug exists in the data that + /// was passed in or the parsing logic. + InvalidLiteral, + + /// A full parse was requested, but data was left over after parsing finished. + Partial, + + /// Tried to parse a kind of command and couldn't + /// TODO: Wrap actual error, the string is a lifetime cop-out + InvalidCommand { + command: Command, + error: String, + }, + + /// Tried to parse a gate and couldn't + InvalidGate, + + /// Unexpected start of an instruction + NotACommandOrGate, + + /// The end of input was reached + EndOfInput, + + /// An instruction was encountered which is not yet supported for parsing by this library + UnsupportedInstruction, + + /// An error occurred in an underlying nom parser. + Parser(nom::error::ErrorKind), +} + +impl From for ErrorKind { + fn from(k: nom::error::ErrorKind) -> Self { + ErrorKind::Parser(k) + } +} + +impl ::nom::error::ParseError for Error { + fn from_error_kind(input: I, kind: nom::error::ErrorKind) -> Self { + Self { + input, + error: kind.into(), + } + } + + fn append(_: I, _: nom::error::ErrorKind, other: Self) -> Self { + other + } +} diff --git a/src/parser/expression.rs b/src/parser/expression.rs new file mode 100644 index 00000000..9afafef1 --- /dev/null +++ b/src/parser/expression.rs @@ -0,0 +1,402 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use nom::combinator::opt; + +use crate::{ + expected_token, + expression::{Expression, ExpressionFunction, InfixOperator, PrefixOperator}, + imag, token, unexpected_eof, +}; + +use super::lexer::{Operator, Token}; +use super::{ParserInput, ParserResult}; + +#[derive(Debug, PartialEq, PartialOrd)] +enum Precedence { + Lowest, + Sum, + Product, + Call, +} + +impl From<&Token> for Precedence { + fn from(token: &Token) -> Self { + match token { + Token::Operator(Operator::Plus) | Token::Operator(Operator::Minus) => Precedence::Sum, + Token::Operator(Operator::Star) | Token::Operator(Operator::Slash) => { + Precedence::Product + } + // TODO: Is this used? + Token::LParenthesis => Precedence::Call, + _ => Precedence::Lowest, + } + } +} + +fn get_precedence(input: ParserInput) -> Precedence { + match input.first() { + Some(v) => Precedence::from(v), + None => Precedence::Lowest, + } +} + +/// Parse an expression at the head of the current input, for as long as the expression continues. +/// Return an error only if the first token(s) do not form an expression. +pub fn parse_expression(input: ParserInput) -> ParserResult { + parse(input, Precedence::Lowest) +} + +/// Recursively parse an expression as long as operator precedence is satisfied. +fn parse(input: ParserInput, precedence: Precedence) -> ParserResult { + let (input, prefix) = opt(parse_prefix)(input)?; + let (mut input, mut left) = match input.split_first() { + None => unexpected_eof!(input), + Some((Token::Integer(value), remainder)) => { + let (remainder, imaginary) = opt(parse_i)(remainder)?; + match imaginary { + None => Ok((remainder, Expression::Number(crate::real!(*value as f64)))), + Some(_) => Ok((remainder, Expression::Number(crate::imag!(*value as f64)))), + } + } + Some((Token::Float(value), remainder)) => { + let (remainder, imaginary) = opt(parse_i)(remainder)?; + match imaginary { + None => Ok((remainder, Expression::Number(crate::real!(*value as f64)))), + Some(_) => Ok((remainder, Expression::Number(crate::imag!(*value as f64)))), + } + } + Some((Token::Variable(name), remainder)) => { + Ok((remainder, Expression::Variable(name.clone()))) + } + Some((Token::Identifier(_), _)) => parse_expression_identifier(input), + Some((Token::LParenthesis, remainder)) => parse_grouped_expression(remainder), + Some((token, _)) => { + expected_token!(input, token, "expression".to_owned()) + } + }?; + + if let Some(prefix) = prefix { + left = Expression::Prefix { + operator: prefix, + expression: Box::new(left), + }; + } + + while get_precedence(input) > precedence { + match input.first() { + None => return Ok((input, left)), + Some(Token::Operator(_)) => { + let (remainder, expression) = parse_infix(input, left)?; + left = expression; + input = remainder; + } + Some(_) => return Ok((input, left)), + } + } + + Ok((input, left)) +} + +/// Returns successfully if the head of input is the identifier `i`, returns error otherwise. +fn parse_i(input: ParserInput) -> ParserResult<()> { + match input.split_first() { + None => unexpected_eof!(input), + Some((Token::Identifier(v), remainder)) if v == "i" => Ok((remainder, ())), + Some((other_token, _)) => expected_token!(input, other_token, "i".to_owned()), + } +} + +/// Given an expression function, parse the expression within its parentheses. +fn parse_function_call<'a>( + input: ParserInput<'a>, + function: ExpressionFunction, +) -> ParserResult<'a, Expression> { + let (input, _) = token!(LParenthesis)(input)?; + let (input, expression) = parse(input, Precedence::Lowest)?; // TODO: different precedence? + let (input, _) = token!(RParenthesis)(input)?; + Ok(( + input, + Expression::FunctionCall { + function, + expression: Box::new(expression), + }, + )) +} + +/// Identifiers have to be handled specially because some have special meaning +fn parse_expression_identifier(input: ParserInput) -> ParserResult { + match input.split_first() { + None => unexpected_eof!(input), + Some((Token::Identifier(ident), remainder)) => match ident.as_str() { + "cis" => parse_function_call(remainder, ExpressionFunction::Cis), + "cos" => parse_function_call(remainder, ExpressionFunction::Cosine), + "exp" => parse_function_call(remainder, ExpressionFunction::Exponent), + "i" => Ok((remainder, Expression::Number(imag!(1f64)))), + "pi" => Ok((remainder, Expression::PiConstant)), + "sin" => parse_function_call(remainder, ExpressionFunction::Sine), + other => expected_token!( + input, + Token::Identifier(other.to_owned()), + "defined function".to_owned() + ), + }, + Some((other_token, _)) => expected_token!(input, other_token, "function call".to_owned()), + } +} + +/// To be called following an opening parenthesis, this will parse the expression to its end +/// and then expect a closing right parenthesis. +fn parse_grouped_expression(input: ParserInput) -> ParserResult { + let (input, expression) = parse(input, Precedence::Lowest)?; + match input.split_first() { + None => unexpected_eof!(input), + Some((Token::RParenthesis, remainder)) => Ok((remainder, expression)), + Some((other_token, _)) => { + expected_token!(input, other_token, "right parenthesis".to_owned()) + } + } +} + +/// Parse an infix operator and then the expression to the right of the operator, and return the +/// resulting infixed expression. +fn parse_infix(input: ParserInput, left: Expression) -> ParserResult { + match input.split_first() { + None => unexpected_eof!(input), + Some((Token::Operator(token_operator), remainder)) => { + let expression_operator = match token_operator { + Operator::Plus => InfixOperator::Plus, + Operator::Minus => InfixOperator::Minus, + Operator::Caret => InfixOperator::Caret, + Operator::Slash => InfixOperator::Slash, + Operator::Star => InfixOperator::Star, + }; + let precedence = get_precedence(remainder); + let (remainder, right) = parse(remainder, precedence)?; + let infix_expression = Expression::Infix { + left: Box::new(left), + operator: expression_operator, + right: Box::new(right), + }; + Ok((remainder, infix_expression)) + } + Some((other_token, _)) => expected_token!(input, other_token, "infix operator".to_owned()), + } +} + +/// Return the prefix operator at the beginning of the input, if any. +fn parse_prefix(input: ParserInput) -> ParserResult { + match input.split_first() { + None => unexpected_eof!(input), + Some((Token::Operator(Operator::Minus), remainder)) => { + Ok((remainder, PrefixOperator::Minus)) + } + Some((other_token, _)) => expected_token!(input, other_token, "prefix operator".to_owned()), + } +} + +#[cfg(test)] +mod tests { + use crate::{expression::PrefixOperator, parser::lexer::lex}; + use crate::{ + expression::{Expression, ExpressionFunction, InfixOperator}, + imag, real, + }; + + use super::parse_expression; + + macro_rules! test { + ($name: ident, $parser: ident, $input: expr, $expected: expr) => { + #[test] + fn $name() { + let tokens = lex($input); + let (remainder, parsed) = $parser(&tokens).unwrap(); + assert_eq!(remainder.len(), 0); + assert_eq!(parsed, $expected); + } + }; + } + + // given a function and vector of (input, expected) tuples, invoke the function on each and + // panic on the first mismatch + fn compare(cases: Vec<(&str, Expression)>) { + for case in cases { + let tokens = lex(case.0); + let (remainder, parsed) = parse_expression(&tokens).unwrap(); + assert_eq!(remainder.len(), 0); + assert_eq!(case.1, parsed); + } + } + + // Round-trip expressions to validate parsing & display + #[test] + fn display() { + let cases = vec![ + "pi", + "sin(pi)", + "(1+(2*3))", + "((1+2)*3)", + "%theta", + "cis(%theta)", + "(%a+%b)", + ]; + + for case in cases { + let tokens = lex(case); + let (remainder, parsed) = parse_expression(&tokens).unwrap(); + assert_eq!(remainder.len(), 0); + assert_eq!(format!("{}", parsed), case); + } + } + + test!( + function_call, + parse_expression, + "sin(1)", + Expression::FunctionCall { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Number(real!(1f64))), + } + ); + + test!( + nested_function_call, + parse_expression, + "sin(sin(1))", + Expression::FunctionCall { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::FunctionCall { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Number(real!(1f64))), + }), + } + ); + + test!( + simple_infix, + parse_expression, + "1+2", + Expression::Infix { + left: Box::new(Expression::Number(real!(1f64))), + operator: InfixOperator::Plus, + right: Box::new(Expression::Number(real!(2f64))), + } + ); + + test!( + infix_with_function_call, + parse_expression, + "-i*sin(%theta/2)", + Expression::Infix { + left: Box::new(Expression::Prefix { + operator: PrefixOperator::Minus, + expression: Box::new(Expression::Number(imag!(1f64))), + }), + operator: InfixOperator::Star, + right: Box::new(Expression::FunctionCall { + function: ExpressionFunction::Sine, + expression: Box::new(Expression::Infix { + left: Box::new(Expression::Variable("theta".to_owned())), + operator: InfixOperator::Slash, + right: Box::new(Expression::Number(real!(2f64))), + }), + }), + } + ); + + test!( + infix_parenthesized, + parse_expression, + "(1+2i)*%a", + Expression::Infix { + left: Box::new(Expression::Infix { + left: Box::new(Expression::Number(real!(1f64))), + operator: InfixOperator::Plus, + right: Box::new(Expression::Number(imag!(2f64))), + }), + operator: InfixOperator::Star, + right: Box::new(Expression::Variable("a".to_owned())), + } + ); + + #[test] + fn parenthetical() { + let cases = vec![ + ( + "1 + ( 2 + 3 )", + Expression::Infix { + left: Box::new(Expression::Number(real!(1f64))), + operator: InfixOperator::Plus, + right: Box::new(Expression::Infix { + left: Box::new(Expression::Number(real!(2f64))), + operator: InfixOperator::Plus, + right: Box::new(Expression::Number(real!(3f64))), + }), + }, + ), + ( + "1+(2+3)", + Expression::Infix { + left: Box::new(Expression::Number(real!(1f64))), + operator: InfixOperator::Plus, + right: Box::new(Expression::Infix { + left: Box::new(Expression::Number(real!(2f64))), + operator: InfixOperator::Plus, + right: Box::new(Expression::Number(real!(3f64))), + }), + }, + ), + ( + "(1+2)+3", + Expression::Infix { + left: Box::new(Expression::Infix { + left: Box::new(Expression::Number(real!(1f64))), + operator: InfixOperator::Plus, + right: Box::new(Expression::Number(real!(2f64))), + }), + operator: InfixOperator::Plus, + right: Box::new(Expression::Number(real!(3f64))), + }, + ), + ( + "(((cos(((pi))))))", + Expression::FunctionCall { + function: ExpressionFunction::Cosine, + expression: Box::new(Expression::PiConstant), + }, + ), + ]; + + compare(cases); + } + + #[test] + fn pi() { + let cases = vec![("pi", Expression::PiConstant)]; + + compare(cases); + } + + #[test] + fn variable() { + let cases = vec![ + ("%theta", Expression::Variable("theta".to_owned())), + ("%pi", Expression::Variable("pi".to_owned())), + ("%sin", Expression::Variable("sin".to_owned())), + ]; + + compare(cases); + } +} diff --git a/src/parser/gate.rs b/src/parser/gate.rs new file mode 100644 index 00000000..76b42d8a --- /dev/null +++ b/src/parser/gate.rs @@ -0,0 +1,46 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use nom::{combinator::opt, multi::many0, multi::separated_list0, sequence::delimited}; + +use crate::{instruction::Instruction, token}; + +use super::{ + common::{self, parse_gate_modifier}, + expression::parse_expression, + ParserInput, ParserResult, +}; + +/// Parse a gate instruction. +pub fn parse_gate<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + let (input, modifiers) = many0(parse_gate_modifier)(input)?; + let (input, name) = token!(Identifier(v))(input)?; + let (input, parameters) = opt(delimited( + token!(LParenthesis), + separated_list0(token!(Comma), parse_expression), + token!(RParenthesis), + ))(input)?; + let parameters = parameters.unwrap_or_default(); + let (input, qubits) = common::parse_qubits(input)?; + Ok(( + input, + Instruction::Gate { + name, + modifiers, + parameters, + qubits, + }, + )) +} diff --git a/src/parser/instruction.rs b/src/parser/instruction.rs new file mode 100644 index 00000000..72377a37 --- /dev/null +++ b/src/parser/instruction.rs @@ -0,0 +1,418 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use nom::{ + combinator::all_consuming, + multi::{many0, many1}, + sequence::{delimited, preceded}, +}; + +use crate::{ + instruction::{ArithmeticOperator, Instruction}, + token, +}; + +use super::{ + command, common, + error::{Error, ErrorKind}, + gate, + lexer::{Command, Token}, + ParserInput, ParserResult, +}; + +/// Parse the next instructon from the input, skipping past leading newlines, comments, and semicolons. +pub fn parse_instruction(input: ParserInput) -> ParserResult { + let (input, _) = common::skip_newlines_and_comments(input)?; + match input.split_first() { + None => Err(nom::Err::Error(Error { + input, + error: ErrorKind::EndOfInput, + })), + Some((Token::Command(command), remainder)) => { + match command { + Command::Add => command::parse_arithmetic(ArithmeticOperator::Add, remainder), + // Command::And => {} + Command::Capture => command::parse_capture(remainder), + // Command::Convert => {} + Command::Declare => command::parse_declare(remainder), + Command::DefCal => command::parse_defcal(remainder), + // Command::DefCircuit => {} + Command::DefFrame => command::parse_defframe(remainder), + // Command::DefGate => Ok((remainder, cut(parse_command_defgate))), + Command::DefWaveform => command::parse_defwaveform(remainder), + Command::Delay => command::parse_delay(remainder), + Command::Div => command::parse_arithmetic(ArithmeticOperator::Divide, remainder), + // Command::Eq => {} + // Command::Exchange => {} + // Command::Fence => {} + // Command::GE => {} + // Command::GT => {} + Command::Halt => Ok((remainder, Instruction::Halt)), + // Command::Include => {} + // Command::Ior => {} + Command::Jump => command::parse_jump(remainder), + Command::JumpUnless => command::parse_jump_unless(remainder), + Command::JumpWhen => command::parse_jump_when(remainder), + Command::Label => command::parse_label(remainder), + // Command::LE => {} + Command::Load => command::parse_load(remainder), + // Command::LT => {} + Command::Measure => command::parse_measurement(remainder), + Command::Move => command::parse_move(remainder), + Command::Exchange => command::parse_exchange(remainder), + Command::Mul => command::parse_arithmetic(ArithmeticOperator::Multiply, remainder), + // Command::Neg => {} + // Command::Nop => {} + // Command::Not => {} + Command::Pragma => command::parse_pragma(remainder), + Command::Pulse => command::parse_pulse(input), + Command::RawCapture => command::parse_raw_capture(remainder), + // Command::Reset => {} + // Command::SetFrequency => {} + // Command::SetPhase => {} + // Command::SetScale => {} + // Command::ShiftFrequency => {} + // Command::ShiftPhase => {} + Command::Store => command::parse_store(remainder), + Command::Sub => command::parse_arithmetic(ArithmeticOperator::Subtract, remainder), + // Command::Wait => {} + // Command::Xor => {} + _ => Err(nom::Err::Failure(Error { + input: &input[..1], + error: ErrorKind::UnsupportedInstruction, + })), + } + .map_err(|err| { + nom::Err::Failure(Error { + input: &input[..1], + error: ErrorKind::InvalidCommand { + command: command.clone(), + error: format!("{}", err), + }, + }) + }) + } + Some((Token::NonBlocking, _)) => command::parse_pulse(input), + Some((Token::Identifier(_), _)) => gate::parse_gate(input), + Some((_, _)) => Err(nom::Err::Failure(Error { + input: &input[..1], + error: ErrorKind::NotACommandOrGate, + })), + } +} + +/// Parse all instructions from the input, trimming leading and trailing newlines and comments. +/// Returns an error if it does not reach the end of input. +pub fn parse_instructions(input: ParserInput) -> ParserResult> { + all_consuming(delimited( + common::skip_newlines_and_comments, + many0(parse_instruction), + common::skip_newlines_and_comments, + ))(input) +} + +/// Parse a block of indented "block instructions." +pub fn parse_block(input: ParserInput) -> ParserResult> { + many1(parse_block_instruction)(input) +} + +/// Parse a single indented "block instruction." +pub fn parse_block_instruction<'a>(input: ParserInput<'a>) -> ParserResult<'a, Instruction> { + preceded( + token!(NewLine), + preceded(token!(Indentation), parse_instruction), + )(input) +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use crate::{ + expression::Expression, + instruction::{ + ArithmeticOperand, ArithmeticOperator, AttributeValue, FrameIdentifier, Instruction, + MemoryReference, Qubit, WaveformInvocation, + }, + make_test, real, + }; + use crate::{instruction::Calibration, parser::lexer::lex}; + + use super::parse_instructions; + + make_test!( + semicolons_are_newlines, + parse_instructions, + "X 0; Y 1\nZ 2", + vec![ + Instruction::Gate { + name: "X".to_owned(), + parameters: vec![], + qubits: vec![Qubit::Fixed(0)], + modifiers: vec![], + }, + Instruction::Gate { + name: "Y".to_owned(), + parameters: vec![], + qubits: vec![Qubit::Fixed(1)], + modifiers: vec![], + }, + Instruction::Gate { + name: "Z".to_owned(), + parameters: vec![], + qubits: vec![Qubit::Fixed(2)], + modifiers: vec![], + }, + ] + ); + + make_test!( + arithmetic, + parse_instructions, + "ADD ro 2\nMUL ro 1.0\nSUB ro[1] -3\nDIV ro[1] -1.0\nADD ro[1] ro[2]", + vec![ + Instruction::Arithmetic { + operator: ArithmeticOperator::Add, + destination: ArithmeticOperand::MemoryReference(MemoryReference { + name: "ro".to_owned(), + index: 0 + }), + source: ArithmeticOperand::LiteralInteger(2), + }, + Instruction::Arithmetic { + operator: ArithmeticOperator::Multiply, + destination: ArithmeticOperand::MemoryReference(MemoryReference { + name: "ro".to_owned(), + index: 0 + }), + source: ArithmeticOperand::LiteralReal(1.0), + }, + Instruction::Arithmetic { + operator: ArithmeticOperator::Subtract, + destination: ArithmeticOperand::MemoryReference(MemoryReference { + name: "ro".to_owned(), + index: 1 + }), + source: ArithmeticOperand::LiteralInteger(-3), + }, + Instruction::Arithmetic { + operator: ArithmeticOperator::Divide, + destination: ArithmeticOperand::MemoryReference(MemoryReference { + name: "ro".to_owned(), + index: 1 + }), + source: ArithmeticOperand::LiteralReal(-1f64), + }, + Instruction::Arithmetic { + operator: ArithmeticOperator::Add, + destination: ArithmeticOperand::MemoryReference(MemoryReference { + name: "ro".to_owned(), + index: 1 + }), + source: ArithmeticOperand::MemoryReference(MemoryReference { + name: "ro".to_owned(), + index: 2 + }), + } + ] + ); + + make_test!( + capture_instructions, + parse_instructions, + "CAPTURE 0 \"rx\" my_custom_waveform ro\nRAW-CAPTURE 0 1 \"rx\" 2e9 ro", + vec![ + Instruction::Capture { + frame: FrameIdentifier { + name: "rx".to_owned(), + qubits: vec![Qubit::Fixed(0)] + }, + waveform: Box::new(WaveformInvocation { + name: "my_custom_waveform".to_owned(), + parameters: HashMap::new() + }), + memory_reference: MemoryReference { + name: "ro".to_owned(), + index: 0 + } + }, + Instruction::RawCapture { + frame: FrameIdentifier { + name: "rx".to_owned(), + qubits: vec![Qubit::Fixed(0), Qubit::Fixed(1)] + }, + duration: Expression::Number(real![2e9]), + memory_reference: MemoryReference { + name: "ro".to_owned(), + index: 0 + } + } + ] + ); + + make_test!(comment, parse_instructions, "# Questions:\n\n\n", vec![]); + + make_test!( + comment_and_gate, + parse_instructions, + "# Questions:\nX 0", + vec![Instruction::Gate { + name: "X".to_owned(), + parameters: vec![], + qubits: vec![Qubit::Fixed(0)], + modifiers: vec![], + }] + ); + + make_test!( + comment_after_block, + parse_instructions, + "DEFFRAME 0 \"ro_rx\":\n\tDIRECTION: \"rx\"\n\n# (Pdb) settings.gates[GateID(name=\"x180\", targets=(0,))]\n\n", + vec![Instruction::FrameDefinition { + identifier: FrameIdentifier { name: "ro_rx".to_owned(), qubits: vec![Qubit::Fixed(0)] }, + attributes: [("DIRECTION".to_owned(), AttributeValue::String("rx".to_owned()))].iter().cloned().collect() + }]); + + make_test!( + simple_gate, + parse_instructions, + "RX 0", + vec![Instruction::Gate { + name: "RX".to_owned(), + parameters: vec![], + qubits: vec![Qubit::Fixed(0)], + modifiers: vec![], + }] + ); + + make_test!( + parametric_gate, + parse_instructions, + "RX(pi) 10", + vec![Instruction::Gate { + name: "RX".to_owned(), + parameters: vec![Expression::PiConstant], + qubits: vec![Qubit::Fixed(10)], + modifiers: vec![], + }] + ); + + make_test!( + parametric_calibration, + parse_instructions, + "DEFCAL RX(%theta) %qubit:\n\tPULSE 1 \"xy\" custom_waveform(a: 1)", + vec![Instruction::CalibrationDefinition(Box::new(Calibration { + name: "RX".to_owned(), + parameters: vec![Expression::Variable("theta".to_owned())], + qubits: vec![Qubit::Variable("qubit".to_owned())], + modifiers: vec![], + instructions: vec![Instruction::Pulse { + blocking: true, + frame: FrameIdentifier { + name: "xy".to_owned(), + qubits: vec![Qubit::Fixed(1)] + }, + waveform: Box::new(WaveformInvocation { + name: "custom_waveform".to_owned(), + parameters: [("a".to_owned(), Expression::Number(crate::real![1f64]))] + .iter() + .cloned() + .collect() + }) + }] + }))] + ); + + make_test!( + frame_definition, + parse_instructions, + "DEFFRAME 0 \"rx\":\n\tINITIAL-FREQUENCY: 2e9", + vec![Instruction::FrameDefinition { + identifier: FrameIdentifier { + name: "rx".to_owned(), + qubits: vec![Qubit::Fixed(0)] + }, + attributes: [( + "INITIAL-FREQUENCY".to_owned(), + AttributeValue::Expression(Expression::Number(crate::real![2e9])) + )] + .iter() + .cloned() + .collect() + }] + ); + + make_test!( + control_flow, + parse_instructions, + "LABEL @hello\nJUMP @hello\nJUMP-WHEN @hello ro", + vec![ + Instruction::Label("hello".to_owned()), + Instruction::Jump { + target: "hello".to_owned() + }, + Instruction::JumpWhen { + target: "hello".to_owned(), + condition: MemoryReference { + name: "ro".to_owned(), + index: 0 + } + } + ] + ); + + make_test!( + pulse, + parse_instructions, + "PULSE 0 \"xy\" custom\nNONBLOCKING PULSE 0 \"xy\" custom", + vec![ + Instruction::Pulse { + blocking: true, + frame: FrameIdentifier { + name: "xy".to_owned(), + qubits: vec![Qubit::Fixed(0)] + }, + waveform: Box::new(WaveformInvocation { + name: "custom".to_owned(), + parameters: HashMap::new() + }) + }, + Instruction::Pulse { + blocking: false, + frame: FrameIdentifier { + name: "xy".to_owned(), + qubits: vec![Qubit::Fixed(0)] + }, + waveform: Box::new(WaveformInvocation { + name: "custom".to_owned(), + parameters: HashMap::new() + }) + } + ] + ); + + make_test!( + moveit, + parse_instructions, + "MOVE a 1.0", + vec![Instruction::Move { + destination: ArithmeticOperand::MemoryReference(MemoryReference { + name: "a".to_owned(), + index: 0 + }), + source: ArithmeticOperand::LiteralReal(1.0) + }] + ); +} diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs new file mode 100644 index 00000000..96f11d61 --- /dev/null +++ b/src/parser/lexer.rs @@ -0,0 +1,584 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use nom::{ + branch::alt, + bytes::complete::{is_a, is_not, tag, take_until, take_while, take_while1}, + character::complete::{digit1, one_of}, + combinator::{all_consuming, map, recognize, value}, + multi::many0, + number::complete::float, + sequence::{delimited, preceded, terminated, tuple}, + IResult, +}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Token { + As, + Colon, + Comma, + Command(Command), + Comment(String), + // Constant(Constant), + DataType(DataType), + Float(f64), + Identifier(String), + Indentation, + Integer(u64), + Label(String), + LBracket, + LParenthesis, + NonBlocking, + MathematicalFunction(MathematicalFunction), + Matrix, + Modifier(Modifier), + NewLine, + Offset, + Operator(Operator), + Permutation, + RBracket, + RParenthesis, + Semicolon, + Sharing, + String(String), + Variable(String), +} + +impl nom::InputLength for Token { + fn input_len(&self) -> usize { + // All tokens take up exactly one place in the input token stream + 1 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Command { + Add, + And, + Capture, + Convert, + Declare, + DefCal, + DefCircuit, + DefFrame, + DefGate, + DefWaveform, + Delay, + Div, + Eq, + Exchange, + Fence, + GE, + GT, + Halt, + Include, + Ior, + Jump, + JumpUnless, + JumpWhen, + Label, + LE, + Load, + LT, + Measure, + Move, + Mul, + Neg, + Nop, + Not, + Pragma, + Pulse, + RawCapture, + Reset, + SetFrequency, + SetPhase, + SetScale, + ShiftFrequency, + ShiftPhase, + Store, + Sub, + Wait, + Xor, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum DataType { + Bit, + Octet, + Real, + Integer, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum MathematicalFunction { + Cis, + Cos, + Exp, + Sin, + Sqrt, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Modifier { + Controlled, + Dagger, + Forked, // Not in the Quil grammar +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Operator { + Caret, + Minus, + Plus, + Slash, + Star, +} + +pub type LexResult<'a> = IResult<&'a str, Token>; + +/// Completely lex a string, returning the tokens within. Panics if the string cannot be completely read. +pub fn lex(input: &str) -> Vec { + let result: Result<(&str, Vec), String> = + all_consuming(_lex)(input).map_err(|err| format!("Error remains: {:?}", err)); + result.unwrap().1 +} + +fn _lex(input: &str) -> IResult<&str, Vec> { + terminated( + many0(alt(( + value(Token::Indentation, tag(" ")), + preceded(many0(tag(" ")), lex_token), + ))), + many0(one_of("\n\t ")), + )(input) +} + +fn lex_token(input: &str) -> LexResult { + alt(( + lex_comment, + // Instruction must come before identifier + lex_instruction, + lex_data_type, + lex_modifier, + lex_punctuation, + lex_label, + lex_string, + // Operator must come before number (or it may be parsed as a prefix) + lex_operator, + lex_number, + lex_variable, + lex_non_blocking, + // This should come last because it's sort of a catch all + lex_identifier, + ))(input) +} + +fn lex_data_type(input: &str) -> LexResult { + alt(( + value(Token::DataType(DataType::Bit), tag("BIT")), + value(Token::DataType(DataType::Integer), tag("INTEGER")), + value(Token::DataType(DataType::Octet), tag("OCTET")), + value(Token::DataType(DataType::Real), tag("REAL")), + ))(input) +} + +fn lex_comment(input: &str) -> LexResult { + let (input, _) = tag("#")(input)?; + let (input, content) = is_not("\n")(input)?; + Ok((input, Token::Comment(content.to_owned()))) +} + +fn lex_instruction(input: &str) -> LexResult { + use Command::*; + + // TODO: Switch these `map`s over to `value`s + + // The `alt`s are nested because there is a limit to how many branches are allowed in one of them + // https://github.com/Geal/nom/issues/1144 + let result = alt(( + alt(( + map(tag("DEFGATE"), |_| DefGate), + map(tag("DEFCIRCUIT"), |_| DefCircuit), + map(tag("MEASURE"), |_| Measure), + map(tag("HALT"), |_| Halt), + map(tag("JUMP-WHEN"), |_| JumpWhen), + map(tag("JUMP-UNLESS"), |_| JumpUnless), + // Note: this must follow the other jump commands + map(tag("JUMP"), |_| Jump), + map(tag("RESET"), |_| Reset), + map(tag("WAIT"), |_| Wait), + map(tag("NOP"), |_| Nop), + map(tag("INCLUDE"), |_| Include), + map(tag("PRAGMA"), |_| Pragma), + map(tag("DECLARE"), |_| Declare), + value(Label, tag("LABEL")), + )), + alt(( + map(tag("ADD"), |_| Add), + map(tag("AND"), |_| And), + map(tag("CONVERT"), |_| Convert), + map(tag("DIV"), |_| Div), + map(tag("EQ"), |_| Eq), + map(tag("EXCHANGE"), |_| Exchange), + map(tag("GE"), |_| GE), + map(tag("GT"), |_| GT), + map(tag("IOR"), |_| Ior), + map(tag("LE"), |_| LE), + map(tag("LOAD"), |_| Load), + map(tag("LT"), |_| LT), + map(tag("MOVE"), |_| Move), + map(tag("MUL"), |_| Mul), + map(tag("NEG"), |_| Neg), + map(tag("NOT"), |_| Not), + map(tag("STORE"), |_| Store), + map(tag("SUB"), |_| Sub), + map(tag("XOR"), |_| Xor), + )), + alt(( + value(Capture, tag("CAPTURE")), + value(DefCal, tag("DEFCAL")), + value(DefFrame, tag("DEFFRAME")), + value(DefWaveform, tag("DEFWAVEFORM")), + value(Delay, tag("DELAY")), + value(Fence, tag("FENCE")), + value(Pulse, tag("PULSE")), + value(RawCapture, tag("RAW-CAPTURE")), + value(SetFrequency, tag("SET-FREQUENCY")), + value(SetPhase, tag("SET-PHASE")), + value(SetScale, tag("SET-SCALE")), + value(ShiftFrequency, tag("SHIFT-FREQUENCY")), + value(ShiftPhase, tag("SHIFT-PHASE")), + )), + ))(input)?; + Ok((result.0, Token::Command(result.1))) +} + +fn is_valid_identifier_character(chr: char) -> bool { + is_valid_identifier_leading_character(chr) || chr.is_ascii_digit() || chr == '\\' || chr == '-' +} + +fn is_valid_identifier_leading_character(chr: char) -> bool { + chr.is_ascii_alphabetic() || chr == '_' +} + +fn lex_identifier_raw(input: &str) -> IResult<&str, String> { + map( + tuple(( + take_while1(is_valid_identifier_leading_character), + take_while(is_valid_identifier_character), + )), + |result| [result.0, result.1].concat(), + )(input) +} + +fn lex_identifier(input: &str) -> LexResult { + lex_identifier_raw(input).map(|(input, ident)| (input, Token::Identifier(ident))) +} + +fn lex_label(input: &str) -> LexResult { + let (input, _) = tag("@")(input)?; + let (input, label) = lex_identifier_raw(input)?; + Ok((input, Token::Label(label))) +} + +fn lex_non_blocking(input: &str) -> LexResult { + value(Token::NonBlocking, tag("NONBLOCKING"))(input) +} + +fn lex_number(input: &str) -> LexResult { + let (input, float_string): (&str, &str) = recognize(float)(input)?; + let integer_parse_result: IResult<&str, _> = all_consuming(digit1)(float_string); + Ok(( + input, + match integer_parse_result { + Ok(_) => Token::Integer(float_string.parse::().unwrap()), + Err(_) => Token::Float(float(float_string)?.1 as f64), + }, + )) +} + +fn lex_modifier(input: &str) -> LexResult { + alt(( + value(Token::As, tag("AS")), + value(Token::Matrix, tag("MATRIX")), + value(Token::Modifier(Modifier::Controlled), tag("CONTROLLED")), + value(Token::Modifier(Modifier::Dagger), tag("DAGGER")), + value(Token::Modifier(Modifier::Forked), tag("FORKED")), + value(Token::Permutation, tag("PERMUTATION")), + value(Token::Sharing, tag("SHARING")), + ))(input) +} + +fn lex_operator(input: &str) -> LexResult { + use Operator::*; + map( + alt(( + value(Caret, tag("^")), + value(Minus, tag("-")), + value(Plus, tag("+")), + value(Slash, tag("/")), + value(Star, tag("*")), + )), + Token::Operator, + )(input) +} + +fn lex_punctuation(input: &str) -> LexResult { + use Token::*; + // println!("'{}'", input); + alt(( + value(Colon, tag(":")), + value(Comma, tag(",")), + value(Indentation, alt((tag(" "), tag("\t")))), + value(LBracket, tag("[")), + value(LParenthesis, tag("(")), + value(NewLine, is_a("\n")), + value(RBracket, tag("]")), + value(RParenthesis, tag(")")), + value(Semicolon, tag(";")), + ))(input) +} + +fn lex_string(input: &str) -> LexResult { + map( + delimited(tag("\""), take_until("\""), tag("\"")), + |v: &str| Token::String(v.to_owned()), + )(input) +} + +fn lex_variable(input: &str) -> LexResult { + map(preceded(tag("%"), lex_identifier_raw), |ident| { + Token::Variable(ident) + })(input) +} + +#[cfg(test)] +mod tests { + use super::{lex, Command, Operator, Token}; + + #[test] + fn comment() { + let input = "# hello\n#world"; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::Comment(" hello".to_owned()), + Token::NewLine, + Token::Comment("world".to_owned()) + ] + ) + } + + #[test] + fn keywords() { + let input = "DEFGATE DEFCIRCUIT JUMP-WHEN MATRIX"; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::Command(Command::DefGate), + Token::Command(Command::DefCircuit), + Token::Command(Command::JumpWhen), + Token::Matrix, + ] + ) + } + + #[test] + fn number() { + let input = "2 2i 2.0 2e3 2.0e3 (1+2i)"; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::Integer(2), + Token::Integer(2), + Token::Identifier("i".to_owned()), + Token::Float(2.0), + Token::Float(2000f64), + Token::Float(2000f64), + Token::LParenthesis, + Token::Integer(1), + Token::Operator(Operator::Plus), + Token::Integer(2), + Token::Identifier("i".to_owned()), + Token::RParenthesis + ] + ) + } + + #[test] + fn string() { + let input = "\"hello\"\n\"world\""; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::String("hello".to_owned()), + Token::NewLine, + Token::String("world".to_owned()) + ] + ) + } + + #[test] + fn gate_operation() { + let input = "I 0; RX 1\nCZ 0 1"; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::Identifier("I".to_owned()), + Token::Integer(0), + Token::Semicolon, + Token::Identifier("RX".to_owned()), + Token::Integer(1), + Token::NewLine, + Token::Identifier("CZ".to_owned()), + Token::Integer(0), + Token::Integer(1), + ] + ) + } + + #[test] + fn label() { + let input = "@hello\n@world"; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::Label("hello".to_owned()), + Token::NewLine, + Token::Label("world".to_owned()) + ] + ) + } + + #[test] + fn indentation() { + let input = " "; + let tokens = lex(input); + assert_eq!(tokens, vec![Token::Indentation,]) + } + + #[test] + fn indented_block() { + let input = "DEFGATE Name AS PERMUTATION:\n\t1,0\n 0,1"; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::Command(Command::DefGate), + Token::Identifier("Name".to_owned()), + Token::As, + Token::Permutation, + Token::Colon, + Token::NewLine, + Token::Indentation, + Token::Integer(1), + Token::Comma, + Token::Integer(0), + Token::NewLine, + Token::Indentation, + Token::Integer(0), + Token::Comma, + Token::Integer(1), + ] + ) + } + + #[test] + fn surrounding_whitespace() { + let input = "\nI 0\n \n"; + let tokens = lex(input); + assert_eq!( + tokens, + vec![ + Token::NewLine, + Token::Identifier("I".to_owned()), + Token::Integer(0), + Token::NewLine, + Token::Indentation, + Token::NewLine + ] + ) + } + + /// Test that an entire sample program can be lexed without failure. + #[test] + fn whole_program() { + let input = "DECLARE ro BIT[1] + DEFGATE HADAMARD AS MATRIX: + \t(1/sqrt(2)),(1/sqrt(2)) + \t(1/sqrt(2)),((-1)/sqrt(2)) + + DEFGATE RX(%theta) AS MATRIX: + \tcos((%theta/2)),((-1i)*sin((%theta/2))) + \t((-1i)*sin((%theta/2))),cos((%theta/2)) + + DEFGATE Name AS PERMUTATION: + \t1,0 + \t0,1 + + DEFCIRCUIT SIMPLE: + \tX 0 + \tX 1 + + RX 0 + CZ 0 1 + MEASURE 0 ro[0] + RESET 0 + RESET + CAPTURE 0 \"out\" my_waveform() iq[0] + DEFCAL X 0: + \tPULSE 0 \"xy\" my_waveform() + + DEFCAL RX(%theta) 0: + \tPULSE 0 \"xy\" my_waveform() + + DEFCAL MEASURE 0 %dest: + \tDECLARE iq REAL[2] + \tCAPTURE 0 \"out\" flat(duration: 1000000, iqs: (2+3i)) iq[0] + + DEFFRAME 0 \"xy\": + \tSAMPLE-RATE: 3000 + + DEFFRAME 0 \"xy\": + \tDIRECTION: \"rx\" + \tCENTER-FREQUENCY: 1000 + \tHARDWARE-OBJECT: \"some object\" + \tINITIAL-FREQUENCY: 2000 + \tSAMPLE-RATE: 3000 + + DELAY 0 100 + DELAY 0 \"xy\" 100000000 + FENCE 0 + FENCE 0 1 + PULSE 0 \"xy\" my_waveform() + PULSE 0 1 \"cz\" my_parametrized_waveform(a: 1) + RAW-CAPTURE 0 \"out\" 200000000 iqs[0] + SET-FREQUENCY 0 \"xy\" 5400000000 + SET-PHASE 0 \"xy\" pi + SET-SCALE 0 \"xy\" pi + SHIFT-FREQUENCY 0 \"ro\" 6100000000 + SHIFT-PHASE 0 \"xy\" (-pi) + SHIFT-PHASE 0 \"xy\" (%theta*(2/pi)) + SWAP-PHASES 2 3 \"xy\" 3 4 \"xy\""; + + lex(input); + } +} diff --git a/src/parser/macros.rs b/src/parser/macros.rs new file mode 100644 index 00000000..b41a9eb6 --- /dev/null +++ b/src/parser/macros.rs @@ -0,0 +1,89 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +#[macro_export] +macro_rules! expected_token { + ($input: expr, $actual:expr, $expected:expr) => {{ + use crate::parser::error::{Error, ErrorKind}; + Err(nom::Err::Error(Error { + input: $input, + error: ErrorKind::ExpectedToken { + actual: $actual.clone(), + expected: $expected, + }, + })) + }}; +} + +#[macro_export] +macro_rules! token { + ($expected_variant: ident) => {{ + use crate::expected_token; + use crate::parser::error::{Error, ErrorKind}; + use crate::parser::lexer::Token; + move |input: ParserInput<'a>| match input.split_first() { + None => Err(nom::Err::Error(Error { + input, + error: ErrorKind::UnexpectedEOF("something else".to_owned()), + })), + Some((Token::$expected_variant, remainder)) => Ok((remainder, ())), + Some((other_token, _)) => { + expected_token!(input, other_token, stringify!($expected_variant).to_owned()) + } + } + }}; + ($expected_variant: ident($contents: ident)) => {{ + use crate::expected_token; + use crate::parser::error::{Error, ErrorKind}; + use crate::parser::lexer::Token; + move |input: ParserInput<'a>| match input.split_first() { + None => Err(nom::Err::Error(Error { + input, + error: ErrorKind::UnexpectedEOF("something else".to_owned()), + })), + Some((Token::$expected_variant($contents), remainder)) => { + Ok((remainder, $contents.clone())) + } + Some((other_token, _)) => { + expected_token!(input, other_token, stringify!($expected_variant).to_owned()) + } + } + }}; +} + +#[macro_export] +macro_rules! unexpected_eof { + ($input: expr) => {{ + use crate::parser::error::{Error, ErrorKind}; + Err(nom::Err::Error(Error { + input: $input, + error: ErrorKind::UnexpectedEOF("something else".to_owned()), + })) + }}; +} + +#[macro_export] +macro_rules! make_test { + ($name: ident, $parser: ident, $input: expr, $expected: expr) => { + #[test] + fn $name() { + let tokens = lex($input); + let (remainder, parsed) = $parser(&tokens).unwrap(); + assert_eq!(remainder.len(), 0); + assert_eq!(parsed, $expected); + } + }; +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 00000000..6c506328 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,32 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +mod command; +mod gate; +mod macros; + +pub mod common; +pub mod error; +pub mod expression; +pub mod instruction; +pub mod lexer; + +use nom::IResult; + +use error::Error; +use lexer::Token; + +pub type ParserInput<'a> = &'a [Token]; +pub type ParserResult<'a, R> = IResult<&'a [Token], R, Error<&'a [Token]>>; diff --git a/src/program/calibration.rs b/src/program/calibration.rs new file mode 100644 index 00000000..eb848938 --- /dev/null +++ b/src/program/calibration.rs @@ -0,0 +1,269 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use std::collections::HashMap; + +use crate::{ + expression::Expression, + instruction::{Calibration, GateModifier, Instruction, Qubit}, +}; + +/// A collection of Quil calibrations (`DEFCAL` instructions) with utility methods. +#[derive(Clone, Debug, Default)] +pub struct CalibrationSet { + calibrations: Vec, +} + +struct MatchedCalibration<'a> { + pub calibration: &'a Calibration, + pub fixed_qubit_count: usize, +} + +impl<'a> MatchedCalibration<'a> { + pub fn new(calibration: &'a Calibration) -> Self { + Self { + calibration, + fixed_qubit_count: calibration + .qubits + .iter() + .filter(|q| match q { + Qubit::Fixed(_) => true, + Qubit::Variable(_) => false, + }) + .count(), + } + } +} + +impl CalibrationSet { + pub fn new() -> Self { + CalibrationSet { + calibrations: vec![], + } + } + + /// Given an instruction, return the instructions to which it is expanded if there is a match. + pub fn expand(&self, instruction: &Instruction) -> Option> { + match instruction { + Instruction::Gate { + name, + modifiers, + parameters, + qubits, + } => { + let matching_calibration = + self.get_match_for_gate(modifiers, name, parameters, qubits); + + match matching_calibration { + Some(calibration) => { + let mut qubit_expansions: HashMap<&String, Qubit> = HashMap::new(); + for (index, calibration_qubit) in calibration.qubits.iter().enumerate() { + if let Qubit::Variable(identifier) = calibration_qubit { + qubit_expansions.insert(identifier, qubits[index].clone()); + } + } + + let mut instructions = calibration.instructions.clone(); + + for instruction in instructions.iter_mut() { + if let Instruction::Gate { qubits, .. } = instruction { + // Swap all qubits for their concrete implementations + for qubit in qubits { + match qubit { + Qubit::Variable(name) => { + if let Some(expansion) = qubit_expansions.get(name) { + *qubit = expansion.clone(); + } + } + Qubit::Fixed(_) => {} + } + } + } + } + + Some(instructions) + } + None => None, + } + } + _ => None, + } + } + + /// Return the final calibration which matches the gate per the QuilT specification: + /// + /// A calibration matches a gate iff: + /// 1. It has the same name + /// 2. It has the same modifiers + /// 3. It has the same qubit count (any mix of fixed & variable) + /// 4. It has the same parameter count (both specified and unspecified) + /// 5. All fixed qubits in the calibration definition match those in the gate + /// 6. All specified parameters in the calibration definition match those in the gate + pub fn get_match_for_gate( + &self, + gate_modifiers: &[GateModifier], + gate_name: &str, + gate_parameters: &[Expression], + gate_qubits: &[Qubit], + ) -> Option<&Calibration> { + let mut matched_calibration: Option = None; + + for calibration in &self.calibrations { + // Filter out non-matching calibrations: check rules 1-4 + if calibration.name != gate_name + || calibration.modifiers != gate_modifiers + || calibration.parameters.len() != gate_parameters.len() + || calibration.qubits.len() != gate_qubits.len() + { + continue; + } + + let fixed_qubits_match = + calibration + .qubits + .iter() + .enumerate() + .all(|(calibration_index, _)| { + match ( + &calibration.qubits[calibration_index], + &gate_qubits[calibration_index], + ) { + // If they're both fixed, test if they're fixed to the same qubit + ( + Qubit::Fixed(calibration_fixed_qubit), + Qubit::Fixed(gate_fixed_qubit), + ) => calibration_fixed_qubit == gate_fixed_qubit, + // If the calibration is variable, it matches any fixed qubit + (Qubit::Variable(_), _) => true, + // If the calibration is fixed, but the gate's qubit is variable, it's not a match + (Qubit::Fixed(_), _) => false, + } + }); + if !fixed_qubits_match { + continue; + } + + let fixed_parameters_match = + calibration + .parameters + .iter() + .enumerate() + .all(|(calibration_index, _)| { + let calibration_parameters = calibration.parameters[calibration_index] + .clone() + .evaluate(&HashMap::new()); + let gate_parameters = gate_parameters[calibration_index] + .clone() + .evaluate(&HashMap::new()); + match (calibration_parameters, gate_parameters) { + // If the calibration is variable, it matches any fixed qubit + (Expression::Variable(_), _) => true, + // If the calibration is fixed, but the gate's qubit is variable, it's not a match + (calib, gate) => calib == gate, + } + }); + if !fixed_parameters_match { + continue; + } + + matched_calibration = match matched_calibration { + None => Some(MatchedCalibration::new(calibration)), + Some(previous_match) => { + let potential_match = MatchedCalibration::new(calibration); + if potential_match.fixed_qubit_count >= previous_match.fixed_qubit_count { + Some(potential_match) + } else { + Some(previous_match) + } + } + } + } + + matched_calibration.map(|m| m.calibration) + } + + /// Return the count of contained calibrations. + pub fn len(&self) -> usize { + self.calibrations.len() + } + + /// Return true if this contains no data. + pub fn is_empty(&self) -> bool { + self.calibrations.is_empty() + } + + /// Add another calibration to the set. + pub fn push(&mut self, calibration: Calibration) { + self.calibrations.push(calibration) + } + + /// Return the Quil instructions which describe the contained calibrations. + pub fn to_instructions(&self) -> Vec { + self.calibrations + .iter() + .map(|c| Instruction::CalibrationDefinition(Box::new(c.clone()))) + .collect() + } +} + +#[cfg(test)] +mod tests { + use crate::program::Program; + use std::str::FromStr; + + #[test] + fn test_expansion() { + struct TestCase<'a> { + input: &'a str, + expected: &'a str, + } + + let cases = vec![ + // Test match ordering/precedence + TestCase { + input: concat!( + "DEFCAL RX(%theta) %qubit:\n", + " PULSE 1 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n", + "DEFCAL RX(%theta) 0:\n", + " PULSE 2 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n", + "DEFCAL RX(pi/2) 0:\n", + " PULSE 3 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n", + "RX(pi/2) 1\n", + "RX(pi) 0\n", + "RX(pi/2) 0\n" + ), + expected: concat!( + "PULSE 1 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n", + "PULSE 2 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n", + "PULSE 3 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n" + ), + }, + TestCase { + input: concat!( + "DEFCAL X 0:\n", + " PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n", + "X 0\n" + ), + expected: "PULSE 0 \"xy\" gaussian(duration: 1, fwhm: 2, t0: 3)\n", + }, + ]; + + for case in &cases { + let mut program = Program::from_str(case.input).unwrap(); + program.expand_calibrations(); + assert_eq!(program.to_string(false).as_str(), case.expected); + } + } +} diff --git a/src/program/frame.rs b/src/program/frame.rs new file mode 100644 index 00000000..55f53e80 --- /dev/null +++ b/src/program/frame.rs @@ -0,0 +1,87 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use std::collections::{HashMap, HashSet}; + +use crate::instruction::{FrameAttributes, FrameIdentifier, Instruction, Qubit}; + +/// A collection of Quil frames (`DEFFRAME` instructions) with utility methods. +#[derive(Clone, Debug, Default)] +pub struct FrameSet { + frames: HashMap, +} + +impl FrameSet { + pub fn new() -> Self { + FrameSet { + frames: HashMap::new(), + } + } + + /// Return a list of all frame IDs described by this FrameSet. + pub fn get_keys(&self) -> Vec<&FrameIdentifier> { + self.frames.keys().collect() + } + + /// Return all contained FrameIdentifiers which include **exactly** these qubits (in any order) + /// and - if names are provided - match one of the names. + pub fn get_matching_keys(&self, qubits: &[Qubit], names: &[String]) -> Vec<&FrameIdentifier> { + let qubit_set: HashSet<&Qubit> = qubits.iter().collect(); + self.frames + .iter() + .filter(|(identifier, _)| { + (names.is_empty() || names.contains(&identifier.name)) + && qubit_set == identifier.qubits.iter().collect() + }) + .map(|(id, _)| id) + .collect::>() + } + + /// Retrieve the attributes of a frame by its identifier. + pub fn get(&self, identifier: &FrameIdentifier) -> Option<&FrameAttributes> { + self.frames.get(identifier) + } + + /// Insert a new frame by ID, overwriting any existing one. + pub fn insert(&mut self, identifier: FrameIdentifier, attributes: FrameAttributes) { + self.frames.insert(identifier, attributes); + } + + /// Iterate through the contained frames. + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, FrameIdentifier, FrameAttributes> { + self.frames.iter() + } + + /// Return the number of frames described within. + pub fn len(&self) -> usize { + self.frames.len() + } + + /// Return true if this describes no frames. + pub fn is_empty(&self) -> bool { + self.frames.is_empty() + } + + /// Return the Quil instructions which describe the contained frames. + pub fn to_instructions(&self) -> Vec { + self.frames + .iter() + .map(|(identifier, attributes)| Instruction::FrameDefinition { + identifier: identifier.clone(), + attributes: attributes.clone(), + }) + .collect() + } +} diff --git a/src/program/graph.rs b/src/program/graph.rs new file mode 100644 index 00000000..54594e1f --- /dev/null +++ b/src/program/graph.rs @@ -0,0 +1,551 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use std::collections::{HashMap, HashSet}; + +use petgraph::graphmap::GraphMap; +use petgraph::Directed; + +use crate::instruction::{FrameIdentifier, Instruction, MemoryReference}; +use crate::{instruction::InstructionRole, program::Program}; + +use indexmap::IndexMap; + +#[derive(Debug, Clone)] +pub enum ScheduleErrorVariant { + DuplicateLabel, + DurationNotRealConstant, + DurationNotApplicable, + InvalidFrame, + UncalibratedInstruction, + UnscheduleableInstruction, +} + +#[derive(Debug, Clone)] +pub struct ScheduleError { + instruction: Instruction, + variant: ScheduleErrorVariant, +} + +type ScheduleResult = Result; + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Hash, Ord)] +pub enum ScheduledGraphNode { + BlockStart, + InstructionIndex(usize), + BlockEnd, +} + +impl Eq for ScheduledGraphNode {} + +struct MemoryAccess { + pub regions: HashSet, + pub access_type: MemoryAccessType, +} + +/// Express a mode of memory access. +#[derive(Clone, Debug)] +enum MemoryAccessType { + /// Write to a memory location using readout (`CAPTURE` and `RAW-CAPTURE` instructions) + Capture, + + /// Read from a memory location + Read, + + /// Write to a memory location using classical instructions + Write, +} + +/// A MemoryAccessQueue expresses the current state of memory accessors at the time of +/// an instruction's execution. +/// +/// Quil uses a multiple-reader, single-writer concurrency model for memory access. +#[derive(Debug, Default, Clone)] +struct MemoryAccessQueue { + pending_capture: Option, + pending_reads: Vec, + pending_write: Option, +} + +/// A MemoryAccessDependency expresses a dependency that one node has on another to complete +/// some type of memory access prior to the dependent node's execution. +#[derive(Clone, Debug)] +struct MemoryAccessDependency { + pub node_id: ScheduledGraphNode, + // What type of memory access must complete prior to the downstream instruction + pub access_type: MemoryAccessType, +} + +#[derive(Clone, Debug)] +pub enum ExecutionDependency { + /// The downstream instruction must wait for the capture (asynchronous write) to complete. + AwaitCapture, + /// The downstream instruction can execute as soon as the upstream completes. + Immediate, +} + +/// A data structure to be used in the serializing of access to a memory region. +/// This utility helps guarantee strong consistency in a single-writer, multiple-reader model. +impl MemoryAccessQueue { + pub fn flush(mut self) -> Vec { + self.get_blocking_nodes(ScheduledGraphNode::BlockEnd, &MemoryAccessType::Capture) + } + + pub fn get_blocking_nodes( + &mut self, + node_id: ScheduledGraphNode, + access: &MemoryAccessType, + ) -> Vec { + use MemoryAccessType::*; + + let mut result = vec![]; + if let Some(node_id) = self.pending_write { + result.push(MemoryAccessDependency { + node_id, + access_type: Write, + }); + } + self.pending_write = None; + if let Some(node_id) = self.pending_capture { + result.push(MemoryAccessDependency { + node_id, + access_type: Capture, + }); + } + self.pending_capture = None; + + match access { + // Mark the given node as reading from this memory region. If there was a write pending, + // return it to be used as a dependency. + Read => { + self.pending_reads.push(node_id); + result + } + // Mark the given node as writing to this memory region. If there were any reads or another + // write or capture pending, return those as a dependency list. + Capture | Write => { + let mut result = vec![]; + if !self.pending_reads.is_empty() { + for upstream_node_id in self.pending_reads.iter() { + result.push(MemoryAccessDependency { + node_id: *upstream_node_id, + access_type: Read, + }); + } + } + + match access { + Capture => { + self.pending_capture = Some(node_id); + } + Write => { + self.pending_write = Some(node_id); + } + _ => panic!("expected Capture or Write memory dependency"), + } + + result + } + } + } +} + +type DependencyGraph = GraphMap; + +/// An InstructionBlock of a ScheduledProgram is a group of instructions, identified by a string label, +/// which include no control flow instructions aside from an (optional) terminating control +/// flow instruction. +#[derive(Clone, Debug)] +pub struct InstructionBlock { + pub instructions: Vec, + graph: DependencyGraph, + pub terminator: BlockTerminator, +} + +impl InstructionBlock { + pub fn build( + instructions: Vec, + terminator: Option, + program: &Program, + ) -> ScheduleResult { + let mut graph = GraphMap::new(); + // Root node + graph.add_node(ScheduledGraphNode::BlockStart); + + let mut last_classical_instruction = ScheduledGraphNode::BlockStart; + + // Store the instruction index of the last instruction to block that frame + let mut last_instruction_by_frame: HashMap = + HashMap::new(); + + // Store memory access reads and writes. Key is memory region name. + // NOTE: this may be refined to serialize by memory region offset rather than by entire region. + let mut pending_memory_access: HashMap = HashMap::new(); + + for (index, instruction) in instructions.iter().enumerate() { + let node = graph.add_node(ScheduledGraphNode::InstructionIndex(index)); + + let instruction_role = InstructionRole::from(instruction); + match instruction_role { + InstructionRole::ClassicalCompute => { + graph.add_edge( + last_classical_instruction, + node, + ExecutionDependency::Immediate, + ); + last_classical_instruction = node; + Ok(()) + } + InstructionRole::RFControl => { + let frames = Self::get_frames(instruction, program).ok_or(ScheduleError { + instruction: instruction.clone(), + variant: ScheduleErrorVariant::UnscheduleableInstruction, + })?; + + // Mark a dependency on + for frame in frames { + let previous_node_id = last_instruction_by_frame + .entry(frame.clone()) + .or_insert(ScheduledGraphNode::BlockStart); + graph.add_edge(*previous_node_id, node, ExecutionDependency::Immediate); + } + Ok(()) + } + InstructionRole::ControlFlow => Err(ScheduleError { + instruction: instruction.clone(), + variant: ScheduleErrorVariant::UnscheduleableInstruction, + }), + InstructionRole::ProgramComposition => Err(ScheduleError { + instruction: instruction.clone(), + variant: ScheduleErrorVariant::UnscheduleableInstruction, + }), + }?; + + if let Some(memory_accesses) = Self::get_memory_accesses(instruction) { + for region in memory_accesses.regions { + let memory_dependencies = pending_memory_access + .entry(region) + .or_default() + .get_blocking_nodes(node, &memory_accesses.access_type); + for memory_dependency in memory_dependencies { + // If this instruction follows one which performed a capture, we have to wait for the + // capture to complete before proceeding. Otherwise, this is just a simple ordering + // dependency. + let execution_dependency = match memory_dependency.access_type { + MemoryAccessType::Capture => ExecutionDependency::AwaitCapture, + _ => ExecutionDependency::Immediate, + }; + graph.add_edge(memory_dependency.node_id, node, execution_dependency); + } + } + } + } + + // Link all pending dependency nodes to the end of the block, to ensure that the block + // does not terminate until these are complete + + graph.add_edge( + last_classical_instruction, + ScheduledGraphNode::BlockEnd, + ExecutionDependency::Immediate, + ); + + for (_, last_instruction) in last_instruction_by_frame { + graph.add_edge( + last_instruction, + ScheduledGraphNode::BlockEnd, + ExecutionDependency::Immediate, + ); + } + + for (_, memory_access_queue) in pending_memory_access { + let remaining_dependencies = memory_access_queue.flush(); + for dependency in remaining_dependencies { + let execution_dependency = match dependency.access_type { + MemoryAccessType::Capture => ExecutionDependency::AwaitCapture, + _ => ExecutionDependency::Immediate, + }; + graph.add_edge( + dependency.node_id, + ScheduledGraphNode::BlockEnd, + execution_dependency, + ); + } + } + + Ok(InstructionBlock { + graph, + instructions, + terminator: terminator.unwrap_or(BlockTerminator::Continue), + }) + } + + pub fn get_dependency_graph(&self) -> &DependencyGraph { + &self.graph + } + + pub fn get_instruction(&self, node_id: usize) -> Option<&Instruction> { + self.instructions.get(node_id) + } + + /// Return all memory accesses by the instruction - in expressions, captures, and memory manipulation + fn get_memory_accesses(_instruction: &Instruction) -> Option { + None + } + + /// Return the frames defined in the program which are blocked by this instruction. + pub fn get_frames<'a>( + instruction: &'a Instruction, + program: &'a Program, + ) -> Option> { + // FIXME: pass through include_blocked + program.get_blocked_frames(instruction, true) + } + + /// Return the count of executable instructions in this block. + pub fn len(&self) -> usize { + self.instructions.len() + } + + /// Return true if this block contains no executable instructions. + pub fn is_empty(&self) -> bool { + self.instructions.is_empty() + } + + pub fn set_exit_condition(&mut self, jump: BlockTerminator) { + self.terminator = jump + } +} + +#[derive(Clone, Debug)] +pub enum BlockTerminator { + Conditional { + condition: MemoryReference, + target: String, + jump_if_condition_true: bool, + }, + Unconditional { + target: String, + }, + Continue, + Halt, +} + +#[derive(Clone, Debug)] +pub struct ScheduledProgram { + /// All blocks within the ScheduledProgram, keyed on string label. + pub blocks: IndexMap, +} + +macro_rules! terminate_working_block { + ($terminator:expr, $working_instructions:ident, $blocks:ident, $working_label:ident, $program: ident) => {{ + let block = InstructionBlock::build( + $working_instructions.iter().map(|el| el.clone()).collect(), + $terminator, + $program, + )?; + match $blocks.insert($working_label.clone(), block) { + Some(_) => Err(ScheduleError { + instruction: Instruction::Label($working_label.clone()), + variant: ScheduleErrorVariant::DuplicateLabel, + }), // Duplicate label + None => Ok(()), + }?; + $working_instructions = vec![]; + $working_label = Self::generate_autoincremented_label(&$blocks); + Ok(()) + }}; +} + +impl ScheduledProgram { + /// Structure a sequential program + #[allow(unused_assignments)] + pub fn from_program(program: &Program) -> ScheduleResult { + let mut working_label = "start".to_owned(); + let mut working_instructions: Vec = vec![]; + let mut blocks = IndexMap::new(); + + let instructions = program.to_instructions(false); + + for instruction in instructions { + match instruction { + Instruction::Arithmetic { .. } + | Instruction::Capture { .. } + | Instruction::Delay { .. } + | Instruction::Fence { .. } + | Instruction::Move { .. } + | Instruction::Exchange { .. } + | Instruction::Load { .. } + | Instruction::Store { .. } + | Instruction::Pulse { .. } + | Instruction::SetFrequency { .. } + | Instruction::SetPhase { .. } + | Instruction::SetScale { .. } + | Instruction::ShiftFrequency { .. } + | Instruction::ShiftPhase { .. } + | Instruction::SwapPhases { .. } + | Instruction::RawCapture { .. } + | Instruction::Reset { .. } => { + working_instructions.push(instruction); + Ok(()) + } + Instruction::Gate { .. } | Instruction::Measurement { .. } => Err(ScheduleError { + instruction: instruction.clone(), + variant: ScheduleErrorVariant::UncalibratedInstruction, + }), + Instruction::CalibrationDefinition { .. } + | Instruction::CircuitDefinition { .. } + | Instruction::Declaration { .. } + | Instruction::GateDefinition { .. } + | Instruction::FrameDefinition { .. } + | Instruction::MeasureCalibrationDefinition { .. } + | Instruction::WaveformDefinition { .. } => Err(ScheduleError { + instruction: instruction.clone(), + variant: ScheduleErrorVariant::UnscheduleableInstruction, + }), + + Instruction::Pragma { .. } => { + // TODO: Handle pragmas. Here, we just silently discard them, but certain + // pragmas must be supported. + Ok(()) + } + // _ => Err(()), // Unimplemented + Instruction::Label(value) => { + terminate_working_block!( + None, + working_instructions, + blocks, + working_label, + program + )?; + + working_label = value.clone(); + Ok(()) + } + Instruction::Jump { target } => { + terminate_working_block!( + Some(BlockTerminator::Unconditional { + target: target.clone(), + }), + working_instructions, + blocks, + working_label, + program + )?; + Ok(()) + } + Instruction::JumpWhen { target, condition } => { + terminate_working_block!( + Some(BlockTerminator::Conditional { + target: target.clone(), + condition: condition.clone(), + jump_if_condition_true: true, + }), + working_instructions, + blocks, + working_label, + program + )?; + Ok(()) + } + Instruction::JumpUnless { target, condition } => { + terminate_working_block!( + Some(BlockTerminator::Conditional { + target: target.clone(), + condition: condition.clone(), + jump_if_condition_true: false, + }), + working_instructions, + blocks, + working_label, + program + ) + } + Instruction::Halt => { + terminate_working_block!( + Some(BlockTerminator::Halt), + working_instructions, + blocks, + working_label, + program + ) + } + }?; + } + + if !working_instructions.is_empty() { + terminate_working_block!(None, working_instructions, blocks, working_label, program)?; + } + + Ok(ScheduledProgram { blocks }) + } + + fn generate_autoincremented_label(block_labels: &IndexMap) -> String { + let mut suffix = 0; + let mut label = format!("auto-label-{}", suffix); + while block_labels.get(&label).is_some() { + suffix += 1; + label = format!("auto-label-{}", suffix); + } + label + } +} + +#[cfg(test)] +mod tests { + use super::ScheduledProgram; + use crate::program::Program; + use std::str::FromStr; + + #[test] + fn without_control_flow() { + let input = " +DEFFRAME 0 \"rx\": + INITIAL-FREQUENCY: 1e6 +PULSE 0 \"rx\" test(duration: 1e6) +DELAY 0 1.0 +"; + let program = Program::from_str(input).unwrap(); + let scheduled_program = ScheduledProgram::from_program(&program).unwrap(); + assert_eq!(scheduled_program.blocks.len(), 1) + } + + #[test] + fn with_control_flow() { + let input = " +DEFFRAME 0 \"rx\": + INITIAL-FREQUENCY: 1e6 +PULSE 0 \"rx\" test(duration: 1e-6) +PULSE 0 \"rx\" test(duration: 1e-6) +DELAY 0 1.0 +LABEL @part2 +PULSE 0 \"rx\" test(duration: 1e-6) +DELAY 0 2.0 +LABEL @part3 +DELAY 0 3.0 +JUMP @part2 +DELAY 0 4.0 +JUMP @block5 +HALT +LABEL @block5 +DELAY 0 5.0 +HALT +"; + let program = Program::from_str(input).unwrap(); + let scheduled_program = ScheduledProgram::from_program(&program).unwrap(); + println!("{:?}", scheduled_program.blocks); + assert_eq!(scheduled_program.blocks.len(), 7); + } +} diff --git a/src/program/memory.rs b/src/program/memory.rs new file mode 100644 index 00000000..383caa0f --- /dev/null +++ b/src/program/memory.rs @@ -0,0 +1,24 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use crate::instruction::Vector; + +#[derive(Clone, Debug, Hash, PartialEq)] +pub struct MemoryRegion { + pub size: Vector, + pub sharing: Option, +} + +impl Eq for MemoryRegion {} diff --git a/src/program/mod.rs b/src/program/mod.rs new file mode 100644 index 00000000..652f2f02 --- /dev/null +++ b/src/program/mod.rs @@ -0,0 +1,213 @@ +/** + * Copyright 2021 Rigetti Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +use std::collections::HashMap; +use std::str::FromStr; + +use frame::FrameSet; + +use crate::{ + instruction::{FrameIdentifier, Instruction, Waveform}, + parser::{instruction::parse_instructions, lexer}, +}; + +use self::calibration::CalibrationSet; +use self::memory::MemoryRegion; + +pub mod calibration; +pub mod frame; +pub mod graph; +pub mod memory; + +/// A Quil Program instance describes a quantum program with metadata used in execution. +/// +/// This contains not only instructions which are executed in turn on the quantum processor, but +/// also the "headers" used to describe and manipulate those instructions, such as calibrations +/// and frame definitions. +#[derive(Clone, Debug, Default)] +pub struct Program { + pub calibrations: CalibrationSet, + pub frames: FrameSet, + pub memory_regions: HashMap, + pub waveforms: HashMap, + pub instructions: Vec, +} + +impl Program { + pub fn new() -> Self { + Program { + calibrations: CalibrationSet::new(), + frames: FrameSet::new(), + memory_regions: HashMap::new(), + waveforms: HashMap::new(), + instructions: vec![], + } + } + + /// Add an instruction to the end of the program. + pub fn add_instruction(&mut self, instruction: Instruction) { + use Instruction::*; + + match instruction { + CalibrationDefinition(calibration) => { + self.calibrations.push(*calibration); + } + FrameDefinition { + identifier, + attributes, + } => { + self.frames.insert(identifier, attributes); + } + Instruction::Declaration { + name, + size, + sharing, + } => { + self.memory_regions + .insert(name, MemoryRegion { size, sharing }); + } + WaveformDefinition { name, definition } => { + self.waveforms.insert(name, definition); + } + other => self.instructions.push(other), + } + } + + /// Expand any instructions in the program which have a matching calibration, leaving the others + /// unchanged. + pub fn expand_calibrations(&mut self) { + let mut result: Vec = vec![]; + + // TODO: Do this more efficiently, possibly with Vec::splice + for instruction in &self.instructions { + match self.calibrations.expand(instruction) { + Some(expanded) => { + result.extend(expanded.into_iter()); + } + None => { + result.push(instruction.clone()); + } + } + } + + self.instructions = result; + } + + /// Return the frames which are either used or blocked by the given instruction. + /// A frame is "blocked" if the instruction prevents other instructions from playing + /// on that frame until complete; a frame is "used" if the instruction actively + /// executes on that sequencer. + pub fn get_blocked_frames<'a>( + &'a self, + instruction: &'a Instruction, + include_blocked: bool, + ) -> Option> { + use Instruction::*; + match &instruction { + Pulse { + blocking, frame, .. + } => { + if *blocking && include_blocked { + Some(self.frames.get_keys()) + } else { + Some(vec![frame]) + } + } + // FIXME: handle blocking + Delay { + frame_names, + qubits, + .. + } => { + let frame_ids = self.frames.get_matching_keys(qubits, frame_names); + Some(frame_ids) + } + Capture { frame, .. } + | RawCapture { frame, .. } + | SetFrequency { frame, .. } + | SetPhase { frame, .. } + | SetScale { frame, .. } + | ShiftFrequency { frame, .. } + | ShiftPhase { frame, .. } => Some(vec![frame]), + _ => None, + } + } + + pub fn to_instructions(&self, include_headers: bool) -> Vec { + let mut result = vec![]; + + if include_headers { + result.extend(self.frames.to_instructions()); + result.extend(self.calibrations.to_instructions()); + } + + // TODO: other headers/context like frames and waveforms + result.extend(self.instructions.clone()); + + result + } + + pub fn to_string(&self, include_headers: bool) -> String { + self.to_instructions(include_headers) + .iter() + .map(|inst| format!("{}\n", inst)) + .collect() + } +} + +impl FromStr for Program { + type Err = nom::Err; + fn from_str(s: &str) -> Result { + let lexed = lexer::lex(s); + let (_, instructions) = parse_instructions(&lexed).map_err(|err| match err { + nom::Err::Incomplete(_) => nom::Err::Error("incomplete".to_owned()), + nom::Err::Error(error) => nom::Err::Error(format!("{:?}", error)), + nom::Err::Failure(failure) => nom::Err::Error(format!("{:?}", failure)), + })?; + let mut program = Self::new(); + + for instruction in instructions { + program.add_instruction(instruction) + } + + Ok(program) + } +} + +#[cfg(test)] +mod tests { + use super::Program; + use std::str::FromStr; + + #[test] + fn program_headers() { + let input = " +DECLARE ro BIT[5] +DEFCAL I 0: + DELAY 0 1.0 +DEFFRAME 0 \"rx\": + HARDWARE-OBJECT: \"hardware\" +DEFWAVEFORM custom 6.0: + 1,2 +I 0 +"; + let program = Program::from_str(input).unwrap(); + assert_eq!(program.calibrations.len(), 1); + assert_eq!(program.memory_regions.len(), 1); + assert_eq!(program.frames.len(), 1); + assert_eq!(program.waveforms.len(), 1); + assert_eq!(program.instructions.len(), 1); + } +}