Примеры

Калькулятор арифметических выражений

Полный пример парсера и вычислителя математических выражений с приоритетом операций.

Определение токенов

// Типы токенов
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;

// Типы узлов AST
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;
const int AST_NEG = 105; // унарный минус

Лексер

using SynLex;
using SynLex.LexicalAnalysis;
using SynLex.LexicalAnalysis.Rules;

var lexer = new Lexer()
    .AddRule(new SimpleSublineLexerRule(
        new[] { " ", "\t", "\n", "\r" }, 
        TOKEN_WHITESPACE))
    .AddRule(new RegexLexerRule(@"\d+(\.\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));

string expression = "2 + 3 * (4 - 1)";
var lexResult = lexer.Analyze(expression);

// Фильтруем пробелы
var tokens = lexResult.Tokens!
    .Where(t => t.Type != TOKEN_WHITESPACE)
    .ToList();

Парсер с приоритетом операций

using SynLex.SyntacticAnalysis;

// Правило для числа
class NumberRule : IParserRule
{
    public (Token? token, List<(int, int)> subsets) Apply(
        IReadOnlyList<Token> tokens, ParserContext context)
    {
        if (tokens.Count == 1 && tokens[0].Type == TOKEN_NUMBER)
        {
            return (new Token(tokens[0].Content, AST_NUMBER, tokens[0].Position), []);
        }
        return (null, []);
    }
}

// Правило для скобок: (expr)
class ParenRule : IParserRule
{
    public (Token? token, List<(int, int)> subsets) Apply(
        IReadOnlyList<Token> tokens, ParserContext context)
    {
        if (tokens.Count >= 3 && 
            tokens[0].Type == TOKEN_LPAREN && 
            tokens[^1].Type == TOKEN_RPAREN)
        {
            return (tokens[0], [(1, tokens.Count - 1)]);
        }
        return (null, []);
    }
}

// Бинарная операция с приоритетом
class BinaryOpRule : IParserRule
{
    private readonly int[] _operators;
    private readonly Dictionary<int, int> _astTypes;

    public BinaryOpRule(int[] operators, Dictionary<int, int> astTypes)
    {
        _operators = operators;
        _astTypes = astTypes;
    }

    public (Token? token, List<(int, int)> subsets) Apply(
        IReadOnlyList<Token> tokens, ParserContext context)
    {
        int depth = 0;
        // Ищем оператор справа налево (правая ассоциативность для +/-)
        for (int i = tokens.Count - 1; i >= 1; i--)
        {
            var token = tokens[i];
            
            // Отслеживаем вложенность скобок
            if (token.Type == TOKEN_RPAREN) depth++;
            else if (token.Type == TOKEN_LPAREN) depth--;
            else if (depth == 0 && _operators.Contains(token.Type))
            {
                var astType = _astTypes[token.Type];
                var resultToken = new Token(token.Content, astType, token.Position);
                return (resultToken, [(0, i), (i + 1, tokens.Count)]);
            }
        }
        return (null, []);
    }
}

var parser = new Parser()
    .AddRule(new NumberRule())
    .AddRule(new ParenRule())
    // Низкий приоритет: + и -
    .AddRule(new BinaryOpRule(
        [TOKEN_PLUS, TOKEN_MINUS],
        new Dictionary<int, int> {
            { TOKEN_PLUS, AST_ADD },
            { TOKEN_MINUS, AST_SUB }
        }))
    // Высокий приоритет: * и /
    .AddRule(new BinaryOpRule(
        [TOKEN_MULT, TOKEN_DIV],
        new Dictionary<int, int> {
            { TOKEN_MULT, AST_MULT },
            { TOKEN_DIV, AST_DIV }
        }));

var parseResult = parser.Analyze(tokens);

Компилятор (вычислитель)

using SynLex.Compilation;

class CalculatorRule : IEvaluatorRule<double>
{
    public CompilerRuleResult<double> Apply(AbstractSyntaxTree ast, EvaluatorContext context)
    {
        switch (ast.Token.Type)
        {
            case AST_NUMBER:
                return new CompilerRuleResult<double>(true, double.Parse(ast.Token.Content));

            case AST_ADD:
                var left = (double)context.CompiledBranches[0];
                var right = (double)context.CompiledBranches[1];
                return new CompilerRuleResult<double>(true, left + right);

            case AST_SUB:
                left = (double)context.CompiledBranches[0];
                right = (double)context.CompiledBranches[1];
                return new CompilerRuleResult<double>(true, left - right);

            case AST_MULT:
                left = (double)context.CompiledBranches[0];
                right = (double)context.CompiledBranches[1];
                return new CompilerRuleResult<double>(true, left * right);

            case AST_DIV:
                left = (double)context.CompiledBranches[0];
                right = (double)context.CompiledBranches[1];
                if (Math.Abs(right) < 1e-10)
                    throw new DivideByZeroException();
                return new CompilerRuleResult<double>(true, left / right);

            default:
                return CompilerRuleResult<double>.Fail;
        }
    }
}

var evaluator = new Evaluator<double>()
    .AddRule(new CalculatorRule());

var result = evaluator.Compile(parseResult.Ast!);
Console.WriteLine($"{expression} = {result.Result}"); // 2 + 3 * (4 - 1) = 11

JSON Parser

Парсер для упрощённого JSON (строки, числа, массивы, объекты).

Токены

const int TOKEN_LBRACE = 10;    // {
const int TOKEN_RBRACE = 11;    // }
const int TOKEN_LBRACKET = 12;  // [
const int TOKEN_RBRACKET = 13;  // ]
const int TOKEN_COLON = 14;     // :
const int TOKEN_COMMA = 15;     // ,
const int TOKEN_STRING = 16;    // "text"
const int TOKEN_NUMBER = 17;    // 123
const int TOKEN_TRUE = 18;      // true
const int TOKEN_FALSE = 19;     // false
const int TOKEN_NULL = 20;      // null

const int AST_OBJECT = 100;
const int AST_ARRAY = 101;
const int AST_PAIR = 102;
const int AST_STRING = 103;
const int AST_NUMBER = 104;
const int AST_BOOL = 105;
const int AST_NULL = 106;

Лексер

var lexer = new Lexer()
    .AddRule(CommonLexerRules.Whitespace(0))
    .AddRule(new SimpleSublineLexerRule(new[] { "{" }, TOKEN_LBRACE))
    .AddRule(new SimpleSublineLexerRule(new[] { "}" }, TOKEN_RBRACE))
    .AddRule(new SimpleSublineLexerRule(new[] { "[" }, TOKEN_LBRACKET))
    .AddRule(new SimpleSublineLexerRule(new[] { "]" }, TOKEN_RBRACKET))
    .AddRule(new SimpleSublineLexerRule(new[] { ":" }, TOKEN_COLON))
    .AddRule(new SimpleSublineLexerRule(new[] { "," }, TOKEN_COMMA))
    .AddRule(new SimpleSublineLexerRule(new[] { "true" }, TOKEN_TRUE))
    .AddRule(new SimpleSublineLexerRule(new[] { "false" }, TOKEN_FALSE))
    .AddRule(new SimpleSublineLexerRule(new[] { "null" }, TOKEN_NULL))
    .AddRule(CommonLexerRules.DoubleQuotedString(TOKEN_STRING))
    .AddRule(CommonLexerRules.Float(TOKEN_NUMBER));

Парсер

// Объект: { "key": value, ... }
class ObjectRule : IParserRule
{
    public (Token? token, List<(int, int)> subsets) Apply(
        IReadOnlyList<Token> tokens, ParserContext context)
    {
        if (tokens.Count >= 2 && 
            tokens[0].Type == TOKEN_LBRACE && 
            tokens[^1].Type == TOKEN_RBRACE)
        {
            var subsets = new List<(int, int)>();
            // Разбиваем по запятым между парами
            int start = 1;
            for (int i = 1; i < tokens.Count - 1; i++)
            {
                if (tokens[i].Type == TOKEN_COMMA)
                {
                    subsets.Add((start, i));
                    start = i + 1;
                }
            }
            if (start < tokens.Count - 1)
                subsets.Add((start, tokens.Count - 1));

            return (new Token("{}", AST_OBJECT, tokens[0].Position), subsets);
        }
        return (null, []);
    }
}

// Пара: "key": value
class PairRule : IParserRule
{
    public (Token? token, List<(int, int)> subsets) Apply(
        IReadOnlyList<Token> tokens, ParserContext context)
    {
        if (tokens.Count >= 3 &&
            tokens[0].Type == TOKEN_STRING &&
            tokens[1].Type == TOKEN_COLON)
        {
            return (
                new Token(":", AST_PAIR, tokens[1].Position),
                [(0, 1), (2, tokens.Count)]
            );
        }
        return (null, []);
    }
}

// Простые значения
class ValueRule : IParserRule
{
    public (Token? token, List<(int, int)> subsets) Apply(
        IReadOnlyList<Token> tokens, ParserContext context)
    {
        if (tokens.Count != 1) return (null, []);

        var token = tokens[0];
        int astType = token.Type switch
        {
            TOKEN_STRING => AST_STRING,
            TOKEN_NUMBER => AST_NUMBER,
            TOKEN_TRUE or TOKEN_FALSE => AST_BOOL,
            TOKEN_NULL => AST_NULL,
            _ => -1
        };

        if (astType < 0) return (null, []);
        return (new Token(token.Content, astType, token.Position), []);
    }
}

var parser = new Parser()
    .AddRule(new ValueRule())
    .AddRule(new ObjectRule())
    .AddRule(new PairRule());
    // ... добавить ArrayRule аналогично ObjectRule

Компилятор в Dictionary/List

class JsonEvaluatorRule : IEvaluatorRule<object?>
{
    public CompilerRuleResult<object?> Apply(AbstractSyntaxTree ast, EvaluatorContext context)
    {
        switch (ast.Token.Type)
        {
            case AST_STRING:
                var str = ast.Token.Content.Trim('"');
                return new CompilerRuleResult<object?>(true, str);

            case AST_NUMBER:
                return new CompilerRuleResult<object?>(true, double.Parse(ast.Token.Content));

            case AST_BOOL:
                return new CompilerRuleResult<object?>(true, ast.Token.Content == "true");

            case AST_NULL:
                return new CompilerRuleResult<object?>(true, null);

            case AST_OBJECT:
                var obj = new Dictionary<string, object?>();
                foreach (var pair in context.CompiledBranches.Cast<KeyValuePair<string, object?>>())
                    obj[pair.Key] = pair.Value;
                return new CompilerRuleResult<object?>(true, obj);

            case AST_PAIR:
                var key = (string)context.CompiledBranches[0]!;
                var value = context.CompiledBranches[1];
                return new CompilerRuleResult<object?>(true, new KeyValuePair<string, object?>(key, value));

            case AST_ARRAY:
                return new CompilerRuleResult<object?>(true, context.CompiledBranches.ToList());

            default:
                return CompilerRuleResult<object?>.Fail;
        }
    }
}

var evaluator = new Evaluator<object?>()
    .AddRule(new JsonEvaluatorRule());

Простой язык программирования

Минимальный интерпретатор с переменными и условиями.

Грамматика

program    := statement*
statement  := assignment | if-stmt | print
assignment := identifier '=' expr ';'
if-stmt    := 'if' '(' expr ')' '{' statement* '}'
print      := 'print' '(' expr ')' ';'
expr       := number | identifier | expr op expr
op         := '+' | '-' | '*' | '/'

Пример кода

x = 10;
y = 20;
if (x < y) {
    print(x + y);
}

Реализация (схема)

// Лексер с ключевыми словами
var lexer = new Lexer()
    .AddRule(CommonLexerRules.Whitespace(TOKEN_WS))
    .AddRule(CommonLexerRules.Keyword(
        new[] { "if", "print" }, TOKEN_KEYWORD))
    .AddRule(CommonLexerRules.Identifier(TOKEN_ID))
    .AddRule(CommonLexerRules.Integer(TOKEN_NUM))
    .AddRule(new SimpleSublineLexerRule(
        new[] { "=", ";", "(", ")", "{", "}", "+", "-", "*", "/", "<", ">" },
        TOKEN_PUNCT));

// Парсер с правилами для каждой конструкции
var parser = new Parser()
    .AddRule(new AssignmentRule())
    .AddRule(new IfStatementRule())
    .AddRule(new PrintStatementRule())
    .AddRule(new ExpressionRule());

// Интерпретатор с окружением переменных
class InterpreterRule : IEvaluatorRule<object>
{
    private Dictionary<string, object> _env = new();

    public CompilerRuleResult<object> Apply(AbstractSyntaxTree ast, EvaluatorContext context)
    {
        switch (ast.Token.Type)
        {
            case AST_ASSIGN:
                var varName = ast.Branches.First().Token.Content;
                var value = context.CompiledBranches[1];
                _env[varName] = value;
                return new CompilerRuleResult<object>(true, value);

            case AST_VARIABLE:
                return new CompilerRuleResult<object>(true, _env[ast.Token.Content]);

            case AST_IF:
                var condition = (bool)context.CompiledBranches[0];
                if (condition)
                {
                    // Выполняем тело if
                    foreach (var stmt in context.CompiledBranches.Skip(1))
                        ; // выполнено
                }
                return new CompilerRuleResult<object>(true, null!);

            // ... другие случаи
        }
    }
}

Виртуальная машина: байт-код для калькулятора

Компиляция AST калькулятора в стековые инструкции.

Байт-код компилятор

using SynLex.VirtualMachine;

var compiler = new BytecodeCompiler()
    .AddRule(AST_NUMBER, ast => new List<Instruction>
    {
        new(OpCode.Push, double.Parse(ast.Token.Content))
    })
    .AddRule(AST_ADD, ast => new List<Instruction> { new(OpCode.Add) })
    .AddRule(AST_SUB, ast => new List<Instruction> { new(OpCode.Sub) })
    .AddRule(AST_MULT, ast => new List<Instruction> { new(OpCode.Mul) })
    .AddRule(AST_DIV, ast => new List<Instruction> { new(OpCode.Div) });

var instructions = compiler.Compile(ast);

Выполнение

var vm = new VirtualMachine()
    .AddRule(VMBuiltinRules.PushRule())
    .AddRule(VMBuiltinRules.AddRule())
    .AddRule(VMBuiltinRules.SubRule())
    .AddRule(VMBuiltinRules.MulRule())
    .AddRule(VMBuiltinRules.DivRule());

var result = vm.Execute(instructions);
Console.WriteLine($"Результат VM: {result.Value}");

Проект: SynLex
Раздел: Примеры использования

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