如何使用 Red 或 Rebol 解析和转换 DSL



我正在尝试查看是否可以使用Red(或Rebol)来实现一个简单的DSL。我想将我的DSL编译为另一种语言的源代码,可能是Red或C#或两者兼而有之 - 而不是直接解释和执行它。

DSL只有几个简单的语句,以及一个if/else语句。 语句可以分组到规则中。规则将被转换为函数定义,每个语句都是目标语言中的等效语句。

Red/Rebol 中的解析功能很棒,让我很容易实现解析器 - 实际上它基本上只是语法本身的定义。

但是,我无法找到有关如何采取后续步骤的任何示例,特别是处理if语句并将其转换为其他源代码。 翻译 if 语句似乎是一个很好的例子,但仍然有点棘手 - 因为在 Red 中有一个 else 意味着你需要将 if 更改为 要么,而不仅仅是一个额外的可选 else。

传统上,在解析过程中,我会构建一个抽象语法树,然后具有在 AST 上运行并生成新源代码的函数。我应该遵循相同的方法还是红色中还有其他更惯用的方式?

我已经尝试在我的解析规则中使用收集/保留来返回嵌套块块,这实际上形成了 AST。另一种方法是将数据保存到表示不同语句等的特定对象中。

我仍在处理收集/保留,关于何时创建新块以及将保留什么。我还想让我的解析器规则尽可能"干净",尽可能少地交织其他代码。所以我仍然不确定如何最好地在解析规则的圆括号中添加红色代码。过早添加代码可能会导致 Red 代码被执行,即使规则最终失败也是如此。添加代码太晚意味着代码可能无法按预期顺序执行,尤其是在处理多级语句(如 if)时,其中可能包含其他语句。

因此,具体来说,有关如何将我的示例DSL转换为红色源代码的任何帮助将不胜感激。此外,任何在红色或 Rebol 中实现此类 DSL 的链接都会很棒!:)

这是我的解析规则:-

Red [
Purpose: example rules for parsing a simple language
]
SimpleLanguageParser: make object! [
Expr: [string! | integer! | block!]
Data: ['Person.AGE | 'Person.INCOME]
WriteMessageToLog: ['write 'message 'to 'log Expr]
SetData: ['set 'data  Data '= Expr]
IfStatement: ['if Expr [any Statement] opt ['else [any Statement]] 'endif]
Statement: [WriteMessageToLog | SetData | IfStatement]
Rule: [
'rule word!
[any Statement]
'endrule
]
AnySimpLeLanguage: [Rule | [any Statement]]
]
SL: function [slInput] [
parse slInput SimpleLanguageParser/AnySimpleLanguage
]

DSL中某些源代码的示例:-

RULE TooYoung
IF [Person.Age < 15]
WRITE MESSAGE TO LOG "too young to earn an income"
SET DATA Person.Income = 0
ELSE 
WRITE MESSAGE TO LOG "old enough"
ENDIF
ENDRULE 

翻译成红色源代码:-

TooYoung: function [] [
either Person.Age < 15 [
WriteMessageToLog "too young to earn an income"
Person.Income: 0
] [
WriteMessageToLog "old enough"
]
]

数据,即Person.Age,Person.Income和函数WriteMessageToLog都是以前定义的东西。 请注意,为简单起见,我将 Expr 保留为块!等等,而不是在 DSL 本身中更详细地定义 Expr。此外,在函数中设置 Person.Income 不像设置本地那样编码 - 但现在没关系:)

很高兴看到有人挖掘面向语言的编程,继续努力,欢迎来到Red! ;)


指定正确的语法规则是这项工作中最棘手的部分,您已经确定了这一点。剩下的就是在正确的位置穿的PEG(解析表达式语法)与setcopycollect/keep组合和paren!表达式,然后从中创建AST,或者在更简单的情况下,直接发出代码。

这是一个快速出炉(绝对有缺陷!)的例子,说明我是如何解决你的任务的。基本上,它是你的稍微重新设计的代码,其中匹配的模式被setted,copyed或collected,然后绑定到特定的单词,然后粘贴到"模板"(composeemit-rule中的功能)以产生红色代码。

我相信这不是唯一的方法。@rebolek可能会想出更具工业强度的解决方案,因为他有复杂的解析器经验,而我缺乏:P

随访

至于if/else困境,我遵循了上面提出的方法 - 我没有使用opt而是将else-branch的规则包装到块中并添加了一个替代匹配,它只是将false-block设置为none

AST使用什么 - 任何允许表达分层结构的东西,它要么是block!(尽管为了提高性能,你可能希望使用hash!map!)或object!object!的优点是它提供了一个要绑定的上下文,但在这里我们正在接近所谓的绑定学(红色语言的"范围"规则)的领域,这是另一个野兽:)

发出 C# 代码会更难,但可行 - 您需要组装一个字符串而不是 Red 代码。但是,我认为,作为一个新手,您应该坚持直接在块级别进行解析(您在示例中所做的方式),因为它更容易且更具表现力。

另一个有趣(但毛茸茸的)方法是重新定义DSL块中使用的所有单词,以便根据需要工作。

资源

我们在 github 上有一个关于 Red/Rebol 方言的 wiki 条目,你可能会发现它虽然没有用,但读起来很有趣。

另外,红色博客中的两篇文章(这篇和这篇),我想你已经浏览了第一篇(如果没有,你应该!

最后但并非最不重要的一点是,对 Parse 原则和关键字进行了详尽的审查(虽然其中有几个错误的部分,所以,警告 emptor)。它是为 Rebol 编写的,但您应该相当轻松地将示例改编为 Red。

作为一个相对新手,我同意缺乏关于DSL开发的示例和教程,但我们正在努力(至少在我们的头脑中):)

以9214的答案为起点,我编写了一个可能的解决方案。我的方法是:-

  • 尽量保持解析规则尽可能"干净">
  • 使用收集和保留返回块作为结果,而不是尝试构建更复杂的 AST
  • Keep中做一些最小的翻译
  • 生成的块应为有效的红色代码
  • 它使用预定义的函数,需要进行任何更复杂的处理

大多数简单的语句都很容易转换为函数,例如将消息写入日志变得SL_WriteMessageToLog然后可以执行任何需要做的事情。

更复杂的结构语句,例如 If/Else 成为函数,它们采用块参数,然后可以根据需要处理块。

对于 If/Else 的复杂性,我将其分为两个单独的函数,SL_If 和 SL_Else。SL_If按顺序存储条件的结果,SL_Else检查最新结果并将其删除。这允许嵌套的 If/Elses。

可以检查最终结束规则是否存在,以确保正确解析输入。一旦删除它,我们应该有一个有效的函数定义。

这是代码:-

Red [
Purpose: example rules for parsing and translating a simple language
]
; some data
Person.AGE: 0
Person.INCOME: 0
; functions to implement some simple SL statements
SL_WriteMessageToLog: function [value] [
print value
]
SL_SetData: function [parmblock] [
field: parmblock/1
value: parmblock/2
if type? value = word! [
value: do value
]
print ["old value" field "=" do field]
set field value
print ["new value" field "=" do field]
]
; hold the If condition results, to be used to determine whether or not to do Else
IfConditionResults: []
SL_If: function [cond stats] [
cond_result: do cond
head insert IfConditionResults cond_result
if cond_result stats
]
SL_Else: function [stats] [
cond_result: first IfConditionResults
remove IfConditionResults
if not cond_result stats
]
; parsing rules
SimpleLanguageParser: make object! [
Expr: [logic! | string! | integer! | block!]
Data: ['Person.AGE | 'Person.INCOME]
WriteMessageToLog: ['write 'message 'to 'log set x Expr keep ('SL_WriteMessageToLog) keep (x)]
SetData: ['set 'data set d Data '= set x Expr keep ('SL_SetData) keep (reduce [d x])]
IfStatement: ['if keep ('SL_If) keep Expr collect [any Statement] opt ['else keep ('SL_Else) collect [any Statement]] 'endif]
Statement: [WriteMessageToLog | SetData | IfStatement]
Rule: [collect [
'rule set fname word! keep (to set-word! fname) keep ('does)
collect [any Statement]
keep 'endrule
]
]
AnySimpLeLanguage: [Rule | [any Statement]]
]
SL: function [slInput] [
parse slInput SimpleLanguageParser/Rule
]

对于原始帖子中的示例,输出为:-

TooYoung: does [
SL_If [Person.Age < 15] [
SL_WriteMessageToLog "too young to earn an income" 
SL_SetData [Person.Income 0]
] 
SL_Else [
SL_WriteMessageToLog "old enough"
]
]
ENDRULE

感谢您的帮助,让您走到这一步。 如能提供有关此方法和解决方案的反馈,将不胜感激:)

最新更新