]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Filter types: enum, name and element_type getters generated by a new generator mq-object-generator
authorMaria Matejka <mq@ucw.cz>
Fri, 9 Jun 2023 11:15:45 +0000 (13:15 +0200)
committerMaria Matejka <mq@ucw.cz>
Mon, 12 Jun 2023 07:02:51 +0000 (09:02 +0200)
First try on generating C code from a declarative YAML by Python.

Makefile.in
filter/Makefile
filter/data.c
filter/data.h
filter/test.conf
filter/types.yaml [new file with mode: 0644]
tools/objectgenerator.py [new file with mode: 0755]

index 839efe2438fb21fbd89d230a64bc10ec7e53c264..238c07d10e8df2c9ebc8bf0a33a5ae3a5b517d00 100644 (file)
@@ -83,9 +83,13 @@ conf-lex-targets := $(addprefix $(objdir)/conf/,cf-lex.o)
 conf-y-targets := $(addprefix $(objdir)/conf/,cf-parse.y keywords.h commands.h)
 cf-local = $(conf-y-targets): $(s)config.Y
 
+# Generate %.o requests from source declarations
 src-o-files = $(patsubst %.c,$(o)%.o,$(src))
 tests-target-files = $(patsubst %.c,$(o)%,$(tests_src))
 
+# Request python generated files
+pygen = prepare: $(addprefix $(patsubst %.c,$(o)%,$(filter %-pygen.c,$(src))),.c .h)
+
 all-daemon = $(daemon): $(obj)
 all-client = $(client): $(obj)
 
@@ -134,6 +138,18 @@ $(objdir)/%.S: $(objdir)/%.c | prepare
        $(E)echo CC -o $@ -S $<
        $(Q)$(CC) $(CFLAGS) -MMD -MP -o $@ -S $<
 
+# Generic object generator rules
+$(objdir)/%-pygen.h: $(srcdir)/%.yaml tools/objectgenerator.py | $(objdir)/.dir-stamp
+       $(E)echo PYGEN -o $@ from $<
+       $(Q)$(srcdir)/tools/objectgenerator.py $< $@
+
+$(objdir)/%-pygen.c: $(objdir)/%-pygen.h Makefile | $(objdir)/.dir-stamp
+       $(E)echo PYGEN -o $@
+       $(Q)rm -f $@
+       $(Q)ln -s $(notdir $<) $@
+
+$(objdir)/%-pygen.o: CFLAGS += "-DOG_BUILD_BODY=1"
+
 # Finally include the computed dependencies:
 DEPS = $(shell find $(objdir) -name '*.d')
 
index c20625346e9660fff33fe079aa1505bee87e1720..77c9a15029dac65fc741ab97cab0d51298d3b4c1 100644 (file)
@@ -1,5 +1,7 @@
-src := filter.c data.c f-util.c tree.c trie.c inst-gen.c
+src := filter.c data.c f-util.c tree.c trie.c inst-gen.c types-pygen.c
 obj := $(src-o-files)
+$(info $(pygen))
+$(pygen)
 $(all-daemon)
 $(cf-local)
 
index 8bd9459510c582abbd7bb99bde2cc762880ac2cf..250fa0f2b95f0f6af68818df19a15b9e24a51632 100644 (file)
 #include "filter/f-inst.h"
 #include "filter/data.h"
 
-static const char * const f_type_str[] = {
-  [T_VOID]     = "void",
-
-  [T_INT]      = "int",
-  [T_BOOL]     = "bool",
-  [T_PAIR]     = "pair",
-  [T_QUAD]     = "quad",
-
-  [T_ENUM_RTS] = "enum rts",
-  [T_ENUM_BGP_ORIGIN] = "enum bgp_origin",
-  [T_ENUM_SCOPE] = "enum scope",
-  [T_ENUM_RTC] = "enum rtc",
-  [T_ENUM_RTD] = "enum rtd",
-  [T_ENUM_ROA] = "enum roa",
-  [T_ENUM_NETTYPE] = "enum nettype",
-  [T_ENUM_RA_PREFERENCE] = "enum ra_preference",
-  [T_ENUM_AF]  = "enum af",
-
-  [T_IP]       = "ip",
-  [T_NET]      = "prefix",
-  [T_STRING]   = "string",
-  [T_PATH_MASK]        = "bgpmask",
-  [T_PATH]     = "bgppath",
-  [T_CLIST]    = "clist",
-  [T_EC]       = "ec",
-  [T_ECLIST]   = "eclist",
-  [T_LC]       = "lc",
-  [T_LCLIST]   = "lclist",
-  [T_RD]       = "rd",
-};
-
-const char *
-f_type_name(enum f_type t)
-{
-  if (t < ARRAY_SIZE(f_type_str))
-    return f_type_str[t] ?: "?";
-
-  if ((t == T_SET) || (t == T_PREFIX_SET))
-    return "set";
-
-  return "?";
-}
-
-enum f_type
-f_type_element_type(enum f_type t)
-{
-  switch(t) {
-    case T_PATH:   return T_INT;
-    case T_CLIST:  return T_PAIR;
-    case T_ECLIST: return T_EC;
-    case T_LCLIST: return T_LC;
-    default: return T_VOID;
-  };
-}
-
 const struct f_trie f_const_empty_trie = { .ipv4 = -1, };
 const struct f_val f_const_empty_prefix_set = {
   .type = T_PREFIX_SET,
@@ -611,4 +556,3 @@ val_dump(const struct f_val *v) {
   val_format(v, &b);
   return val_dump_buffer;
 }
-
index 6ca857de2936f1e473e4035423229f8d19e7d78f..5ad03c40cc3049288a52218268c99feb04a2f20d 100644 (file)
 
 #include "nest/bird.h"
 
+/* Generated type routines */
+#include "filter/types-pygen.h"
+
 /* Type numbers must be in 0..0xff range */
 #define T_MASK 0xff
-
-/* Internal types */
-enum f_type {
-/* Nothing. Simply nothing. */
-  T_VOID = 0,
-
-/* User visible types, which fit in int */
-  T_INT = 0x10,
-  T_BOOL = 0x11,
-  T_PAIR = 0x12,  /*   Notice that pair is stored as integer: first << 16 | second */
-  T_QUAD = 0x13,
-
-/* Put enumerational types in 0x30..0x3f range */
-  T_ENUM_LO = 0x30,
-  T_ENUM_HI = 0x3f,
-
-  T_ENUM_RTS = 0x30,
-  T_ENUM_BGP_ORIGIN = 0x31,
-  T_ENUM_SCOPE = 0x32,
-  T_ENUM_RTC = 0x33,
-  T_ENUM_RTD = 0x34,
-  T_ENUM_ROA = 0x35,
-  T_ENUM_NETTYPE = 0x36,
-  T_ENUM_RA_PREFERENCE = 0x37,
-  T_ENUM_AF = 0x38,
-
-/* new enums go here */
-  T_ENUM_EMPTY = 0x3f, /* Special hack for atomic_aggr */
-
 #define T_ENUM T_ENUM_LO ... T_ENUM_HI
 
-/* Bigger ones */
-  T_IP = 0x20,
-  T_NET = 0x21,
-  T_STRING = 0x22,
-  T_PATH_MASK = 0x23,  /* mask for BGP path */
-  T_PATH = 0x24,               /* BGP path */
-  T_CLIST = 0x25,              /* Community list */
-  T_EC = 0x26,         /* Extended community value, u64 */
-  T_ECLIST = 0x27,             /* Extended community list */
-  T_LC = 0x28,         /* Large community value, lcomm */
-  T_LCLIST = 0x29,             /* Large community list */
-  T_RD = 0x2a,         /* Route distinguisher for VPN addresses */
-  T_PATH_MASK_ITEM = 0x2b,     /* Path mask item for path mask constructors */
-
-  T_SET = 0x80,
-  T_PREFIX_SET = 0x81,
-} PACKED;
-
 /* Filter value; size of this affects filter memory consumption */
 struct f_val {
   enum f_type type;    /* T_*  */
@@ -274,10 +230,6 @@ trie_match_next_longest_ip6(net_addr_ip6 *n, ip6_addr *found)
 
 #define F_CMP_ERROR 999
 
-const char *f_type_name(enum f_type t);
-
-enum f_type f_type_element_type(enum f_type t);
-
 int val_same(const struct f_val *v1, const struct f_val *v2);
 int val_compare(const struct f_val *v1, const struct f_val *v2);
 void val_format(const struct f_val *v, buffer *buf);
index 600c551e41754507c6dcae72fa60f02d1e708c0d..fc25ded260f0eccdd2c920ef1e31847b522a3881 100644 (file)
@@ -416,6 +416,7 @@ bt_test_suite(t_ip_set, "Testing sets of ip address");
 
 function t_enum()
 {
+       print(RTS_STATIC);
        bt_assert(format(RTS_STATIC) = "(enum 30)1");
        bt_assert(format(NET_IP4) = "(enum 36)1");
        bt_assert(format(NET_VPN6) = "(enum 36)4");
diff --git a/filter/types.yaml b/filter/types.yaml
new file mode 100644 (file)
index 0000000..8e01cb4
--- /dev/null
@@ -0,0 +1,94 @@
+_meta:
+  header_guard: _BIRD_FILTER_TYPES_H_
+  include:
+    - '"nest/bird.h"'
+
+_keys:
+  type:
+    prefix: f_type_
+    key: enum f_type
+    attributes:
+      name:
+        mandatory: true
+        switch:
+          return: const char *
+          format: '"{data}"'
+      element_type:
+        switch:
+          return: enum f_type
+          format: '{data}'
+          default: T_VOID
+
+type:
+  T_VOID:
+    name: void
+    _value: 0
+
+  T_INT:
+    name: int
+    _value: 0x50
+  T_BOOL:
+    name: bool
+  T_PAIR:
+    name: pair
+  T_QUAD:
+    name: quad
+  T_IP:
+    name: ip
+  T_NET:
+    name: prefix
+  T_STRING:
+    name: string
+  T_PATH_MASK:
+    name: bgpmask
+  T_PATH:
+    name: bgppath
+    element_type: T_INT
+  T_CLIST:
+    name: clist
+    element_type: T_PAIR
+  T_EC:
+    name: ec
+  T_ECLIST:
+    name: eclist
+    element_type: T_EC
+  T_LC:
+    name: lc
+  T_LCLIST:
+    name: lclist
+    element_type: T_LC
+  T_RD:
+    name: rd
+  T_PATH_MASK_ITEM:
+    name: path mask item
+  T_SET:
+    name: set
+  T_PREFIX_SET:
+    name: set
+
+  # Put enum types aside to allow for range switch
+  T_ENUM_LO:
+    name: enum low bound
+    _value: 0x2f
+  T_ENUM_RTS:
+    name: enum rts
+  T_ENUM_BGP_ORIGIN:
+    name: enum bgp_origin
+  T_ENUM_SCOPE:
+    name: enum scope
+  T_ENUM_RTC:
+    name: enum rtc
+  T_ENUM_RTD:
+    name: enum rtd
+  T_ENUM_ROA:
+    name: enum roa
+  T_ENUM_NETTYPE:
+    name: enum nettype
+  T_ENUM_RA_PREFERENCE:
+    name: enum ra_preference
+  T_ENUM_AF:
+    name: enum af
+  T_ENUM_EMPTY:
+    name: enum empty # Special hack for atomic_aggr
+  T_ENUM_HI:
+    name: enum high bound
diff --git a/tools/objectgenerator.py b/tools/objectgenerator.py
new file mode 100755 (executable)
index 0000000..34ec1cd
--- /dev/null
@@ -0,0 +1,142 @@
+#!/usr/bin/env python3
+
+import yaml
+import sys
+
+class ObjectGeneratorException(Exception):
+    pass
+
+class OGAttributeSwitch:
+    def __init__(self, attr, mandatory, **kwargs):
+        if "default" in kwargs:
+            self._default = kwargs["default"]
+        else:
+            self._default = None
+
+        if not mandatory and self._default is None:
+            raise ObjectGeneratorException("Switch must be either mandatory or with a default value supplied")
+
+        self.attr = attr
+        self._return = kwargs["return"]
+        self._format = kwargs["format"]
+        self.mandatory = mandatory
+
+    def write_header(self, _file):
+        _file.write(f"{self._return} {self.attr.obj.prefix}{self.attr.name}({self.attr.obj.key});\n")
+
+    def write_body(self, _file, data):
+        _file.write("\n".join([
+            f"{self._return} {self.attr.obj.prefix}{self.attr.name}({self.attr.obj.key} key)",
+            "{",
+            "  switch (key)",
+            "  {",
+            ""]))
+
+        for k in data:
+            if self.attr.name in data[k]:
+                _file.write(f"    case {k}: return {self._format.format(data=data[k][self.attr.name])};\n")
+            elif self.mandatory:
+                raise ObjectGeneratorException("No value for mandatory attribute {self.attr.name} in key {k}")
+
+        if self.mandatory:
+            _file.write(f'    default: bug("Garbled value of {self.attr.obj.key} in {self.attr.obj.prefix}{self.attr.name}: %d", key);\n')
+        else:
+            _file.write(f'    default: return {self._default};\n')
+        _file.write("  }\n}\n\n")
+
+
+class OGAttribute:
+    def __init__(self, obj, name, mandatory=False, switch=None):
+        self.obj = obj
+        self.name = name
+        self.mandatory = mandatory
+
+        if switch is not None:
+            self.resolver = OGAttributeSwitch(attr=self, name=name, mandatory=mandatory, **switch)
+
+        if self.resolver is None:
+            raise ObjectGeneratorException("No resolver specified")
+
+    def write_header(self, _file):
+        self.resolver.write_header(_file)
+
+    def write_body(self, _file, data):
+        self.resolver.write_body(_file, data)
+
+class OGObject:
+    def __init__(self, og, _def, _data):
+        self.og = og
+        self.attributes = { k: OGAttribute(name=k, obj=self, **_def["attributes"][k]) for k in _def["attributes"] }
+        self.data = _data
+        self.prefix = _def["prefix"]
+        self.key = _def["key"]
+
+    def write_header(self, _file):
+        _file.write(f"{self.key} " + "{\n")
+        for k in self.data:
+            if "_value" in self.data[k]:
+                _file.write(f"  {k} = {self.data[k]['_value']},\n")
+            else:
+                _file.write(f"  {k},\n")
+        _file.write("};\n\n")
+
+        for name,a in self.attributes.items():
+            a.write_header(_file)
+
+    def write_body(self, _file):
+        for name, a in self.attributes.items():
+            a.write_body(_file, self.data)
+
+
+class ObjectGenerator:
+    def __init__(self, _meta, _keys, **data):
+        try:
+            self.objects = { k: OGObject(og=self, _def=_keys[k], _data=data[k]) for k in data }
+        except Exception as e:
+            raise ObjectGeneratorException(f"Failed to map data and _keys") from e
+
+        self._meta = _meta
+
+    def write_out(self, _file):
+        self._write_header(_file)
+        self._write_body(_file)
+
+    def _write_header(self, _file):
+        _file.write(f"""/* File generated by ObjectGenerator */
+#ifndef {self._meta["header_guard"]}
+#define {self._meta["header_guard"]}
+
+""")
+        if "include" in self._meta:
+            for i in self._meta["include"]:
+                _file.write(f"#include {i}\n")
+
+        for k, o in self.objects.items():
+            header = f"/* Headers for {k} */"
+            _file.write(header + "\n")
+            _file.write("/" + ("*"*(len(header)-2)) + "/\n")
+            o.write_header(_file)
+            _file.write("\n")
+
+        _file.write(f"#endif /* {self._meta['header_guard']} */\n\n\n")
+
+    def _write_body(self, _file):
+        _file.write(f"""/* Build with -DOG_BUILD_BODY=1 */
+#ifdef OG_BUILD_BODY
+""")
+        
+        for k, o in self.objects.items():
+            header = f"/* Body for {k} */"
+            _file.write(header + "\n")
+            _file.write("/" + ("*"*(len(header)-2)) + "/\n")
+            o.write_body(_file)
+            _file.write("\n")
+
+        _file.write(f"#endif /* OG_BUILD_BODY */\n")
+
+
+with open(sys.argv[1], "r") as f:
+    t = ObjectGenerator(**dict(yaml.safe_load(f).items()))
+
+with open(sys.argv[2], "w") as f:
+    t.write_out(f)