From 2edf04e44e8c36c6921f689e2767c8372d751bc5 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Mon, 14 Dec 2020 19:14:14 -0500 Subject: [PATCH 01/10] Lexer for QASM3 --- QGL/qasm/parse.py | 175 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 QGL/qasm/parse.py diff --git a/QGL/qasm/parse.py b/QGL/qasm/parse.py new file mode 100644 index 00000000..665da1e5 --- /dev/null +++ b/QGL/qasm/parse.py @@ -0,0 +1,175 @@ +""" +Original Author: Guilhem Ribeill + +Copyright 2020 Raytheon BBN Technologies + +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. +""" + +import re, math +from sly import Lexer, Parser + +class QASM3Lexer(Lexer): + """A lexer to tokenize the QASM3 language.""" + + #set of QASM3 tokens + tokens = {NUMCONST, STRCONST, BOOLCONST, + VERSION, INCLUDE, MATH, + IDENT, QUBIT, PHYSQUBIT, + PRECISION, WIDTH, SLICE, MATHFUNC, + BIT, INTTYPE, UINTTYPE, FPTYPE, FLOATTYPE, ANGLETYPE, BOOLTYPE, + CONSTTYPE ,LENGTHTYPE, STRETCHTYPE, TIME, LET, + CNOT, GATE1, GPHASE, INV, POW, CTRL, RESET, MEAS, + BITOP, BOOLOP, NUMOP, ASSIGN, + IF, ELSE, FOR, WHILE, CONTINUE, BREAK, END, + KERNEL, DEF, PRAGMA, LENOF, BOX, BARRIER} + + #Ignored characters + ignore = ' \t' + + #Ignore comments + ignore_single_comment = r'/{2}.*' + ignore_multi_comment = r'/\*[\s\S]*?\*/' + + @_(r'\n+') + def ignore_newline(self, t): + self.lineno += t.value.count('\n') + + literals = {':', ';', ',', '=', '(', ')', '{', '}', '@'} + + @_(r'true|false') + def BOOLCONST(self, t): + """Matches a boolean constant true/false.""" + t.value = True if t.value == "true" else False + return t + + @_(r'pi', r'tau', r'e', r'[+-]?0b[01]+', r'[+-]?0x[0-9a-fA-F]+', + r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?') + def NUMCONST(self, t): + """Matches a numeric constant as either binary (0b), hex (0x), or generic floating point.""" + if t.value == 'pi': + t.value = math.pi + elif t.value == 'tau': + t.value = 2.0*math.pi + elif t.value == 'e': + t.value = math.e + else: + if t.value.startswith('0x'): + t.value = int(t.value[2:], 16) + elif t.value.startswith('0b'): + t.value= int(t.value[2:], 2) + t.value = float(t.value) + return t + + @_(r'\"[\S\t\f ]*\"') + def STRCONST(self, t): + """Matches a string literal constant.""" + t.value = str(t.value[1:-1]) + return t + + VERSION = r'QASMVERSION' + INCLUDE = r'include' #Include another file + + QUBIT = r'qubit|qreg' #Qubit type + PHYSQUBIT = r'\%\d+' #Physical qubit + BIT = r'bit|creg' #Classical bit/register identifier + + PRECISION = r'\d+:\d+:\d+' #Precision identifier for fixed-point numbers + + INTTYPE = r'int' + UINTTYPE = r'uint' + FPTYPE = r'fixed' + FLOATTYPE = r'float' + ANGLETYPE = r'angle' + BOOLTYPE = r'bool' + CONSTTYPE = r'const' + LENGTHTYPE = r'length' + + TIME = r'dt|ns|us|ms|s' + + LET = r'let' + + CNOT = r'CX' + GATE1 = r'U' + GPHASE = r'gphase' + INV = r'inv' + POW = r'pow' + CTRL = r'ctrl' + + RESET = r'reset' + MEAS = r'measure' + + ASSIGN = r'->' + + #These are the operations on bitstring, booleans, and numeric types + BITOP = r'&|\||\^|<<|>>|~|popcount|rotl|rotr' + BOOLOP = r'[><]=?|==|!=?|&&|\|\||in' + NUMOP = r'\+[\+=]?|-[-=]?|\*=|/=?' + + IF = r'if' + ELSE = r'else' + FOR = r'for' + WHILE = r'while' + CONTINUE = r'continue' + BREAK = r'break' + END = r'end' + KERNEL = r'kernel' + DEF = r'def' + LENOF = r'lengthof' + BARRIER = r'barrier' + + @_(r'boxas|boxto') + def BOX(self, t): + t.value = t.value[-2:] + return t + + @_(r'stretch\d{0,3}') + def STRETCHTYPE(self, t): + match = re.search(r'\d+', t.value) + if match: + t.value = int(match.group()) + else: + t.value = 0 + return t + + #Built-in math functions + @_(r'sqrt|floor|ceiling|log|pow|div|mod|sin|cos|tan') + def MATHFUNC(self, t): + if t.value == "ceiling": + t.value = math.ceil + else: + t.value = getattr(math, t.value) + return t + + @_(r'\[\d+\]') + def WIDTH(self, t): + t.value = int(t.value[1:-1]) + return t + + @_(r'\[\d+:\d+\]|\[\d+:\d+:\d+\]') + def SLICE(self, t): + match = re.match(r'\[(\d+):(\d+)\]|\[(\d+):(\d+):(\d+)\]', t.value) + t.value = [int(g) for g in match.groups() if g] + return t + + @_(r'\#PRAGMA[\S\t\f ]+') + def PRAMGA(self, t): + match = re.match(r'\#PRAGMA([\S\t\f ]+)', t.value) + t.value = match.groups()[0].lstrip() + return t + + IDENT = r'[a-zA-Z_%][a-zA-Z0-9_]*' #Variable identifier + + def error(self, t): + print('Line %d: Bad character %r' % (self.lineno, t.value[0])) + self.index += 1 From 65a270927df1b4bd3357a76c2487c831c17c840c Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Mon, 14 Dec 2020 19:16:39 -0500 Subject: [PATCH 02/10] Optional install with qasm compat. --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c3b08ab6..c7282087 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,9 @@ from setuptools import setup, find_packages +extras = { + 'with_qasm': ['sly >= 0.4'] +} + setup(name='QGL', version='2020.1', packages=find_packages(exclude=["tests"]), @@ -18,7 +22,8 @@ long_description_content_type='text/markdown', long_description=open('README.md').read(), python_requires='>=3.6', - keywords="quantum qubit experiment configuration gate language" + keywords="quantum qubit experiment configuration gate language", + extras_requier=extras ) # python setup.py sdist From 366c7c2674bd5a6c2677444aa9480f538e042275 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Tue, 15 Dec 2020 14:33:42 -0500 Subject: [PATCH 03/10] Switch over to Lark for building parser, write out grammar. --- QGL/qasm/grammar.lark | 270 ++++++++++++++++++++++++++++++++++++++++++ QGL/qasm/parse.py | 182 +++++----------------------- setup.py | 2 +- 3 files changed, 300 insertions(+), 154 deletions(-) create mode 100644 QGL/qasm/grammar.lark diff --git a/QGL/qasm/grammar.lark b/QGL/qasm/grammar.lark new file mode 100644 index 00000000..1ceb8baf --- /dev/null +++ b/QGL/qasm/grammar.lark @@ -0,0 +1,270 @@ +////////////////////////////////////////////////////////////////////////////// +//Original Author: Guilhem Ribeill +// +//Copyright 2020 Raytheon BBN Technologies +// +//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. +////////////////////////////////////////////////////////////////////////////// + +// OpenQASM 3.0 Grammar in EBNF syntax for use with the Lark parser engine + +// Comments and whitespace +%import common.WS +%import common.CPP_COMMENT +%import common.C_COMMENT +%ignore WS +%ignore CPP_COMMENT +%ignore C_COMMENT + +_ENDL: ";" + +///////////////////////////////////////////////////////// +// Terminals + +//Constant terminals +%import common.ESCAPED_STRING +%import common.SIGNED_NUMBER +%import common.INT + +STR: ESCAPED_STRING + +TEXT: /[\S\t ]+/ + +BIN_NUMBER: /0b[01]+/ +HEX_NUMBER: /0x[0-9a-fA-F]+/ +CONSTANT : "pi" | "tau" | "e" +NUMBER: HEX_NUMBER | BIN_NUMBER | SIGNED_NUMBER | CONSTANT +BOOL: "true" | "false" + +TIME: "ns" | "us" | "ms" | "s" | "dt" + +//Variable types +ID: /[a-zA-Z_%][a-zA-Z0-9_]*/ //TODO: Support unicode + +//Quantum types +QTYPE : "qubit" | "qreg" + +//Classical types +RTYPE : "bit" | "creg" +NUMTYPE : "int" | "uint" | "float" | "fixed" | "angle" +BTYPE : "bool" +CTYPE : RTYPE | NUMTYPE | BTYPE + +//Math +MATH_FUNCS : "sqrt" | "exp" | "log" | "abs" + | "sin" | "cos" | "tan" + | "popcount" | "lengthof" + +//Operators +UNARY_OP : "~" | "!" | "-" + +INCR_OP : "++" | "--" + +ARITH_OP : "+" | "-" | "*" | "/" +BITS_OP : "&" | "|" | "^" | "<<" | ">>" | "rotl" | "rotr" +BOOL_OP : ">" | "<" | ">=" | "<=" | "==" | "!=" | "&&" | "||" | "in" +BINARY_OP : ARITH_OP | BITS_OP | BOOL_OP + +ASSIGN_OP : "+=" | "-=" | "*=" | "/=" + +BUILTIN_GATE : "U" | "CX" | "reset" + +///////////////////////////////////////////////////////// +//Rules + +//Basic structure +?start : version include* statement+ + +version : "OPENQASM" NUMBER _ENDL + +include : "include" STR _ENDL + +pragma : "#PRAGMA" TEXT + +block : "{" block* statement* "}" + +?statement : qstatement + | qblock + | gatedecl + | cstatement + | branch + | loop + | control + | subroutine + | kernel + | decl + | pragma + +///////////////////////////////////////////////////////// +//Variable delcaration +decl : qdecl _ENDL + | cdecl _ENDL + | const_decl _ENDL + +qdecl : qtype ID + +cdecl : ctype ID assignment? + +tdecl : ttype ID assignment? + +const_decl : "const" ID assignment + +assignment : "=" expr + | ASSIGN_OP expr + +modifier: index + | slice + +///////////////////////////////////////////////////////// +// Register aliasing, concatenation, slicing and indexing + +alias : "let" ID "=" concat + | "let" ID "=" ID slice + | "let" ID "=" ID index + +concat : ID "||" ID ("||" ID)* + +slice : "[" range "]" + +range : expr ":" expr (":" expr)? + +index : "[" expr ("," expr)? "]" + +///////////////////////////////////////////////////////// +//Quantum operatiors and gates + +qblock : "{" qblock* qstatement* "}" + +qstatement : gatecall _ENDL + | meas_decl _ENDL + | measure _ENDL + +gatedecl :"gate" gatedef qblock + +gatedef : ID ("(" carg_list? ")")? id_list + +gatecall : gatemod? gate ("(" expr_list? ")")? duration? id_list + +gate : BUILTIN_GATE + | ID + | gatemod gate + +gatemod : "inv" "@" -> gate_inv + | "pow" "(" SIGNED_NUMBER ")" "@" -> gate_pow + | "ctrl" "@" -> gate_ctrl + +measure : "measure" id_list + +meas_decl : measure "->" id_list + | id_list "=" measure + +///////////////////////////////////////////////////////// +//Classical operations + +cstatement : expr _ENDL + | "return" cstatement -> return + +expr : expr binary_op expr + | unary_op expr + | expr incr_op + | call "(" expr_list? ")" + | member + | measure + | ID assignment + | value + | tvalue + | ID + +call : MATH_FUNCS + | cast + | ID + +cast : ctype + +member : ID "in" "{" expr_list "}" + | ID "in" range + +expr_list : (expr ",")* expr + +binary_op : BINARY_OP +unary_op : UNARY_OP +incr_op : INCR_OP + +///////////////////////////////////////////////////////// +//Control flow +branch_block : statement + | block + +branch : "if" "(" expr ")" branch_block ("else" branch_block)? + +loop : for_loop branch_block + | while_loop branch_block + +for_loop : "for" member + +while_loop: "while" "(" expr ")" + +control : "break" _ENDL -> break + | "continue" _ENDL -> continue + | "end" _ENDL -> end + +///////////////////////////////////////////////////////// +//Timing instructions + +barrier : "barrier" id_list + +delay : "delay" index id_list + +box : "boxas" ID qblock + | "boxto" tvalue qblock + +ttype : "length" -> length + | "stretch" INT? -> stretch + +duration : "[" tvalue "]" + | "[" expr "]" + | "[" "stretchinf" "]" + +///////////////////////////////////////////////////////// +//Subroutines + +subroutine : "def" ID ("(" carg_list? qarg_list? ")")? return_sig? block + +kernel : "kernel" ID ("(" carg_list? ")")? return_sig? + +return_sig : "->" carg + +///////////////////////////////////////////////////////// +// Helpers +id_index : ID index? +?id_list : (id_index ",")* id_index + +carg : ctype association +qarg : qtype association + +?carg_list : (carg ",")* carg +?qarg_list : (qarg ",")* qarg + +association: ":" ID + +//Type declaration with width +?qtype : QTYPE ("[" SIGNED_NUMBER "]")? +?ctype : CTYPE ("[" SIGNED_NUMBER ("," SIGNED_NUMBER)* "]")? + +?tvalue : NUMBER TIME + +?value : NUMBER + | STR + | BOOL + + diff --git a/QGL/qasm/parse.py b/QGL/qasm/parse.py index 665da1e5..14dd91cc 100644 --- a/QGL/qasm/parse.py +++ b/QGL/qasm/parse.py @@ -15,161 +15,37 @@ See the License for the specific language governing permissions and limitations under the License. """ +import os +from lark import Lark, tree -import re, math -from sly import Lexer, Parser +try: + import pydot + _has_pydot = True +except ImportError: + _has_pydot = False -class QASM3Lexer(Lexer): - """A lexer to tokenize the QASM3 language.""" +grammar_path = os.path.join(os.path.dirname( + os.path.abspath(__file__)), + "grammar.lark") +with open(grammar_path, "r") as f: + _QASM_GRAMMAR = f.read() - #set of QASM3 tokens - tokens = {NUMCONST, STRCONST, BOOLCONST, - VERSION, INCLUDE, MATH, - IDENT, QUBIT, PHYSQUBIT, - PRECISION, WIDTH, SLICE, MATHFUNC, - BIT, INTTYPE, UINTTYPE, FPTYPE, FLOATTYPE, ANGLETYPE, BOOLTYPE, - CONSTTYPE ,LENGTHTYPE, STRETCHTYPE, TIME, LET, - CNOT, GATE1, GPHASE, INV, POW, CTRL, RESET, MEAS, - BITOP, BOOLOP, NUMOP, ASSIGN, - IF, ELSE, FOR, WHILE, CONTINUE, BREAK, END, - KERNEL, DEF, PRAGMA, LENOF, BOX, BARRIER} - - #Ignored characters - ignore = ' \t' - - #Ignore comments - ignore_single_comment = r'/{2}.*' - ignore_multi_comment = r'/\*[\s\S]*?\*/' - - @_(r'\n+') - def ignore_newline(self, t): - self.lineno += t.value.count('\n') - - literals = {':', ';', ',', '=', '(', ')', '{', '}', '@'} - - @_(r'true|false') - def BOOLCONST(self, t): - """Matches a boolean constant true/false.""" - t.value = True if t.value == "true" else False - return t - - @_(r'pi', r'tau', r'e', r'[+-]?0b[01]+', r'[+-]?0x[0-9a-fA-F]+', - r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?') - def NUMCONST(self, t): - """Matches a numeric constant as either binary (0b), hex (0x), or generic floating point.""" - if t.value == 'pi': - t.value = math.pi - elif t.value == 'tau': - t.value = 2.0*math.pi - elif t.value == 'e': - t.value = math.e - else: - if t.value.startswith('0x'): - t.value = int(t.value[2:], 16) - elif t.value.startswith('0b'): - t.value= int(t.value[2:], 2) - t.value = float(t.value) - return t - - @_(r'\"[\S\t\f ]*\"') - def STRCONST(self, t): - """Matches a string literal constant.""" - t.value = str(t.value[1:-1]) - return t +class QASM3Parser(Lark): - VERSION = r'QASMVERSION' - INCLUDE = r'include' #Include another file - - QUBIT = r'qubit|qreg' #Qubit type - PHYSQUBIT = r'\%\d+' #Physical qubit - BIT = r'bit|creg' #Classical bit/register identifier - - PRECISION = r'\d+:\d+:\d+' #Precision identifier for fixed-point numbers - - INTTYPE = r'int' - UINTTYPE = r'uint' - FPTYPE = r'fixed' - FLOATTYPE = r'float' - ANGLETYPE = r'angle' - BOOLTYPE = r'bool' - CONSTTYPE = r'const' - LENGTHTYPE = r'length' - - TIME = r'dt|ns|us|ms|s' - - LET = r'let' - - CNOT = r'CX' - GATE1 = r'U' - GPHASE = r'gphase' - INV = r'inv' - POW = r'pow' - CTRL = r'ctrl' - - RESET = r'reset' - MEAS = r'measure' - - ASSIGN = r'->' - - #These are the operations on bitstring, booleans, and numeric types - BITOP = r'&|\||\^|<<|>>|~|popcount|rotl|rotr' - BOOLOP = r'[><]=?|==|!=?|&&|\|\||in' - NUMOP = r'\+[\+=]?|-[-=]?|\*=|/=?' - - IF = r'if' - ELSE = r'else' - FOR = r'for' - WHILE = r'while' - CONTINUE = r'continue' - BREAK = r'break' - END = r'end' - KERNEL = r'kernel' - DEF = r'def' - LENOF = r'lengthof' - BARRIER = r'barrier' - - @_(r'boxas|boxto') - def BOX(self, t): - t.value = t.value[-2:] - return t - - @_(r'stretch\d{0,3}') - def STRETCHTYPE(self, t): - match = re.search(r'\d+', t.value) - if match: - t.value = int(match.group()) - else: - t.value = 0 - return t - - #Built-in math functions - @_(r'sqrt|floor|ceiling|log|pow|div|mod|sin|cos|tan') - def MATHFUNC(self, t): - if t.value == "ceiling": - t.value = math.ceil + def __init__(self, **kwargs): + super().__init__(_QASM_GRAMMAR, **kwargs) + self.cst = None + + def build_tree(self, input): + self.cst = self.parse(input) + + def __str__(self): + return self.cst.pretty() + + def cst_graph(self, filename): + if _has_pydot: + tree.pydot__tree_to_png(self.cst, filename) else: - t.value = getattr(math, t.value) - return t - - @_(r'\[\d+\]') - def WIDTH(self, t): - t.value = int(t.value[1:-1]) - return t - - @_(r'\[\d+:\d+\]|\[\d+:\d+:\d+\]') - def SLICE(self, t): - match = re.match(r'\[(\d+):(\d+)\]|\[(\d+):(\d+):(\d+)\]', t.value) - t.value = [int(g) for g in match.groups() if g] - return t - - @_(r'\#PRAGMA[\S\t\f ]+') - def PRAMGA(self, t): - match = re.match(r'\#PRAGMA([\S\t\f ]+)', t.value) - t.value = match.groups()[0].lstrip() - return t - - IDENT = r'[a-zA-Z_%][a-zA-Z0-9_]*' #Variable identifier - - def error(self, t): - print('Line %d: Bad character %r' % (self.lineno, t.value[0])) - self.index += 1 + raise ModuleNotFoundError("Please install pydot to generate tree graphs.") + + diff --git a/setup.py b/setup.py index c7282087..62271ca5 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages extras = { - 'with_qasm': ['sly >= 0.4'] + 'with_qasm': ['lark-parser >= 0.11'] } setup(name='QGL', From cdaee0c4243a9267cdeede1b9311b8ca4b74e1f5 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Tue, 15 Dec 2020 14:34:12 -0500 Subject: [PATCH 04/10] Basic parser testing --- tests/test_QASM.py | 25 +++++++++++++++++++++++++ tests/test_data/qasm/basic.qasm | 21 +++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/test_QASM.py create mode 100644 tests/test_data/qasm/basic.qasm diff --git a/tests/test_QASM.py b/tests/test_QASM.py new file mode 100644 index 00000000..7109893d --- /dev/null +++ b/tests/test_QASM.py @@ -0,0 +1,25 @@ +# Test QASM parsing and (eventually) compilation +import os +import unittest + +from QGL.qasm.parse import QASM3Parser + +def get_qasm_test_file(filename): + path = os.path.dirname(os.path.abspath(__file__)) + file = os.path.join(path, "test_data", "qasm", filename) + with open(file, "r") as f: + qasm = f.read() + return qasm + +class ParseTestCase(unittest.TestCase): + + def setUp(self): + pass + + def test_parse_simple(self): + qasm = get_qasm_test_file("basic.qasm") + parser = QASM3Parser() + parser.build_tree(qasm) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/test_data/qasm/basic.qasm b/tests/test_data/qasm/basic.qasm new file mode 100644 index 00000000..c5cba60a --- /dev/null +++ b/tests/test_data/qasm/basic.qasm @@ -0,0 +1,21 @@ +OPENQASM 3.0; +#PRAGMA I <3 QGL + +qubit q1; +length t; + +//A very special gate +gate X(angle[32]: phi) q { + U(phi, -pi/2, -pi/2) q; +} + +/* Let's do a ramsey experiment! + Fun! */ + +for t in 4ns:10us:20ns { + reset q1; + X(pi/2) q1; + delay[t] q1; + X(pi/2) q1; + measure q1; +} \ No newline at end of file From a18462b4c35e5df30441e0d941a6e8c1c0b73916 Mon Sep 17 00:00:00 2001 From: Matthew Ware Date: Thu, 1 Jul 2021 13:14:39 -0400 Subject: [PATCH 05/10] Add lark-parser to requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d0054aad..723b4d45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ numpy >= 1.11.1 scipy >= 0.17.1 networkx >= 1.11 bqplot >= 0.12.2 -sqlalchemy >= 1.2.15 \ No newline at end of file +sqlalchemy >= 1.2.15 +lark-parser >= 0.11.3 From 1c0d6ba8c9099ba621ba60cfc56f422032536c33 Mon Sep 17 00:00:00 2001 From: Matthew Ware Date: Thu, 1 Jul 2021 13:22:28 -0400 Subject: [PATCH 06/10] Add parser to setup file --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 62271ca5..13bdc5ae 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,8 @@ "scipy >= 0.17.1", "networkx >= 1.11", "bqplot >= 0.12.2", - "sqlalchemy >= 1.2.15" + "sqlalchemy >= 1.2.15", + "lark-parser >= 0.11.03" ], description="Quantum Gate Language (QGL) is a domain specific language embedded in python for specifying pulse sequences.", long_description_content_type='text/markdown', From 21d6548d452b651034805ec7c857df0216e5367b Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 1 Sep 2021 23:31:46 -0400 Subject: [PATCH 07/10] Do something! --- QGL/qasm/parse.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/QGL/qasm/parse.py b/QGL/qasm/parse.py index 14dd91cc..904c64cd 100644 --- a/QGL/qasm/parse.py +++ b/QGL/qasm/parse.py @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. """ -import os +import os, sys from lark import Lark, tree try: @@ -48,4 +48,8 @@ def cst_graph(self, filename): else: raise ModuleNotFoundError("Please install pydot to generate tree graphs.") - +if __name__ == "__main__": + with open(sys.argv[1]) as f: + parser = QASM3Parser() + parser.build_tree(f.read()) + parser.cst_graph("test_qasm.png") From eea53aea6230e3d2b6cf4d1a834bc7d7c860ece8 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 1 Sep 2021 23:31:57 -0400 Subject: [PATCH 08/10] Fix statement priority --- QGL/qasm/grammar.lark | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/QGL/qasm/grammar.lark b/QGL/qasm/grammar.lark index 1ceb8baf..68afcc97 100644 --- a/QGL/qasm/grammar.lark +++ b/QGL/qasm/grammar.lark @@ -93,17 +93,18 @@ pragma : "#PRAGMA" TEXT block : "{" block* statement* "}" -?statement : qstatement - | qblock - | gatedecl - | cstatement - | branch - | loop - | control - | subroutine - | kernel - | decl - | pragma +?statement : kernel + | pragma + | decl + | gatedecl + | branch + | loop + | control + | subroutine + | qblock + | qstatement + | cstatement + ///////////////////////////////////////////////////////// //Variable delcaration From 00bbac738997222d99a51e0c4c1a38f24959e7ac Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Thu, 2 Sep 2021 13:24:11 -0400 Subject: [PATCH 09/10] Filling in the parser gaps... --- QGL/qasm/grammar.lark | 21 +++++++++++++++++---- QGL/qasm/parse.py | 8 ++++++-- QGL/qasm/test_all.py | 25 +++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 QGL/qasm/test_all.py diff --git a/QGL/qasm/grammar.lark b/QGL/qasm/grammar.lark index 68afcc97..a3f08c4a 100644 --- a/QGL/qasm/grammar.lark +++ b/QGL/qasm/grammar.lark @@ -49,7 +49,7 @@ BOOL: "true" | "false" TIME: "ns" | "us" | "ms" | "s" | "dt" //Variable types -ID: /[a-zA-Z_%][a-zA-Z0-9_]*/ //TODO: Support unicode +ID: /\$*[a-zA-Z_%][a-zA-Z0-9_]*/ //TODO: Support unicode //Quantum types QTYPE : "qubit" | "qreg" @@ -93,17 +93,19 @@ pragma : "#PRAGMA" TEXT block : "{" block* statement* "}" -?statement : kernel - | pragma +?statement : pragma | decl | gatedecl | branch | loop | control | subroutine + | kernel + | extern | qblock | qstatement | cstatement + | defcal ///////////////////////////////////////////////////////// @@ -111,6 +113,7 @@ block : "{" block* statement* "}" decl : qdecl _ENDL | cdecl _ENDL | const_decl _ENDL + | tdecl _ENDL qdecl : qtype ID @@ -169,9 +172,15 @@ measure : "measure" id_list meas_decl : measure "->" id_list | id_list "=" measure +defcal : "defcal" gatedef qblock* cblock* + | "defcalgrammar" "\"" ID "\"" _ENDL + + ///////////////////////////////////////////////////////// //Classical operations +cblock : "{" cblock* cstatement* "}" + cstatement : expr _ENDL | "return" cstatement -> return @@ -193,7 +202,7 @@ call : MATH_FUNCS cast : ctype member : ID "in" "{" expr_list "}" - | ID "in" range + | ID "in" slice expr_list : (expr ",")* expr @@ -231,6 +240,7 @@ box : "boxas" ID qblock ttype : "length" -> length | "stretch" INT? -> stretch + | "duration" -> duration_decl duration : "[" tvalue "]" | "[" expr "]" @@ -243,8 +253,11 @@ subroutine : "def" ID ("(" carg_list? qarg_list? ")")? return_sig? block kernel : "kernel" ID ("(" carg_list? ")")? return_sig? +extern : "extern" ID ("(" id_list? ")")? (return_sig ID)? _ENDL + return_sig : "->" carg + ///////////////////////////////////////////////////////// // Helpers id_index : ID index? diff --git a/QGL/qasm/parse.py b/QGL/qasm/parse.py index 904c64cd..c8629e6e 100644 --- a/QGL/qasm/parse.py +++ b/QGL/qasm/parse.py @@ -16,7 +16,10 @@ limitations under the License. """ import os, sys -from lark import Lark, tree +import logging +from lark import Lark, tree, logger + +#logger.setLevel(logging.DEBUG) try: import pydot @@ -50,6 +53,7 @@ def cst_graph(self, filename): if __name__ == "__main__": with open(sys.argv[1]) as f: - parser = QASM3Parser() + parser = QASM3Parser(debug=True) parser.build_tree(f.read()) + print(parser) parser.cst_graph("test_qasm.png") diff --git a/QGL/qasm/test_all.py b/QGL/qasm/test_all.py new file mode 100644 index 00000000..48d2c647 --- /dev/null +++ b/QGL/qasm/test_all.py @@ -0,0 +1,25 @@ +import os, sys, glob +from parse import * + +qasm_path = "/home/gribeill/GitHub/openqasm/examples/*.qasm" + +fails = [] + +files = glob.glob(qasm_path) + +print(files) + +for fn in files: + p = QASM3Parser() + with open(fn) as f: + try: + p.build_tree(f.read()) + except: + fails.append(fn) + +Nt = len(files) +Nf = len(fails) + +print(f"Failed on {Nf} out of {Nt} files!") +for f in fails: + print(f) From 63855b378fd93531ffe62a005d413182b843b851 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Fri, 3 Sep 2021 11:30:28 -0400 Subject: [PATCH 10/10] Fixed order of assignment expressions, beginning of AST messing --- QGL/qasm/create_ast.py | 159 +++++++++++++++++++++++++++++++++++++++++ QGL/qasm/grammar.lark | 16 ++--- 2 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 QGL/qasm/create_ast.py diff --git a/QGL/qasm/create_ast.py b/QGL/qasm/create_ast.py new file mode 100644 index 00000000..46740f75 --- /dev/null +++ b/QGL/qasm/create_ast.py @@ -0,0 +1,159 @@ +""" +Original Author: Guilhem Ribeill + +Copyright 2020 Raytheon BBN Technologies + +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. +""" +import os, sys +from lark import Lark, tree, ast_utils, Transformer, v_args +from lark.tree import Meta +import ast + +py=""" +pots = 1000 +pans = 2000 +pens = 2001 +fens = pens + 1 +fens += 1 +q1 = qubit() +""" +print("---- Python AST -----") +# print("Python AST:", tree) +print(ast.dump(ast.parse(py))) + + + + +this_module = sys.modules[__name__] + +from dataclasses import dataclass + +# Define AST passthroughs + +class _Ast(ast_utils.Ast): + pass + +class _Decl(_Ast): + pass + +class _Expr(_Ast): + pass + +class _Assignment(_Ast): + pass +# Define useful AST classes + +@dataclass +class ConstDecl(_Decl): + name: str + value: _Expr + +@dataclass +class Version(_Ast): + number: int + +@dataclass +class Assign(_Ast): + value: object + +@dataclass +class AugAssign(_Ast): + operand: str + value: object + +class ClassicalAssignment(_Ast): + def __init__(self, name, expr): + self.name = name + self.expr = expr + print(name, expr) + # return None + def __str__(self): + return(f"ClassicalAssignment(name={self.name}, expr={self.expr})") + + +try: + import pydot + _has_pydot = True +except ImportError: + _has_pydot = False + +grammar_path = os.path.join(os.path.dirname( + os.path.abspath(__file__)), + "grammar.lark") +with open(grammar_path, "r") as f: + _QASM_GRAMMAR = f.read() + +class QASM3Parser(Lark): + + def __init__(self, **kwargs): + super().__init__(_QASM_GRAMMAR, **kwargs) + self.cst = None + + def build_tree(self, input): + self.cst = self.parse(input) + + def __str__(self): + return self.cst.pretty() + + def cst_graph(self, filename): + if _has_pydot: + tree.pydot__tree_to_png(self.cst, filename) + else: + raise ModuleNotFoundError("Please install pydot to generate tree graphs.") + + def run(self): + transformer = ast_utils.create_transformer(sys.modules[__name__], QASM3Transformer()) + self.ast = transformer.transform(self.cst) + return self.ast + # return QASM3Transformer().transform(self.cst) + + def ast_graph(self, filename): + if _has_pydot: + tree.pydot__tree_to_png(self.ast, filename) + else: + raise ModuleNotFoundError("Please install pydot to generate tree graphs.") + + +class QASM3Transformer(Transformer): + def start(self, x): + return x + def expr(self, x): + return x + def NUMBER(self, tok): + "Convert the value of `tok` from string to int, while maintaining line number & column." + return float(tok) #tok.update(value=float(tok)) + def id(self, tok): + return str(tok[0].value) + + +if __name__ == '__main__': + q = QASM3Parser() + with open(sys.argv[1]) as f: + src = f.read() + q.build_tree(src) + q.cst_graph("qasm_tree.png") + print("---- SRC -----") + print(src) + print("---- CST -----") + print(q.cst) + print("\n---- AST -----") + # def prpr(x, n=1): + # if isinstance(x, list): + + for l in q.run(): + # if isinstance(l, list): + print(l) + # print(q.run()) + # q.ast_graph("qasm_tree_ast.png") + diff --git a/QGL/qasm/grammar.lark b/QGL/qasm/grammar.lark index 68afcc97..1141eae2 100644 --- a/QGL/qasm/grammar.lark +++ b/QGL/qasm/grammar.lark @@ -108,8 +108,8 @@ block : "{" block* statement* "}" ///////////////////////////////////////////////////////// //Variable delcaration -decl : qdecl _ENDL - | cdecl _ENDL +?decl : qdecl _ENDL + | cdecl _ENDL | const_decl _ENDL qdecl : qtype ID @@ -118,10 +118,10 @@ cdecl : ctype ID assignment? tdecl : ttype ID assignment? -const_decl : "const" ID assignment +const_decl : "const" ID "=" expr -assignment : "=" expr - | ASSIGN_OP expr +?assignment : "=" expr -> assign + | ASSIGN_OP expr -> aug_assign modifier: index | slice @@ -172,16 +172,16 @@ meas_decl : measure "->" id_list ///////////////////////////////////////////////////////// //Classical operations -cstatement : expr _ENDL +?cstatement : expr _ENDL | "return" cstatement -> return -expr : expr binary_op expr +expr : ID assignment -> classical_assignment | unary_op expr | expr incr_op | call "(" expr_list? ")" | member | measure - | ID assignment + | expr binary_op expr -> bin_op | value | tvalue | ID