在,我们已经为DSL建立了一个完整的语义模型。我们距离 DSL 语法示例的实际运行还有一步之遥。即基于语法树(逆波兰风格)提取信息,组装语义模型,加载语义模型。在对各种规则处理器进行建模和实例化之后,我们就可以处理数据了!以下是我们部署在海洋浮标上的指标采集程序全景图:
在本文中,我们将按照上图c语言 逆波兰计算,使用语法树提取反向润色并组装语义模型,使我们的语法示例真正按预期工作!
《Go语言提升之路:从新手到高手的编程思想、方法和技能集2册》将从编程思维和实战技巧两个维度给出答案,助你在Go的道路上进步一半努力。
编程思维水平:
只有真正了解一门语言的设计理念和编程思想,并且能够在实践中使用,才算精通这门语言。
《Go 语言改进之路:从新手到高手的编程思想、方法和技能集 2 卷》从 Go 语言设计者的角度梳理了 Go 背后的设计理念和编程思想。分析引导读者了解那些看似随意但经过深思熟虑的设计背后的秘密。
实践技能水平:
实用技巧来源于Go开发团队和Go社区开发的优质代码…………”,”作者”:[“白明”],”publisher “:”机械工业出版社”},”appuin”:”3208869061″,”isNewCpsKOL”:0}”>
一. from 语法树提取逆波兰语并组装语义模型
通过上面对语义模型的解释,我们知道语法树与语义模型的联系包括逆波兰语、windowsRange、result和enumableFunc。主要的环节是逆波兰,windowsRange、result、enumableFunc等信息比较容易提取。
接下来我们看看如何从DSL的语法树结构中提取逆波兰语,完成逆波兰语的提取,我们的语义模型组装工作已经完成了大半。好吧,让我们关注 DSL 语法树。
为了重点讲解原理,本文只实现了语法示例文件中包含单个规则的语法树的逆波兰类型等信息的提取。语法示例文件中有多个规则的情况留作思考题。
在中,我们知道ANTLR Listener对DSL语法树的遍历默认是前序遍历。在这样的遍历过程中,我们提取变量、文字、一元运算符和二元运算符c语言 逆波兰计算,并以逆波兰形式组织它们的操作顺序。我们使用的提取和变换算法如下:
以下是具体的代码实现,我们构建一个ReversePolishExprListener结构,从语法树中提取信息用于构建语义模型:
// tdat/reverse_polish_expr_listener.go
type ReversePolishExprListener struct {
*parser.BaseTdatListener
ruleID string
// for constructing Reverse Polish expression
//
// infixExpr:($speed<5)and($temperature<2)or(roundDown($sanility)
//
// reversePolishExpr:
// $speed,5,<,$temperature,2,<,and,$sanility,roundDown,600,<,or
//
reversePolishExpr []semantic.Value
s1 semantic.Stack[*Item] // temp stack for constructing reversePolishExpr, for final result
s2 semantic.Stack[*Item] // temp stack for constructing reversePolishExpr, for operator temporarily
// for windowsRange
low int
high int
// for enumerableFunc
ef string
// for result
result []string
}
对于变量和字面量,直接压入s1栈;对于一元运算符,直接压入s2栈;对于二元运算符,我们以比较运算符(comparisonOp)为例,看看它的处理逻辑:
func (l *ReversePolishExprListener) ExitComparisonOp(c *parser.ComparisonOpContext) {
l.handleBinOperator(c.BaseParserRuleContext)
}
func (l *ReversePolishExprListener) handleBinOperator(c *antlr.BaseParserRuleContext) {
v := c.GetText()
lvl := getLevel(c)
for {
lastOp := l.s2.Top()
if lastOp == nil {
l.s2.Push(&Item{
level: lvl,
val: &semantic.BinaryOperator{
Val: v,
},
})
return
}
if lvl > lastOp.level {
l.s2.Push(&Item{
level: lvl,
val: &semantic.BinaryOperator{
Val: v,
},
})
return
}
l.s1.Push(l.s2.Pop())
}
}
算术运算符和逻辑运算符等二元运算符类似于比较运算符,直接调用handleBinOperator。 handleBinOperator 的逻辑就像我们之前描述的算法步骤一样。首先,比较s2栈顶节点的层级。如果节点的深度小于s2栈顶节点的深度(级别),则弹出s2栈顶的节点。 ,并将其推入 s1 堆栈;循环这一步,直到s2栈为空或者当前节点深度大于s2栈顶节点的深度,然后将该节点打包为semantic.BinaryOperator并压入s2栈中。
我们根据最顶层conditionExpr中的s1栈得到我们期望的逆波兰表达式:
func (l *ReversePolishExprListener) ExitConditionExpr(c *parser.ConditionExprContext) {
// get the rule index of parent context
if i, ok := c.GetParent().(antlr.RuleContext); ok {
if i.GetRuleIndex() != parser.TdatParserRULE_ruleLine {
// 非最顶层的conditionExpr节点
return
}
}
// pop all left in the stack
for l.s2.Len() != 0 {
l.s1.Push(l.s2.Pop())
}
// fill in the reversePolishExpr
var vs []semantic.Value
for l.s1.Len() != 0 {
vs = append(vs, l.s1.Pop().val)
}
for i := len(vs) - 1; i >= 0; i-- {
l.reversePolishExpr = append(l.reversePolishExpr, vs[i])
}
}
其他如result,windowsRange,提取构造语义模型所需的信息比较简单,可以直接参考ReversePolishExprListener对应方法的源码。
二.实例化处理器并运行语法示例
是时候将语言的前端(语法树)和后端(语义模型)串起来了!为此,我们定义了一种Processor来组装前后端:
type Processor struct {
name string // for ruleid
model *semantic.Model
}
同时,每个Processor实例对应一个语法规则,如果有多个规则,可以实例化不同的Processor,然后我们可以使用Processor实例的Exec方法来处理数据:
func (p *Processor) Exec(in []map[string]interface{}) (map[string]interface{}, error) {
return p.model.Exec(in)
}
我们来看看main函数:
// tdat/main.go
func main() {
println("input file:", os.Args[1])
input, err := antlr.NewFileStream(os.Args[1])
if err != nil {
panic(err)
}
lexer := parser.NewTdatLexer(input)
stream := antlr.NewCommonTokenStream(lexer, 0)
p := parser.NewTdatParser(stream)
tree := p.Prog()
l := NewReversePolishExprListener()
antlr.ParseTreeWalkerDefault.Walk(l, tree)
processor := &Processor{
name: l.ruleID,
model: semantic.NewModel(l.reversePolishExpr, semantic.NewWindowsRange(l.low, l.high), l.ef, l.result),
}
// r0006: Each { |1,3| ($speed < 50) and (($temperature + 1) < 4) or ((roundDown($salinity) <= 600.0) or (roundUp($ph) > 8.0)) } => ();
in := []map[string]interface{}{
{
"speed": 30,
"temperature": 6,
"salinity": 500.0,
"ph": 7.0,
},
{
"speed": 31,
"temperature": 7,
"salinity": 501.0,
"ph": 7.1,
},
{
"speed": 30,
"temperature": 6,
"salinity": 498.0,
"ph": 6.9,
},
}
out, err := processor.Exec(in)
if err != nil {
panic(err)
}
fmt.Printf("%vn", out)
}
主要功能的步骤大致是:构建语法树(p.Prog),提取语义模型所需的信息(ParseTreeWalkerDefault.Walk),然后实例化Processor,连接前后端,然后最后通过processor.Exec处理输入数据。
接下来,我们定义samples/sample4.t作为语法示例来测试main:
// samples/sample4.t
r0006: Each { |1,3| ($speed < 50) and (($temperature + 1) < 4) or ((roundDown($salinity) <= 600.0) or (roundUp($ph) > 8.0)) } => ();
构建并执行 main:
$make
$./tdat samples/sample4.t
map[ph:7 salinity:500 speed:30 temperature:6]
我们看到程序输出了我们期望的结果!
三.总结
至此,我们在《后天》中为气象学家构建的DSL语言及其处理引擎的核心已经介绍完毕。上面的代码目前只能处理源文件中的一个规则。扩展处理引擎以支持在源文件中放置多个规则的任务作为“工作”留给您^_^。
看完这个系列的四篇文章,相信你对如何设计和实现一门基于ANTLR和Go的DSL语言有了基本的了解。现在,您可以设计一个 DSL 供您自己或您的团队在您的领域使用。欢迎大家在文末留言交流,我们一起提升DSL的设计和实现水平。
本文涉及的代码可以在这里下载[3] – .
“地鼠部落”知识星球[4]旨在打造一个优质的围棋学习和高级社区!优质首发Go技术文章,“三天”首发阅读权,一年两次Go语言发展现状分析,每天提前1小时阅读新鲜Gopher,在线课程,技术专栏,图书内容预览,六小时内必答 保证满足你对Go语言生态的所有需求! 2022 年,Gopher 部落将全面改版,将继续分享 Go 语言和 Go 应用领域的知识、技能和实践,并增加多种交互形式。欢迎大家加入!
p>
Gopher 每日档案库-
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 欧资源网