9ae3a8
From db918ab21be13a3c1f3c65d3821c06cf12528099 Mon Sep 17 00:00:00 2001
9ae3a8
Message-Id: <db918ab21be13a3c1f3c65d3821c06cf12528099.1387369730.git.minovotn@redhat.com>
9ae3a8
In-Reply-To: <091eecc4fa42754760dfff393dabcc2b444e9693.1387369730.git.minovotn@redhat.com>
9ae3a8
References: <091eecc4fa42754760dfff393dabcc2b444e9693.1387369730.git.minovotn@redhat.com>
9ae3a8
From: Markus Armbruster <armbru@redhat.com>
9ae3a8
Date: Tue, 10 Dec 2013 15:29:13 +0100
9ae3a8
Subject: [PATCH 13/21] qapi.py: Restructure lexer and parser
9ae3a8
9ae3a8
RH-Author: Markus Armbruster <armbru@redhat.com>
9ae3a8
Message-id: <1386689361-30281-11-git-send-email-armbru@redhat.com>
9ae3a8
Patchwork-id: 56127
9ae3a8
O-Subject: [PATCH 7.0 qemu-kvm 10/18] qapi.py: Restructure lexer and parser
9ae3a8
Bugzilla: 997915
9ae3a8
RH-Acked-by: Laszlo Ersek <lersek@redhat.com>
9ae3a8
RH-Acked-by: Kevin Wolf <kwolf@redhat.com>
9ae3a8
RH-Acked-by: Luiz Capitulino <lcapitulino@redhat.com>
9ae3a8
9ae3a8
From: Markus Armbruster <armbru@redhat.com>
9ae3a8
9ae3a8
The parser has a rather unorthodox structure:
9ae3a8
9ae3a8
    Until EOF:
9ae3a8
9ae3a8
        Read a section:
9ae3a8
9ae3a8
            Generator function get_expr() yields one section after the
9ae3a8
            other, as a string.  An unindented, non-empty line that
9ae3a8
            isn't a comment starts a new section.
9ae3a8
9ae3a8
        Lexing:
9ae3a8
9ae3a8
            Split section into a list of tokens (strings), with help
9ae3a8
            of generator function tokenize().
9ae3a8
9ae3a8
        Parsing:
9ae3a8
9ae3a8
            Parse the first expression from the list of tokens, with
9ae3a8
            parse(), throw away any remaining tokens.
9ae3a8
9ae3a8
            In parse_schema(): record value of an enum, union or
9ae3a8
            struct key (if any) in the appropriate global table,
9ae3a8
            append expression to the list of expressions.
9ae3a8
9ae3a8
    Return list of expressions.
9ae3a8
9ae3a8
Known issues:
9ae3a8
9ae3a8
(1) Indentation is significant, unlike in real JSON.
9ae3a8
9ae3a8
(2) Neither lexer nor parser have any idea of source positions.  Error
9ae3a8
    reporting is hard, let's go shopping.
9ae3a8
9ae3a8
(3) The one error we bother to detect, we "report" via raise.
9ae3a8
9ae3a8
(4) The lexer silently ignores invalid characters.
9ae3a8
9ae3a8
(5) If everything in a section gets ignored, the parser crashes.
9ae3a8
9ae3a8
(6) The lexer treats a string containing a structural character exactly
9ae3a8
    like the structural character.
9ae3a8
9ae3a8
(7) Tokens trailing the first expression in a section are silently
9ae3a8
    ignored.
9ae3a8
9ae3a8
(8) The parser accepts any token in place of a colon.
9ae3a8
9ae3a8
(9) The parser treats comma as optional.
9ae3a8
9ae3a8
(10) parse() crashes on unexpected EOF.
9ae3a8
9ae3a8
(11) parse_schema() crashes when a section's expression isn't a JSON
9ae3a8
    object.
9ae3a8
9ae3a8
Replace this piece of original art by a thoroughly unoriginal design.
9ae3a8
Takes care of (1), (2), (5), (6) and (7), and lays the groundwork for
9ae3a8
addressing the others.  Generated source files remain unchanged.
9ae3a8
9ae3a8
Signed-off-by: Markus Armbruster <armbru@redhat.com>
9ae3a8
Reviewed-by: Eric Blake <eblake@redhat.com>
9ae3a8
Message-id: 1374939721-7876-4-git-send-email-armbru@redhat.com
9ae3a8
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
9ae3a8
(cherry picked from commit c7a3f25200c8692e969f21c7f2555630ec0d0d30)
9ae3a8
---
9ae3a8
 scripts/qapi.py                                | 163 +++++++++++++------------
9ae3a8
 tests/qapi-schema/indented-expr.out            |   2 +-
9ae3a8
 tests/qapi-schema/missing-colon.out            |   4 +-
9ae3a8
 tests/qapi-schema/quoted-structural-chars.err  |   1 +
9ae3a8
 tests/qapi-schema/quoted-structural-chars.exit |   2 +-
9ae3a8
 tests/qapi-schema/quoted-structural-chars.out  |   3 -
9ae3a8
 6 files changed, 88 insertions(+), 87 deletions(-)
9ae3a8
9ae3a8
Signed-off-by: Michal Novotny <minovotn@redhat.com>
9ae3a8
---
9ae3a8
 scripts/qapi.py                                | 163 +++++++++++++------------
9ae3a8
 tests/qapi-schema/indented-expr.out            |   2 +-
9ae3a8
 tests/qapi-schema/missing-colon.out            |   4 +-
9ae3a8
 tests/qapi-schema/quoted-structural-chars.err  |   1 +
9ae3a8
 tests/qapi-schema/quoted-structural-chars.exit |   2 +-
9ae3a8
 tests/qapi-schema/quoted-structural-chars.out  |   3 -
9ae3a8
 6 files changed, 88 insertions(+), 87 deletions(-)
9ae3a8
9ae3a8
diff --git a/scripts/qapi.py b/scripts/qapi.py
9ae3a8
index 38c808e..58e315b 100644
9ae3a8
--- a/scripts/qapi.py
9ae3a8
+++ b/scripts/qapi.py
9ae3a8
@@ -2,9 +2,11 @@
9ae3a8
 # QAPI helper library
9ae3a8
 #
9ae3a8
 # Copyright IBM, Corp. 2011
9ae3a8
+# Copyright (c) 2013 Red Hat Inc.
9ae3a8
 #
9ae3a8
 # Authors:
9ae3a8
 #  Anthony Liguori <aliguori@us.ibm.com>
9ae3a8
+#  Markus Armbruster <armbru@redhat.com>
9ae3a8
 #
9ae3a8
 # This work is licensed under the terms of the GNU GPLv2.
9ae3a8
 # See the COPYING.LIB file in the top-level directory.
9ae3a8
@@ -32,91 +34,92 @@ builtin_type_qtypes = {
9ae3a8
     'uint64':   'QTYPE_QINT',
9ae3a8
 }
9ae3a8
 
9ae3a8
-def tokenize(data):
9ae3a8
-    while len(data):
9ae3a8
-        ch = data[0]
9ae3a8
-        data = data[1:]
9ae3a8
-        if ch in ['{', '}', ':', ',', '[', ']']:
9ae3a8
-            yield ch
9ae3a8
-        elif ch in ' \n':
9ae3a8
-            None
9ae3a8
-        elif ch == "'":
9ae3a8
-            string = ''
9ae3a8
-            esc = False
9ae3a8
-            while True:
9ae3a8
-                if (data == ''):
9ae3a8
-                    raise Exception("Mismatched quotes")
9ae3a8
-                ch = data[0]
9ae3a8
-                data = data[1:]
9ae3a8
-                if esc:
9ae3a8
-                    string += ch
9ae3a8
-                    esc = False
9ae3a8
-                elif ch == "\\":
9ae3a8
-                    esc = True
9ae3a8
-                elif ch == "'":
9ae3a8
-                    break
9ae3a8
-                else:
9ae3a8
-                    string += ch
9ae3a8
-            yield string
9ae3a8
-
9ae3a8
-def parse(tokens):
9ae3a8
-    if tokens[0] == '{':
9ae3a8
-        ret = OrderedDict()
9ae3a8
-        tokens = tokens[1:]
9ae3a8
-        while tokens[0] != '}':
9ae3a8
-            key = tokens[0]
9ae3a8
-            tokens = tokens[1:]
9ae3a8
-
9ae3a8
-            tokens = tokens[1:] # :
9ae3a8
-
9ae3a8
-            value, tokens = parse(tokens)
9ae3a8
-
9ae3a8
-            if tokens[0] == ',':
9ae3a8
-                tokens = tokens[1:]
9ae3a8
-
9ae3a8
-            ret[key] = value
9ae3a8
-        tokens = tokens[1:]
9ae3a8
-        return ret, tokens
9ae3a8
-    elif tokens[0] == '[':
9ae3a8
-        ret = []
9ae3a8
-        tokens = tokens[1:]
9ae3a8
-        while tokens[0] != ']':
9ae3a8
-            value, tokens = parse(tokens)
9ae3a8
-            if tokens[0] == ',':
9ae3a8
-                tokens = tokens[1:]
9ae3a8
-            ret.append(value)
9ae3a8
-        tokens = tokens[1:]
9ae3a8
-        return ret, tokens
9ae3a8
-    else:
9ae3a8
-        return tokens[0], tokens[1:]
9ae3a8
-
9ae3a8
-def evaluate(string):
9ae3a8
-    return parse(map(lambda x: x, tokenize(string)))[0]
9ae3a8
-
9ae3a8
-def get_expr(fp):
9ae3a8
-    expr = ''
9ae3a8
-
9ae3a8
-    for line in fp:
9ae3a8
-        if line.startswith('#') or line == '\n':
9ae3a8
-            continue
9ae3a8
-
9ae3a8
-        if line.startswith(' '):
9ae3a8
-            expr += line
9ae3a8
-        elif expr:
9ae3a8
-            yield expr
9ae3a8
-            expr = line
9ae3a8
+class QAPISchema:
9ae3a8
+
9ae3a8
+    def __init__(self, fp):
9ae3a8
+        self.fp = fp
9ae3a8
+        self.src = fp.read()
9ae3a8
+        if self.src == '' or self.src[-1] != '\n':
9ae3a8
+            self.src += '\n'
9ae3a8
+        self.cursor = 0
9ae3a8
+        self.exprs = []
9ae3a8
+        self.accept()
9ae3a8
+
9ae3a8
+        while self.tok != None:
9ae3a8
+            self.exprs.append(self.get_expr())
9ae3a8
+
9ae3a8
+    def accept(self):
9ae3a8
+        while True:
9ae3a8
+            bol = self.cursor == 0 or self.src[self.cursor-1] == '\n'
9ae3a8
+            self.tok = self.src[self.cursor]
9ae3a8
+            self.cursor += 1
9ae3a8
+            self.val = None
9ae3a8
+
9ae3a8
+            if self.tok == '#' and bol:
9ae3a8
+                self.cursor = self.src.find('\n', self.cursor)
9ae3a8
+            elif self.tok in ['{', '}', ':', ',', '[', ']']:
9ae3a8
+                return
9ae3a8
+            elif self.tok == "'":
9ae3a8
+                string = ''
9ae3a8
+                esc = False
9ae3a8
+                while True:
9ae3a8
+                    ch = self.src[self.cursor]
9ae3a8
+                    self.cursor += 1
9ae3a8
+                    if ch == '\n':
9ae3a8
+                        raise Exception("Mismatched quotes")
9ae3a8
+                    if esc:
9ae3a8
+                        string += ch
9ae3a8
+                        esc = False
9ae3a8
+                    elif ch == "\\":
9ae3a8
+                        esc = True
9ae3a8
+                    elif ch == "'":
9ae3a8
+                        self.val = string
9ae3a8
+                        return
9ae3a8
+                    else:
9ae3a8
+                        string += ch
9ae3a8
+            elif self.tok == '\n':
9ae3a8
+                if self.cursor == len(self.src):
9ae3a8
+                    self.tok = None
9ae3a8
+                    return
9ae3a8
+
9ae3a8
+    def get_members(self):
9ae3a8
+        expr = OrderedDict()
9ae3a8
+        while self.tok != '}':
9ae3a8
+            key = self.val
9ae3a8
+            self.accept()
9ae3a8
+            self.accept()        # :
9ae3a8
+            expr[key] = self.get_expr()
9ae3a8
+            if self.tok == ',':
9ae3a8
+                self.accept()
9ae3a8
+        self.accept()
9ae3a8
+        return expr
9ae3a8
+
9ae3a8
+    def get_values(self):
9ae3a8
+        expr = []
9ae3a8
+        while self.tok != ']':
9ae3a8
+            expr.append(self.get_expr())
9ae3a8
+            if self.tok == ',':
9ae3a8
+                self.accept()
9ae3a8
+        self.accept()
9ae3a8
+        return expr
9ae3a8
+
9ae3a8
+    def get_expr(self):
9ae3a8
+        if self.tok == '{':
9ae3a8
+            self.accept()
9ae3a8
+            expr = self.get_members()
9ae3a8
+        elif self.tok == '[':
9ae3a8
+            self.accept()
9ae3a8
+            expr = self.get_values()
9ae3a8
         else:
9ae3a8
-            expr += line
9ae3a8
-
9ae3a8
-    if expr:
9ae3a8
-        yield expr
9ae3a8
+            expr = self.val
9ae3a8
+            self.accept()
9ae3a8
+        return expr
9ae3a8
 
9ae3a8
 def parse_schema(fp):
9ae3a8
+    schema = QAPISchema(fp)
9ae3a8
     exprs = []
9ae3a8
 
9ae3a8
-    for expr in get_expr(fp):
9ae3a8
-        expr_eval = evaluate(expr)
9ae3a8
-
9ae3a8
+    for expr_eval in schema.exprs:
9ae3a8
         if expr_eval.has_key('enum'):
9ae3a8
             add_enum(expr_eval['enum'])
9ae3a8
         elif expr_eval.has_key('union'):
9ae3a8
diff --git a/tests/qapi-schema/indented-expr.out b/tests/qapi-schema/indented-expr.out
9ae3a8
index 98ae692..98af89a 100644
9ae3a8
--- a/tests/qapi-schema/indented-expr.out
9ae3a8
+++ b/tests/qapi-schema/indented-expr.out
9ae3a8
@@ -1,3 +1,3 @@
9ae3a8
-[OrderedDict([('id', 'eins')])]
9ae3a8
+[OrderedDict([('id', 'eins')]), OrderedDict([('id', 'zwei')])]
9ae3a8
 []
9ae3a8
 []
9ae3a8
diff --git a/tests/qapi-schema/missing-colon.out b/tests/qapi-schema/missing-colon.out
9ae3a8
index 50f827e..e67068c 100644
9ae3a8
--- a/tests/qapi-schema/missing-colon.out
9ae3a8
+++ b/tests/qapi-schema/missing-colon.out
9ae3a8
@@ -1,3 +1,3 @@
9ae3a8
-[OrderedDict([('enum', ','), ('data', ['good', 'bad', 'ugly'])])]
9ae3a8
-[',']
9ae3a8
+[OrderedDict([('enum', None), ('data', ['good', 'bad', 'ugly'])])]
9ae3a8
+[None]
9ae3a8
 []
9ae3a8
diff --git a/tests/qapi-schema/quoted-structural-chars.err b/tests/qapi-schema/quoted-structural-chars.err
9ae3a8
index e69de29..48c849d 100644
9ae3a8
--- a/tests/qapi-schema/quoted-structural-chars.err
9ae3a8
+++ b/tests/qapi-schema/quoted-structural-chars.err
9ae3a8
@@ -0,0 +1 @@
9ae3a8
+Crashed: <type 'exceptions.AttributeError'>
9ae3a8
diff --git a/tests/qapi-schema/quoted-structural-chars.exit b/tests/qapi-schema/quoted-structural-chars.exit
9ae3a8
index 573541a..d00491f 100644
9ae3a8
--- a/tests/qapi-schema/quoted-structural-chars.exit
9ae3a8
+++ b/tests/qapi-schema/quoted-structural-chars.exit
9ae3a8
@@ -1 +1 @@
9ae3a8
-0
9ae3a8
+1
9ae3a8
diff --git a/tests/qapi-schema/quoted-structural-chars.out b/tests/qapi-schema/quoted-structural-chars.out
9ae3a8
index 85405be..e69de29 100644
9ae3a8
--- a/tests/qapi-schema/quoted-structural-chars.out
9ae3a8
+++ b/tests/qapi-schema/quoted-structural-chars.out
9ae3a8
@@ -1,3 +0,0 @@
9ae3a8
-[OrderedDict([('key1', 'value1'), ('key2', [])])]
9ae3a8
-[]
9ae3a8
-[]
9ae3a8
-- 
9ae3a8
1.7.11.7
9ae3a8