Task: Brace expansion


C++: #include <iostream>
#include <iterator>
#include <string>
#include <utility>
#include <vector>

namespace detail {

template <typename ForwardIterator>
class tokenizer
{
	
	ForwardIterator _tbegin, _tend, _end;
	
public:
	
	tokenizer(ForwardIterator begin, ForwardIterator end)
		: _tbegin(begin), _tend(begin), _end(end)
	{ }
	
	template <typename Lambda>
	bool next(Lambda istoken)
	{
		if (_tbegin == _end) {
			return false;
		}
		_tbegin = _tend;
		for (; _tend != _end && !istoken(*_tend); ++_tend) {
			if (*_tend == '\\' && std::next(_tend) != _end) {
				++_tend;
			}
		}
		if (_tend == _tbegin) {
			_tend++;
		}
		return _tbegin != _end;
	}
	
	ForwardIterator begin() const { return _tbegin; }
	ForwardIterator end()   const { return _tend; }
	bool operator==(char c) { return *_tbegin == c; }
	
};

template <typename List>
void append_all(List & lista, const List & listb)
{
	if (listb.size() == 1) {
		for (auto & a : lista) {
			a += listb.back();
		}
	} else {
		List tmp;
		for (auto & a : lista) {
			for (auto & b : listb) {
				tmp.push_back(a + b);
			}
		}
		lista = std::move(tmp);
	}
}

template <typename String, typename List, typename Tokenizer>
List expand(Tokenizer & token)
{
	
	std::vector<List> alts{ { String() } };
	
	while (token.next([](char c) { return c == '{' || c == ',' || c == '}'; })) {
		
		if (token == '{') {
			append_all(alts.back(), expand<String, List>(token));
		} else if (token == ',') {
			alts.push_back({ String() });
		} else if (token == '}') {
			if (alts.size() == 1) {
				for (auto & a : alts.back()) {
					a = '{' + a + '}';
				}
				return alts.back();
			} else {
				for (std::size_t i = 1; i < alts.size(); i++) {
					alts.front().insert(alts.front().end(),
						std::make_move_iterator(std::begin(alts[i])),
						std::make_move_iterator(std::end(alts[i])));
				}
				return std::move(alts.front());
			}
		} else {
			for (auto & a : alts.back()) {
				a.append(token.begin(), token.end());
			}
		}
		
	}
	
	List result{ String{ '{' } };
	append_all(result, alts.front());
	for (std::size_t i = 1; i < alts.size(); i++) {
		for (auto & a : result) {
			a += ',';
		}
		append_all(result, alts[i]);
	}
	return result;
}

} 

template <
	typename ForwardIterator,
	typename String = std::basic_string<
		typename std::iterator_traits<ForwardIterator>::value_type
	>,
	typename List = std::vector<String>
>
List expand(ForwardIterator begin, ForwardIterator end)
{
	detail::tokenizer<ForwardIterator> token(begin, end);
	List list{ String() };
	while (token.next([](char c) { return c == '{'; })) {
		if (token == '{') {
			detail::append_all(list, detail::expand<String, List>(token));
		} else {
			for (auto & a : list) {
				a.append(token.begin(), token.end());
			}
		}
	}
	return list;
}

template <
	typename Range,
	typename String = std::basic_string<typename Range::value_type>,
	typename List = std::vector<String>
>
List expand(const Range & range)
{
	using Iterator = typename Range::const_iterator;
	return expand<Iterator, String, List>(std::begin(range), std::end(range));
}

int main()
{
	
	for (std::string string : {
		R"(~/{Downloads,Pictures}/*.{jpg,gif,png})",
		R"(It{{em,alic}iz,erat}e{d,}, please.)",
		R"({,{,gotta have{ ,\, again\, }}more }cowbell!)",
		R"({}} some {\\{edge,edgy} }{ cases, here\\\})",
		R"(a{b{1,2}c)",
		R"(a{1,2}b}c)",
		R"(a{1,{2},3}b)",
		R"(a{b{1,2}c{}})",
		R"(more{ darn{ cowbell,},})",
		R"(ab{c,d\,e{f,g\h},i\,j{k,l\,m}n,o\,p}qr)",
		R"({a,{\,b}c)",
		R"(a{b,{{c}})",
		R"({a{\}b,c}d)",
		R"({a,b{{1,2}e}f)",
		R"({}} some }{,{\\{ edge, edge} \,}{ cases, {here} \\\\\})",
		R"({{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{)",
	}) {
		std::cout << string << '\n';
		for (auto expansion : expand(string)) {
			std::cout << "    " << expansion << '\n';
		}
		std::cout << '\n';
	}
	
	return 0;
}


C#: using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using static System.Linq.Enumerable;

public static class BraceExpansion
{
    enum TokenType { OpenBrace, CloseBrace, Separator, Text, Alternate, Concat }
    const char L = '{', R = '}', S = ',';
    
    public static void Main() {
        string[] input = {
            "It{{em,alic}iz,erat}e{d,}, please.",
            "~/{Downloads,Pictures}/*.{jpg,gif,png}",
            @"{,{,gotta have{ ,\, again\, }}more }cowbell!",
            @"{}} some }{,{\\{ edge, edge} \,}{ cases, {here} \\\\\}"
        };
        foreach (string text in input) Expand(text);
    }
    
    static void Expand(string input) {
        Token token = Tokenize(input);
        foreach (string value in token) Console.WriteLine(value);
        Console.WriteLine();
    }
    
    static Token Tokenize(string input) {
        var tokens = new List<Token>();
        var buffer = new StringBuilder();
        bool escaping = false;
        int level = 0;
        
        foreach (char c in input) {
            (escaping, level, tokens, buffer) = c switch {
                _ when escaping => (false, level, tokens, buffer.Append(c)),
                '\\' => (true, level, tokens, buffer.Append(c)),
                L => (escaping, level + 1,
                    tokens.With(buffer.Flush()).With(new Token(c.ToString(), TokenType.OpenBrace)), buffer),
                S when level > 0 => (escaping, level,
                    tokens.With(buffer.Flush()).With(new Token(c.ToString(), TokenType.Separator)), buffer),
                R when level > 0 => (escaping, level - 1,
                    tokens.With(buffer.Flush()).With(new Token(c.ToString(), TokenType.CloseBrace)).Merge(), buffer),
                _ => (escaping, level, tokens, buffer.Append(c))
            };
        }
        if (buffer.Length > 0) tokens.Add(buffer.Flush());
        for (int i = 0; i < tokens.Count; i++) {
            if (tokens[i].Type == TokenType.OpenBrace || tokens[i].Type == TokenType.Separator) {
                tokens[i] = tokens[i].Value; 
            }
        }
        return new Token(tokens, TokenType.Concat);
    }
    
    static List<Token> Merge(this List<Token> list) {
        int separators = 0;
        int last = list.Count - 1;
        for (int i = list.Count - 3; i >= 0; i--) {
            if (list[i].Type == TokenType.Separator) {
                separators++;
                Concat(list, i + 1, last);
                list.RemoveAt(i);
                last = i;
            } else if (list[i].Type == TokenType.OpenBrace) {
                Concat(list, i + 1, last);
                if (separators > 0) {
                    list[i] = new Token(list.Range((i+1)..^1), TokenType.Alternate);
                    list.RemoveRange(i+1, list.Count - i - 1);
                } else {
                    list[i] = L.ToString();
                    list[^1] = R.ToString();
                    Concat(list, i, list.Count);
                }
                break;
            }
        }
        return list;
    }
    
    static void Concat(List<Token> list, int s, int e) {
        for (int i = e - 2; i >= s; i--) {
            (Token a, Token b) = (list[i], list[i+1]);
            switch (a.Type, b.Type) {
                case (TokenType.Text, TokenType.Text):
                    list[i] = a.Value + b.Value;
                    list.RemoveAt(i+1);
                    break;
                case (TokenType.Concat, TokenType.Concat):
                    a.SubTokens.AddRange(b.SubTokens);
                    list.RemoveAt(i+1);
                    break;
                case (TokenType.Concat, TokenType.Text) when b.Value == "":
                    list.RemoveAt(i+1);
                    break;
                case (TokenType.Text, TokenType.Concat) when a.Value == "":
                    list.RemoveAt(i);
                    break;
                default:
                    list[i] = new Token(new [] { a, b }, TokenType.Concat);
                    list.RemoveAt(i+1);
                    break;
            }
        }
    }
    
    private struct Token : IEnumerable<string>
    {
        private List<Token>? _subTokens;
        
        public string Value { get; }
        public TokenType Type { get; }
        public List<Token> SubTokens => _subTokens ??= new List<Token>();
        
        public Token(string value, TokenType type) => (Value, Type, _subTokens) = (value, type, null);
        public Token(IEnumerable<Token> subTokens, TokenType type) => (Value, Type, _subTokens) = ("", type, subTokens.ToList());
        
        public static implicit operator Token(string value) => new Token(value, TokenType.Text);
        
        public IEnumerator<string> GetEnumerator() => (Type switch
        {
            TokenType.Concat => SubTokens.Select(t => t.AsEnumerable()).CartesianProduct().Select(p => string.Join("", p)),
            TokenType.Alternate => from t in SubTokens from s in t select s,
            _ => Repeat(Value, 1)
        }).GetEnumerator();
        
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();        
    }
    
    
    static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) {
        IEnumerable<IEnumerable<T>> emptyProduct = new[] { Empty<T>() };
        return sequences.Aggregate(
            emptyProduct,
            (accumulator, sequence) =>
                from acc in accumulator
                from item in sequence
                select acc.Concat(new [] { item }));
    }
    
    static List<Token> With(this List<Token> list, Token token) {
        list.Add(token);
        return list;
    }
    
    static IEnumerable<Token> Range(this List<Token> list, Range range) {
        int start = range.Start.GetOffset(list.Count);
        int end = range.End.GetOffset(list.Count);
        for (int i = start; i < end; i++) yield return list[i];
    }
    
    static string Flush(this StringBuilder builder) {
        string result = builder.ToString();
        builder.Clear();
        return result;
    }
}