|
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,"
|
|
50 | 52 | from ._antlr.openpulseParserVisitor import openpulseParserVisitor
|
51 | 53 |
|
52 | 54 |
|
53 |
| -def parse(input_: str) -> ast.Program: |
| 55 | +class OpenPulseParsingError(Exception): |
| 56 | + """An error raised by the AST visitor during the AST-generation phase. This is raised in cases where the |
| 57 | + given program could not be correctly parsed.""" |
| 58 | + |
| 59 | + |
| 60 | +def parse(input_: str, permissive: bool = False) -> ast.Program: |
54 | 61 | """
|
55 | 62 | Parse a complete OpenPulse program from a string.
|
56 | 63 |
|
57 | 64 | :param input_: A string containing a complete OpenQASM 3 program.
|
| 65 | + :param permissive: A Boolean controlling whether ANTLR should attempt to |
| 66 | + recover from incorrect input or not. Defaults to ``False``; if set to |
| 67 | + ``True``, the reference AST produced may be invalid if ANTLR emits any |
| 68 | + warning messages during its parsing phase. |
58 | 69 | :return: A complete :obj:`~ast.Program` node.
|
59 | 70 | """
|
60 |
| - qasm3_ast = parse_qasm3(input_) |
61 |
| - CalParser().visit(qasm3_ast) |
| 71 | + qasm3_ast = parse_qasm3(input_, permissive=permissive) |
| 72 | + CalParser(permissive=permissive).visit(qasm3_ast) |
62 | 73 | return qasm3_ast
|
63 | 74 |
|
64 | 75 |
|
65 |
| -def parse_openpulse(input_: str, in_defcal: bool) -> openpulse_ast.CalibrationBlock: |
| 76 | +def parse_openpulse( |
| 77 | + input_: str, in_defcal: bool, permissive: bool = True |
| 78 | +) -> openpulse_ast.CalibrationBlock: |
66 | 79 | lexer = openpulseLexer(InputStream(input_))
|
67 | 80 | stream = CommonTokenStream(lexer)
|
68 | 81 | parser = openpulseParser(stream)
|
69 |
| - tree = parser.calibrationBlock() |
| 82 | + if not permissive: |
| 83 | + # For some reason, the Python 3 runtime for ANTLR 4 is missing the |
| 84 | + # setter method `setErrorHandler`, so we have to set the attribute |
| 85 | + # directly. |
| 86 | + parser._errHandler = BailErrorStrategy() |
| 87 | + try: |
| 88 | + tree = parser.calibrationBlock() |
| 89 | + except (RecognitionException, ParseCancellationException) as exc: |
| 90 | + raise OpenPulseParsingError() from exc |
70 | 91 | result = (
|
71 | 92 | OpenPulseNodeVisitor(in_defcal).visitCalibrationBlock(tree)
|
72 | 93 | if tree.children
|
@@ -316,16 +337,24 @@ def visitOpenpulseStatement(self, ctx: openpulseParser.OpenpulseStatementContext
|
316 | 337 |
|
317 | 338 |
|
318 | 339 | class CalParser(QASMVisitor[None]):
|
319 |
| - """Visit OpenQASM3 AST and pase calibration""" |
| 340 | + """Visit OpenQASM3 AST and parse calibration |
| 341 | +
|
| 342 | + Attributes: |
| 343 | + permissive: should OpenPulse parsing be permissive? If True, ANTLR |
| 344 | + will attempt error recovery (although parsing may still fail elsewhere). |
| 345 | + """ |
| 346 | + |
| 347 | + def __init__(self, permissive: bool = False): |
| 348 | + self.permissive = permissive |
320 | 349 |
|
321 | 350 | def visit_CalibrationDefinition(
|
322 | 351 | self, node: ast.CalibrationDefinition
|
323 | 352 | ) -> openpulse_ast.CalibrationDefinition:
|
324 | 353 | node.__class__ = openpulse_ast.CalibrationDefinition
|
325 |
| - node.body = parse_openpulse(node.body, in_defcal=True).body |
| 354 | + node.body = parse_openpulse(node.body, in_defcal=True, permissive=self.permissive).body |
326 | 355 |
|
327 | 356 | def visit_CalibrationStatement(
|
328 | 357 | self, node: ast.CalibrationStatement
|
329 | 358 | ) -> openpulse_ast.CalibrationStatement:
|
330 | 359 | node.__class__ = openpulse_ast.CalibrationStatement
|
331 |
| - node.body = parse_openpulse(node.body, in_defcal=False).body |
| 360 | + node.body = parse_openpulse(node.body, in_defcal=False, permissive=self.permissive).body |
0 commit comments