Лаб. 4 ТАЯК
.docxЛабораторная работа №4
Разработка транслятора заданных конструкций языка СИ++. Разработка синтаксического анализатора, обнаруживающего максимальное число ошибок
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
// Типы токенов
public enum TokenType
{
// Ключевые слова
INT, BOOL, VOID, MAIN, FOR, IF, RETURN,
// Идентификаторы и литералы
IDENTIFIER, NUMBER,
// Операторы и разделители
ASSIGN, LT, GT, EQ, NE,
// Скобки и пунктуация
LPAREN, RPAREN, LBRACE, RBRACE, SEMICOLON, COMMA,
// Специальные токены
EOF, ERROR
}
// Класс для токена
public class Token
{
public TokenType Type { get; } // тип токена
public string Value { get; } // строковое значение токена
public int Line { get; } // позиция в коде: строка
public int Column { get; } // позиция в коде: столбец
public Token(TokenType type, string value, int line, int column)
{
Type = type;
Value = value;
Line = line;
Column = column;
}
public override string ToString()
{
return $"{Type}({Value}) at {Line}:{Column}";
}
}
// Лексический анализатор
public class Lexer
{
private readonly string _input;
private int _position;
private int _line;
private int _column;
private readonly List<Token> _tokens;
public Lexer(string input)
{
_input = input;
_position = 0;
_line = 1;
_column = 1;
_tokens = new List<Token>();
}
// Преобразование исходного кода в токены
public List<Token> Tokenize()
{
_tokens.Clear();
while (_position < _input.Length)
{
var current = Peek();
if (char.IsWhiteSpace(current))
{
SkipWhitespace();
}
else if (char.IsLetter(current) || current == '_')
{
ScanIdentifierOrKeyword();
}
else if (char.IsDigit(current))
{
ScanNumber();
}
else
{
ScanSymbol();
}
}
_tokens.Add(new Token(TokenType.EOF, "", _line, _column));
return _tokens;
}
// Просмотр текущего символа без смещения позиции
private char Peek(int offset = 0)
{
return _position + offset < _input.Length ? _input[_position + offset] : '\0';
}
// Чтение текущего символ со смещением позиции
private char Next()
{
var current = Peek();
if (_position < _input.Length)
{
_position++;
if (current == '\n')
{
_line++;
_column = 1;
}
else
{
_column++;
}
}
return current;
}
// Пропуск пробелов
private void SkipWhitespace()
{
while (_position < _input.Length && char.IsWhiteSpace(Peek()))
{
Next();
}
}
// Определение слов
private void ScanIdentifierOrKeyword()
{
var start = _position;
var startLine = _line;
var startColumn = _column;
while (_position < _input.Length && (char.IsLetterOrDigit(Peek()) || Peek() == '_'))
{
Next();
}
var value = _input.Substring(start, _position - start);
var type = value.ToLower() switch
{
"int" => TokenType.INT,
"bool" => TokenType.BOOL,
"void" => TokenType.VOID,
"main" => TokenType.MAIN,
"for" => TokenType.FOR,
"if" => TokenType.IF,
"return" => TokenType.RETURN,
_ => TokenType.IDENTIFIER
};
_tokens.Add(new Token(type, value, startLine, startColumn));
}
// Определение чисел
private void ScanNumber()
{
var start = _position;
var startLine = _line;
var startColumn = _column;
while (_position < _input.Length && char.IsDigit(Peek()))
{
Next();
}
var value = _input.Substring(start, _position - start);
_tokens.Add(new Token(TokenType.NUMBER, value, startLine, startColumn));
}
// Определение символов и операторов
private void ScanSymbol()
{
var startLine = _line;
var startColumn = _column;
var current = Next();
switch (current)
{
case '(':
_tokens.Add(new Token(TokenType.LPAREN, "(", startLine, startColumn));
break;
case ')':
_tokens.Add(new Token(TokenType.RPAREN, ")", startLine, startColumn));
break;
case '{':
_tokens.Add(new Token(TokenType.LBRACE, "{", startLine, startColumn));
break;
case '}':
_tokens.Add(new Token(TokenType.RBRACE, "}", startLine, startColumn));
break;
case ';':
_tokens.Add(new Token(TokenType.SEMICOLON, ";", startLine, startColumn));
break;
case '=':
if (Peek() == '=')
{
Next();
_tokens.Add(new Token(TokenType.EQ, "==", startLine, startColumn));
}
else
{
_tokens.Add(new Token(TokenType.ASSIGN, "=", startLine, startColumn));
}
break;
case '<':
_tokens.Add(new Token(TokenType.LT, "<", startLine, startColumn));
break;
case '>':
_tokens.Add(new Token(TokenType.GT, ">", startLine, startColumn));
break;
case '!':
if (Peek() == '=')
{
Next();
_tokens.Add(new Token(TokenType.NE, "!=", startLine, startColumn));
}
else
{
_tokens.Add(new Token(TokenType.ERROR, "!", startLine, startColumn));
}
break;
default:
_tokens.Add(new Token(TokenType.ERROR, current.ToString(), startLine, startColumn));
break;
}
}
}
// Класс ошибок
public class SyntaxError
{
public string Message { get; } // сообщение ошибки
public int Line { get; } // позиция в коде: строка
public int Column { get; } // позиция в коде: столбец
public string ErrorType { get; } // тип ошибки
public SyntaxError(string message, int line, int column, string errorType)
{
Message = message;
Line = line;
Column = column;
ErrorType = errorType;
}
public override string ToString()
{
return $"{ErrorType} на {Line}:{Column} - {Message}";
}
}
// Шаги анализа
public class AnalysisStep
{
public string Stack { get; }
public string Input { get; }
public string Output { get; }
public AnalysisStep(string stack, string input, string output)
{
Stack = stack;
Input = input;
Output = output;
}
}
// Таблица разбора (грамматика)
public class ParsingTable
{
private readonly Dictionary<string, Dictionary<TokenType, string[]>> _table;
public ParsingTable()
{
_table = new Dictionary<string, Dictionary<TokenType, string[]>>();
InitializeTable();
}
private void InitializeTable()
{
// Program → Type main ( ) { Statements }
AddEntry("Program", TokenType.INT, new[] { "Type", "main", "(", ")", "{", "Statements", "}" });
AddEntry("Program", TokenType.BOOL, new[] { "Type", "main", "(", ")", "{", "Statements", "}" });
AddEntry("Program", TokenType.VOID, new[] { "Type", "main", "(", ")", "{", "Statements", "}" });
// Type → int | bool | void
AddEntry("Type", TokenType.INT, new[] { "int" });
AddEntry("Type", TokenType.BOOL, new[] { "bool" });
AddEntry("Type", TokenType.VOID, new[] { "void" });
// Statements → Statement Statements | ε
AddEntry("Statements", TokenType.INT, new[] { "Statement", "Statements" });
AddEntry("Statements", TokenType.BOOL, new[] { "Statement", "Statements" });
AddEntry("Statements", TokenType.VOID, new[] { "Statement", "Statements" });
AddEntry("Statements", TokenType.LBRACE, new[] { "Statement", "Statements" });
AddEntry("Statements", TokenType.FOR, new[] { "Statement", "Statements" });
AddEntry("Statements", TokenType.IF, new[] { "Statement", "Statements" });
AddEntry("Statements", TokenType.RETURN, new[] { "Statement", "Statements" });
AddEntry("Statements", TokenType.RBRACE, new[] { "ε" });
AddEntry("Statements", TokenType.EOF, new[] { "ε" }); // Добавляем ε для EOF
// Statement → Declaration ; | { Statements } | For Statement | If Statement | Return
AddEntry("Statement", TokenType.INT, new[] { "Declaration", ";" });
AddEntry("Statement", TokenType.BOOL, new[] { "Declaration", ";" });
AddEntry("Statement", TokenType.VOID, new[] { "Declaration", ";" });
AddEntry("Statement", TokenType.LBRACE, new[] { "{", "Statements", "}" });
AddEntry("Statement", TokenType.FOR, new[] { "For", "Statement" });
AddEntry("Statement", TokenType.IF, new[] { "If", "Statement" });
AddEntry("Statement", TokenType.RETURN, new[] { "Return" });
// Declaration → Type id Assign
AddEntry("Declaration", TokenType.INT, new[] { "Type", "id", "Assign" });
AddEntry("Declaration", TokenType.BOOL, new[] { "Type", "id", "Assign" });
AddEntry("Declaration", TokenType.VOID, new[] { "Type", "id", "Assign" });
// Assign → = AssignEnd | ε
AddEntry("Assign", TokenType.ASSIGN, new[] { "=", "AssignEnd" });
AddEntry("Assign", TokenType.SEMICOLON, new[] { "ε" });
AddEntry("Assign", TokenType.RBRACE, new[] { "ε" });
// AssignEnd → id | num
AddEntry("AssignEnd", TokenType.IDENTIFIER, new[] { "id" });
AddEntry("AssignEnd", TokenType.NUMBER, new[] { "num" });
// For → for ( Declaration ; BoolExpression ; )
AddEntry("For", TokenType.FOR, new[] { "for", "(", "Declaration", ";", "BoolExpression", ";", ")" });
// BoolExpression → Expression Relop Expression
AddEntry("BoolExpression", TokenType.IDENTIFIER, new[] { "Expression", "Relop", "Expression" });
AddEntry("BoolExpression", TokenType.NUMBER, new[] { "Expression", "Relop", "Expression" });
// Expression → id | num
AddEntry("Expression", TokenType.IDENTIFIER, new[] { "id" });
AddEntry("Expression", TokenType.NUMBER, new[] { "num" });
// Relop → < | > | == | !=
AddEntry("Relop", TokenType.LT, new[] { "<" });
AddEntry("Relop", TokenType.GT, new[] { ">" });
AddEntry("Relop", TokenType.EQ, new[] { "==" });
AddEntry("Relop", TokenType.NE, new[] { "!=" });
// If → if ( BoolExpression )
AddEntry("If", TokenType.IF, new[] { "if", "(", "BoolExpression", ")" });
// Return → return num ;
AddEntry("Return", TokenType.RETURN, new[] { "return", "num", ";" });
}
private void AddEntry(string nonTerminal, TokenType tokenType, string[] production)
{
if (!_table.ContainsKey(nonTerminal))
_table[nonTerminal] = new Dictionary<TokenType, string[]>();
_table[nonTerminal][tokenType] = production;
}
public string[] GetProduction(string nonTerminal, TokenType tokenType)
{
if (_table.ContainsKey(nonTerminal) && _table[nonTerminal].ContainsKey(tokenType))
return _table[nonTerminal][tokenType];
return null;
}
}
// Синтаксический анализатор
public class PredictiveParser
{
private readonly List<Token> _tokens;
private int _position;
private readonly List<SyntaxError> _errors;
private readonly List<AnalysisStep> _analysisSteps;
private readonly Stack<string> _stack;
private readonly ParsingTable _parsingTable;
public PredictiveParser(List<Token> tokens)
{
_tokens = tokens;
_position = 0;
_errors = new List<SyntaxError>();
_analysisSteps = new List<AnalysisStep>();
_stack = new Stack<string>();
_parsingTable = new ParsingTable();
_stack.Push("$");
_stack.Push("Program");
}
public (bool Success, List<SyntaxError> Errors, List<AnalysisStep> Steps) Parse()
{
_errors.Clear();
_analysisSteps.Clear();
AddStep("Начало анализа");
while (_stack.Count > 0 && _stack.Peek() != "$")
{
var X = _stack.Peek();
var a = CurrentToken();
if (IsTerminal(X))
{
if (MatchesTerminal(X, a))
{
_stack.Pop();
_position++;
AddStep($"Совпадение: {X}");
}
else
{
ReportError($"Ожидается '{GetTerminalName(X)}', но найдено '{a.Value}'", a.Line, a.Column);
// Вместо просто удаления терминала, используем режим паники
if (!PanicModeRecoveryForTerminal(X, a))
{
_stack.Pop(); // Если не удалось восстановиться, удаляем терминал
}
}
}
else
{
var production = _parsingTable.GetProduction(X, a.Type);
if (production != null)
{
_stack.Pop();
if (production.Length == 1 && production[0] == "ε")
{
AddStep($"{X}: ε");
}
else
{
for (int i = production.Length - 1; i >= 0; i--)
{
if (production[i] != "ε")
{
_stack.Push(production[i]);
}
}
AddStep($"{X}: {string.Join(" ", production)}");
}
}
else
{
ReportError($"Синтаксическая ошибка: нет правила для {X} с токеном '{a.Value}'", a.Line, a.Column);
PanicModeRecovery(X);
}
}
}
// Проверяем, что достигли конца файла
if (_stack.Count > 0 && _stack.Peek() == "$" && CurrentToken().Type != TokenType.EOF)
{
ReportError("Неожиданные токены после конца программы", CurrentToken().Line, CurrentToken().Column);
}
AddStep("Анализ завершен");
return (_errors.Count == 0, _errors, _analysisSteps);
}
private bool PanicModeRecoveryForTerminal(string terminal, Token currentToken)
{
// Для некоторых терминалов можем попробовать вставить пропущенный токен
if (terminal == ";" && IsStatementStarter(currentToken.Type))
{
AddStep($"Восстановление: вставляем пропущенную ';'");
_stack.Pop(); // Удаляем ожидаемый ';' из стека
// Не двигаем позицию - currentToken будет обработан как начало нового statement
return true;
}
return false;
}
private bool IsStatementStarter(TokenType tokenType)
{
return tokenType == TokenType.INT || tokenType == TokenType.BOOL || tokenType == TokenType.VOID ||
tokenType == TokenType.FOR || tokenType == TokenType.IF || tokenType == TokenType.RETURN ||
tokenType == TokenType.LBRACE || tokenType == TokenType.RBRACE;
}
private Token CurrentToken()
{
return _position < _tokens.Count ? _tokens[_position] : _tokens[^1];
}
private string GetRemainingInput()
{
var remaining = _tokens.Skip(_position).Take(5);
return string.Join("", remaining.Select(t => t.Value)) +
(_tokens.Count - _position > 5 ? "..." : "");
}
private void AddStep(string output = "")
{
var stackStr = string.Join(" ", _stack.Reverse());
var inputStr = GetRemainingInput();
_analysisSteps.Add(new AnalysisStep(stackStr, inputStr, output));
}
private bool IsTerminal(string symbol)
{
return symbol switch
{
"int" or "bool" or "void" or "main" or "for" or "if" or "return" or
"id" or "num" or "=" or "<" or ">" or "==" or "!=" or
"(" or ")" or "{" or "}" or ";" => true,
_ => false
};
}
private bool MatchesTerminal(string terminal, Token token)
{
return terminal switch
{
"int" => token.Type == TokenType.INT,
"bool" => token.Type == TokenType.BOOL,
"void" => token.Type == TokenType.VOID,
"main" => token.Type == TokenType.MAIN,
"for" => token.Type == TokenType.FOR,
"if" => token.Type == TokenType.IF,
"return" => token.Type == TokenType.RETURN,
"id" => token.Type == TokenType.IDENTIFIER,
"num" => token.Type == TokenType.NUMBER,
"=" => token.Type == TokenType.ASSIGN,
"<" => token.Type == TokenType.LT,
">" => token.Type == TokenType.GT,
"==" => token.Type == TokenType.EQ,
"!=" => token.Type == TokenType.NE,
"(" => token.Type == TokenType.LPAREN,
")" => token.Type == TokenType.RPAREN,
"{" => token.Type == TokenType.LBRACE,
"}" => token.Type == TokenType.RBRACE,
";" => token.Type == TokenType.SEMICOLON,
_ => false
};
}
private string GetTerminalName(string terminal)
{
return terminal switch
{
"id" => "идентификатор",
"num" => "число",
_ => terminal
};
}
private void ReportError(string message, int line, int column)
{
_errors.Add(new SyntaxError(message, line, column, ""));
}
// Режим паники
private void PanicModeRecovery(string currentNonTerminal)
{
var syncTokens = GetSyncTokens(currentNonTerminal);
// Пропускаем токены до синхронизирующего
while (_position < _tokens.Count && !syncTokens.Contains(CurrentToken().Type))
{
AddStep($"Режим паники: пропускаем '{CurrentToken().Value}'");
_position++;
}
// Удаляем нетерминал из стека
_stack.Pop();
AddStep($"Режим паники: удаляем {currentNonTerminal} из стека");
}
// Получение синхронизирующих токенов для нетерминалов
private List<TokenType> GetSyncTokens(string nonTerminal)
{
return nonTerminal switch
{
"Program" => new List<TokenType> { TokenType.EOF },
"Statements" => new List<TokenType> { TokenType.RBRACE, TokenType.EOF },
"Statement" => new List<TokenType> { TokenType.SEMICOLON, TokenType.RBRACE, TokenType.EOF },
"Declaration" => new List<TokenType> { TokenType.SEMICOLON, TokenType.RBRACE, TokenType.EOF },
"Assign" => new List<TokenType> { TokenType.SEMICOLON, TokenType.RBRACE, TokenType.EOF },
"BoolExpression" => new List<TokenType> { TokenType.SEMICOLON, TokenType.RPAREN, TokenType.RBRACE, TokenType.EOF },
"Expression" => new List<TokenType> { TokenType.SEMICOLON, TokenType.RPAREN, TokenType.RBRACE, TokenType.EOF },
_ => new List<TokenType> { TokenType.SEMICOLON, TokenType.RBRACE, TokenType.EOF }
};
}
}
public class AnalysisTableGenerator
{
public static string GenerateAnalysisTable(List<AnalysisStep> steps)
{
// Определяем максимальные ширины для каждого столбца
int maxStackWidth = 0;
int maxInputWidth = 0;
int maxOutputWidth = 0;
foreach (var step in steps)
{
maxStackWidth = Math.Max(maxStackWidth, step.Stack.Length);
maxInputWidth = Math.Max(maxInputWidth, step.Input.Length);
maxOutputWidth = Math.Max(maxOutputWidth, step.Output.Length);
}
// Добавляем отступы для читаемости
maxStackWidth = Math.Max(maxStackWidth + 4, 30);
maxInputWidth = Math.Max(maxInputWidth + 4, 30);
maxOutputWidth = Math.Max(maxOutputWidth + 4, 40);
var table = "Стек".PadRight(maxStackWidth) + "Вход".PadRight(maxInputWidth) + "Выход/Примечание".PadRight(maxOutputWidth) + "\n";
table += new string('=', maxStackWidth + maxInputWidth + maxOutputWidth) + "\n";
foreach (var step in steps)
{
table += step.Stack.PadRight(maxStackWidth) +
step.Input.PadRight(maxInputWidth) +
step.Output.PadRight(maxOutputWidth) + "\n";
}
return table;
}
}
class Program
{
static void Main(string[] args)
{
string testProgram = @"int main() {
int a = 5
for (int a = 3; b > 7; {
int l = 7;
if (e == 3){
for (int w = 5; 1 != f; ){
int = 2;
}
}
bool e;
return 3;
}";
try
{
// Лексический анализ
var lexer = new Lexer(testProgram);
var tokens = lexer.Tokenize();
Console.WriteLine("Токены:");
foreach (var token in tokens)
{
if (token.Type != TokenType.EOF)
{
Console.WriteLine($" {token}");
}
}
Console.WriteLine();
// Синтаксический анализ с таблицей
var parser = new PredictiveParser(tokens);
var (success, errors, steps) = parser.Parse();
// Вывод результатов
Console.WriteLine($"Результат анализа: {(success ? "УСПЕШНЫЙ УСПЕХ!!!" : "ОШИБКА :(")}");
Console.WriteLine($"Количество ошибок: {errors.Count}\n");
if (errors.Count > 0)
{
Console.WriteLine("Обнаруженные ошибки:");
foreach (var error in errors)
{
Console.WriteLine($" {error}");
}
Console.WriteLine();
}
// Генерация таблицы анализа
string table = AnalysisTableGenerator.GenerateAnalysisTable(steps);
Console.WriteLine("Таблица анализа:");
Console.WriteLine(table);
//File.WriteAllText("analysis_table.txt", table);
//Console.WriteLine("Таблица анализа сохранена в файл: analysis_table.txt");
}
catch (Exception ex)
{
Console.WriteLine($"Критическая ошибка: {ex.Message}");
}
}
}
