Найти тему

Написание компилятора своего языка. Часть 3.2.

Всем привет! Как мы помним в предыдущей статье мы подготовились к написанию парсера, и именно в этой статье мы начнем с вами писать парсер.

Сразу начнем писать код. Вот такой будет начальный код.

public class Parser : Lexer
{

AST _abstract_syntax_tree;

Node parentNode;

SystemParserError systemErrors;

public AST ast
{
get
{
return _abstract_syntax_tree;
}
}

public Parser() : base () {

_abstract_syntax_tree = new AST();

systemErrors = new SystemParserError();

}

public void GenerateAST(string code)
{

LexCode(code);

if (nodes.Count < 1) return;

if (nodes[0].TypeNode != Token.programm_t )
{
systemErrors.Error_c(ErrorEnum.no_find_programm_error, "No find programm in zero position");

}

if(nodes[1].TypeNode != Token.var_t)
{
systemErrors.Error_c(ErrorEnum.unavailable_name_programm_error, "Unavailable name programm");
}

parentNode = ast.NodeAST ;

}

В данном коде мы класс Parser наследуем от Lexer с целью инкапсуляции и простоты парсера.

В методе GenerateAST мы будем генерировать само дерево.

В нашем языке нужно будет обязательно сперва писать программа и ее имя, иначе компилятор выдаст ошибку.

Внимательный читатель может заметить переменную parentNode и спросить зачем она?
Я отвечу ему так: Данная переменная будет указателем на Node в который нужно что то добавлять.

Ну что снова приступим к написанию кода.

Для начала добавьте этот код в switch(strCode) в функцию AnalyzeStrToken класса Lexer:

case "if":
token = Token.if_t;
break;
case "else":
token = Token.else_t;
break;
case "while":
token = Token.while_t;
break;
case "for":
token = Token.for_t;
break;

Данный участок кода добавить в GenerateAST.

switch (nodes[index].TypeNode)
{
case Token.programm_t:

if(parentNode != ast.NodeAST)
{
systemErrors.Error_c(ErrorEnum.programm_local_error, "The program can only be global");
}

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

index++;

if(nodes[index].TypeNode != Token.var_t)
{
systemErrors.Error_c(ErrorEnum.unavailable_name_programm_error, "Unavailable name programm");
}

parentNode.AddChild(nodes[index]);

index++;

if(nodes[index].TypeNode != Token.open_curly_bracket_t)
{

}

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

break;
case Token.func_t:

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

index++;

if(nodes[index].TypeNode != Token.var_t)
{

}

parentNode.AddChild(nodes[index]) ;

index++;

if (nodes[index].TypeNode != Token.open_round_bracket_t)
{

}

parentNode.AddChild(nodes[index]);
parentNode = nodes[index];

index++;

ParseArguments(ref index);

index++;

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

break;
case Token.while_t:
case Token.if_t:

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

index++;

if (nodes[index].TypeNode != Token.open_round_bracket_t)
{

}

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

index++;

ParseArguments(ref index);

index++;

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

break;
case Token.else_t:

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

index++;

if(nodes[index].TypeNode != Token.open_curly_bracket_t)
{

}

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

break;

case Token.for_t:

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

index++;

if(nodes[index].TypeNode != Token.open_round_bracket_t)
{

}

parentNode.AddChild(nodes[index]);
parentNode = nodes[index];

for(int i = 0; i < 3; i++)
{
while(nodes[index].TypeNode != Token.end_expr_t)
{
if(index == nodes.Count)
{
//error
return;
}
index++;
}
}

if (nodes[index].TypeNode != Token.close_round_bracket_t)
{

}

index++;

if (nodes[index].TypeNode != Token.open_curly_bracket_t)
{

}

parentNode.AddChild(nodes[index]);

parentNode = nodes[index];

break;

case Token.close_curly_bracket_t:

parentNode = parentNode.Parent.Parent;
break;

}

}

И еще добавьте функцию ParseArguments.

void ParseArguments(ref int index)
{
while (nodes[index].TypeNode != Token.close_round_bracket_t)
{
if (nodes.Count == index)
{

return;
}


index++;

}

parentNode = parentNode.Parent;
}

Мы ее потом допишем и она будет парсить аргументы в функции и условии.

Потом мы сделаем выброс ошибок щас главное поместить условия в нужные места, а добавить выброс ошибок очень просто как вы помните из прошлой статьи.

Ну сам код довольно прост (хотя многие, и я тоже, думали в начале при разработке парсера что код парсера довольно сложен ), и вот его объяснение:
Сначала перебираем лист нодов.

Если встречается программа то проверяем находится ли она в главном ноде ABS, дальше добавляем к parentNode программу и ту же программу делаем parentNode, двигаем индекс и проверяем точно ли это является именем а не зарезервированным, а простым словом. Двигаем индекс и проверяем является ли нод открытой фигурной скобкой. Дальше добавляем эту скобку в parentNode и parentNode становится этой скобкой.

Если встречается функция почти тоже самое только после имени, мы увеличиваем индекс и проверяем является ли нод круглой скобкой, добавляем нод в parentNode и скобка становится parentNode,увеличиваем индекс и вызываем ParseArguments. А дальше по накатанной с фигурной скобкой.

ParseArguments пока еще не полностью готов, но он уже проходит аргументы и делает возврат parentNode. ParseArguments доделаем в следующих частях.

Если встречается if то почти тоже самое что с функцией только без имени.

Если встречается else просто двигаем индекс и проверяем является ли нод открытой фигурной скобкой, добавляем скобку в parentNode и parentNode становится этой скобкой.

Если встречается for то двигаем индекс, делаем проверку, и делаем 3 обхода цикла while который двигает индекс пока не встретится конец выражения в нашем случае это знак ;.

Если встречается закрытая фигурная скобка, то parentNode становится родителем родителя своего.

Ну теперь давайте протестируем наш начальный парсер который обрабатывать только конструкции и то не полностью (for нужны получше методы).

Вот такой код нужен в вашем Main.

Parser parser = new Parser();

string code = "programm name { func main () { if(){ if(){ } } }";

parser.GenerateAST( code );

Console.WriteLine(parser.ast.NodeAST.GetChild(0).GetChild(1).GetChild(0).ValueNode);
Console.WriteLine("\t" +parser.ast.NodeAST.GetChild(0).GetChild(1).GetChild(0).GetChild(0).ValueNode);
Console.WriteLine("\t\t" + parser.ast.NodeAST.GetChild(0).GetChild(1).GetChild(0).GetChild(2).GetChild(0).ValueNode);

То получим мы это.

На этом пока все. В следующей части мы продолжим делать парсер.