Лаб. 1 ТАЯК
.docxЛабораторная работа №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");
}
}
}
}
