#!/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')