一个实验性的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库的设计思路——用内建表达式的类型推导搞事情,而不是手动提供类型推导的方式。

时间: 2024-07-30 18:27:43

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

Illegal instruction错误的定位---忽略编译期警告的代价

在原计算机的linux c++程序可以正确跑起来,但是换了一台机器运行时出现致命错误,程序直接当掉,错误提示如下: Illegal instruction (core dumped) 造成改错的主要原因是,我们来先看下编译的提示,警告如下: /home/xxx/src/aa.cpp:621:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ 仔细审查代码发现,函数少了返回值,疼的很,居然编译还通过,只是

JVM-程序编译与代码早期(编译期)优化

早期(编译期)优化 一.Javac编译器 1.Javac的源代码与调试 Javac的源代码放在JDK_SRC_HOME/langtools/src/shares/classes/com/sun/tools/javac中,除了JDK自身的API之外,就只引用了JDK_SRC_HOME/langtools/src/shares/classes/com/sun/*里面的代码,调试环境建立起来简单方便,因为基本上不需要处理依赖关系. 编译过程大致可以分成3个过程: (1)解析与填充符号表过程 (2)插入

JVM总结(六):早期(编译期)优化

这节我们来总结一下JVM编译器优化问题. JVM编译器优化 Javac编译器 Javac的源码和调试 解析与填充符号表 注解处理器 语法分析与字节码生成 Java语法糖 泛型和类型擦除 自动装箱.拆箱与遍历循环 条件编译 JVM编译器优化 JVM的编译器可以分为三个编译器: 1.前端编译器:把.java转变为.class的过程.如Sun的Javac.Eclipse JDT中的增量式编译器(ECJ). 2.JIT编译器:把字节码转变为机器码的过程,如HotSpot VM的C1.C2编译器. 3.A

Javac早期(编译期)

从Sun Javac的代码来看,编译过程大致可以分为3个过程: 解析与填充符号表过程. 插入式注解处理器的注解处理过程. 分析与字节码生成过程. Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,上述3个过程的代码逻辑集中在这个类的compile()和compile2()方法中,整个编译最关键的处理就由图中标注的8个方法来完成,下面我们具体看一下这8个方法实现了什么功能. 解析与填充符号表 解析步骤由上图中的parseFiles()方法(过程

Java注解(3)-注解处理器(编译期|RetentionPolicy.SOURCE)

注解的处理除了可以在运行时通过反射机制处理外,还可以在编译期进行处理.在编译期处理注解时,会处理到不再产生新的源文件为止,之后再对所有源文件进行编译. Java5中提供了apt工具来进行编译期的注解处理.apt是命令行工具,与之配套的是一套描述"程序在编译时刻的静态结构"的API:Mirror API(com.sun.mirror.*).通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供自定义的处理逻辑.具体的处理工具交给apt来处理.编写注解处理器的核心是两个

转载:C++编译期多态与运行期多态

前言 今日的C++不再是个单纯的“带类的C”语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出 彩的那部分.在面向对象C++编程中,多态是OO三大特性之一,这种多态称为运行期多态,也称为动态多态:在泛型编程中,多态基于template(模 板)的具现化与函数的重载解析,这种多态在编译期进行,因此称为编译期多态或静态多态.在本文中,我们将了解: 什么是运行期多态 什么是编译期多态 它们的优缺点在哪 回到顶部 运行期多态 运行期多态的设计思想要归结到

读书笔记 effective c++ Item 41 理解隐式接口和编译期多态

1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), 1 class Widget { 2 public: 3 Widget(); 4 virtual ~Widget(); 5 6 virtual std::size_t size() const; 7 virtual void normalize(); 8 9 void swap(Widget& other); // see Item 25 10 11 ... 12 13 }; 考虑下

Java编译期和运行期

Q.下面的代码片段中,行A和行B所标识的代码有什么区别呢? ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ConstantFolding {       static final  int number1 = 5;       static final  int number2 = 6;       static int number3 = 5;       static int number4= 6;       

Java编译期优化与运行期优化技术浅析

Java语言的“编译期”是一段不确定的过程,因为它可能指的是前端编译器把java文件转变成class字节码文件的过程,也可能指的是虚拟机后端运行期间编译器(JIT)把字节码转变成机器码的过程. 下面讨论的编译期优化指的是javac编译器将java文件转化为字节码的过程,而运行期间优化指的是JIT编译器所做的优化. 编译期优化 虚拟机设计团队把对性能的优化集中到了后端的即时编译器(JIT)中,这样可以让那些不是由javac编译器产生的class文件也同样能享受到编译器优化所带来的好处.但是java