【发布时间】:2023-04-11 01:55:02
【问题描述】:
编译器如何在不构造显式解析树的情况下进行操作?显式解析树构造的优缺点是什么?
我知道编译器可以在没有显式解析树的情况下通过使用 SDT 并在解析期间运行与之关联的语义来进行构造。但我想知道显式解析树构造的优缺点。
【问题讨论】:
标签: compiler-construction parse-tree
编译器如何在不构造显式解析树的情况下进行操作?显式解析树构造的优缺点是什么?
我知道编译器可以在没有显式解析树的情况下通过使用 SDT 并在解析期间运行与之关联的语义来进行构造。但我想知道显式解析树构造的优缺点。
【问题讨论】:
标签: compiler-construction parse-tree
我是个菜鸟,所以请耐心等待我...谢谢...
但是要回答您的问题,递归体面的编译(没有解析树)只能在没有前向引用且符号仅从其声明点而非其整个范围内有效的最简单情况下完成.
显然它不适用于像 java 这样的语言。如果有前向引用,那么至少需要两遍,如果上面有像java中那样的重载函数,则需要三遍(或者如果你知道如何在不到三遍的时间内完成,请赐教) .为此,我们构建了一个解析树。
最简单的解析树节点可能看起来像这样(免责声明:这不是真正的代码)。
package compiler;
import java.util.ArrayList;
import scanner.Token;
import scanner.TokenSet;
class Production
{
Token leading; // the first token in the production
int productionID; // a unique integer that identifies the production
ArrayList<Production> childNodes; // duh
Production mother; // mother node (may be null)
public Production (Token leading, int productionID)
{
this.leading = leading;
this.productionID = productionID;
childNodes = new ArrayList<Production>();
}
public void append (Production child) // add a new child node
{
child nodes.add(child);
child.mother = this;
}
public abstract void build1 (TokenSet follow, TokenSet anchor); // implements pass 1
public abstract void build2 ....
}
但更强大的方法是在每个产生式上派生一个新的子类,并将子节点声明为字段变量。然后我们可以消除 productionID 并使用 instanceof 检查。您甚至可以在定义符号的给定节点子类上实现符号接口,并将节点直接插入符号表中;定义嵌套范围的产品也可以有自己的符号表(我不会在这里这样做)。关键是这样可以将句法和语义分析都集成到解析树结构中,甚至最终的翻译也可以。唯一的缺点是那些可怕的 java 接口 :lol:
例如,我们可以将 Modula-2 头文件声明为:
// i wont bother with imports since this isnt real code
class DefinitionModule extends Production
{
Identifier name;
ArrayList<ImportClause> importClauses;
ArrayList<ExportClause> exportClauses;
ArrayList<Production> itemList; // CONST-,TYPE-, & VAR- declarators & function headers
public DefinitionModule() // no more productionID
{
super(lastTokenRead()); // always sits on DEFINITION
importClauses = new ArrayList<ImportClause>;
}
// build()
//
// DefinitionModule ::= DEFINITION MODULE Identifier ";" {ImportClause}{ExportClause}{HeaderItem} END Identifier
//
// where HeaderItem ::= ConstDeclarator | TypeDeclarator | VarDeclator | ProcedureHeader.
// Identifier, ImportClause, & ExportClause below are all derived from
// Production, above
public void build (TokenSet follow, TokenSet anchor)
{
Scanner.getToken(); // skip the DEFINITION
Scanner.expectToken(Token.ID_MODULE); // make sure MODULE is there & then skip it
name = name.build(new TokenSet(Token.ID_SEMICOLON));
expectToken(Token.ID_SEMICOLON);
while (lastTokenRead()==Token.ID_IMPORT || lastTokenRead()==Token.ID_FROM)
{
ImportClause IC = new ImportClause(lastTokenRead());
importClauses.add(IC.build(new TokenSet(Token.ID_SEMICOLON));
Scanner.expectToken(Token.ID_SEMICOLON);
}
while (lastTokenRead()==Token.ID_EXPORT)
{
ExportClause XC = new ExportClause(lastTokenRead());
exportClauses.add(XC.build(new TokenSet(Token.ID_SEMICOLON));
Scanner.expectToken(Token.ID_SEMICOLON);
}
// etc, etc, etc
}
}
如果你这样做,编译器将围绕语言的特性构建自己,而不是编译器的传统传递。
祝你好运……
【讨论】: