fmt 库:如何使用 RegEx 添加编译时字符串检查?

Posted

技术标签:

【中文标题】fmt 库:如何使用 RegEx 添加编译时字符串检查?【英文标题】:fmt library: How to use RegEx to add compile time string checking?fmt 库:如何使用 RegEx 添加编译时字符串检查? 【发布时间】:2020-07-02 11:50:43 【问题描述】:

我正在使用fmt 库。

不幸的是,几天后我的程序崩溃了,因为我的格式字符串无效。很容易修复 - 但如果还有更多呢?

可以做compile time checking of string formats,它会捕捉到这个错误:

// Replace this:
fmt::print("",42)
// With this:
fmt::print(FMT_STRING(""),42)

我可以在整个代码库中使用大约 500 个打印语句手动执行此操作。

但我想知道 - 有没有办法使用 RegEx 和 Visual Studio 中的查找/替换来做到这一点?

我已经使用了.NET RegEx tester 和一个字符串匹配:

print[(]".*".*[)];

但是,经过数小时的尝试,我仍然无法实现强大的搜索和替换功能。


2020 年 7 月 4 日更新。

使用我在下面的答案来解决问题。幸运的是,其余的都很完美。

【问题讨论】:

顺便说一句,我认为霍华德与图书馆没有多大关系。根据自述文件:“fmt 库由 Victor Zverovich (vitaut) 和 Jonathan Müller (foonathan) 维护,并得到了许多其他人的贡献。” @user975989 我绝对正确。另一位用户经过深思熟虑地编辑了问题以进行更正。 如果你必须使用宏,你不妨做一个包装fmt::print的宏(用来代替它)并在第一个参数上自动调用FMT_STRING 【参考方案1】:

在 VSCode 中,这是可行的:

模式:.*(fmt::print\()"\\"(,.*\)).*

替换:$1FMT_STRING("")$2


如果您是 Python 爱好者,如果您将 C++ 脚本作为字符串读取,这也适用:

import re

pattern = '.*(fmt::print\()""(.*\)).*'
fmt_snippet = 'fmt::print("",42)'
re.sub(pattern, r'\1FMT_STRING("")\2', fmt_snippet)

【讨论】:

非常感谢!该示例在 VSCode 中运行,但需要进行一些编辑才能在 Visual Studio 中运行。当您投入所有工作时,我已将您的答案标记为官方答案。 谢谢!两者有什么区别?我曾经使用过 VS,但随着最近对库使用方式的更新,它变得有点迟钝。 FWIW,我将 VSC 与 C++ 一起使用,效果很好。设置不同的环境轻而易举。 我认为 VS Code 使用 \( 作为文字,而 Visual Studio 使用 .NET 样式的 RegEx,它使用 [(] 作为文字。我还必须稍微调整它以使其能够处理我代码库中的绝大多数实例。大约 500 行替换了 50k 行,其中 5 行是手动修复的(见答案)。【参考方案2】:

扩展 Mark Moretto 的出色回答:

在 Visual Studio 2019 中,这在我的整个 C++ 代码库中运行良好:

替换这个:

(.*print[(])(".*?")(.*[)];)

有了这个:

$1FMT_STRING($2)$3

然后重复这个来处理fmt::format

(.*format[(])(".*?")(.*[)];)

对于全局 Search'n'Replace,确保 RegEx 图标(即.*)已打开。

我使用.NET Regex Tester 来构成表达式。

它在大约 5 秒内正确修复了 505 个实例中的 500 个,但它确实遇到了我手动修复的这类问题:

// Required manual fix (shift rogue bracket away from end). 
auto y = format("Test=\"\""), 42); 

说明(可选)

我一直发现 RegEx 表达式非常复杂,但是通过这个示例,我的脑海中不知何故打开了灯泡。

    (.*print[(]) 匹配从行首到开头print( 的任何内容,并替换为$1(".*?") 匹配从开头到结尾的引号,并替换为 $2(.*[)];) 匹配结束 ); 的所有内容,并替换为 $3$1FMT_STRING($2)$3 在正确的位置插入 FMT_STRING()

注意事项:

关于第 1 点: 注意使用[(] 来表示文字(。 注意.* 的使用。这是一个通配符,其中. 表示任意字符,* 表示任意重复次数。 它还将匹配fmt::print(。需要不同的正则表达式来处理 format(fmt::format((见上文)。 关于第 2 点: 注意使用 ? 表示它应该在它看到的第一个结束引号处停止(即“非贪婪匹配”)。

附录 A:测试用例(可选)

将下面的Input Tests Cases 粘贴到.NET Regex Tester 中,以便语法高亮和更轻松地编辑表达式以稍微修改它。

输入测试用例:

// Test non-namespace match.
print("Hello, world!");
print("Test: ", 42);
print("Test: : ", 42, "A");
print("Test: :0:  : ", 42, "A", myVariable);
print(", , ", 42, "A", "B");
    print("Hello, world!");
    print("Test: ", 42);
    print("Test: : ", 42, "A");
    print("Test: :0:  : ", 42, "A", myVariable);
    print(", , ", 42, "A", "B");

// Test namespace match.
fmt::print("Hello, world!");
fmt::print("Test: ", 42);
fmt::print("Test: : ", 42, "A");
fmt::print("Test: :0:  : ", 42, "A", myVariable);
fmt::print(", , ", 42, "A", "B");
    fmt::print("Hello, world!");
    fmt::print("Test: ", 42);
    fmt::print("Test: : ", 42, "A");
    fmt::print("Test: :0:  : ", 42, "A", myVariable);
    fmt::print(", , ", 42, "A", "B");

// Test compatibility with existing (should be no change).
​fmt::print(FMT_STRING("Hello, world!"));
​fmt::print(FMT_STRING("Test: "), 42);
​fmt::print(FMT_STRING("Test: : "), 42, "A");
​fmt::print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​fmt::print(FMT_STRING(", , "), 42, "A", "B");
​    fmt::print("Hello, world!");
​    fmt::print(FMT_STRING("Test: "), 42);
​    fmt::print(FMT_STRING("Test: : "), 42, "A");
​    fmt::print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​    fmt::print(FMT_STRING(", , "), 42, "A", "B");

测试用例的输出(全部正确):

// Test non-namespace match.
​print(FMT_STRING("Hello, world!"));
​print(FMT_STRING("Test: "), 42);
​print(FMT_STRING("Test: : "), 42, "A");
​print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​print(FMT_STRING(", , "), 42, "A", "B");
​    print(FMT_STRING("Hello, world!"));
​    print(FMT_STRING("Test: "), 42);
​    print(FMT_STRING("Test: : "), 42, "A");
​    print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​    print(FMT_STRING(", , "), 42, "A", "B");
​
​// Test namespace match.
​fmt::print(FMT_STRING("Hello, world!"));
​fmt::print(FMT_STRING("Test: "), 42);
​fmt::print(FMT_STRING("Test: : "), 42, "A");
​fmt::print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​fmt::print(FMT_STRING(", , "), 42, "A", "B");
​    fmt::print(FMT_STRING("Hello, world!"));
​    fmt::print(FMT_STRING("Test: "), 42);
​    fmt::print(FMT_STRING("Test: : "), 42, "A");
​    fmt::print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​    fmt::print(FMT_STRING(", , "), 42, "A", "B");
​
​// Test compatibility with existing (should be no change).
​​fmt::print(FMT_STRING("Hello, world!"));
​​fmt::print(FMT_STRING("Test: "), 42);
​​fmt::print(FMT_STRING("Test: : "), 42, "A");
​​fmt::print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​​fmt::print(FMT_STRING(", , "), 42, "A", "B");
​​    fmt::print(FMT_STRING("Hello, world!"));
​​    fmt::print(FMT_STRING("Test: "), 42);
​​    fmt::print(FMT_STRING("Test: : "), 42, "A");
​​    fmt::print(FMT_STRING("Test: :0:  : "), 42, "A", myVariable);
​​    fmt::print(FMT_STRING(", , "), 42, "A", "B");

【讨论】:

以上是关于fmt 库:如何使用 RegEx 添加编译时字符串检查?的主要内容,如果未能解决你的问题,请参考以下文章

fmt:编译时字符串格式检查不起作用

带有编译时格式字符串检查的自定义 fmt 格式化函数

一文了解 Go fmt 标准库输出函数的使用

如何将库路径添加到编译器

[C/C++11]_[初级]_[使用正则表达式库regex]

C++有关regex