199 lines
6.4 KiB
Python
Executable File
199 lines
6.4 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("""\
|
|
#ifndef CRUFT_UTIL_PREPROCESSOR_HPP
|
|
#define CRUFT_UTIL_PREPROCESSOR_HPP
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 STRINGIZE_DETAIL(x) #x
|
|
#define STRINGIZE(x) STRINGIZE_DETAIL(x)
|
|
#define STRINGIZE_LIST(x) STRINGIZE(x),
|
|
|
|
|
|
#define STATIC_ASSERT(MSG) static_assert(false, MSG);
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
""")
|
|
|
|
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')
|
|
|
|
dst.writelines("#endif\n")
|