如何使用Red或Rebol

时间:2018-01-26 01:20:22

标签: parsing dsl rebol red

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

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

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

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

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

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

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

因此,特别是,如何将我的示例DSL转换为Red源代码的任何帮助将不胜感激。在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
]

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并不像设置本地一样编码 - 但现在可以了:)

2 个答案:

答案 0 :(得分:1)

总是很高兴看到有人挖掘面向语言的编程,继续保持并欢迎使用Red! ;)

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

实施例

这是一个quickly baked(并且绝对错误!)我如何解决你的任务的例子。基本上,它是稍微重写的代码,其中匹配的模式是setcopy ed或collect ed,然后绑定到特定的单词,然后只粘贴到“模板”( <{1}}内的compose函数生成红色代码。

我相信这不是唯一的方式。 @rebolek 可能会提供更多的工业级解决方案,因为他有复杂的解析器的经验,我缺乏:P

跟进

关于emit-rule困境,我遵循了上面提出的方法 - 而不是使用if/else我将else-branch包装成块的规则并添加了另一个匹配,它只设置了{{1} } opt

用于AST的内容 - 允许表达层次结构的任何内容,可以是false-block(但为了提高性能,您可能希望使用noneblock!)或hash!map!的优点在于它提供了一个被束缚的上下文,但在这里我们正在接近所谓的Bindology(红色语言的“范围”规则)领域,这是另一个野兽:)

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

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

资源

我们在github上有一个关于Red / Rebol方言的wiki entry,你可能会发现这些方言虽然没用,但读起来很有意思。

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

最后,但并非最不重要的是,详尽的review Parse原则和关键字(虽然它有一些错误的部分,所以,请注意)。它是为Rebol编写的,但你应该很容易地将例子改编为Red。

作为该语言的相对新手,我确实认为缺乏关于DSL开发的示例和教程,但我们正在努力(至少在我们的脑海中):)

答案 1 :(得分:0)

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

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

大多数简单的语句很容易转换为函数,例如,WRITE MESSAGE TO LOG变为SL_WriteMessageToLog,然后可以执行任何需要执行的操作。

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

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

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

这是代码: -

<ng-container *ngFor="let pack of myobj.packages">
   <th *ngIf="myobj.type != 'A' && myobj.type != 'B'">
       {{pack.name}} 
   </th>
   <th *ngIf="(myobj.type!= 'A' && myobj.type != 'B') && myobj.electable">
       <span>
          <a (click)="myfn()">Elect All</a>
          <span *ngIf="myobj.type!='O'">|<a (click)="myfn()">Unelect All</a>
          </span>
       </span>
       <ng-container *ngFor="let pack of myobj.packages">
           <th *ngIf="myobj.type=='B' || myobj.type=='A'">
               {{pack.name}} 
           </th>
       </ng-container>
   </th>
</ng-container> 

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

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
]

感谢您的帮助,以实现这一目标。 对此方法和解决方案的反馈将不胜感激:)