All of lore.kernel.org
 help / color / mirror / Atom feed
From: marcandre.lureau@redhat.com
To: qemu-devel@nongnu.org
Cc: "Peter Maydell" <peter.maydell@linaro.org>,
	"Michael Roth" <mdroth@linux.vnet.ibm.com>,
	"Juan Quintela" <quintela@redhat.com>,
	"Markus Armbruster" <armbru@redhat.com>,
	"Dr. David Alan Gilbert" <dgilbert@redhat.com>,
	"Gerd Hoffmann" <kraxel@redhat.com>,
	"Marc-André Lureau" <marcandre.lureau@redhat.com>,
	"Paolo Bonzini" <pbonzini@redhat.com>,
	jsnow@redhat.com, "Eduardo Habkost" <ehabkost@redhat.com>
Subject: [PATCH v2 3/9] qapi: start building an 'if' predicate tree
Date: Thu,  5 Nov 2020 16:28:02 +0400	[thread overview]
Message-ID: <20201105122808.1182973-4-marcandre.lureau@redhat.com> (raw)
In-Reply-To: <20201105122808.1182973-1-marcandre.lureau@redhat.com>

From: Marc-André Lureau <marcandre.lureau@redhat.com>

The following patches are going to express conditions in a target
language agnostic way. For that, let's start building a predicate tree
of configuration options.

This intermediary steps still uses C-preprocessor expressions as
predicates: "if: [STR, ..]" is translated to a "IfCond -> IfAll ->
IfOption[STR]" tree, which will generate "#if STR && .." C code. After
the necessary condition tree nodes are introduced, the 'if' C
expressions will be converted.

(the tree will be less expressive than the full C macros expressions,
and will be based only on identifiers. It will only support these
operations over the configuration identifiers: 'all', 'any' and 'not')

For now, the documentation will continue to use a C condition
expression (with defined(FOO), &&, || etc). Eventually, we may want to
generate a more human friendly syntax later.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/sphinx/qapidoc.py                 |  6 +--
 scripts/qapi/common.py                 | 42 ++++++++++++++++++-
 scripts/qapi/schema.py                 | 47 +++++++++++----------
 tests/qapi-schema/doc-good.out         | 12 +++---
 tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
 5 files changed, 104 insertions(+), 61 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index db9520f37f..31b47e4745 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -112,12 +112,10 @@ class QAPISchemaGenRSTVisitor(QAPISchemaVisitor):
     def _nodes_for_ifcond(self, ifcond, with_if=True):
         """Return list of Text, literal nodes for the ifcond
 
-        Return a list which gives text like ' (If: cond1, cond2, cond3)', where
-        the conditions are in literal-text and the commas are not.
+        Return a list which gives text like ' (If: C-condition)'.
         If with_if is False, we don't return the "(If: " and ")".
         """
-        condlist = intersperse([nodes.literal('', c) for c in ifcond.ifcond],
-                               nodes.Text(', '))
+        condlist = [nodes.literal('', ifcond.gen_if())]
         if not with_if:
             return condlist
 
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 6ee72956b5..01c4c0c878 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -12,7 +12,7 @@
 # See the COPYING file in the top-level directory.
 
 import re
-from typing import Optional
+from typing import Optional, Sequence
 
 
 #: Magic string that gets removed along with all space to its right.
@@ -192,3 +192,43 @@ def guardend(name: str) -> str:
 #endif /* %(name)s */
 ''',
                  name=c_fname(name).upper())
+
+
+class IfPredicate:
+    def cgen(self) -> str:
+        raise NotImplementedError()
+
+
+class IfOption(IfPredicate):
+    def __init__(self, option: str):
+        self.option = option
+
+    def cgen(self) -> str:
+        return self.option
+
+    def __repr__(self) -> str:
+        return repr(self.option)
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, IfOption):
+            return False
+        return self.option == other.option
+
+
+class IfAll(IfPredicate):
+    def __init__(self, pred_list: Sequence[IfPredicate]):
+        self.pred_list = pred_list
+
+    def cgen(self) -> str:
+        return " && ".join([p.cgen() for p in self.pred_list])
+
+    def __bool__(self) -> bool:
+        return bool(self.pred_list)
+
+    def __repr__(self) -> str:
+        return f"IfAll({self.pred_list})"
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, IfAll):
+            return False
+        return self.pred_list == other.pred_list
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 92961aedcf..4a4dbf2da7 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -70,7 +70,13 @@ from typing import (
     overload,
 )
 
-from .common import POINTER_SUFFIX, c_name, mcgen
+from .common import (
+    POINTER_SUFFIX,
+    IfAll,
+    IfOption,
+    c_name,
+    mcgen,
+)
 from .error import QAPISemError, QAPISourceError
 from .expr import check_exprs
 from .parser import ParsedExpression, QAPIDoc, QAPISchemaParser
@@ -87,34 +93,33 @@ class Visitable:
 
 class QAPISchemaIf:
     def __init__(self, ifcond: Optional[Sequence[str]] = None):
-        self.ifcond = ifcond or []
-
-    def gen_if(self) -> str:
-        ret = ''
-        for ifc in self.ifcond:
-            ret += mcgen('''
-#if %(cond)s
-''', cond=ifc)
-        return ret
-
-    def gen_endif(self) -> str:
-        ret = ''
-        for ifc in reversed(self.ifcond):
-            ret += mcgen('''
-#endif /* %(cond)s */
-''', cond=ifc)
-        return ret
+        pred_list = [IfOption(opt) for opt in ifcond or []]
+        self.pred = IfAll(pred_list)
 
     def __bool__(self) -> bool:
-        return bool(self.ifcond)
+        return bool(self.pred)
 
     def __repr__(self) -> str:
-        return repr(self.ifcond)
+        return repr(self.pred)
 
     def __eq__(self, other: object) -> bool:
         if not isinstance(other, QAPISchemaIf):
             return NotImplemented
-        return self.ifcond == other.ifcond
+        return self.pred == other.pred
+
+    def gen_if(self) -> str:
+        if self.pred:
+            return mcgen('''
+#if %(cond)s
+''', cond=self.pred.cgen())
+        return ""
+
+    def gen_endif(self) -> str:
+        if self.pred:
+            return mcgen('''
+#endif // %(cond)s
+''', cond=self.pred.cgen())
+        return ""
 
 
 class QAPISchemaEntity(Visitable):
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index 419284dae2..0d27323c47 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -12,15 +12,15 @@ enum QType
 module doc-good.json
 enum Enum
     member one
-        if ['defined(IFONE)']
+        if IfAll(['defined(IFONE)'])
     member two
-    if ['defined(IFCOND)']
+    if IfAll(['defined(IFCOND)'])
     feature enum-feat
 object Base
     member base1: Enum optional=False
 object Variant1
     member var1: str optional=False
-        if ['defined(IFSTR)']
+        if IfAll(['defined(IFSTR)'])
         feature member-feat
     feature variant1-feat
 object Variant2
@@ -29,7 +29,7 @@ object Object
     tag base1
     case one: Variant1
     case two: Variant2
-        if ['IFTWO']
+        if IfAll(['IFTWO'])
     feature union-feat1
 object q_obj_Variant1-wrapper
     member data: Variant1 optional=False
@@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper
 enum SugaredUnionKind
     member one
     member two
-        if ['IFTWO']
+        if IfAll(['IFTWO'])
 object SugaredUnion
     member type: SugaredUnionKind optional=False
     tag type
     case one: q_obj_Variant1-wrapper
     case two: q_obj_Variant2-wrapper
-        if ['IFTWO']
+        if IfAll(['IFTWO'])
     feature union-feat2
 alternate Alternate
     tag type
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 8868ca0dca..8ba658cd38 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -298,65 +298,65 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio
 object TestIfStruct
     member foo: int optional=False
     member bar: int optional=False
-        if ['defined(TEST_IF_STRUCT_BAR)']
-    if ['defined(TEST_IF_STRUCT)']
+        if IfAll(['defined(TEST_IF_STRUCT_BAR)'])
+    if IfAll(['defined(TEST_IF_STRUCT)'])
 enum TestIfEnum
     member foo
     member bar
-        if ['defined(TEST_IF_ENUM_BAR)']
-    if ['defined(TEST_IF_ENUM)']
+        if IfAll(['defined(TEST_IF_ENUM_BAR)'])
+    if IfAll(['defined(TEST_IF_ENUM)'])
 object q_obj_TestStruct-wrapper
     member data: TestStruct optional=False
 enum TestIfUnionKind
     member foo
     member union_bar
-        if ['defined(TEST_IF_UNION_BAR)']
-    if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']
+        if IfAll(['defined(TEST_IF_UNION_BAR)'])
+    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
 object TestIfUnion
     member type: TestIfUnionKind optional=False
     tag type
     case foo: q_obj_TestStruct-wrapper
     case union_bar: q_obj_str-wrapper
-        if ['defined(TEST_IF_UNION_BAR)']
-    if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']
+        if IfAll(['defined(TEST_IF_UNION_BAR)'])
+    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
 object q_obj_TestIfUnionCmd-arg
     member union_cmd_arg: TestIfUnion optional=False
-    if ['defined(TEST_IF_UNION)']
+    if IfAll(['defined(TEST_IF_UNION)'])
 command TestIfUnionCmd q_obj_TestIfUnionCmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if ['defined(TEST_IF_UNION)']
+    if IfAll(['defined(TEST_IF_UNION)'])
 alternate TestIfAlternate
     tag type
     case foo: int
     case bar: TestStruct
-        if ['defined(TEST_IF_ALT_BAR)']
-    if ['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)']
+        if IfAll(['defined(TEST_IF_ALT_BAR)'])
+    if IfAll(['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)'])
 object q_obj_TestIfAlternateCmd-arg
     member alt_cmd_arg: TestIfAlternate optional=False
-    if ['defined(TEST_IF_ALT)']
+    if IfAll(['defined(TEST_IF_ALT)'])
 command TestIfAlternateCmd q_obj_TestIfAlternateCmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if ['defined(TEST_IF_ALT)']
+    if IfAll(['defined(TEST_IF_ALT)'])
 object q_obj_TestIfCmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-        if ['defined(TEST_IF_CMD_BAR)']
-    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
+        if IfAll(['defined(TEST_IF_CMD_BAR)'])
+    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
 command TestIfCmd q_obj_TestIfCmd-arg -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
+    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
 command TestCmdReturnDefThree None -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
 array TestIfEnumList TestIfEnum
-    if ['defined(TEST_IF_ENUM)']
+    if IfAll(['defined(TEST_IF_ENUM)'])
 object q_obj_TestIfEvent-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnumList optional=False
-        if ['defined(TEST_IF_EVT_BAR)']
-    if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
+        if IfAll(['defined(TEST_IF_EVT_BAR)'])
+    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
 event TestIfEvent q_obj_TestIfEvent-arg
     boxed=False
-    if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
+    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
 object FeatureStruct0
     member foo: int optional=False
 object FeatureStruct1
@@ -379,17 +379,17 @@ object FeatureStruct4
 object CondFeatureStruct1
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if IfAll(['defined(TEST_IF_FEATURE_1)'])
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if IfAll(['defined(TEST_IF_FEATURE_1)'])
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if IfAll(['defined(TEST_IF_FEATURE_2)'])
 object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
+        if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
 enum FeatureEnum1
     member eins
     member zwei
@@ -429,17 +429,17 @@ command test-command-features3 None -> None
 command test-command-cond-features1 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if IfAll(['defined(TEST_IF_FEATURE_1)'])
 command test-command-cond-features2 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if IfAll(['defined(TEST_IF_FEATURE_1)'])
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if IfAll(['defined(TEST_IF_FEATURE_2)'])
 command test-command-cond-features3 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
+        if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
 event TEST-EVENT-FEATURES1 None
     boxed=False
     feature deprecated
-- 
2.29.0



  parent reply	other threads:[~2020-11-05 12:34 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-11-05 12:27 [PATCH v2 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
2020-11-05 12:28 ` [PATCH v2 1/9] qapi: replace List[str] by QAPISchemaIf marcandre.lureau
2020-11-05 12:28 ` [PATCH v2 2/9] qapi: move gen_if/gen_endif to QAPIIfSchema marcandre.lureau
2020-11-05 12:28 ` marcandre.lureau [this message]
2020-11-05 12:28 ` [PATCH v2 4/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
2020-11-05 12:28 ` [PATCH v2 5/9] qapi: add IfNot marcandre.lureau
2020-11-05 12:28 ` [PATCH v2 6/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
2020-11-05 12:28 ` [PATCH v2 7/9] qapi: convert 'if' C expressions to the new literal form marcandre.lureau
2020-11-05 12:28 ` [PATCH v2 8/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
2020-11-05 12:28 ` [PATCH v2 9/9] docs: update the documentation about schema configuration marcandre.lureau
2020-11-05 13:43   ` Eric Blake
2020-11-05 14:40     ` Marc-André Lureau

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201105122808.1182973-4-marcandre.lureau@redhat.com \
    --to=marcandre.lureau@redhat.com \
    --cc=armbru@redhat.com \
    --cc=dgilbert@redhat.com \
    --cc=ehabkost@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=kraxel@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=quintela@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.