0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-09-16 18:35:19 +00:00

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.
This commit is contained in:
Maria Matejka 2023-06-09 13:15:45 +02:00
parent 72f3189ca5
commit 9fa4cd148a
7 changed files with 259 additions and 108 deletions

View 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')

View 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)

View File

@ -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;
}

View File

@ -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);

View 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");

94
filter/types.yaml Normal file
View File

@ -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

142
tools/objectgenerator.py Executable file
View File

@ -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)