libcruft-util/preprocessor.py

210 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
import itertools
###############################################################################
def chunks(values, size):
it = iter(values)
item = list(itertools.islice(it, size))
while item:
yield item
item = list(itertools.islice(it, size))
###############################################################################
# Construct a variadic macro of the form:
#
# ARITY_DISPATCH(_0001, _0002, NAME, ...) NAME
#
# Where the numbered arguments is equal to the predefined maximum arity.
def generate_dispatch(num):
values = itertools.chain(
("_%04u" % n for n in range (1, num+1)),
["NAME"]
)
yield "#define ARITY_DISPATCH(\\"
for group in chunks(values, 8):
yield ", ".join(group) + ", \\"
yield "...) NAME"
###############################################################################
# Construct a variadic macro of the form:
#
# define REDUCE_1_0002(F,X,Y) F(X,Y)
# define REDUCE_1_0003(F,X,Y,...) REDUCE_2_0002(F,F(X,Y),__VA_ARGS__)
# define REDUCE_1_0004(F,X,Y,...) REDUCE_2_0003(F,F(X,Y),__VA_ARGS__)
#
# and so on
#
# We do not provide a definition for REDUCE_1_0000 because it does not
# actually reduce anything. Instead we define it as a static_assert to
# simplify the general dispatch generation code.
#
# The macro is defined as if it were arity 1, because the dispatch generation
# code operates in terms of the tail call and offsets the dispatch arguments
# by the arity.
def generate_reduce(num):
yield '#define REDUCE_1_0000(...) STATIC_ASSERT("REDUCE requires at least two arguments")'
yield "#define REDUCE_1_0001(F,X,Y) F(X,Y)"
for n in range(2, num):
yield "#define REDUCE_1_%04u(F,X,Y,...) REDUCE_1_%04u(F,F(X,Y),__VA_ARGS__)" % (n, n - 1)
###############################################################################
# Generate a variadic macro of the form:
#
# define MAP_ARITY_0000(F,A,B) F(A,B)
# define MAP_ARITY_0001(F,A,B,...) F(X)(MAP_0000_0000(F,A,B,__VA_ARGS___)
# define MAP_ARITY_0002(F,A,B,...) F(X)(MAP_0000_0001(F,A,B,__VA_ARGS___)
#
# and so on...
#
# Note: the arguments A, B, etc are replaced with a quantity of arguments
# indicated by the `arity' parameter.
def generate_map(num, arity):
args = ",".join("ARG%02u" % n for n in range(arity))
if args:
args += ','
yield "#define MAP_%(arity)u_0001(F,%(args)sX) F(%(args)sX)" % { 'arity': arity, 'args': args }
for n in range(2, num):
yield "#define %(curr)s(F,%(args)sX,...) F(%(args)sX)%(next)s(F,%(args)s__VA_ARGS__)" % {
'curr': 'MAP_%(arity)u_%(index)04u' % { 'arity': arity, 'index': n },
'next': 'MAP_%(arity)u_%(index)04u' % { 'arity': arity, 'index': n - 1 },
'arity': arity,
'args': args,
}
##-----------------------------------------------------------------------------
def dispatch(name, num, arity):
yield "#define %(name)s%(arity)u(FUNC,...)\\" % { 'name': name, 'arity': arity }
yield "ARITY_DISPATCH(__VA_ARGS__,\\"
for group in chunks(range(num-arity,0,-1), 4):
yield ",".join (
"%(name)s_%(arity)u_%(n)04u" % {
'name': name,
'arity': arity,
'n': n
} for n in group
) + ",\\"
yield ', STATIC_ASSERT("invalid arity for %s")\\' % name
yield ')(FUNC,__VA_ARGS__)'
###############################################################################
def generate_argcount(num):
yield "#define VA_ARGS_COUNT(...)\\"
yield "ARITY_DISPATCH(__VA_ARGS__,\\"
literals = ",".join(str(n) for n in range(num,0,-1))
for group in chunks(range(num, 0, -1), 4):
yield ", ".join('% 4u' % g for g in group) + ',\\'
yield ', STATIC_ASSERT("invalid value for VA_ARGS_COUNT"))'
###############################################################################
import argparse
##-----------------------------------------------------------------------------
if __name__ == '__main__':
#logging.getLogger().setLevel(logging.INFO)
parser = argparse.ArgumentParser(description='Generate variadic macro functions')
parser.add_argument('dst', type=str, help='the output path for the header')
parser.add_argument('num', type=int, help='the maximum argument count')
args = parser.parse_args()
dst = open(args.dst, 'w')
num = args.num
arity = 3
dst.write("""\
#pragma once
///////////////////////////////////////////////////////////////////////////////
//
// This is autogenerated code, do not modify. Instead, look at preprocessor.py
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Here be dragons. For the love of God, only use these macros for token
// pasting applications. Don't judge me...
///////////////////////////////////////////////////////////////////////////////
// Token concatenation wrapper that prevents macro expansion. You should not
// be using this outside of this header file (and probably not even then for
// the most part).
#define PASTE_DETAIL(x, y) x##y
// A token concatenation wrapper. You should prefer PASTE over PASTE2. This
// is defined purely so that PASTE (defined laterR) has something to give
// REDUCE1.
#define PASTE2(x,y) PASTE_DETAIL(x,y)
// Pair token concatenation with a trailing comma.
#define PASTE_LIST(x,y) PASTE2(x,y),
// Pair token concatenation, transforming into a namespace and type.
#define NAMESPACE_LIST(NS,KLASS) NS::KLASS,
#define EMPTY()
#define DEFER(X) X EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define EXPAND(...) __VA_ARGS__
#define APPEND_COMMA(X) X,
#define ONE_PLUS(ARG) 1+
/// Given the functional macro `FUNC`, that applies some macro to a list of
/// elements, find the number of elements in `FUNC`.
#define COUNT_MAP(FUNC) (FUNC(ONE_PLUS)+0)
#define STRINGIZE_DETAIL(x) #x
#define STRINGIZE(x) STRINGIZE_DETAIL(x)
#define STRINGIZE_LIST(x) STRINGIZE(x),
#define STATIC_ASSERT(MSG) static_assert(false, MSG);
#define DECLARE_STRUCT(KLASS) struct KLASS;
#define DECLARE_CLASS(KLASS) class KLASS;
///////////////////////////////////////////////////////////////////////////////
""")
dst.write("#define MAX_VA_DISPATCH_ARGS %u\n" % num)
dst.write("#define MAX_VA_DISPATCH_ARITY %u\n" % arity)
lines = itertools.chain (
generate_dispatch(num),
generate_map(num, 0),
generate_map(num, 1),
generate_map(num, 2),
dispatch("MAP", num, 0),
dispatch("MAP", num, 1),
dispatch("MAP", num, 2),
generate_reduce(num),
dispatch("REDUCE", num, 1),
[ "#define PASTE(...) REDUCE1(PASTE2,__VA_ARGS__)" ],
generate_argcount(num)
)
for l in lines:
dst.write(l)
dst.write('\n')