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 添加编译时字符串检查?的主要内容,如果未能解决你的问题,请参考以下文章