Всем привет! Как мы помним в предыдущей статье мы подготовились к написанию парсера, и именно в этой статье мы начнем с вами писать парсер.
Сразу начнем писать код. Вот такой будет начальный код.
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);
То получим мы это.
На этом пока все. В следующей части мы продолжим делать парсер.