如何使用Red或Rebol解析和翻译DSL

gur*_*aku 6 dsl parsing rebol red

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

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

Red/Rebol中的解析功能很棒,让我可以非常轻松地实现解析器 - 实际上它基本上只是语法本身的定义.

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

传统上,在解析期间,我将构建一个抽象语法树,然后具有在AST上操作并生成新源代码的函数.我应该遵循同样的方法还是在Red中还有其他一些更惯用的方法?

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

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

因此,特别是,如何将我的示例DSL转换为红色源代码的任何帮助将不胜感激.在Red或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
]
Run Code Online (Sandbox Code Playgroud)

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 
Run Code Online (Sandbox Code Playgroud)

转换为红色源代码: -

TooYoung: function [] [
    either Person.Age < 15 [
        WriteMessageToLog "too young to earn an income"
        Person.Income: 0
    ] [
        WriteMessageToLog "old enough"
    ]
]
Run Code Online (Sandbox Code Playgroud)

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

921*_*214 3

总是很高兴看到有人挖掘面向语言的编程,坚持下去,欢迎来到 Red!;)


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

例子

这是我如何解决您的任务的一个快速完成的(并且绝对有问题!)示例。基本上,它是对您的代码进行了稍微修改,其中匹配的模式是setted、copyed 或collected,然后绑定到特定单词,然后将其粘贴到“模板”(compose内部函数emit-rule)中以生成红色代码。

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

跟进

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

用于 AST 的内容 - 任何允许表达层次结构的东西,可以是 a block!(尽管为了提高性能,您可能想要使用hash!or map!)或object!. 它的优点object!是它提供了一个要绑定的上下文,但这里我们正在接近所谓的 Bindology(Red 语言的“范围”规则)的领域,这是另一个野兽:)

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

另一种有趣(但很麻烦)的方法是重新定义 DSL 块中使用的所有单词,使其按照您的需要工作。

资源

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

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

最后但并非最不重要的一点是,对 Parse 原则和关键字进行了详尽的回顾(尽管其中有一些错误的部分,所以买者自负)。它是为 Rebol 编写的,但您应该相当容易地将示例改编为 Red。

作为该语言的相对新手,我确实同意缺乏有关 DSL 开发的示例和教程,但我们正在努力解决这个问题(至少在我们的头脑中):)