我在不同的上下文中做了一些类似的事情(从输入规范生成代码),所以我将概述我所做的事情以提供一些思考。我使用了Config4 *(披露:我开发了它)。如果您对我在下面描述的方法感兴趣,那么我建议您阅读Config4* 入门指南的第 2 章和第 3 章,以大致了解 Config4* 语法和 API。或者,用不同的配置语法表达以下概念,例如 XML。
Config4* 是一种配置语法,与本次讨论相关的语法子集如下:
# this is a comment
name1 = "simple value";
name2 = ["a", "list of", "values"];
# a list can be laid out in columns to simulate a table of information
name3 = [
# item colour
#------------------
"car", "red",
"jeans", "blue",
"roses", "red",
];
在代码生成器应用程序中,我使用表格提供规则来指定如何生成代码以将值分配给消息字段。如果没有为特定字段指定规则,则某些内置规则会提供默认行为。该表如下所示:
field_rules = [
# wildcarded message.field instruction
#----------------------------------------------------------------
"Msg1.username", "@config:username",
"Msg1.password", "@config:password",
"Msg3.price", "@order:price",
"*.account", "@string:foobar",
"*.secondary_account", "@ignore",
"*.heartbeat_interval", "@expr:_heartbeatInterval * 1000",
"*.send_timestamp", "@now",
];
当我的代码生成器想要生成代码来为字段赋值时,代码生成器构造了一个格式为 的字符串"<message-name>.<field-name>"
,例如Msg3.price
. 然后它field_rules
逐行检查表(从顶部开始)以找到第一列匹配的行"<message-name>.<field-name>"
。匹配逻辑允许*
作为可以匹配零个或多个字符的通配符。(方便地,Config4* 提供了patternMatch()
提供此功能的实用程序操作。)
如果找到匹配项,则instruction
列中的值会告诉代码生成器要生成哪种代码。(如果未找到匹配项,则使用内置规则,如果未应用这些规则,则不会为该字段生成代码。)
每条指令都是一个形式的字符串"@<keyword>:optional,arguments"
。这被标记为提供关键字和可选参数。关键字被转换为enum
, 并驱动switch
生成代码的语句。例如:
- 该
@config:username
指令指定应生成代码以将username
运行时配置文件中的变量值分配给该字段。
- 该
@order:price
指令指定应生成代码以将调用返回的值分配orderObj->getPrice()
给该字段。
-
应将
@string:foobar
指定字符串文字的指令分配给该字段。foobar
- 该
@expr:_heartbeatInterval * 1000
指令指定应生成代码以将表达式的值分配给_heartbeatInterval * 1000
字段。
- 该
@ignore
指令指定不应生成任何代码来为该字段分配值。
- 该
@now
指令指定应生成代码以将当前时钟时间分配给该字段。
我已经在几个项目中使用了上述技术,并且每次我都针对特定项目的需求发明了指令。如果您决定使用这种技术,那么显然您将需要发明指令来指定运行时转换,而不是生成代码的指令。此外,不要觉得您必须将所有基于翻译的配置硬塞到一个表中。例如,您可以使用一个表来提供源 URL -> 目标 URL映射,并使用另一个表来提供翻译消息中的字段的说明。
如果这项技术对您和我的项目一样有效,那么您的翻译应用程序最终将成为一个“引擎”,其行为完全由配置文件驱动,实际上是一个 DSL(特定领域的语言)。该 DSL 文件可能非常紧凑(少于 100 行),并且将成为用户可见的应用程序的一部分。因此,值得投入精力使 DSL 尽可能直观且易于阅读/修改,因为这样做会使翻译应用程序:(1) 用户友好,以及 (2) 易于以用户手册。