在 TypeScript 类型中实现正则表达式(糟糕地)

这是一个关于如何在纯 TypeScript 类型中编写(糟糕的)正则表达式解析器和求值器的警示故事。

Summary:通过在纯 TypeScript 类型中编写代码来实现编译时正则表达式检查,如定义HexStrIsMatch类型来判断字符串是否为十六进制字符串。此技术的灵感来自 2020 年关于RegEx 验证字符串类型讨论的评论,旨在自动在编译时检查十六进制字符串的有效性。

Backstory:为准备万圣节编写castSpell函数,需接受有效十六进制字符串,在 TypeScript 中通常使用品牌类型类型谓词来处理。目前保证只有有效十六进制字符串传递给castSpell的选项有:委托给下一个程序员在编译时检查(优点:在编译时检查,缺点:可能出错)或委托给 JS 运行时在运行时检查(优点:总是正确,缺点:在运行时检查)。但如果想在编译时自动检查十六进制字符串的有效性呢?

Compile-time RegExp checking is feasible:TypeScript 的类型系统是图灵完备的,所以解析和评估正则表达式是可行的。只需要解决“如何在编译时检查正则表达式是否与字符串匹配”的问题。Brzozowski 导数可用于检查字符串是否与正则表达式匹配。

Proof-of-concept:展示了一个编译时正则表达式检查的概念验证代码,通过Derivative类型来缩短待匹配字符串,IsHexStr类型来判断字符串是否为十六进制字符串,RecognizeHex类型用于识别十六进制字符串。但在实际的正则表达式求值器中,需要跟踪正则表达式的部分以进行消耗。

And then I got carried away.:接着写出了一个基本完整的正则表达式解析器和求值器,scc统计代码行数为 805 行,估计开发成本为 51771 美元,开发周期为 4.46 个月,所需人数为 1.03 人。

Reflections

  • Current uses & the path forwardbrzozowski_ts目前在以下情况有用:验证字符串常量是否属于大量难以枚举的字符串集;编写能正确描述大量字符串集的正则表达式;为用户提供字符串常量有效性的快速反馈;使用名义类型验证动态字符串;查找正则表达式将产生的捕获数量和捕获组名称。可能的用例包括基础设施即代码函数接受 IP 地址、有最大长度的字符串、路径和路由路径、URL、电子邮件地址、十六进制/Base64 字符串等。主要用途是查看编译时正则表达式在实践中的效果,该库可能会经过多次迭代,但不打算达到生产质量,希望通过实验为 TypeScript 中的编译时正则表达式验证做出贡献。
  • You probably shouldn’t use my code

    • 对于大多数场景来说过于复杂:如果是检查字符串是否属于有限数量的字符串(最多几百个),使用大字符串联合;如果是无限集且字符串有少量前缀或后缀,使用模板字面量类型;如果是无限集且字符串有简单模式(如上述十六进制字符串),自己使用此技术,简单代码比引入 800 多行类型推断代码更快。
    • 对于许多高级用例,编译时正则表达式不合适:如果是检查字符串是否属于具有嵌套模式的大量字符串(如HTML),正则表达式无法匹配嵌套结构;如果是检查字符串是否属于具有复杂但规则模式的大量可能无限长字符串(如 CSV),在运行时解析输入。解析,不要验证对此有很好的概述。
    • 会减慢编译时间:尚未确定具体减慢多少,正在进行基准测试。在 8 核 32GB linux x86_64 机器上开发,在 VSCode 中增量编译响应迅速,运行测试套件花费约 2.4 秒。
    • 是 alpha 质量:正则表达式解析和求值尚未经过广泛的模糊测试,实现中几乎肯定有错误,也容易受到正则表达式拒绝服务 (ReDoS)的攻击,只是攻击编译时间而不是服务器。
    • 是 alpha 版本:可能会无警告地破坏公共 API。
    • 根本不是发布,只是一个仓库:在进行模糊测试以确保代码正确之前,不会将代码发布到 NPM。
  • Tricks for programming in TypeScript Generics

    • 阅读type-fest中的技巧:其中有一些非常酷的技巧,如使用元组长度进行算术运算、如何检查类型是否等于never等。
    • 不要说never:使用可扩展的错误类型,错误类型中包含上下文信息,便于调试。
    • 边开发边写测试用例:可以使用块作用域将测试用例放在正在构造的类型旁边,避免污染模块命名空间,还可以使用//@ts-expect-error指令标记应该出错的测试用例。
    • prettier --experimental-ternaries可以优雅地处理深度嵌套的类型。
  • A nerd snipe for someone else:有人已经编写了针对 TypeScript 类型的编译器。
阅读 20
0 条评论