Expand the variadic expression

One of the cool features introduced in the C++11 standard is the ability to declare a template parameter as being variadic; i.e. that parameter represents a sequence of an arbitrary number of arguments (including zero!).

For example, suppose the parameter pack args expands contains the sequence A0, A1, A2. To expand the pack, an expression is followed using the expansion operator ..., and the expression is repeated for each argument with the parameter pack replaced by each element of the pack.

Some example expansions (assume f and g are not variadic packs):

args... -> A0, A1, A2
f(args)... -> f(A0), f(A1), f(A2)
args(f,g)... -> A0(f,g), A1(f,g), A2(f,g)

If the expansion operator is used on an expression which contains multiple un-expanded parameter packs, the packs are expanded in lock-step. Note that it is required that all parameter packs which participate in a single expansion operator must all have the same length.

For example, consider the packs args0 -> A0, A1, A2 and args1 -> B0, B1, B2. Then the following expressions expand to:

f(args0, args1)... -> f(A0, B0), f(A1, B1), f(A2, B2)
g(args0..., args1)... -> g(A0, A1, A2, B0), g(A0, A1, A2, B1), g(A0, A1, A2, B2)

The goal of this challenge is given an expression and set of parameter packs, produce the expanded equivalent.

Note that there must be at least one un-expanded parameter pack as the argument to the expansion operator. All of the following are syntax errors (assume args0 = A0, A1, A2, args1 = B0, B1):

f(args0, args1)... // packs must have the same length
f(c)... // no un-expanded packs in expansion operator
f(args0...)... // no un-expanded packs in expansion operator

Your program needs not handle any expressions which result in a syntax error.

Input

Your program/function should take as input a dictionary-like object which associates a parameter pack name with it's arguments, and an expression to be expanded (expression syntax explained later). For example, in Python-like syntax the dictionary of packs might look something like this:

{'args0': ['A0', 'A1', 'A2'],
'args1': [],
'args2': ['B0', 'A1', 'B2']}

Feel free to adapt the parameter pack input dictionary as desired. For example, it could be a list of strings where each string is a space-separated list and the first value is the pack name:

args0 A0 A1 A2
args1
args2 B0 A1 B2

Expressions to be expanded will contain only valid C++ identifiers (case-sensitive alpha-numerics and underscore), function call operators, parenthesis grouping, commas, and the expansion operator. Any identifiers in the expression which are not in the dictionary of parameter packs are not variadic. You may assume the given expression has a valid expansion. It is your decision on what restrictions there are on how whitespace is used (for example, every token has a space between it and other tokens, or no whitespace at all).

The input may come from any source desired (stdin, function parameter, etc.)

Output

The output of your program/function is a single string representing the expanded expression. You are allowed to remove/add any whitespace or parenthesis grouping so long as the expression is equivalent. For example, the expression f(a,b,c,d) is equivalent to all of the following:

(f(((a,b,c,d))))
f ( a,  b, c , d )
f((a), (b), (c), (d))

The output can be to any source desired(stdout, function return value, etc.)

Examples

All examples are formatted as such:

The dictionary of packs are expressed as lines of strings in the example format given above (space separated, first value is the pack name), followed by the expression, and the expected result.

case 1:

args0 A0
args2 B0 A0
args1
args0...
A0

case 2:

args0 A0
args2 B0 A0
args1
args1...
// nothing

case 3:

args0 A0
args2 B0 A0
args1
f(args1)...
// nothing

case 4:

args0 A0
args2 B0 A0
args1
f(args1...)
f()

case 5:

args0 A0
args2 B0 A0
args1
f(C0, args0..., args2)...
f(C0, A0, B0), f(C0, A0, A0)

case 6:

args0 A0 A1
args1 B0 B1
args2 C0 C1
f(args0, args1, args2)...
f(A0, B0, C0), f(A1, B1, C1)

case 7:

args0 A0 A1
args0(C0)...
A0(C0), A1(C0)

case 8:

args0 A0 A1
args1 B0 B1
args0(args1(f))...
A0(B0(f)), A1(B1(f))

case 9:

args0 A0 A1
args1 B0 B1
f_(c)
f_(c)

case 10:

args0
args1 A0 A1
f(args0..., args1)...
f(A0), f(A1)

Scoring

This is code-golf; shortest code in bytes wins. Standard loop-holes apply. You may use any built-ins desired.

• Do we need to handle the case where an empty parameter pack is part of a larger comma-separated list? Example: with args empty, f(a, args...) -> f(a) or f(args..., b) -> f(b) – feersum Sep 17 '16 at 19:18
• @feersum yes, I added case 10 to test something similar to that case – helloworld922 Sep 17 '16 at 19:21
• Happy 1000 rep! – Conor O'Brien Sep 18 '16 at 1:32