pascalabcnet/AdditionalLanguages/SPython/SPythonParserKrylovMovchan/IndentArranger.cs
Александр Земляк 17f6a9b956
Spython test suite (#3362)
* Reorganize files in TestSuite dir for multiple languages

* Add SPython samples to TestSuite

* Update TestRunner to support multiple languages

* Delete dll files accidentally pushed

* Update .gitignore

* Revert "Delete dll files accidentally pushed"

This reverts commit 62bcba2b821d58ae51ec98e9dadcf621bb0fc003.

* Update testing dir path in Testing.cs

* Improve TestRunner output

* Add random module and it's usage sample

* Reorganize gitignore files

* Move pascal tests back to TestSuite and create new TestSuiteLanguagePlugins folder for SPython

* Revert "Update testing dir path in Testing.cs"

This reverts commit 7d94eafb25292421bce8315520b4f6a0de001bbd.

* Revert crlf changes in pascal tests

* Test changes of _RebuildReleaseAndRunTestsForGitHubActions.bat

* Revert "Test changes of _RebuildReleaseAndRunTestsForGitHubActions.bat"

This reverts commit 234ceae3e3a339fbc68a6170e2bd201dab14d67d.

* Test changes in StorageLocationPicker

* Another test changes in StorageLocationPicker.cs

* Rename LanguagePlugins folder to AdditionalLanguages

* Decouple TestRunner from language names and parameters

* Revert calling TestRunner without parameters in .bat files

* Fix formatter tests nogui exception handling

* Small refactoring in Testing.cs and TestRunner fix of working directory
2025-12-21 21:11:52 +03:00

239 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Linq;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using SyntaxVisitors;
using PascalABCCompiler.SyntaxTree;
namespace SPythonParser
{
public class IndentArranger
{
private int lineCounter = -1;
// количество пробелов соответствующее одному \t (в одном отступе)
private const int indentSpaceNumber = 4;
private const string indentToken = "#{";
private const string unindentToken = "#}";
private const string badIndentToken = "#!";
private const string programBeginWithIndentToken = "#b";
private const string endOfLineToken = "#;";
private const string endOfFIleToken = "#$";
public const string createdFileNameAddition = "_processed";
// регулярное выражение разбивающее строку программы на группы, последняя из которых - комментарий в конце строки
private static readonly Regex programLineRegex = new Regex("^(([^\n\"\'#])+|(\'([^\'\n\\\\]|\\\\.)*\')|(\"([^\"\n\\\\]|\\\\.)*\"))*(#.*)?$");
private static readonly Regex reg = new Regex(@"'([^'\\]*(\\.[^'\\]*)*)'|""([^""\\]*(\\.[^""\\]*)*)""");
public IndentArranger() {}
public void ProcessSourceText(ref string sourceText)
{
//string[] programLines = sourceText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
// В редакторе в особых случаях добавляется \n вместо \r\n
string[] programLines = sourceText.Split(new string[] { "\n" }, StringSplitOptions.None);
// удаляем комментарии в тексте программы
EraseComments(ref programLines);
// добавляем токены для начала/конца отступов и ; в конце stmt
ArrangeIndents(ref programLines);
sourceText = String.Join("\n", programLines);
// нужен токен в конце для правильного позиционирования ошибок,
// допущенных в конце программы
sourceText += endOfFIleToken;
// создание файла с полученным текстом для дебага
//File.WriteAllText("./processed_file.txt", sourceText);
}
private void EraseComments(ref string[] programLines)
{
for (int i = 0; i < programLines.Length; ++i)
{
Match programLineMatch = programLineRegex.Match(programLines[i]);
if (programLineMatch.Success)
{
Group programLineComment = programLineMatch.Groups[programLineMatch.Groups.Count - 1];
if (programLineComment.Success)
programLines[i] = programLines[i].Substring(0, programLineComment.Index);
}
}
}
int bracketCount = 0;
int prevBracketCount = 0;
private void CountBrackets(string programLine)
{
string cleanLine = reg.Replace(programLine, match => "");
foreach (char c in cleanLine)
{
switch (c)
{
case '(':
case '{':
case '[':
++bracketCount;
break;
case ')':
case '}':
case ']':
--bracketCount;
break;
}
}
}
// считывает первый токен в строке (с первого символа латиницы или _)
// возвращает тип этого токена
private bool IsFirstTokenElseOrElif(string line)
{
int beginIndex = 0;
for (beginIndex = 0; beginIndex < line.Length; ++beginIndex)
if (!Char.IsWhiteSpace(line[beginIndex]))
break;
if (line.Length < beginIndex + 4) return false;
// получаем строковое представление токена
string firstToken = line.Substring(beginIndex, 4);
return firstToken == "else" || firstToken == "elif";
}
private void ArrangeIndents(ref string[] programLines)
{
Stack<int> indentStack = new Stack<int>();
indentStack.Push(0);
bool skipped_first = false;
foreach (var line in programLines)
{
lineCounter++;
prevBracketCount = bracketCount;
CountBrackets(line);
if (lineCounter > 0 && LineEndsWithBackSlash(ref programLines[lineCounter - 1]))
continue;
if (prevBracketCount != 0)
continue;
bool isEmptyLine = true; // строка не содержит символов кроме \s\t\n\r
int currentLineSpaceCounter = 0;
for (int i = 0; i < line.Length; ++i)
{
if (line[i] == '\t')
{
// один \t выравнивает до следующего отступа
currentLineSpaceCounter += indentSpaceNumber;
currentLineSpaceCounter &= ~(indentSpaceNumber - 1);
}
else if (char.IsWhiteSpace(line[i]))
currentLineSpaceCounter++;
else
{
isEmptyLine = false;
break;
}
}
if (isEmptyLine) continue;
int previosSpaceCounter = indentStack.Peek();
// текущий отступ соответствует предыдущему отступу
if (currentLineSpaceCounter == previosSpaceCounter)
{
if (!skipped_first)
{
skipped_first = true;
continue;
}
AddSemicolonIfNeeded(ref programLines[lineCounter - 1]);
}
// текущий отступ соответствует увеличению
else if (currentLineSpaceCounter > previosSpaceCounter)
{
if (skipped_first && lineCounter != 0)
{
programLines[lineCounter - 1] += indentToken;
indentStack.Push(currentLineSpaceCounter);
}
else
programLines[lineCounter] = programBeginWithIndentToken;
}
// оставшиеся случаи: отступ некорректный или уменьшение на один или несколько отступов
else
{
int unindentCounter = 0;
while (currentLineSpaceCounter < previosSpaceCounter)
{
indentStack.Pop();
unindentCounter++;
previosSpaceCounter = indentStack.Peek();
}
// текущий отступ соответствует уменьшению на один или несколько отступов
if (currentLineSpaceCounter == previosSpaceCounter)
{
// если сейчас ветка elif/else, то это не конец команды
// поэтому ставить ; в конце не надо (она будет после блока elif/else)
bool isEndOfStatement = !IsFirstTokenElseOrElif(line);
unindentCounter--;
AddSemicolonIfNeeded(ref programLines[lineCounter - 1]);
programLines[lineCounter - 1] += unindentToken +
string.Concat(Enumerable.Repeat(endOfLineToken + unindentToken, unindentCounter))
+ (isEndOfStatement ? endOfLineToken : "");
}
// количество пробелов в строке является некорректным
// (не соответствует количеству пробелов в строке с любым отступом)
else // currentLineSpaceCounter > indentStack.Peek()
{
if (lineCounter != 0)
programLines[lineCounter - 1] += badIndentToken;
}
}
}
if (lineCounter != -1 && indentStack.Count() > 1)
{
// закрытие всех отступов в конце файла
AddSemicolonIfNeeded(ref programLines[lineCounter]);
programLines[lineCounter] += unindentToken +
string.Concat(Enumerable.Repeat(endOfLineToken + unindentToken, indentStack.Count() - 2));
}
}
private int IndexOfLastSignificantSymbol(string s)
{
int i = s.Length - 1;
while (i > -1 && Char.IsWhiteSpace(s[i])) --i;
return i;
}
private bool LineEndsWithBackSlash(ref string s)
{
int i = IndexOfLastSignificantSymbol(s);
if (i == -1 || s[i] != '\\')
return false;
s = s.Substring(0, i);
return true;
}
private void AddSemicolonIfNeeded(ref string s)
{
int i = IndexOfLastSignificantSymbol(s);
if (i == -1 || s[i] != ';')
s += endOfLineToken;
}
}
}