First try on generating C code from a declarative YAML by Python.
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)
$(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')
-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)
#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,
val_format(v, &b);
return val_dump_buffer;
}
-
#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_* */
#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);
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");
--- /dev/null
+_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
--- /dev/null
+#!/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)