CST 与 AST
原来树与树是不同的?我还以为所有的都叫 AST 抽象语法树
CST
CST(Concrete Syntax Tree,具体语法树)
CST 是对输入源代码的一种更接近原始语法结构的直接表示,它保留了更多的语法细节,包括可能的冗余和不必要的信息
像这样
Node Expr
├── Node Term
│ ├── Node Factor
│ │ └── Node Expr
│ │ ├── Node Term
│ │ │ ├── Node Factor
│ │ │ │ └── Token Int [10]
│ │ │ └── Node ε
│ │ └── Node Expr'
│ │ ├── Token Sign [-]
│ │ ├── Node Term
│ │ │ ├── Node Factor
│ │ │ │ └── Token Int [2]
│ │ │ └── Node ε
│ │ └── Node ε
│ └── Node Term'
│ ├── Token Sign [*]
│ ├── Node Factor
│ │ └── Token Int [4]
│ └── Node ε
└── Node ε
AST
AST (Abstract Syntax Tree,抽象语法树)
也就是把 CST 去掉没用的 ε 与只包含一个子节点的节点,但保留子节点
像这样
Node Expr
├── Node Factor
│ ├── token Int [10]
│ └── Node Expr'
│ ├── Token Sign [-]
│ └── Token Int [2]
└── Node Term'
├── Token Sign [*]
└── Token Int [4]
这么样?看着是不是清净很多?
Thinking
CST 可以直接生成的,也可以直接生成 AST,不过可能会没直接生成 CST 简单. 我生成的就是 CST, display 出来的树比较难看, 所以, 我还想往 Tree 类再加一个成员函数, 起名为 cst2ast()
, 它在 CST 生成之后执行, 通过遍历一棵树将使得其变得更抽象, 应该也不算难
半晌后更新
已经写成了, 输出:
Node Term
├── Node Expr
│ ├── Token Int [10]
│ └── Node Expr'
│ │ ├── Token Sign [-]
│ │ └── Token Int [2]
└── Node Term'
│ ├── Token Sign [*]
│ └── Token Int [4]
code:
Tree* Parser::cst2ast(Tree* tr) {
if (tr->type == treeType_Token) return tr;
std::vector<Tree*> & c = tr->children; // 酱紫写起来比较好看
for (auto it = c.begin(); it != c.end(); /*这里是空的*/) {
if ((*it)->type == treeType_Node && (*it)->label == treeTypeNode_Epsilon) { // 如果是空集
it = c.erase(it); // “擦”去它
} else { // 否则 (废话)
Tree* result = cst2ast(*it); // 将一个子节点扔给另一个自己处理
if (result == nullptr) it = c.erase(it); // 如果其返回 nullptr, 则擦去它
else { // 否则
*it = result; // 使用它
it ++; // 调到下一个迭代器
}
}
}
if (c.empty()) return nullptr; // 如果是空的,直接返回 nullptr (算是标记吧)
if (c.size() == 1) return c[0]; // 如果只要一个子节点,就单独返回它,不包括自己
return tr; // 最后剩下的一个完美的 Tree
}
注意: 在循环中, 如果是空集(treeTypeNode_Epsilon), 并且 it = c.erase(it)
, 那么就不需要it ++
了, 所以 for 循环的第三个表达式是空的, 之后根据情况再在循环体内写
它的结果与我自己动手做成的完全一样, 不过只有那些线不一样, 这就该埋怨我的 Tree::display() 了, 详见打印一棵树
两天后
其实这样输出的也并不是十分的 AST, 因为撇来撇去的,所以我想再加上一些语句把一些撇去掉,只保留...那叫什么东西
反正就是,原来是这样的
Node Expr
├── Token Int [1]
└── Node Expr'
│ ├── Token Sign [+]
│ └── Token Int [3]
操作之后就变成
Node Expr
├── Token Int [1]
├── Token Sign [+]
└── Token Int [3]
我想你懂我的意思。感觉也简单,就是在 return tr;
前加上一些判断的代码, 再做一些什么操作,不过要有一个函数判断是不是带撇的
稍等
不过,突然感觉没那个必要了,刚写的代码放这里纪念一下(说不定中间代码生成时有用)
bool treeTypeNodeLabelIs_(treeTypeNodeLabel label) {
switch(label) {
case treeTypeNode_Main: return false;
case treeTypeNode_None: return false;
case treeTypeNode_Epsilon: return false;
case treeTypeNode_Expr: return false;
case treeTypeNode_Expr_: return true;
case treeTypeNode_Term: return false;
case treeTypeNode_Term_: return true;
case treeTypeNode_Factor: return false;
}
return false;
}
bool treeTypeNodeLabelTogether(treeTypeNodeLabel l1, treeTypeNodeLabel l2) {
if (l1 == treeTypeNode_Expr && l2 == treeTypeNode_Expr_) return true;
if (l1 == treeTypeNode_Term && l2 == treeTypeNode_Term_) return true;
return false;
}