* 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
239 lines
10 KiB
C#
239 lines
10 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|