Теперь, как мне узнать тип, область действия идентификаторов, так как все
знать значение лексемы и номер строки для этого конкретного идентификатора (после
лексический анализ).
Как уже упоминалось в EJP, вам нужно пройтись по дереву разбора. Ваше дерево должно было быть создано, чтобы вы могли выполнять обход в порядке, посещая каждый узел в том же порядке, в котором оцениваются операторы и выражения исходного кода. Узлы вашего дерева также должны соответствовать определенной языковой конструкции, например, WhileStmtNode
, MethodDeclNode
и т. Д.
Предположим, я строю таблицу символов, рекурсивно шагая по дереву, и я только что вошел в узел тела метода. Я мог бы иметь что-то вроде следующего:
public void doAction(MethodBodyNode methodBody) {
currScope = 2;
methodBody.getExpr().applyAction(this);
currScope = 2;
}
Я храню глобальную переменную для управления областью действия. Каждый раз, когда я вхожу в блок, где меняется область действия, я увеличиваю currScope
. Точно так же я бы сохранил переменные currClass
и currMethod
для хранения с именем символа, типом, смещением и т. Д. Для последующих этапов.
Обновление:
Теперь скажите, я обхожу дерево, каждый раз, когда я сталкиваюсь с
придется ввести значение в таблицу символов вместе с типом,
область и другие, скажем, для области я проверяю, если я сталкиваюсь 'или'
имя функции, но как мне узнать, какой это тип идентификатора.
Каждый узел дерева должен содержать всю необходимую информацию для всей конструкции. Если вы используете генератор синтаксических анализаторов, таких как CUP или Bison, вы можете указать, как построить дерево в действиях по грамматике. Э.Г.
variableDeclaration::= identifier:i identifier:i2 SEMICOLON {: RESULT = new VarDeclNode(i, i2, null); :};
identifier::= ID:i {: RESULT = new IdNode(i.getLineNum(), i.getCharNum(), i.getStringValue()); :};
Эти произведения будут соответствовать Foo f;
и добавят узел объявления переменной в дерево. Этот узел инкапсулирует два узла-идентификатора, которые содержат номер строки, номер символа и строковое значение лексемы. Первый узел идентификатора - это тип, а второй - имя переменной. ID
- это символ терминала, возвращаемый лексером при сопоставлении идентификатора. Я предполагаю, что вы в некоторой степени знакомы с этим.
public class VarDeclNode extends StmtNode {
private IdNode id;
private IdNode type;
private ExprNode expr;
public VarDeclNode(IdNode id, IdNode type, ExprNode expr) {
super();
this.id = id;
this.type = type;
this.expr = expr;
}
}
Когда у вас есть синтаксическое дерево с такими узлами, вы получаете всю необходимую информацию.
2-е обновление:
Неважно, используете ли вы собственный анализатор или сгенерированный, есть одна особая точка, в которой вы добавляете узел в дерево после сопоставления с производством. И не важно, какой язык вы используете. С структурами все будет хорошо.
если он не является терминалом, информация указана как имя Нетерминала, и если оно
это терминал, то есть токен, то информация в токене, то есть значение лексемы,
Имя токена и номер строки сохраняются
У вас должны быть специализированные узлы в дереве, например, ClassNode, TypeNode, MethodDeclNode, IfStmtNode, ExprNode. Вы не можете просто сохранить один тип узла и поместить в него нетерминалы и терминалы. Нетерминал представлен как древовидный узел, нет никакой другой информации о нем, кроме частей, которые его составляют, которые, как правило, сами являются нетерминалами. Вы не будете хранить информацию о токене. Есть только несколько случаев, когда вы действительно сохраняете строковое значение лексемы: для идентификатора и для строкового / логического / целочисленного литерала.
Посмотрите на этот пример. Во время первого сокращения, когда S
уменьшается до (S + F)
, вы добавляете ParenExprNode
к корню дерева. Вы также добавляете AddExprNode
как дочерний элемент ParenExprNode
. Эта логика должна быть жестко запрограммирована в вашем синтаксическом анализаторе при применении сокращения по правилу 2 грамматики.
Дерево:
ExprNode (root)
|
ParenExprNode
|
AddExprNode
/ \
ExprNode ExprNode
код:
struct ExprNode { void* exprNode; };
struct ParenExprNode { void* exprNode; };
struct AddExprNode { void* op1, * op2; };
struct IdNode { char* val; int line; int charNum; };
struct IntLiteralNode { int val; int line; int charNum; };
void reduce_rule_2(ExprNode* expr) {
//remove stack symbols
//create nodes
struct ParenExprNode* parenExpr = malloc(sizeof(struct ParenExprNode));
struct AddExprNode* addExpr = malloc(sizeof(struct AddExprNode));
addExpr->op1 = malloc(sizeof(struct ExprNode));
addExpr->op2 = malloc(sizeof(struct ExprNode));
//link them
parenExpr->exprNode = (void*)addExpr;
expr->exprNode = (void*)parenExpr;
}
На следующем шаге левая скобка удаляется из ввода. После этого S
находится на вершине стека и уменьшается до F
по правилу 1. Поскольку F
не является терминалом для идентификатора, он представлен IdNode
.
Дерево:
ExprNode
|
ParenExprNode
|
AddExprNode
/ \
ExprNode ExprNode
|
IdNode
код:
reduce_rule_2(addExpr->op1);
void reduce_rule_1(ExprNode* expr) {
//reduce stack symbols
struct IdNode* id = malloc(sizeof(struct IdNode));
id->val = parser_matched_text();
id->lineNum = parser_line_num();
id->charNum = parser_char_num();
expr->exprNode = (void*)id;
}
И так далее ...