我试图看看我是否可以使用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并不像设置本地一样编码 - 但现在可以了:)
答案 0 :(得分:1)
总是很高兴看到有人挖掘面向语言的编程,继续保持并欢迎使用Red! ;)
指定正确的语法规则是这项工作中最棘手的部分,而且你已经确定了这一点。剩下的就是在正确的位置用set
,copy
,collect/keep
组合和paren!
表达式散布您的PEG(解析表达式语法),然后从中创建一个AST或者,在简单的情况下,直接发出代码。
这是一个quickly baked(并且绝对错误!)我如何解决你的任务的例子。基本上,它是稍微重写的代码,其中匹配的模式是set
,copy
ed或collect
ed,然后绑定到特定的单词,然后只粘贴到“模板”( <{1}}内的compose
函数生成红色代码。
我相信这不是唯一的方式。 @rebolek 可能会提供更多的工业级解决方案,因为他有复杂的解析器的经验,我缺乏:P
关于emit-rule
困境,我遵循了上面提出的方法 - 而不是使用if/else
我将else-branch包装成块的规则并添加了另一个匹配,它只设置了{{1} } opt
。
用于AST的内容 - 允许表达层次结构的任何内容,可以是false-block
(但为了提高性能,您可能希望使用none
或block!
)或hash!
。 map!
的优点在于它提供了一个被束缚的上下文,但在这里我们正在接近所谓的Bindology(红色语言的“范围”规则)领域,这是另一个野兽:)
发送C#代码会更难,但可行 - 你需要组装一个字符串而不是Red代码。但是,我认为,作为一个新手,你应该坚持在块级直接解析(你在你的例子中的方式),因为它更容易和更具表现力。
另一个有趣(但很多毛)的方法是重新定义你的DSL块中使用的所有单词,以便按你的需要工作。
我们在github上有一个关于Red / Rebol方言的wiki entry,你可能会发现这些方言虽然没用,但读起来很有意思。
另外,红色博客中的两篇文章(this和this),我认为你已经浏览了第一篇(如果没有,你应该!)。
最后,但并非最不重要的是,详尽的review Parse原则和关键字(虽然它有一些错误的部分,所以,请注意)。它是为Rebol编写的,但你应该很容易地将例子改编为Red。
作为该语言的相对新手,我确实认为缺乏关于DSL开发的示例和教程,但我们正在努力(至少在我们的脑海中):)
答案 1 :(得分:0)
以9214的答案为出发点,我编写了一个可能的解决方案。我的方法是: -
大多数简单的语句很容易转换为函数,例如,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
]
感谢您的帮助,以实现这一目标。 对此方法和解决方案的反馈将不胜感激:)