|
27 | 27 | from typing import List, Union |
28 | 28 |
|
29 | 29 | try: |
30 | | - from antlr4 import CommonTokenStream, InputStream, ParserRuleContext |
| 30 | + from antlr4 import CommonTokenStream, InputStream, ParserRuleContext, RecognitionException |
| 31 | + from antlr4.error.Errors import ParseCancellationException |
| 32 | + from antlr4.error.ErrorStrategy import BailErrorStrategy |
31 | 33 | except ImportError as exc: |
32 | 34 | raise ImportError( |
33 | 35 | "Parsing is not available unless the [parser] extra is installed," |
|
49 | 51 | from ._antlr.openpulseParser import openpulseParser |
50 | 52 | from ._antlr.openpulseParserVisitor import openpulseParserVisitor |
51 | 53 |
|
| 54 | +class OpenPulseParsingError(Exception): |
| 55 | + """An error raised by the AST visitor during the AST-generation phase. This is raised in cases where the |
| 56 | + given program could not be correctly parsed.""" |
52 | 57 |
|
53 | | -def parse(input_: str) -> ast.Program: |
| 58 | +def parse(input_: str, permissive: bool = False) -> ast.Program: |
54 | 59 | """ |
55 | 60 | Parse a complete OpenPulse program from a string. |
56 | 61 |
|
57 | 62 | :param input_: A string containing a complete OpenQASM 3 program. |
| 63 | + :param permissive: A Boolean controlling whether ANTLR should attempt to |
| 64 | + recover from incorrect input or not. Defaults to ``False``; if set to |
| 65 | + ``True``, the reference AST produced may be invalid if ANTLR emits any |
| 66 | + warning messages during its parsing phase. |
58 | 67 | :return: A complete :obj:`~ast.Program` node. |
59 | 68 | """ |
60 | | - qasm3_ast = parse_qasm3(input_) |
61 | | - CalParser().visit(qasm3_ast) |
| 69 | + qasm3_ast = parse_qasm3(input_, permissive=permissive) |
| 70 | + CalParser(permissive=permissive).visit(qasm3_ast) |
62 | 71 | return qasm3_ast |
63 | 72 |
|
64 | 73 |
|
65 | | -def parse_openpulse(input_: str, in_defcal: bool) -> openpulse_ast.CalibrationBlock: |
| 74 | +def parse_openpulse(input_: str, in_defcal: bool, permissive: bool = True) -> openpulse_ast.CalibrationBlock: |
66 | 75 | lexer = openpulseLexer(InputStream(input_)) |
67 | 76 | stream = CommonTokenStream(lexer) |
68 | 77 | parser = openpulseParser(stream) |
69 | | - tree = parser.calibrationBlock() |
| 78 | + if not permissive: |
| 79 | + # For some reason, the Python 3 runtime for ANTLR 4 is missing the |
| 80 | + # setter method `setErrorHandler`, so we have to set the attribute |
| 81 | + # directly. |
| 82 | + parser._errHandler = BailErrorStrategy() |
| 83 | + try: |
| 84 | + tree = parser.calibrationBlock() |
| 85 | + except (RecognitionException, ParseCancellationException) as exc: |
| 86 | + raise OpenPulseParsingError() from exc |
70 | 87 | result = ( |
71 | 88 | OpenPulseNodeVisitor(in_defcal).visitCalibrationBlock(tree) |
72 | 89 | if tree.children |
@@ -318,14 +335,17 @@ def visitOpenpulseStatement(self, ctx: openpulseParser.OpenpulseStatementContext |
318 | 335 | class CalParser(QASMVisitor[None]): |
319 | 336 | """Visit OpenQASM3 AST and pase calibration""" |
320 | 337 |
|
| 338 | + def __init__(self, permissive: bool = False): |
| 339 | + self.permissive = permissive |
| 340 | + |
321 | 341 | def visit_CalibrationDefinition( |
322 | 342 | self, node: ast.CalibrationDefinition |
323 | 343 | ) -> openpulse_ast.CalibrationDefinition: |
324 | 344 | node.__class__ = openpulse_ast.CalibrationDefinition |
325 | | - node.body = parse_openpulse(node.body, in_defcal=True).body |
| 345 | + node.body = parse_openpulse(node.body, in_defcal=True, permissive=self.permissive).body |
326 | 346 |
|
327 | 347 | def visit_CalibrationStatement( |
328 | 348 | self, node: ast.CalibrationStatement |
329 | 349 | ) -> openpulse_ast.CalibrationStatement: |
330 | 350 | node.__class__ = openpulse_ast.CalibrationStatement |
331 | | - node.body = parse_openpulse(node.body, in_defcal=False).body |
| 351 | + node.body = parse_openpulse(node.body, in_defcal=False, permissive=self.permissive).body |
0 commit comments