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

Posted MQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个实验性的C++编译期正则表达式parser相关的知识,希望对你有一定的参考价值。

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

因为是实验性的,只支持三个基本元素:连接,或,克林闭包,不支持括号,我也不是很想继续写下去(已经写下去了,支持90%的ECMA Script正则表达式标准,但是不是很想拿出来)

值得一提的是,这里使用了C++14/17时代的现代模板元编程方法,与其像C++11那样用模板特化来搞事情,不如用函数重载和返回类型推导,写法更加自然。同时这个方法也是被boost hana等现代模板元编程库所使用的。

不说废话,直接上代码

  • 首先是一点通用的东西
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();
}

}

顺便说上面的template<class TChar, TChar... chars> constexpr decltype(auto) operator"" _regex()是gnu的扩展,是一个在编译期把字符串字面量展开成TChar…序列的功能,这个东西标准没有给,标准只给了编译器展开数字常量到char…的功能。

  • 验证一下结果
auto a = "ab*|c"_regex;
std::cout << typeid(a).name() << "\n";

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

输出的东西demangling一下

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

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

以上是关于一个实验性的C++编译期正则表达式parser的主要内容,如果未能解决你的问题,请参考以下文章

编译原理 | 学习目标

Boost C++ 示例代码 - 静态编译错误

c++正则表达式使用和汇总

shell编程初步grep及正则表达式

ES6小实验-复习正则表达式

如何更好的学习编译原理?