All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Marc-André Lureau" <marcandre.lureau@gmail.com>
To: Markus Armbruster <armbru@redhat.com>
Cc: Eric Blake <eblake@redhat.com>, John Snow <jsnow@redhat.com>,
	QEMU <qemu-devel@nongnu.org>
Subject: Re: [PATCH v5 4/9] qapi: start building an 'if' predicate tree
Date: Fri, 18 Jun 2021 14:31:41 +0400	[thread overview]
Message-ID: <CAJ+F1CJDP4U8Bj32i+3AMgFivNm+VL0REgK++Fx_H7NjeD3=_g@mail.gmail.com> (raw)
In-Reply-To: <87bl88y0is.fsf@dusky.pond.sub.org>

[-- Attachment #1: Type: text/plain, Size: 16817 bytes --]

Hi

On Mon, Jun 14, 2021 at 6:39 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > The following patches are going to express schema 'if' conditions in a
> > target language agnostic way. For that, let's start building a predicate
> > tree of the configuration options.
> >
> > This intermediary steps still uses C-preprocessor expressions as
> > the predicates:
> >
> > "if: [STR, ..]" is translated to a "IfCond -> IfAll ->
> > [IfOption, ..]" tree, which will generate "#if STR && .." C code.
> >
> > Once the boolean operation tree nodes are introduced, the 'if'
> > expressions will be converted to replace the C syntax (no more
> > !defined(), &&, ...) and based only on option identifiers.
> >
> > For now, the condition tree will be less expressive than a full C macro
> > expression as it will only support these operations: 'all', 'any' and
> > 'not', the only ones needed so far.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> > Tested-by: John Snow <jsnow@redhat.com>
> > ---
> >  docs/sphinx/qapidoc.py                 |  6 +--
> >  scripts/qapi/common.py                 | 53 ++++++++++++++++++++++-
> >  scripts/qapi/schema.py                 | 17 ++++++--
> >  tests/qapi-schema/doc-good.out         | 12 +++---
> >  tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
> >  tests/qapi-schema/test-qapi.py         |  2 +-
> >  6 files changed, 103 insertions(+), 45 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index b737949007..0f87fb16ce 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -112,12 +112,10 @@ def _make_section(self, title):
> >      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: 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.docgen())]
> >          if not with_if:
> >              return condlist
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index c305aaf2f1..01e3203545 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 Match, Optional
> > +from typing import Match, Optional, Sequence
> >
> >
> >  #: Magic string that gets removed along with all space to its right.
> > @@ -214,3 +214,54 @@ def must_match(pattern: str, string: str) ->
> Match[str]:
> >      match = re.match(pattern, string)
> >      assert match is not None
> >      return match
> > +
> > +
> > +class IfPredicate:
>
> This is the abstract base class of the two (soon four) forms 'if'.
> qapi-code-gen.txt calls them "conditionals", and the code so far uses
> names like @ifcond.  I'd prefer not to throw "predicate" into the
> cauldron.  IfCond?  IfConditional?
>
>
ok


> > +    """An 'if' condition predicate"""
> > +
> > +    def cgen(self) -> str:
> > +        raise NotImplementedError()
> > +
> > +    def docgen(self) -> str:
> > +        raise NotImplementedError()
> > +
> > +
> > +class IfOption(IfPredicate):
>
> The name IfOption tells me nothing.
>
> At this point in the series, the IfOption are arbitrary C preprocessor
> expressions.
>
> At the end of the series, they are operands of the C preprocessor's
> unary operator defined, i.e. a C macro name.
>
> Once I know that, IfOption kind of makes sense.  Hmm.  IfConfigOption?
> IfIdentifier?  IfLeaf?  I'm not quite sure which one I dislike the least
> :)
>

Ok IfConfigOption.

>
> > +    def __init__(self, option: str):
> > +        self.option = option
> > +
> > +    def cgen(self) -> str:
> > +        return self.option
> > +
> > +    def docgen(self) -> str:
> > +        return self.option
> > +
> > +    def __repr__(self) -> str:
> > +        return f"{type(self).__name__}({self.option!r})"
>
> Intended use?
>

%s in test-qapi


> > +
> > +    def __eq__(self, other: object) -> bool:
> > +        if not isinstance(other, IfOption):
> > +            return NotImplemented
> > +        return self.option == other.option
>
> Why raise on type mismatch?  Feels rather un-pythonic to me.
>

removed


> > +
> > +
> > +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])
>
> Fragile.  See my review of PATCH 3.
>
>
ok

> +
> > +    def docgen(self) -> str:
> > +        return " and ".join([p.docgen() for p in self.pred_list])
> > +
> > +    def __bool__(self) -> bool:
> > +        return bool(self.pred_list)
>
> Not as confusing as QAPISchemaIfCond.__bool__() as long as it's kept
> well away from None.  Still, I'm not sure I like it.
>
>
removed

> +
> > +    def __repr__(self) -> str:
> > +        return f"{type(self).__name__}({self.pred_list!r})"
> > +
> > +    def __eq__(self, other: object) -> bool:
> > +        if not isinstance(other, IfAll):
> > +            return NotImplemented
> > +        return self.pred_list == other.pred_list
>
> Same as above.
>
> Why are these classes in common.py?
>

moved to schema.py


>
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index aa4715c519..f52caaeecc 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -19,7 +19,12 @@
> >  import re
> >  from typing import Optional
> >
> > -from .common import POINTER_SUFFIX, c_name
> > +from .common import (
> > +    POINTER_SUFFIX,
> > +    IfAll,
> > +    IfOption,
> > +    c_name,
> > +)
> >  from .error import QAPIError, QAPISemError, QAPISourceError
> >  from .expr import check_exprs
> >  from .parser import QAPISchemaParser
> > @@ -28,18 +33,22 @@
> >  class QAPISchemaIfCond:
> >      def __init__(self, ifcond=None):
> >          self.ifcond = ifcond or []
> > +        self.pred = IfAll([IfOption(opt) for opt in self.ifcond])
>
> In the common case of just one element, we get a no-op IfAll wrapped
> around it.  Okay.
>
> self.ifcond goes away in PATCH 7.  Okay.
>
> > +
> > +    def docgen(self):
> > +        return self.pred.docgen()
> >
> >      def cgen(self):
> > -        return ' && '.join(self.ifcond)
> > +        return self.pred.cgen()
> >
> >      # Returns true if the condition is not void
> >      def __bool__(self):
> > -        return bool(self.ifcond)
> > +        return bool(self.pred)
> >
> >      def __eq__(self, other):
> >          if not isinstance(other, QAPISchemaIfCond):
> >              return NotImplemented
> > -        return self.ifcond == other.ifcond
> > +        return self.pred == other.pred
>
> Not much left in this class, and it's going to get even thinner.
>

Yes, see v7.


> >
> >
> >  class QAPISchemaEntity:
> > diff --git a/tests/qapi-schema/doc-good.out
> b/tests/qapi-schema/doc-good.out
> > index 8f54ceff2e..db1d23c6bf 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([IfOption('defined(IFONE)')])
> >      member two
> > -    if ['defined(IFCOND)']
> > +    if IfAll([IfOption('defined(IFCOND)')])
> >      feature enum-feat
> >  object Base
> >      member base1: Enum optional=False
> >  object Variant1
> >      member var1: str optional=False
> > -        if ['defined(IFSTR)']
> > +        if IfAll([IfOption('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([IfOption('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([IfOption('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([IfOption('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 e0b8a5f0b6..e4e0fb173a 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([IfOption('defined(TEST_IF_STRUCT_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_STRUCT)')])
> >  enum TestIfEnum
> >      member foo
> >      member bar
> > -        if ['defined(TEST_IF_ENUM_BAR)']
> > -    if ['defined(TEST_IF_ENUM)']
> > +        if IfAll([IfOption('defined(TEST_IF_ENUM_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_ENUM)')])
> >  object q_obj_TestStruct-wrapper
> >      member data: TestStruct optional=False
> >  enum TestIfUnionKind
> >      member foo
> >      member bar
> > -        if ['defined(TEST_IF_UNION_BAR)']
> > -    if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']
> > +        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_UNION) &&
> defined(TEST_IF_STRUCT)')])
> >  object TestIfUnion
> >      member type: TestIfUnionKind optional=False
> >      tag type
> >      case foo: q_obj_TestStruct-wrapper
> >      case bar: q_obj_str-wrapper
> > -        if ['defined(TEST_IF_UNION_BAR)']
> > -    if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']
> > +        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_UNION) &&
> defined(TEST_IF_STRUCT)')])
> >  object q_obj_test-if-union-cmd-arg
> >      member union-cmd-arg: TestIfUnion optional=False
> > -    if ['defined(TEST_IF_UNION)']
> > +    if IfAll([IfOption('defined(TEST_IF_UNION)')])
> >  command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
> >      gen=True success_response=True boxed=False oob=False preconfig=False
> > -    if ['defined(TEST_IF_UNION)']
> > +    if IfAll([IfOption('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([IfOption('defined(TEST_IF_ALT_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_ALT) &&
> defined(TEST_IF_STRUCT)')])
> >  object q_obj_test-if-alternate-cmd-arg
> >      member alt-cmd-arg: TestIfAlternate optional=False
> > -    if ['defined(TEST_IF_ALT)']
> > +    if IfAll([IfOption('defined(TEST_IF_ALT)')])
> >  command test-if-alternate-cmd q_obj_test-if-alternate-cmd-arg -> None
> >      gen=True success_response=True boxed=False oob=False preconfig=False
> > -    if ['defined(TEST_IF_ALT)']
> > +    if IfAll([IfOption('defined(TEST_IF_ALT)')])
> >  object q_obj_test-if-cmd-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([IfOption('defined(TEST_IF_CMD_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_CMD)'),
> IfOption('defined(TEST_IF_STRUCT)')])
> >  command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
> >      gen=True success_response=True boxed=False oob=False preconfig=False
> > -    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
> > +    if IfAll([IfOption('defined(TEST_IF_CMD)'),
> IfOption('defined(TEST_IF_STRUCT)')])
> >  command test-cmd-return-def-three None -> UserDefThree
> >      gen=True success_response=True boxed=False oob=False preconfig=False
> >  array TestIfEnumList TestIfEnum
> > -    if ['defined(TEST_IF_ENUM)']
> > +    if IfAll([IfOption('defined(TEST_IF_ENUM)')])
> >  object q_obj_TEST_IF_EVENT-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([IfOption('defined(TEST_IF_EVT_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_EVT) &&
> defined(TEST_IF_STRUCT)')])
> >  event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
> >      boxed=False
> > -    if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
> > +    if IfAll([IfOption('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([IfOption('defined(TEST_IF_FEATURE_1)')])
> >  object CondFeatureStruct2
> >      member foo: int optional=False
> >      feature feature1
> > -        if ['defined(TEST_IF_FEATURE_1)']
> > +        if IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
> >      feature feature2
> > -        if ['defined(TEST_IF_FEATURE_2)']
> > +        if IfAll([IfOption('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([IfOption('defined(TEST_IF_COND_1)'),
> IfOption('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([IfOption('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([IfOption('defined(TEST_IF_FEATURE_1)')])
> >      feature feature2
> > -        if ['defined(TEST_IF_FEATURE_2)']
> > +        if IfAll([IfOption('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([IfOption('defined(TEST_IF_COND_1)'),
> IfOption('defined(TEST_IF_COND_2)')])
> >  event TEST_EVENT_FEATURES0 FeatureStruct1
> >      boxed=False
> >  event TEST_EVENT_FEATURES1 None
> > diff --git a/tests/qapi-schema/test-qapi.py
> b/tests/qapi-schema/test-qapi.py
> > index 2ec328b22e..631e255fba 100755
> > --- a/tests/qapi-schema/test-qapi.py
> > +++ b/tests/qapi-schema/test-qapi.py
> > @@ -95,7 +95,7 @@ def _print_variants(variants):
> >      @staticmethod
> >      def _print_if(ifcond, indent=4):
> >          if ifcond:
> > -            print('%sif %s' % (' ' * indent, ifcond.ifcond))
> > +            print('%sif %s' % (' ' * indent, ifcond.pred))
> >
> >      @classmethod
> >      def _print_features(cls, features, indent=4):
>
>
>

-- 
Marc-André Lureau

[-- Attachment #2: Type: text/html, Size: 22681 bytes --]

  reply	other threads:[~2021-06-18 10:34 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 1/9] docs: update the documentation about schema configuration marcandre.lureau
2021-06-15 11:48   ` Markus Armbruster
2021-06-18 10:32     ` Marc-André Lureau
2021-06-08 12:07 ` [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
2021-06-14 12:20   ` Markus Armbruster
2021-06-16 10:28     ` Marc-André Lureau
2021-06-18  9:35       ` Markus Armbruster
2021-06-08 12:07 ` [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string marcandre.lureau
2021-06-14 12:45   ` Markus Armbruster
2021-06-14 14:14     ` Markus Armbruster
2021-06-16 10:44     ` Marc-André Lureau
2021-06-18  9:41       ` Markus Armbruster
2021-06-08 12:07 ` [PATCH v5 4/9] qapi: start building an 'if' predicate tree marcandre.lureau
2021-06-14 14:38   ` Markus Armbruster
2021-06-18 10:31     ` Marc-André Lureau [this message]
2021-06-08 12:07 ` [PATCH v5 5/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 6/9] qapi: add IfNot marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
2021-06-15 11:34   ` Markus Armbruster
2021-06-18 10:36     ` Marc-André Lureau
2021-06-08 12:07 ` [PATCH v5 8/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 9/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
2021-06-18  9:36   ` Markus Armbruster
2021-06-15 11:56 ` [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor Markus Armbruster

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='CAJ+F1CJDP4U8Bj32i+3AMgFivNm+VL0REgK++Fx_H7NjeD3=_g@mail.gmail.com' \
    --to=marcandre.lureau@gmail.com \
    --cc=armbru@redhat.com \
    --cc=eblake@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=qemu-devel@nongnu.org \
    /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.