一个实验性的C++编译期正则表达式parse工具

孙明琦孙明琦

使用了C++17和gnu扩展

这个东西主要是用来在编译期把正则表达式字符串字面量处理成正则表达式语法树(表达式模板),然后运行期可以直接使用这棵语法树来匹配文字了,避免了运行期编译正则表达式的性能负担,并且类型安全,语法有错的话根本通不过编译

毕竟99%的正则表达式使用场合都是可以编译期确定整段表达式的

目前只支持三个基本元素:连接,或,克林闭包,不支持括号,我也不是很想继续写下去

值得一提的是,我没有使用传统的模板元编程方法,即C++98就有的,使用模板类来搞事情,通过特化来分支,通过成员模板来模拟函数调用,而是使用了boost hana的思路,一种使用C++表达式来表现模板元编程逻辑的方法,借助表达式和auto返回值推导,代码更加友好

不说废话,直接上代码

首先是一点通用的东西

namespace mq
{

template<char c>
struct char_constant : std::integral_constant<char, c>
{
};

template<class T, T c1, T c2>
constexpr std::bool_constant<c1 == c2> operator==(std::integral_constant<T, c1>, std::integral_constant<T, c2>)
{
    return{};
}

template<class T, T c1, T c2>
constexpr std::bool_constant<c1 != c2> operator!=(std::integral_constant<T, c1>, std::integral_constant<T, c2>)
{
    return{};
}

template<class T, T c1, T c2>
constexpr std::bool_constant<(c1 > c2)> operator>(std::integral_constant<T, c1>, std::integral_constant<T, c2>)
{
    return{};
}

template<class T, T c1, T c2>
constexpr std::bool_constant<(c1 >= c2)> operator>=(std::integral_constant<T, c1>, std::integral_constant<T, c2>)
{
    return{};
}

template<class T, T c1, T c2>
constexpr std::bool_constant<(c1 < c2)> operator<(std::integral_constant<T, c1>, std::integral_constant<T, c2>)
{
    return{};
}

template<class T, T c1, T c2>
constexpr std::bool_constant<(c1 <= c2)> operator<=(std::integral_constant<T, c1>, std::integral_constant<T, c2>)
{
    return{};
}

template<bool b1, bool b2>
constexpr std::bool_constant<b1 && b2> operator&&(std::bool_constant<b1>, std::bool_constant<b2>)
{
    return{};
}

template<bool b1, bool b2>
constexpr std::bool_constant<b1 || b2> operator||(std::bool_constant<b1>, std::bool_constant<b2>)
{
    return{};
}

template<bool v, class T1, class T2>
constexpr decltype(auto) cond(std::bool_constant<v>, T1 a, T2 b)
{
    if constexpr (v)
    {
        return a;
    }
    else
    {
        return b;
    }
}

template<class Curr, class Cond, class Iter>
constexpr decltype(auto) iter(Curr i, Cond c, Iter e)
{
    //static_assert(c(i).value);
    if constexpr (c(i).value)
    {
        return iter(e(i), c, e);
    }
    else
    {
        return i;
    }
}

} //namespace mq

然后是本体:

namespace mq
{

template<char c>
constexpr static auto cc = char_constant<c>{};

template<char... chars>
struct char_sequence
{
    template<size_t i>
    constexpr static decltype(auto) get()
    {
        static_assert(i < sizeof...(chars), "internal error");
        return char_constant<std::get<i>(std::make_tuple(chars...))>{};
    }
};

template<class Sequence, size_t _i, class Result>
struct parse_result
{
    constexpr static decltype(auto) sequence()
    {
        return Sequence{};
    }

    constexpr static decltype(auto) get()
    {
        return Sequence::template get<_i>();
    }

    constexpr static decltype(auto) peek()
    {
        return Sequence::template get<_i + 1>();
    }

    constexpr static decltype(auto) result()
    {
        return Result{};
    }

    constexpr static decltype(auto) forward()
    {
        return parse_result<Sequence, _i + 1, Result>{};
    }

    template<class R>
    constexpr static decltype(auto) result(R)
    {
        return parse_result<Sequence, _i, R>{};
    }
};

template<class Derived>
struct regex
{
};

template<char c>
struct match : regex<match<c>>
{
};

template<char c>
constexpr decltype(auto) mkmatch(char_constant<c>)
{
    return match<c>{};
}

template<char c>
struct kleene : regex<kleene<c>>
{
};

template<char c>
constexpr decltype(auto) mkkleene(char_constant<c>)
{
    return kleene<c>{};
}

template<class... Regexes>
struct concat : regex<concat<Regexes...>>
{
};

template<class... Ts>
constexpr decltype(auto) mkconcat(regex<Ts>...)
{
    return concat<Ts...>{};
}

template<class... Rs, class... Ts>
constexpr decltype(auto) mkconcat(concat<Rs...>, regex<Ts>...)
{
    return concat<Rs..., Ts...>{};
}

template<class... Regexes>
struct alter : regex<alter<Regexes...>>
{
};

template<class... Ts>
constexpr decltype(auto) mkalter(regex<Ts>...)
{
    return alter<Ts...>{};
}

template<class... Rs, class... Ts>
constexpr decltype(auto) mkalter(alter<Rs...>, regex<Ts>...)
{
    return alter<Rs..., Ts...>{};
}

struct regex_parser
{
    template<class Seq>
    constexpr static decltype(auto) parse(Seq s)
    {
        return parse_alternative(parse_result<Seq, 0, void>{});
    }
private:
    template<class ParseResult>
    constexpr static decltype(auto) parse_alternative(ParseResult r)
    {
        return iter(parse_concatination(r),
            [](auto res)
        {
            return res.get() != cc<'\0'>;
        },
            [](auto res)
        {
            static_assert((res.get() == cc<'|'>).value);
            auto e = parse_concatination(res.forward());
            return e.result(mkalter(res.result(), e.result()));
        });
    }

    template<class ParseResult>
    constexpr static decltype(auto) parse_concatination(ParseResult r)
    {
        return iter(parse_kleene(r),
            [](auto res)
        {
            return (res.get() != cc<'\0'>) && (res.get() != cc<'|'>);
        },
            [](auto res)
        {
            auto e = parse_kleene(res);
            return e.result(mkconcat(res.result(), e.result()));
        });
        /* 相当于
        auto regex = mkconcat(parse_kleene(r));
        for (;;)
        {
            if (r.get() != '\0' && r.get() != '|')
            {
                regex = mkconcat(regex, parse_kleene(r.forward()));
            }
            else
            {
                return regex;
            }
        }
        */
    }

    template<class ParseResult>
    constexpr static decltype(auto) parse_kleene(ParseResult r)
    {
        auto token = r.get();
        auto next = r.peek();
        return cond(next == cc<'*'>,
            [=] { return r.forward().forward().result(mkkleene(token)); },
            [=] { return r.forward().result(mkmatch(token)); })();
    }

};

template<class TChar, TChar... chars>
constexpr decltype(auto) operator"" _regex()
{
    return regex_parser::parse(char_sequence<chars..., '\0'>{}).result();
}

}

哦顺便说上面那个operator""_regex是gnu的扩展,还挺好用的

验证一下结果:

auto a = "ab*|c"_regex;
std::cout << typeid(a).name() << "\n";

请注意我这里使用的是typeid,这就证明了a的类型在编译期已经确定了

输出的东西demangle一下

mq::alter<mq::concat<mq::match<(char)97>, mq::kleene<(char)98> >, mq::match<(char)99> >

完美


简单总结一下,这一大堆东西,看起来像普通的代码,实际上都是元编程。这里面所有的值,他是什么,我都是不关心的,我甚至不关系表达式是否被求值,lambda是否被真正的调用。我关心的,是他们的类型,这是编译期可以确定的,而这就是boost hana等现代TMP库的设计思路。


各位觉得知乎的阅读体验太差的话,欢迎来我的博客,我会在近期把这篇文章写详细了更新上去。

我的博客是存粹的技术分享,没有吸粉和变现的内容,欢迎大家关注。

18 条评论
评论已关闭
track image