规则解析器(Rule Parser):规则语言基础与AST构建

规则解析器,说白了就是让计算机能读懂我们写的设计规则。你想想看,我们平时写规则,用的都是SKILL、Tcl这类脚本语言。但机器不懂这些,它只认0和1。所以我们需要一个翻译官——这就是规则解析器要做的事。

我个人习惯把规则解析器分成三个层次:词法分析、语法分析、AST构建。这三个层次就像剥洋葱,一层层把规则语言翻译成计算机能理解的数据结构。

规则语言基础:SKILL与Tcl

先聊聊规则语言。EDA领域最常用的就是SKILL和Tcl。SKILL是Cadence家的看家本领,Tcl则是Synopsys和Mentor的宠儿。

SKILL语言的特点

  • Lisp风格,括号满天飞
  • 强类型,变量需要声明
  • 内置大量EDA专用函数

Tcl语言的特点

  • 一切皆字符串
  • 弱类型,用起来灵活
  • 扩展性强,可以嵌入C/C++

我在项目中遇到过最头疼的事:团队里有人用SKILL写规则,有人用Tcl写。最后不得不写个统一的解析器,把两种语言都转成内部表示。嗯,那段时间真是加班加到怀疑人生。

来看个简单的规则例子:

// SKILL写法
procedure(checkNetWidth(net minWidth)
  let((width)
    width = getNetWidth(net)
    if(width < minWidth then
      error("Net %L width %f < %f" net width minWidth)
    )
  )
)

# Tcl写法
proc checkNetWidth {net minWidth} {
  set width [getNetWidth $net]
  if {$width < $minWidth} {
    error "Net $net width $width < $minWidth"
  }
}

两种语言都能表达同样的逻辑,但语法差异很大。解析器要做的就是把这些差异抹平,统一成内部表示。

词法分析:把字符串变成Token流

词法分析,说白了就是分词。把一整段规则代码,拆成一个个有意义的单词(Token)。

举个例子,对于这段代码:

if(width < 10) then error("太窄了")

词法分析器会把它拆成:

Token类型 Token值
KEYWORD if
LPAREN (
IDENTIFIER width
OPERATOR <
NUMBER 10
RPAREN )
KEYWORD then
IDENTIFIER error
LPAREN (
STRING "太窄了"
RPAREN )

词法分析器的核心是状态机。每个字符进来,根据当前状态决定下一步怎么走。我曾经踩过一个坑:字符串里的转义字符没处理好,导致规则解析总是报错。后来加了个转义状态才搞定。

小技巧:词法分析时,最好把注释和空白符直接过滤掉。这样语法分析器就不用处理这些无关信息了。

语法分析:从Token流到语法树

词法分析完了,我们得到了一堆Token。但这些Token之间是什么关系?这就需要语法分析器来搞定了。

语法分析器会按照语法规则,把Token流组织成一棵树。这棵树就是抽象语法树(AST)。

常见的语法分析方法有两种:

  • 自顶向下:从根节点开始,递归下降
  • 自底向上:从叶子节点开始,规约到根

我个人偏爱自顶向下的递归下降法。为什么?因为代码写起来直观,调试也方便。你想想看,每个语法规则对应一个函数,多清晰。

来看个简单的递归下降解析器伪代码:

// 解析if语句
parseIfStatement() {
  matchToken(KEYWORD, "if")
  matchToken(LPAREN)
  condition = parseExpression()  // 解析条件表达式
  matchToken(RPAREN)
  matchToken(KEYWORD, "then")
  body = parseStatement()        // 解析执行体
  return new IfNode(condition, body)
}

// 解析表达式
parseExpression() {
  left = parsePrimary()          // 解析基本表达式
  while (currentToken is OPERATOR) {
    op = currentToken
    advance()
    right = parsePrimary()
    left = new BinaryOpNode(left, op, right)
  }
  return left
}

注意:递归下降解析器容易陷入左递归。比如表达式解析时,如果写成 parseExpression -> parseExpression + parseTerm,就会无限递归。解决办法是改成循环或者用右递归。

构建抽象语法树(AST)

AST是规则解析器的最终产物。它把规则代码变成了一棵结构清晰的树,每个节点代表一个语法结构。

AST节点通常包含:

  • 节点类型(如IfNode、BinaryOpNode)
  • 子节点列表
  • 属性信息(如操作符、变量名)
  • 位置信息(行号、列号,方便报错)

下面我用SVG画一张AST的示意图,帮你理解它的结构:

IfNode BinaryOp CallNode Identifier width Operator < Number 10 String "太窄了" Identifier error

这张图展示的是 if(width < 10) then error("太窄了") 的AST结构。根节点是IfNode,下面分条件分支和执行体分支。条件分支又拆成左操作数、操作符、右操作数。执行体分支则是一个函数调用。

AST建好了,后续的规则检查就简单了。你只需要遍历这棵树,对每个节点执行相应的检查逻辑就行。

我个人习惯在AST节点里加一个visit方法。这样遍历树的时候,每个节点自己知道该怎么处理。这就是访问者模式,用起来特别顺手。

避坑指南

做规则解析器,有几个坑我踩过,分享给你:

  • 错误恢复:解析到语法错误时,别直接崩溃。最好能跳过错误,继续解析后面的内容,这样一次能报出多个错误。
  • 位置信息:每个AST节点都要记录行号和列号。不然报错时,用户根本不知道哪里出了问题。
  • 性能优化:规则文件可能很大,解析器要高效。我试过用缓存机制,把解析过的规则缓存起来,避免重复解析。

我曾经接手过一个项目,规则解析器没有错误恢复功能。用户写错一个括号,解析器就崩了,连错误位置都不报。后来我花了三天重写了解析器,加了错误恢复和位置信息。嗯,从那以后,团队里再没人抱怨规则解析器难用了。

好了,规则解析器的核心内容就这些。记住三个关键词:词法分析、语法分析、AST构建。把这三点吃透了,你就能写出自己的规则解析器。


专注资料整理