From 9fa4cd148aec4480c4c4e8c23bc21e4a5753c195 Mon Sep 17 00:00:00 2001 From: Maria Matejka Date: Fri, 9 Jun 2023 13:15:45 +0200 Subject: [PATCH] Filter types: enum, name and element_type getters generated by a new generator First try on generating C code from a declarative YAML by Python. --- Makefile.in | 16 +++++ filter/Makefile | 4 +- filter/data.c | 56 --------------- filter/data.h | 54 +-------------- filter/test.conf | 1 + filter/types.yaml | 94 ++++++++++++++++++++++++++ tools/objectgenerator.py | 142 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 259 insertions(+), 108 deletions(-) create mode 100644 filter/types.yaml create mode 100755 tools/objectgenerator.py diff --git a/Makefile.in b/Makefile.in index 839efe24..238c07d1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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') diff --git a/filter/Makefile b/filter/Makefile index c2062534..77c9a150 100644 --- a/filter/Makefile +++ b/filter/Makefile @@ -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) diff --git a/filter/data.c b/filter/data.c index 8bd94595..250fa0f2 100644 --- a/filter/data.c +++ b/filter/data.c @@ -25,61 +25,6 @@ #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; } - diff --git a/filter/data.h b/filter/data.h index 6ca857de..5ad03c40 100644 --- a/filter/data.h +++ b/filter/data.h @@ -12,57 +12,13 @@ #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); diff --git a/filter/test.conf b/filter/test.conf index 600c551e..fc25ded2 100644 --- a/filter/test.conf +++ b/filter/test.conf @@ -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 index 00000000..8e01cb48 --- /dev/null +++ b/filter/types.yaml @@ -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 index 00000000..34ec1cd4 --- /dev/null +++ b/tools/objectgenerator.py @@ -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)