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

{fmt} library: How to use RegEx to add compile time string checking?

我正在使用 {fmt} 库。

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

可以执行 compile time checking of string formats,这会捕获此错误:

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

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

但我想知道 - 有没有办法用正则表达式和 Visual Studio 中的 find/replace 来做到这一点?

我使用 .NET RegEx tester 和字符串匹配:

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

但是,经过数小时的尝试,我仍然无法找到可靠的搜索和替换。


更新 2020-07-04。

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

在 VSCode 中有效:

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

替换:FMT_STRING("{}")


如果您是 Python 粉丝,如果您以字符串形式读取 C++ 脚本,这也适用:

import re

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

扩展 Mark Moretto 的出色回答:

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

替换为:

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

有了这个:

FMT_STRING()

然后重复此处理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 表达式非常复杂,但是通过这个例子不知何故让我的脑袋亮了起来。

  1. (.*print[(]) 匹配从行首到开头 print( 的任何内容,并替换为 </code>.</li> <li><code>(".*?") 匹配从开盘到收盘的报价,并替换为 </code>.</li> <li><code>(.*[)];) 匹配结束 ); 的所有内容,并替换为 </code>.</li> <li><code>FMT_STRING() 在正确的位置插入 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");