Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лаб. 1 ТАЯК

.docx
Скачиваний:
0
Добавлен:
25.01.2026
Размер:
119.46 Кб
Скачать

Лабораторная работа №1

Построение простейшего синтаксического анализатора выражений

using System;

using System.Collections.Generic;

using System.Globalization;

using System.Linq;

namespace KURS_4_C_SHARP

{

enum TokenType

{

Number, // Число

Text, // Текст (слово/буква)

Operator, // Оператор

OpeningParenthesis, // Открывающая скобка

ClosingParenthesis, // Закрывающая скобка

Comma, // Запятая

Function // Функция

}

class Token

{

public TokenType Type { get; } // Тип

public string Text { get; } // Строка с токеном

public int Index { get; } // Позиция (индекс) в строке

public Token(TokenType type, string text, int index)

{

Type = type;

Text = text;

Index = index;

}

}

class Operator

{

public string Symbol { get; } // Символ

public int Priority { get; } // Приоритет

public bool RightAssociative { get; } // Правая ассоциативность?

//public int Arity { get; } // Арность ( 1 - унарный, 2 - бинарый)

public Operator(string symbol, int priority, bool rightAssociative, int arity)

{

Symbol = symbol;

Priority = priority;

RightAssociative = rightAssociative;

//Arity = arity;

}

}

class FunctionDefinition

{

public string Name { get; } // Название

public Func<double, double, double> Impl { get; } // Функция с 2-мя аргументами

public FunctionDefinition(string name, Func<double, double, double> impl)

{

Name = name;

Impl = impl;

}

}

static class ParserAndEvaluator

{

// Операторы

static Dictionary<string, Operator> Operators = new()

{

{ "+", new Operator("+", 2, false, 2) },

{ "-", new Operator("-", 2, false, 2) },

{ "*", new Operator("*", 3, false, 2) },

{ "/", new Operator("/", 3, false, 2) },

{ "u-", new Operator("u-", 4, true, 1) } // Унарный минус

};

// Функции

static Dictionary<string, FunctionDefinition> Functions = new(StringComparer.OrdinalIgnoreCase)

{

{ "log", new FunctionDefinition("log", (a,b) => Math.Log(a, b)) }

};

// Разбивка на токены (элементы)

public static (List<Token> tokens, string error) Tokenize(string inputString)

{

List<Token> tokens = new List<Token>();

int i = 0;

while (i < inputString.Length)

{

char charI = inputString[i];

// Если символ пробел, то пропускаем

if (char.IsWhiteSpace(charI))

{

i++;

continue;

}

// Если символ цифра или точка

if (char.IsDigit(charI) || charI == '.')

{

int startPosition = i; // Начальная позиция числа

bool findDot = false; // Встречена ли точка?

while (i < inputString.Length && (char.IsDigit(inputString[i]) || (!findDot && inputString[i] == '.')))

{

if (inputString[i] == '.')

{

// Если найдена 2-ая точка, то операнд закончился

if (findDot)

{

break;

}

findDot = true;

}

i++;

}

// Операнд (число)

string numText = inputString.Substring(startPosition, i - startPosition);

// Если операнд является только лишь точкой

if (numText == ".")

{

return (null, $"Неверный формат числа в позиции {startPosition}: '{numText}'");

}

tokens.Add(new Token(TokenType.Number, numText, startPosition));

continue;

}

if (char.IsLetter(charI))

{

int start = i; // Начальная позиция слова

i++;

while (i < inputString.Length && char.IsLetter(inputString[i]))

{

i++;

}

// Операнд (число) или функция

string text = inputString.Substring(start, i - start);

tokens.Add(new Token(TokenType.Text, text, start));

continue;

}

switch (charI)

{

case '+':

case '-':

case '*':

case '/':

tokens.Add(new Token(TokenType.Operator, charI.ToString(), i));

i++;

break;

case '(':

tokens.Add(new Token(TokenType.OpeningParenthesis, "(", i));

i++;

break;

case ')':

tokens.Add(new Token(TokenType.ClosingParenthesis, ")", i));

i++;

break;

case ',':

tokens.Add(new Token(TokenType.Comma, ",", i));

i++;

break;

default:

return (null, $"Неизвестный символ '{charI}' в позиции {i}");

}

}

return (tokens, null);

}

public static (List<Token> outputString, string error, bool hasText) ShuntingYard(List<Token> tokens, bool printInfo)

{

Stack<Token> stack = new Stack<Token>(); // Стек

List<Token> outputString = new List<Token>(); // Выходная строка

bool hasText = false;

for (int t = 0; t < tokens.Count; t++)

{

Token token = tokens[t];

// Распознавание функции: functionName(

if (token.Type == TokenType.Text && t + 1 < tokens.Count && tokens[t + 1].Type == TokenType.OpeningParenthesis)

{

stack.Push(new Token(TokenType.Function, token.Text, token.Index));

if (printInfo)

{

PrintInfo(token.Index, token.Text, stack, outputString);

}

continue;

}

// Число сразу идёт в выходную строку

if (token.Type == TokenType.Number)

{

outputString.Add(token);

if (printInfo)

{

PrintInfo(token.Index, token.Text, stack, outputString);

}

continue;

}

// Слово сразу идёт в выходную строку

if (token.Type == TokenType.Text)

{

hasText = true;

outputString.Add(token);

if (printInfo)

{

PrintInfo(token.Index, token.Text, stack, outputString);

}

continue;

}

//

if (token.Type == TokenType.Operator)

{

string opSymbol = token.Text; // Оператор

bool isUnary = false; // Унарный?

// Распознавание унарного -

if (opSymbol == "-")

{

// Если в начале выражения

if (t == 0)

isUnary = true;

else

{

Token prevTok = tokens[t - 1];

// Если предыдущий токен оператор/открывающая скобка/запятая

if (prevTok.Type == TokenType.Operator || prevTok.Type == TokenType.OpeningParenthesis || prevTok.Type == TokenType.Comma)

{

isUnary = true;

}

}

}

if (isUnary)

{

opSymbol = "u-";

}

Operator op1 = Operators[opSymbol];

while (stack.Count > 0)

{

var topOp = stack.Peek(); // Верхний оператор в стеке

if (topOp.Type == TokenType.Operator)

{

string topOpText = topOp.Text;

Operator op2 = null;

if (!Operators.TryGetValue(topOpText, out op2))

{

return (null, $"Неизвестный оператор в стеке: {topOpText}", hasText);

}

// b) опеpация выталкивает из стека все опеpации с большим или pавным пpиоpитетом в выходную стpоку

if ((op1.RightAssociative == false && op1.Priority <= op2.Priority) ||

(op1.RightAssociative == true && op1.Priority < op2.Priority))

{

outputString.Add(stack.Pop());

}

else

{

break;

}

}

else

{

break;

}

}

// a) если стек пуст, то опеpация из входной стpоки пеpеписывается в стек;

stack.Push(new Token(TokenType.Operator, opSymbol, token.Index));

if (printInfo)

{

PrintInfo(token.Index, (isUnary ? "u" : "") + token.Text, stack, outputString);

}

continue;

}

// c) если очеpедной символ из исходной стpоки есть откpывающая скобка, то он пpоталкивается в стек;

if (token.Type == TokenType.OpeningParenthesis)

{

stack.Push(token);

if (printInfo)

{

PrintInfo(token.Index, token.Text, stack, outputString);

}

continue;

}

// d) закpывающая кpуглая скобка выталкивает все опеpации из стека до ближайшей откpывающей скобки, сами скобки в выходную стpоку не пеpеписываются, а уничтожают дpуг дpуга.

if (token.Type == TokenType.ClosingParenthesis)

{

bool foundOpeningParenthesis = false; // Найдена ли открывающая скобка

while (stack.Count > 0)

{

var topOp = stack.Pop();

if (topOp.Type == TokenType.OpeningParenthesis)

{

foundOpeningParenthesis = true;

break;

}

else

{

outputString.Add(topOp);

}

}

if (!foundOpeningParenthesis)

{

return (null, $"Несоответствие количества '(' и ')' — закрывающая скобка без открывающей в позиции {token.Index}", hasText);

}

// Если после удаление открывающей скобки на верхушке функция, то она тоже перебрасывается в выходную строку

if (stack.Count > 0 && stack.Peek().Type == TokenType.Function)

{

outputString.Add(stack.Pop());

}

if (printInfo)

{

PrintInfo(token.Index, token.Text, stack, outputString);

}

continue;

}

if (token.Type == TokenType.Comma)

{

bool foundOpeningParenthesis = false; // Найдена ли открывающая скобка

while (stack.Count > 0)

{

var topOp = stack.Peek();

if (topOp.Type == TokenType.OpeningParenthesis)

{

foundOpeningParenthesis = true;

break;

}

outputString.Add(stack.Pop());

}

if (!foundOpeningParenthesis)

{

return (null, $"Запятая вне списка аргументов функции в позиции {token.Index}", hasText);

}

if (printInfo)

{

PrintInfo(token.Index, token.Text, stack, outputString);

}

continue;

}

}

while (stack.Count > 0)

{

var topOp = stack.Pop();

if (topOp.Type == TokenType.OpeningParenthesis || topOp.Type == TokenType.ClosingParenthesis)

{

return (null, "Несоответствие количества ( и ) — осталась незакрытая скобка", hasText);

}

outputString.Add(topOp);

}

return (outputString, null, hasText);

}

public static (double? result, string error) CheckAndCalculateRpn(List<Token> rpn, bool hasText)

{

int stackDepth = 0; // Кол-во эл-ов в стеке для вычисления

Stack<double> stack = new Stack<double>();

foreach (var tk in rpn)

{

if (tk.Type == TokenType.Number || tk.Type == TokenType.Text)

{

stackDepth++;

if (!hasText && tk.Type == TokenType.Number)

{

if (!double.TryParse(tk.Text, NumberStyles.Float, CultureInfo.InvariantCulture, out double val))

{

return (null, $"Невозможно распарсить число '{tk.Text}' в позиции {tk.Index}");

}

stack.Push(val);

}

continue;

}

if (tk.Type == TokenType.Operator)

{

if (tk.Text == "u-")

{

if (stackDepth < 1)

{

return (null, $"Недостаточно операндов для унарного '-' в позиции {tk.Index}");

}

if (!hasText)

{

stack.Push(-stack.Pop());

}

}

else

{

if (stackDepth < 2)

{

return (null, $"Недостаточно операндов для бинарного оператора '{tk.Text}' в позиции {tk.Index}");

}

if (!hasText)

{

double b = stack.Pop();

double a = stack.Pop();

switch (tk.Text)

{

case "+":

stack.Push(a + b);

break;

case "-":

stack.Push(a - b);

break;

case "*":

stack.Push(a * b);

break;

case "/":

if (b == 0.0)

{

return (null, $"Ошибка: деление на ноль (операция {a} / {b}).");

}

stack.Push(a / b);

break;

default:

return (null, $"Неизвестный оператор '{tk.Text}' при вычислении");

}

}

stackDepth--; // 2 операнда уходят, результат кладётся

}

continue;

}

if (tk.Type == TokenType.Function)

{

if (stackDepth < 2)

{

return (null, $"Недостаточно аргументов для функции '{tk.Text}' в позиции {tk.Index}");

}

if (!hasText)

{

double arg2 = stack.Pop();

double arg1 = stack.Pop();

if (!Functions.TryGetValue(tk.Text, out var func))

{

return (null, $"Неизвестная встроенная функция '{tk.Text}'");

}

try

{

double res = func.Impl(arg1, arg2);

if (double.IsNaN(res) || double.IsInfinity(res))

{

return (null, $"Результат функции '{tk.Text}({arg1},{arg2})' некорректен: {res}");

}

stack.Push(res);

}

catch (Exception ex)

{

return (null, $"Ошибка при выполнении функции '{tk.Text}': {ex.Message}");

}

}

stackDepth--; // 2 операнда уходят, результат кладётся

}

}

if (stackDepth != 1)

{

return (null, $"Некорректное выражение: после обработки в стеке осталось {stackDepth} элементов.");

}

if (hasText)

{

return (null, "Выражение корректно, но содержит идентификаторы/переменные — вычислить числовое значение невозможно.");

}

return (stack.Pop(), null);

}

static void PrintInfo(int index, string token, Stack<Token> stack, List<Token> outputString)

{

//Console.WriteLine($"[idx={index}] tok='{token}' | stack=[{string.Join(" ", ops.Reverse().Select(x => x.Text))}] | output=[{string.Join(" ", output.Select(x => x.Text))}]");

Console.WriteLine($" index: {index}");

Console.WriteLine($" token: {token}");

Console.WriteLine($" stack: [{string.Join(" ", stack.Reverse().Select(x => x.Text))}]");

Console.WriteLine($"outputString: [{string.Join(" ", outputString.Select(x => x.Text))}]");

Console.WriteLine("--------------------------");

}

}

class Program

{

static void Main()

{

bool printInfo = true;

Console.WriteLine("Можно вводить операнды вида: a, Qwerty, 1, 223, 4.2, 34., .86");

Console.WriteLine("Можно вводить операторы:");

Console.WriteLine("Унарные: - (пример: -2)");

Console.WriteLine("Бинарные: +, -, *, / ");

Console.WriteLine("Можно вводить функции: log(a, b)");

Console.WriteLine("Для выхода нажмите Enter при пустой строке");

Console.WriteLine("--------------------------");

while (true)

{

Console.Write("Введите строку: ");

string inputString = Console.ReadLine();

if (string.IsNullOrWhiteSpace(inputString))

{

break;

}

var (tokens, errorTokenize) = ParserAndEvaluator.Tokenize(inputString);

if (errorTokenize != null)

{

Console.WriteLine("Ошибка при разделении на токены: " + errorTokenize);

continue;

}

//else

//{

// Console.WriteLine(string.Join(" ", tokens.Select(x => x.Text)));

//}

var (rpn, errorShuntingYard, hasText) = ParserAndEvaluator.ShuntingYard(tokens, printInfo);

if (errorShuntingYard != null)

{

Console.WriteLine("Синтаксическая ошибка: " + errorShuntingYard);

continue;

}

Console.WriteLine("\nПостфиксная запись (ОПН):");

Console.WriteLine(string.Join(" ", rpn.Select(t => t.Text)));

var (res, errorCheckAndCalculateRpn) = ParserAndEvaluator.CheckAndCalculateRpn(rpn, hasText);

if (errorCheckAndCalculateRpn != null)

{

Console.WriteLine("\n" + errorCheckAndCalculateRpn + "\n");

continue;

}

Console.WriteLine($"\nРезультат: {res}\n");

}

}

}

}

Соседние файлы в предмете Теория алгоритмических языков и компиляторов