|
27 | 27 | from typing import List
|
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
|
@@ -310,14 +327,17 @@ def visitOpenpulseStatement(self, ctx: openpulseParser.OpenpulseStatementContext
|
310 | 327 | class CalParser(QASMVisitor[None]):
|
311 | 328 | """Visit OpenQASM3 AST and pase calibration"""
|
312 | 329 |
|
| 330 | + def __init__(self, permissive: bool = False): |
| 331 | + self.permissive = permissive |
| 332 | + |
313 | 333 | def visit_CalibrationDefinition(
|
314 | 334 | self, node: ast.CalibrationDefinition
|
315 | 335 | ) -> openpulse_ast.CalibrationDefinition:
|
316 | 336 | node.__class__ = openpulse_ast.CalibrationDefinition
|
317 |
| - node.body = parse_openpulse(node.body, in_defcal=True).body |
| 337 | + node.body = parse_openpulse(node.body, in_defcal=True, permissive=self.permissive).body |
318 | 338 |
|
319 | 339 | def visit_CalibrationStatement(
|
320 | 340 | self, node: ast.CalibrationStatement
|
321 | 341 | ) -> openpulse_ast.CalibrationStatement:
|
322 | 342 | node.__class__ = openpulse_ast.CalibrationStatement
|
323 |
| - node.body = parse_openpulse(node.body, in_defcal=False).body |
| 343 | + node.body = parse_openpulse(node.body, in_defcal=False, permissive=self.permissive).body |
0 commit comments