Начало работы

Установка

Добавление в проект

SynLex является частью Igdrasil Engine и доступен как проект в решении. Для использования добавьте ссылку на проект в ваш .csproj:

<ItemGroup>
    <ProjectReference Include="..\SynLex\SynLex.csproj" />
</ItemGroup>

Требования

Импорт пространств имен

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!;

Типичные ошибки

Лексер:

Парсер:

Компилятор:

Следующие шаги


Проект: SynLex
Документация: Начало работы

© 2026 Alexander Izmailov. Все права защищены.