Начало работы
Установка
Добавление в проект
SynLex является частью Igdrasil Engine и доступен как проект в решении. Для использования добавьте ссылку на проект в ваш .csproj:
<ItemGroup>
<ProjectReference Include="..\SynLex\SynLex.csproj" />
</ItemGroup>
Требования
- .NET 8.0 или выше
- C# 12 с поддержкой primary constructors
- Nullable reference types включены
Импорт пространств имен
using SynLex;
using SynLex.LexicalAnalysis;
using SynLex.SyntacticAnalysis;
using SynLex.Compilation;
using SynLex.VirtualMachine;
Первый пример: Калькулятор
Создадим простой калькулятор для вычисления математических выражений.
Шаг 1: Определение типов токенов
// Определяем константы для типов токенов
const int TOKEN_WHITESPACE = 0;
const int TOKEN_NUMBER = 1;
const int TOKEN_PLUS = 2;
const int TOKEN_MINUS = 3;
const int TOKEN_MULT = 4;
const int TOKEN_DIV = 5;
const int TOKEN_LPAREN = 6;
const int TOKEN_RPAREN = 7;
Шаг 2: Создание лексера
using SynLex.LexicalAnalysis;
using SynLex.LexicalAnalysis.Rules;
var lexer = new Lexer()
// Пробелы (игнорируем)
.AddRule(new SimpleSublineLexerRule(
new[] { " ", "\t", "\n", "\r" },
TOKEN_WHITESPACE
))
// Числа
.AddRule(new RegexLexerRule(@"\d+", TOKEN_NUMBER))
// Операторы
.AddRule(new SimpleSublineLexerRule(new[] { "+" }, TOKEN_PLUS))
.AddRule(new SimpleSublineLexerRule(new[] { "-" }, TOKEN_MINUS))
.AddRule(new SimpleSublineLexerRule(new[] { "*" }, TOKEN_MULT))
.AddRule(new SimpleSublineLexerRule(new[] { "/" }, TOKEN_DIV))
// Скобки
.AddRule(new SimpleSublineLexerRule(new[] { "(" }, TOKEN_LPAREN))
.AddRule(new SimpleSublineLexerRule(new[] { ")" }, TOKEN_RPAREN));
Шаг 3: Лексический анализ
string expression = "2 + 3 * (4 - 1)";
var lexResult = lexer.Analyze(expression);
if (!lexResult.IsSuccess)
{
Console.WriteLine("Ошибки лексического анализа:");
foreach (var error in lexResult.Errors)
Console.WriteLine($" - {error}");
return;
}
// Выводим токены (опционально)
Console.WriteLine("Токены:");
foreach (var token in lexResult.Tokens!)
{
if (token.Type != TOKEN_WHITESPACE) // Пропускаем пробелы
Console.WriteLine($" {token.Type}: '{token.Content}' at {token.Position}");
}
Вывод:
Токены:
1: '2' at (1:1)
2: '+' at (1:3)
1: '3' at (1:5)
4: '*' at (1:7)
6: '(' at (1:9)
1: '4' at (1:10)
3: '-' at (1:12)
1: '1' at (1:14)
7: ')' at (1:15)
Шаг 4: Фильтрация токенов
// Удаляем пробелы перед парсингом
var tokens = lexResult.Tokens!
.Where(t => t.Type != TOKEN_WHITESPACE)
.ToList();
Шаг 5: Создание правил парсера
using SynLex.SyntacticAnalysis;
using SynLex.SyntacticAnalysis.Rules;
// Правило для числа (терминальный символ)
class NumberRule : IParserRule
{
public bool StopOnMatch => false;
public (Token? token, List<(int minIndex, int maxIndex)> subsets) Apply(
IReadOnlyList<Token> tokens,
ParserContext context)
{
var current = context.CurrentToken(tokens);
if (current?.Type == TOKEN_NUMBER)
{
context.Advance();
return (current.Value, new List<(int, int)>());
}
return (null, new List<(int, int)>());
}
}
// Правило для бинарной операции: expr op expr
class BinaryOpRule : IParserRule
{
private readonly int _operatorType;
private readonly int _resultType;
public bool StopOnMatch => false;
public BinaryOpRule(int operatorType, int resultType)
{
_operatorType = operatorType;
_resultType = resultType;
}
public (Token? token, List<(int minIndex, int maxIndex)> subsets) Apply(
IReadOnlyList<Token> tokens,
ParserContext context)
{
// Ищем оператор в середине выражения
for (int i = 1; i < tokens.Count - 1; i++)
{
if (tokens[i].Type == _operatorType)
{
var resultToken = new Token(
tokens[i].Content,
_resultType,
tokens[i].Position
);
// Левая часть: [0, i), правая часть: [i+1, end)
var subsets = new List<(int, int)>
{
(0, i),
(i + 1, tokens.Count)
};
return (resultToken, subsets);
}
}
return (null, new List<(int, int)>());
}
}
// Правило для скобок: (expr)
class ParenthesesRule : IParserRule
{
public bool StopOnMatch => false;
public (Token? token, List<(int minIndex, int maxIndex)> subsets) Apply(
IReadOnlyList<Token> tokens,
ParserContext context)
{
if (tokens.Count >= 3 &&
tokens[0].Type == TOKEN_LPAREN &&
tokens[^1].Type == TOKEN_RPAREN)
{
var subsets = new List<(int, int)>
{
(1, tokens.Count - 1) // Содержимое внутри скобок
};
return (tokens[0], subsets);
}
return (null, new List<(int, int)>());
}
}
Шаг 6: Создание парсера
const int AST_NUMBER = 100;
const int AST_ADD = 101;
const int AST_SUB = 102;
const int AST_MULT = 103;
const int AST_DIV = 104;
var parser = new Parser()
.AddRule(new NumberRule())
.AddRule(new ParenthesesRule())
// Приоритет операций: сначала +/-, затем *//
.AddRule(new BinaryOpRule(TOKEN_PLUS, AST_ADD))
.AddRule(new BinaryOpRule(TOKEN_MINUS, AST_SUB))
.AddRule(new BinaryOpRule(TOKEN_MULT, AST_MULT))
.AddRule(new BinaryOpRule(TOKEN_DIV, AST_DIV));
var parseResult = parser.Analyze(tokens);
if (!parseResult.IsSuccess)
{
Console.WriteLine("Ошибки синтаксического анализа:");
foreach (var error in parseResult.Errors)
Console.WriteLine($" - {error}");
return;
}
Console.WriteLine("AST построено успешно!");
Шаг 7: Создание правил компилятора
using SynLex.Compilation;
using SynLex.Compilation.Rules;
// Правило для числа
class NumberEvalRule : IEvaluatorRule<int>
{
public bool CanApply(AbstractSyntaxTree ast) =>
ast.Token.Type == TOKEN_NUMBER;
public int? Apply(AbstractSyntaxTree ast, EvaluatorContext context)
{
return int.Parse(ast.Token.Content);
}
}
// Правило для операций
class OperatorEvalRule : IEvaluatorRule<int>
{
public bool CanApply(AbstractSyntaxTree ast) =>
ast.Token.Type is AST_ADD or AST_SUB or AST_MULT or AST_DIV;
public int? Apply(AbstractSyntaxTree ast, EvaluatorContext context)
{
if (context.CompiledBranches.Count != 2)
throw new Exception("Binary operator requires 2 operands");
int left = (int)context.CompiledBranches[0];
int right = (int)context.CompiledBranches[1];
return ast.Token.Type switch
{
AST_ADD => left + right,
AST_SUB => left - right,
AST_MULT => left * right,
AST_DIV => left / right,
_ => throw new Exception($"Unknown operator: {ast.Token.Type}")
};
}
}
Шаг 8: Компиляция и вычисление
var evaluator = new Evaluator<int>()
.AddRule(new NumberEvalRule())
.AddRule(new OperatorEvalRule());
var evalResult = evaluator.Compile(parseResult.Ast!);
if (!evalResult.IsSuccess)
{
Console.WriteLine("Ошибки компиляции:");
foreach (var error in evalResult.Errors)
Console.WriteLine($" - {error}");
return;
}
Console.WriteLine($"Результат: {expression} = {evalResult.Result}");
Вывод:
Результат: 2 + 3 * (4 - 1) = 11
Полный код примера
using SynLex;
using SynLex.LexicalAnalysis;
using SynLex.LexicalAnalysis.Rules;
using SynLex.SyntacticAnalysis;
using SynLex.Compilation;
// Константы токенов
const int TOKEN_WHITESPACE = 0;
const int TOKEN_NUMBER = 1;
const int TOKEN_PLUS = 2;
const int TOKEN_MULT = 4;
// Константы AST
const int AST_ADD = 101;
const int AST_MULT = 103;
// Лексер
var lexer = new Lexer()
.AddRule(new SimpleSublineLexerRule(new[] { " " }, TOKEN_WHITESPACE))
.AddRule(new RegexLexerRule(@"\d+", TOKEN_NUMBER))
.AddRule(new SimpleSublineLexerRule(new[] { "+" }, TOKEN_PLUS))
.AddRule(new SimpleSublineLexerRule(new[] { "*" }, TOKEN_MULT));
// Парсер
var parser = new Parser()
.AddRule(new NumberRule())
.AddRule(new BinaryOpRule(TOKEN_PLUS, AST_ADD))
.AddRule(new BinaryOpRule(TOKEN_MULT, AST_MULT));
// Компилятор
var evaluator = new Evaluator<int>()
.AddRule(new NumberEvalRule())
.AddRule(new OperatorEvalRule());
// Использование
string expr = "2 + 3 * 4";
var lexResult = lexer.Analyze(expr);
var tokens = lexResult.Tokens!.Where(t => t.Type != TOKEN_WHITESPACE).ToList();
var parseResult = parser.Analyze(tokens);
var evalResult = evaluator.Compile(parseResult.Ast!);
Console.WriteLine($"{expr} = {evalResult.Result}"); // 2 + 3 * 4 = 14
Обработка ошибок
Проверка результатов
Все операции возвращают результат с флагом IsSuccess:
var result = lexer.Analyze(text);
if (!result.IsSuccess)
{
// Обработка ошибок
foreach (var error in result.Errors)
LogError(error);
return;
}
// Работаем с результатом
var tokens = result.Tokens!;
Типичные ошибки
Лексер:
- Нет правила для части текста → "No rule matched at position X"
Парсер:
- Незавершенный разбор → "Incomplete parse: stopped at position X"
- Нет подходящего правила → "No valid parse found"
Компилятор:
- Нет правила для узла AST → "No compilation results"
- Ошибка в правиле → сообщение из правила
Следующие шаги
- Лексический анализ - детальное описание лексеров
- Синтаксический анализ - работа с парсерами
- Компиляция - преобразование AST
- Примеры - готовые решения
Проект: SynLex
Документация: Начало работы
© 2026 Alexander Izmailov. Все права защищены.