Всем привет в этой части я расскажу вам каким будет наш язык и напишем лексический анализатор ( лексер ).
Для начала нужно определится что будет уметь наш язык. Уметь он будет достаточно для простого языка, а именно:
Типы данных: программа(programm), функция (func) , целые числа (byte (1 byte) , word (2 byte) , int (4 byte), long (8 byte) ), числа с плавающий точкой ( float (4 byte), double ( 8 byte ) ), строковой тип (string), bool, структуры.
Переменные: будут доступны как глобальные переменные, так и локальные.
Математические операции: будут доступны +, -, *, /,= также знаки сравнения < > == != .
Конструкции языка нам будут доступны: программы (programm) , функции, if/else, циклы for, while.
Возможности языка: динамическая и статическая типизация (если пользователь хочет динамически типизировать переменную то просто нужно написать i = 5 а если статически i:int = 5 . Парсер будет определять тип переменной. ).Препроцессор ( из директив у нас будут #define, #include, #ifdef, #ifndef, #endif ), точка с запятой будет обозначать конец выражения , ООП у нас так такого не будет но можно будет имитировать структурами и функциями.
Лексический анализатор.
Как помните из прошлой статьи, я говорил что лексический анализ нужен для преобразования текста исходного в поток токенов. То есть если передать лексическому анализатору подобный код "
a = 5;
b:int = 7;
c:word = a + b;
print(c);
То лексер нам выдаст вот такой поток токенов
{
var_t, assigm_t, number_t, end_expr_t, var_t, colon_t, int_t ,assigm, number_t, end_expr_t, var_t, colon_t, word_t, assigm_t, var_t, plus_t, var_t, end_expr_t, var_t, open_round_bracket_t, var, close_round_bracket, end_expr_t
}
окончание _t указывает что это токен.
Также кроме токена нам нужно запоминать строковое значение. Например:
var_t, "a"
Теперь я думаю мы разобрались что делает лексер, так что давайте его напишем на C#.
Для начала создадим файл Token.cs в нашем проекте.
В файле будет перечисление Token. Оно будет таким:
public enum Token
{
programm_t,
func_t,
byte_t,
word_t,
int_t,
long_t,
float_t,
double_t,
string_t,
bool_t,
struct_t,
//math_op
plus_t,
minus_t,
mul_t,
div_t,
assigm_t,
less_t,
more_t,
equal_t,
no_equal_t,
//
//keys
end_expr_t,
//
//con_statement
if_t,
else_t,
for_t,
while_t,
//
//conditional symbols
open_round_bracket_t,
close_round_bracket_t,
open_curly_bracket_t,
close_curly_bracket_t,
//
//other
var_t,
number_t
}
Теперь нужно создать файл в котором будет класс Node который будет хранить в себе токен , string значение, и List экземпляров класса Node (это будет потом для парсера).
В этом классе нам нужно подключить пространство имен System.Collections.Generic, для использования List.
Вот таким будет класс Node:
public class Node
{
private Token typeNode;
private string value_n;
private List<Node> childs;
public Token TypeNode
{
get
{
return typeNode;
}
}
public string ValueNode
{
get
{
return value_n;
}
set
{
value_n = value;
}
}
public IEnumerable<Node> Childs
{
get
{
return childs;
}
}
public int ChildsCount
{
get
{
return childs.Count;
}
}
public Node(Token tn, string val)
{
childs = new List<Node>();
typeNode = tn;
value_n = val;
}
public void AddChild(Node node)
{
childs.Add(node);
}
public Node GetChild(int i)
{
return childs[i];
}
}
Данный класс инкапсулирует в себе эти 3 переменные.:
Ну теперь самое вкусное лексер. Для начала создадим файл Lexer.cs и напишем в нем класс Lexer. Также нужно будет подключить System.Collections.Generic и System.Text.RegularExpressions. Он будет вот таким:
class Lexer
{
protected List<Node> nodes;
public IEnumerable<Node> Nodes
{
get
{
return nodes;
}
}
public Lexer()
{
nodes = new List<Node>();
}
public void LexCode(string code)
{
string[] strTokens = Regex.Split(code, @"([\ \n(){};+*/=\-])");
for (int i =0; i < strTokens.Length; i++)
{
if( strTokens[i] != " "
&& strTokens[i] != string.Empty
&& strTokens[i] != "\r"
&& strTokens[i] != "\n")
{
nodes.Add(new Node( AnalyzeStrToken( strTokens[i]), strTokens[i]) );
}
}
}
public Token AnalyzeStrToken(string strToken)
{
Token token = Token.int_t;
switch (strToken)
{
case "func":
token = Token.func_t;
break;
case "programm":
token = Token.programm_t;
break;
case "byte":
token = Token.byte_t;
break;
case "word":
token = Token.word_t;
break;
case "int":
token = Token.int_t;
break;
case "long":
token = Token.long_t;
break;
case "float":
token = Token.float_t;
break;
case "double":
token = Token.double_t;
break;
case "string":
token = Token.string_t;
break;
case "bool":
token = Token.bool_t;
break;
case "struct":
token = Token.struct_t;
break;
case "+":
token = Token.plus_t;
break;
case "-":
token = Token.minus_t;
break;
case "*":
token = Token.mul_t;
break;
case "/":
token = Token.div_t;
break;
case "=":
token = Token.assigm_t;
break;
case "<":
token = Token.less_t;
break;
case ">":
token = Token.more_t;
break;
case ";":
token = Token.end_expr_t;
break;
case "(":
token = Token.open_round_bracket_t;
break;
case ")":
token = Token.close_round_bracket_t;
break;
case "{":
token = Token.open_curly_bracket_t;
break;
case "}":
token = Token.close_curly_bracket_t;
break;
default:
double number = 0;
if(double.TryParse(strToken, out number))
{
token = Token.number_t;
break;
}
token = Token.var_t;
break;
}
return token;
}
}
Вот тут нужно прояснить некоторые моменты в коде.
Сначала Regex с помощью метода Split в который закладывается паттерн с сепараторами ( пробел, переход на новую строку , круглые скобки, фигурные скобки, точка с запятой, плюс, минус, умножение, деление, равно), делит строку коду на множество строк которые можно уже обработать.
Обработка строк происходит следующим способом.
Сначала создаем экземпляр класса Node который принимает 2 аргумента в конструктор это токен и строку. Токен вычесляется в методе AnalyzeStrToken(string strToken). Данный метод сравнивает строку с другими строками в switch и возращает нужный токен.
Вот теперь мы можем протестировать данный лексер.
Lexer lexer = new Lexer();
string code = "int i = 0 ; func main { testf(); }";
lexer.LexCode(code);
foreach(Node node in lexer.Nodes)
{
Console.WriteLine(node.TypeNode);
}
И нам на консоль выдаст нужные токены.
На этом пока все. В следующей части мы будем писать уже парсер. Спасибо за прочтение.