diff mbox series

[15/26] qapi/parser.py: add ParsedExpression type

Message ID 20200922223525.4085762-16-jsnow@redhat.com
State New
Headers show
Series qapi: static typing conversion, pt5 | expand

Commit Message

John Snow Sept. 22, 2020, 10:35 p.m. UTC
This is an immutable, named, typed tuple; it's nicer than arbitrary
dicts for passing data around when using strict typing.

Turn parser.exprs into a list of ParsedExpressions instead, and adjust
expr.py to match.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 scripts/qapi/expr.py   | 56 +++++++++++++++---------------------------
 scripts/qapi/parser.py | 32 +++++++++++++-----------
 scripts/qapi/schema.py |  6 ++---
 3 files changed, 41 insertions(+), 53 deletions(-)
diff mbox series

Patch

diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index cfd342aa04..f2059c505c 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -46,7 +46,7 @@ 
 
 from .common import c_name
 from .error import QAPISemError
-from .parser import QAPIDoc
+from .parser import ParsedExpression
 from .source import QAPISourceInfo
 
 
@@ -517,7 +517,7 @@  class ExpressionType(str, Enum):
 }
 
 
-def check_exprs(exprs: List[_JSObject]) -> List[_JSObject]:
+def check_exprs(exprs: List[ParsedExpression]) -> List[ParsedExpression]:
     """
     Validate and normalize a list of parsed QAPI schema expressions. [RW]
 
@@ -526,49 +526,33 @@  def check_exprs(exprs: List[_JSObject]) -> List[_JSObject]:
 
     :param exprs: The list of expressions to normalize/validate.
     """
-    for expr_elem in exprs:
-        # Expression
-        assert isinstance(expr_elem['expr'], dict)
-        expr: Expression = expr_elem['expr']
-        for key in expr.keys():
-            assert isinstance(key, str)
-
-        # QAPISourceInfo
-        assert isinstance(expr_elem['info'], QAPISourceInfo)
-        info: QAPISourceInfo = expr_elem['info']
-
-        # Optional[QAPIDoc]
-        tmp = expr_elem.get('doc')
-        assert tmp is None or isinstance(tmp, QAPIDoc)
-        doc: Optional[QAPIDoc] = tmp
-
+    for expr in exprs:
         for kind in ExpressionType:
-            if kind in expr:
+            if kind in expr.expr:
                 meta = kind
                 break
         else:
-            raise QAPISemError(info, "expression is missing metatype")
+            raise QAPISemError(expr.info, "expression is missing metatype")
 
         if meta == ExpressionType.INCLUDE:
             continue
 
-        name = cast(str, expr[meta])  # asserted right below:
-        check_name_is_str(name, info, "'%s'" % meta.value)
-        info.set_defn(meta.value, name)
-        check_defn_name_str(name, info, meta.value)
+        name = cast(str, expr.expr[meta])  # asserted right below:
+        check_name_is_str(name, expr.info, "'%s'" % meta.value)
+        expr.info.set_defn(meta.value, name)
+        check_defn_name_str(name, expr.info, meta.value)
 
-        if doc:
-            if doc.symbol != name:
-                raise QAPISemError(
-                    info, "documentation comment is for '%s'" % doc.symbol)
-            doc.check_expr(expr)
-        elif info.pragma.doc_required:
-            raise QAPISemError(info,
-                               "documentation comment required")
+        if expr.doc:
+            if expr.doc.symbol != name:
+                msg = f"documentation comment is for '{expr.doc.symbol}'"
+                raise QAPISemError(expr.info, msg)
+            expr.doc.check_expr(expr.expr)
+        elif expr.info.pragma.doc_required:
+            raise QAPISemError(expr.info, "documentation comment required")
 
-        _CHECK_FN[meta](expr, info)
-        check_if(expr, info, meta.value)
-        check_features(expr.get('features'), info)
-        check_flags(expr, info)
+        _CHECK_FN[meta](expr.expr, expr.info)
+        check_if(expr.expr, expr.info, meta.value)
+        check_features(expr.expr.get('features'), expr.info)
+        check_flags(expr.expr, expr.info)
 
     return exprs
diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
index 490436b48a..f65afa4eb2 100644
--- a/scripts/qapi/parser.py
+++ b/scripts/qapi/parser.py
@@ -19,9 +19,8 @@ 
 import os
 import re
 from typing import (
-    Any,
-    Dict,
     List,
+    NamedTuple,
     Optional,
     Set,
     Type,
@@ -34,13 +33,18 @@ 
 from .source import QAPISourceInfo
 
 
-Expression = Dict[str, Any]
 _Value = Union[List[object], 'OrderedDict[str, object]', str, bool]
 # Necessary imprecision: mypy does not (yet?) support recursive types;
 # so we must stub out that recursion with 'object'.
 # Note, we do not support numerics or null in this parser.
 
 
+class ParsedExpression(NamedTuple):
+    expr: 'OrderedDict[str, object]'
+    info: QAPISourceInfo
+    doc: Optional['QAPIDoc']
+
+
 class QAPIParseError(QAPISourceError):
     """Error class for all QAPI schema parsing errors."""
     T = TypeVar('T', bound='QAPIParseError')
@@ -87,7 +91,7 @@  def __init__(self,
         self.line_pos = 0
 
         # Parser output:
-        self.exprs: List[Expression] = []
+        self.exprs: List[ParsedExpression] = []
         self.docs: List[QAPIDoc] = []
 
         # Showtime!
@@ -139,8 +143,7 @@  def _parse(self) -> None:
                                        "value of 'include' must be a string")
                 incl_fname = os.path.join(os.path.dirname(self._fname),
                                           include)
-                self.exprs.append({'expr': {'include': incl_fname},
-                                   'info': info})
+                self._add_expr(OrderedDict({'include': incl_fname}), info)
                 exprs_include = self._include(include, info, incl_fname,
                                               self._included)
                 if exprs_include:
@@ -157,20 +160,21 @@  def _parse(self) -> None:
                 for name, value in pragma.items():
                     self._pragma(name, value, info)
             else:
-                expr_elem = {'expr': expr,
-                             'info': info}
-                if cur_doc:
-                    if not cur_doc.symbol:
-                        raise QAPISemError(
-                            cur_doc.info, "definition documentation required")
-                    expr_elem['doc'] = cur_doc
-                self.exprs.append(expr_elem)
+                if cur_doc and not cur_doc.symbol:
+                    raise QAPISemError(
+                        cur_doc.info, "definition documentation required")
+                self._add_expr(expr, info, cur_doc)
             cur_doc = None
         self.reject_expr_doc(cur_doc)
 
     def _parse_error(self, msg: str) -> QAPIParseError:
         return QAPIParseError.make(self, msg)
 
+    def _add_expr(self, expr: 'OrderedDict[str, object]',
+                  info: QAPISourceInfo,
+                  doc: Optional['QAPIDoc'] = None) -> None:
+        self.exprs.append(ParsedExpression(expr, info, doc))
+
     @classmethod
     def reject_expr_doc(cls, doc: Optional['QAPIDoc']) -> None:
         if doc and doc.symbol:
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 121d8488d2..51af0449f5 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -1106,9 +1106,9 @@  def _def_event(self, expr, info, doc):
 
     def _def_exprs(self, exprs):
         for expr_elem in exprs:
-            expr = expr_elem['expr']
-            info = expr_elem['info']
-            doc = expr_elem.get('doc')
+            expr = expr_elem.expr
+            info = expr_elem.info
+            doc = expr_elem.doc
             if 'enum' in expr:
                 self._def_enum_type(expr, info, doc)
             elif 'struct' in expr: