Directives fixes (#3110)

* Unify directive errors

* Add new quotesAreSpecialSymbols field to DirectifeInfo

* Rewrite directive parsing and checks

* Delete R+ directive from pascal tests
This commit is contained in:
AlexanderZemlyak 2024-05-05 18:12:35 +03:00 committed by GitHub
parent edf3358b39
commit acb1be8c92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 140 additions and 65 deletions

View file

@ -9,6 +9,7 @@ using System.Text.RegularExpressions;
using QUT.Gppg;
using PascalABCCompiler.Parsers;
using PascalABCCompiler.ParserTools.Directives;
using System.Text;
namespace PascalABCCompiler.ParserTools
{
@ -106,19 +107,96 @@ namespace PascalABCCompiler.ParserTools
}
/// <summary>
/// Проверка директивы с помощью проверок из Parser.ValidDirectives
/// Обычный разбор параметров директивы (кавычки - не спецсимволы)
/// </summary>
protected List<string> SplitDirectiveParamsOrdinary(string parameters)
{
List<string> words;
words = parameters.Split(new char[] { ' ', '\t', '\v', '\f', '\u00A0' }, StringSplitOptions.RemoveEmptyEntries).ToList();
return words;
}
/// <summary>
/// Разбор параметров директивы, который воспринимает кавычки как спецсимволы (все символы внутри кавычек добавляются)
/// </summary>
protected List<string> SplitDirectiveParamsWithQuotesAsSpecialSymbols(string parameters)
{
List<string> words = new List<string>();
bool isInQuotes = false;
StringBuilder currentWord = new StringBuilder();
for (int i = 0; i < parameters.Length; i++)
{
char c = parameters[i];
if (c == '\'' && !isInQuotes)
{
isInQuotes = true;
}
else if (c == '\'' && isInQuotes)
{
isInQuotes = false;
}
else if (char.IsWhiteSpace(c) && !isInQuotes)
{
if (currentWord.Length != 0)
{
words.Add(currentWord.ToString());
currentWord.Clear();
}
}
else
{
currentWord.Append(c);
}
}
if (isInQuotes)
currentWord.Insert(0, '\'');
if (currentWord.Length != 0)
{
words.Add(currentWord.ToString());
}
return words;
}
/// <summary>
/// Получение имени директивы из строки с именем и параметрами
/// </summary>
protected string GetDirectiveName(string directiveText)
{
string directiveTrimmed = directiveText.Trim();
string directiveName = "";
for (int i = 0; i < directiveTrimmed.Length; i++)
{
char c = directiveTrimmed[i];
if (char.IsWhiteSpace(c))
{
directiveName = directiveTrimmed.Substring(0, i);
break;
}
}
if (directiveName == "")
directiveName = directiveTrimmed;
return directiveName;
}
/// <summary>
/// Проверка парамтеров директивы с помощью проверок из Parser.ValidDirectives
/// </summary>
public void CheckDirectiveParams(string directiveName, List<string> directiveParams, SourceContext loc)
{
BaseParser parserNeeded = ParserCached;
// проверка имени директивы
if (!parserNeeded.ValidDirectives.ContainsKey(directiveName))
{
AddWarningFromResource("UNKNOWN_DIRECTIVE{0}", loc, directiveName); // предупреждение для совместимости с Delphi
return;
}
var directiveInfo = parserNeeded.ValidDirectives[directiveName];
// случай директивы, переданной без параметров

View file

@ -16,11 +16,13 @@ namespace PascalABCCompiler.ParserTools.Directives
public int[] paramsNums = new int[1] { 1 }; // по умолчанию один параметр
public bool checkParamsNumNeeded;
public bool quotesAreSpecialSymbols;
// по умолчанию никаких проверок параметров, но включена проверка их кол-ва
public DirectiveInfo(ParamChecksCollection paramChecks = null, bool checkParamsNumNeeded = true, int[] paramsNums = null)
public DirectiveInfo(ParamChecksCollection paramChecks = null, bool quotesAreSpecialSymbols = false, bool checkParamsNumNeeded = true, int[] paramsNums = null)
{
this.checks = paramChecks;
this.quotesAreSpecialSymbols = quotesAreSpecialSymbols;
if (paramsNums != null)
this.paramsNums = paramsNums;
this.checkParamsNumNeeded = checkParamsNumNeeded;
@ -110,7 +112,7 @@ namespace PascalABCCompiler.ParserTools.Directives
public override bool CheckParam(string param)
{
return Regex.IsMatch(param, @"^[\p{L}_][\p{L}0-9_]*$");
return Regex.IsMatch(param, @"^[\p{L}_][\p{L}0-9_]*$", RegexOptions.Compiled);
}
}
@ -216,6 +218,11 @@ namespace PascalABCCompiler.ParserTools.Directives
{
return new ParamChecksCollection(new IsValidIdentifierCheck(1));
}
public static ParamChecksCollection IsValidVersionCheck()
{
return new ParamChecksCollection(new FuncCheck(s => Regex.IsMatch(s, @"^\d+\.\d+\.\d+$"), ParserErrorsStringResources.Get("INVALID_VERSION")));
}
#endregion
}
}

View file

@ -107,41 +107,35 @@ namespace PascalABCSavParser
/// </summary>
public void ParseDirective(string directive, QUT.Gppg.LexLocation location, out string directiveName, out List<string> directiveParams)
{
directiveName = null;
// текст без спецсимволов {$}
string directiveText = directive.Substring(2, directive.Length - 3);
directiveName = GetDirectiveName(directiveText);
directiveParams = new List<string>();
List<string> words = Regex.Split(directive, @"('.*?')|\s+").Where(word => word != "").ToList();
// подсоединяем } к последнему слову
if (words.Last() == "}")
{
words[words.Count - 2] += "}";
words.RemoveAt(words.Count - 1);
}
// пустая директива - ошибка
if (words[0] == "{$}")
if (directiveName == "")
{
AddErrorFromResource("EMPTY_DIRECTIVE", location);
return;
}
// подсоединяем первое слово к {$
if (words[0] == "{$")
// проверка имени директивы
if (!ParserCached.ValidDirectives.ContainsKey(directiveName))
{
words[0] += words[1];
words.RemoveAt(1);
AddErrorFromResource("UNKNOWN_DIRECTIVE{0}", location, directiveName);
return;
}
if (words.Count == 1)
// подстрока с параметрами
string paramsString = directiveText.Substring(directiveText.IndexOf(directiveName) + directiveName.Length);
if (ParserCached.ValidDirectives[directiveName].quotesAreSpecialSymbols)
{
directiveName = words[0].Substring(2, words[0].Length - 3);
directiveParams = SplitDirectiveParamsWithQuotesAsSpecialSymbols(paramsString);
}
else
{
directiveName = words[0].Substring(2, words[0].Length - 2);
words[words.Count - 1] = words[words.Count - 1].TrimEnd('}');
directiveParams = words.Skip(1).Select(word => DeleteQuotesFromDirectiveParam(word)).ToList();
directiveParams = SplitDirectiveParamsOrdinary(paramsString);
}
}

View file

@ -10,6 +10,7 @@ using GPPGParserScanner;
using GPPGPreprocessor3;
using PascalABCCompiler.ParserTools.Directives;
using static PascalABCCompiler.ParserTools.Directives.DirectiveHelper;
using System.Text.RegularExpressions;
namespace PascalABCCompiler.PascalABCNewParser
{
@ -129,8 +130,8 @@ namespace PascalABCCompiler.PascalABCNewParser
ValidDirectives = new Dictionary<string, DirectiveInfo>(StringComparer.CurrentCultureIgnoreCase)
{
[StringConstants.compiler_directive_apptype] = new DirectiveInfo(SingleAnyOfCheck("console", "windows", "dll", "pcu")),
[StringConstants.compiler_directive_reference] = new DirectiveInfo(), // нет параметров - никаких проверок
[StringConstants.compiler_directive_include_namespace] = new DirectiveInfo(),
[StringConstants.compiler_directive_reference] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_include_namespace] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_savepcu] = new DirectiveInfo(SingleAnyOfCheck("true", "false")),
[StringConstants.compiler_directive_zerobasedstrings] = new DirectiveInfo(SingleAnyOfCheck("on", "off"), paramsNums: new int[2] { 0, 1 }),
[StringConstants.compiler_directive_zerobasedstrings_ON] = null, // от null, скорее всего, придется избавиться, это не лучший подход EVA
@ -139,7 +140,7 @@ namespace PascalABCCompiler.PascalABCNewParser
[StringConstants.compiler_directive_nullbasedstrings_OFF] = null,
[StringConstants.compiler_directive_initstring_as_empty_ON] = null,
[StringConstants.compiler_directive_initstring_as_empty_OFF] = null,
[StringConstants.compiler_directive_resource] = new DirectiveInfo(),
[StringConstants.compiler_directive_resource] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_platformtarget] = new DirectiveInfo(SingleAnyOfCheck("x86", "x64", "anycpu", "dotnet5win", "dotnet5linux", "dotnet5macos", "native")),
[StringConstants.compiler_directive_faststrings] = null,
[StringConstants.compiler_directive_gendoc] = new DirectiveInfo(SingleAnyOfCheck("true", "false")),
@ -151,16 +152,16 @@ namespace PascalABCCompiler.PascalABCNewParser
[StringConstants.compiler_directive_else] = new DirectiveInfo(SingleIsValidIdCheck(), paramsNums: new int[2] { 0, 1 }),
[StringConstants.compiler_directive_undef] = new DirectiveInfo(SingleIsValidIdCheck()),
[StringConstants.compiler_directive_define] = new DirectiveInfo(SingleIsValidIdCheck()),
[StringConstants.compiler_directive_include] = new DirectiveInfo(),
[StringConstants.compiler_directive_include] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_targetframework] = new DirectiveInfo(),
[StringConstants.compiler_directive_hidden_idents] = null,
[StringConstants.compiler_directive_version_string] = new DirectiveInfo(),
[StringConstants.compiler_directive_product_string] = new DirectiveInfo(),
[StringConstants.compiler_directive_company_string] = new DirectiveInfo(),
[StringConstants.compiler_directive_trademark_string] = new DirectiveInfo(),
[StringConstants.compiler_directive_main_resource_string] = new DirectiveInfo(),
[StringConstants.compiler_directive_title_string] = new DirectiveInfo(),
[StringConstants.compiler_directive_description_string] = new DirectiveInfo(),
[StringConstants.compiler_directive_version_string] = new DirectiveInfo(IsValidVersionCheck()),
[StringConstants.compiler_directive_product_string] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_company_string] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_trademark_string] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_main_resource_string] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_title_string] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_description_string] = new DirectiveInfo(quotesAreSpecialSymbols: true),
[StringConstants.compiler_directive_omp] = new DirectiveInfo(SingleAnyOfCheck("critical", "parallel"), checkParamsNumNeeded: false)
};
#endregion

View file

@ -1,6 +1,4 @@
{$R+}
var
var
b : byte:=1;
sh : shortint:=2;
sm : smallint:=3;

View file

@ -1,6 +1,4 @@
{$R+}
var
var
b : byte:=1;
sh : shortint:=2;
sm : smallint:=3;

View file

@ -1,5 +1,4 @@
unit u_set7;
{$R+}
unit u_set7;
var
b : byte:=1;

View file

@ -17,7 +17,7 @@ DUPLICATE_DIRECTIVE{0}=Duplicate directive {0}
FILE_{0}_NOT_FOUND=File '{0}' not found
NAMESPACE_CAN_BE_COMPILED_ONLY_IN_PROJECTS=Files with namespaces can be compiled only inside projects
NAMESPACE_CANNOT_HAVE_IN_SECTION=Namespace cannot have 'in' section
APPTYPE_DLL_IS_ALLOWED_ONLY_FOR_LIBRARIES= {$apptype dll} directive is allowed only for libraries
APPTYPE_DLL_IS_ALLOWED_ONLY_FOR_LIBRARIES=Parameter 'dll' in 'apptype' directive is allowed only for libraries
USES_IN_WRONG_NAME=Unit name in uses-in ({0}) must be same as file name ({1})
UNSUPPORTED_TARGETFRAMEWORK_{0}=TargetFramework '{0}' is not supported
UNSUPPORTED_TARGET_PLATFORM{0}=Platform '{0}' is not supported

View file

@ -13,6 +13,7 @@ BAD_SOURCEFILE{0}_EXT=Can not choose parser for file '{0}'
COMPILATION_ERROR{0}{1}{2}={0}{1} Compilation error: {2}
EXPECTED_IDENTIFIER=Expected identifier
INVALID_VERSION=The version format should be MAJOR.MINOR.PATCH
AVAILABLE_PARAM_VARIANTS{0}=Possible values are: {0}
AVAILABLE_EXT_VARIANTS{0}=Possible file extensions: {0}
SEE_THE_HELP_FOR_VARIANTS=You can find the possible values in the language documentation

View file

@ -27,12 +27,11 @@ UNNECESSARY_DIRECTIVE_PARAM{0}=Directive '{0}' is used without parameters
INCORRECT_DIRECTIVE_PARAM{0}{1}{2}=Invalid parameter '{0}' passed to directive '{1}'. {2}
EMPTY_DIRECTIVE=Empty directive is not allowed
DIRECTIVE_WRONG_NUMBER_OF_PARAMS{0}{1}=Directive '{0}' must have {1}
INCLUDE_COULDNT_OPEN_FILE{0}=Compiler directive $include: can't open file '{0}'
INCLUDE_EMPTY_FILE=Compiler directive $include: file name is absent
RECUR_INCLUDE=Compiler directive $include: recursive include
UNNECESSARY $endif=Extra $endif
ENDIF_ABSENT=Not closed directive $ifdef or $ifndef - $endif is absent
UNNECESSARY $else=Extra $else
INCLUDE_COULDNT_OPEN_FILE{0}=Compiler directive 'include': can't open file '{0}'
RECUR_INCLUDE=Compiler directive 'include': recursive include
UNNECESSARY $endif=Extra 'endif'
ENDIF_ABSENT=Not closed directive 'ifdef' or 'ifndef' - 'endif' is absent
UNNECESSARY $else=Extra 'else'
TYPE_NAME_EXPECTED=Type expected
MULTILINE_STRING_CONTAINS_INCONSISTENT_INDENTS=Multiline string contains inconsistent indents
NON_WHITESPACE_CHARACTERS_BEFORE_CLOSING_QUOTES_OF_MULTILINE_STRING=There should be no non-whitespace characters before the closing quotes of the multiline string

View file

@ -9,7 +9,7 @@ UNIT_{0}_NOT_FOUND=Модуль '{0}' не найден
SOURCE_FILE_{0}_NOT_FOUND=Файл '{0}' не найден
NO_ACCESS_TO_FILE{0}=Не могу получить доступ к файлу '{0}'
CYCLIC_UNIT_REFERENCE_WITH_UNIT_{0}=Циклическая связь с модулем '{0}'
UNDEFINED_SEMANTIC_ERROR{0}=Неопределенная семантическая ошибка : {0}
UNDEFINED_SEMANTIC_ERROR{0}=Неопределенная семантическая ошибка : {0}
RESOURCEFILE_{0}_NOT_FOUND=Файл '{0}' не найден
DUPLICATE_USES_UNIT{0}=Повторное подключение модуля {0} в секции uses
MAINRESOURCE_NOT_ALLOWED=Недопустимо совместное использование директивы mainresource и директив product, version, company, trademark, title, description, copyright
@ -17,7 +17,7 @@ DUPLICATE_DIRECTIVE{0}=Повторное объявление директив
NAMESPACE_CAN_BE_COMPILED_ONLY_IN_PROJECTS=Компиляция файлов с пространствами имен допустима только в проектах
FILE_{0}_NOT_FOUND=Файл '{0}' не найден
NAMESPACE_CANNOT_HAVE_IN_SECTION=Для пространства имен не может указываться секция in
APPTYPE_DLL_IS_ALLOWED_ONLY_FOR_LIBRARIES=Директива {$apptype dll} возможна только для библиотек
APPTYPE_DLL_IS_ALLOWED_ONLY_FOR_LIBRARIES=Параметр 'dll' директивы 'apptype' разрешается только для библиотек
USES_IN_WRONG_NAME=Имя модуля в uses-in ({0}) должно совпадать с именем файла ({1})
UNSUPPORTED_TARGETFRAMEWORK_{0}=TargetFramework '{0}' не поддерживается
UNSUPPORTED_TARGET_PLATFORM{0}=Платформа '{0}' не поддерживается

View file

@ -14,6 +14,7 @@ BAD_SOURCEFILE{0}_EXT=Не могу выбрать подходящий парс
COMPILATION_ERROR{0}{1}{2}={0}{1} Ошибка компиляции: {2}
EXPECTED_IDENTIFIER=Ожидался идентификатор
INVALID_VERSION=Формат версии должен соответствовать шаблону MAJOR.MINOR.PATCH
AVAILABLE_PARAM_VARIANTS{0}=Допустимые значения: {0}
AVAILABLE_EXT_VARIANTS{0}=Допустимые расширения файла: {0}
SEE_THE_HELP_FOR_VARIANTS=Уточните допустимые значения в документации языка

View file

@ -26,12 +26,11 @@ UNNECESSARY_DIRECTIVE_PARAM{0}=Директива '{0}' не допускает
INCORRECT_DIRECTIVE_PARAM{0}{1}{2}=Недопустимый параметр '{1}' для директивы '{0}'. {2}
EMPTY_DIRECTIVE=Пустая директива недопустима
DIRECTIVE_WRONG_NUMBER_OF_PARAMS{0}{1}=Директива '{0}' должна иметь {1}
INCLUDE_COULDNT_OPEN_FILE{0}=Директива компилятора $include: нельзя открыть файл '{0}'
INCLUDE_EMPTY_FILE=Директива компилятора $include: не указано имя файла
RECUR_INCLUDE=Директива компилятора $include: рекурсивное включение файла
UNNECESSARY $endif=Лишний $endif
ENDIF_ABSENT=Незакрытая директива $ifdef или $ifndef - отсутствует $endif
UNNECESSARY $else=Лишний $else
INCLUDE_COULDNT_OPEN_FILE{0}=Директива компилятора 'include': нельзя открыть файл '{0}'
RECUR_INCLUDE=Директива компилятора 'include': рекурсивное включение файла
UNNECESSARY $endif=Лишний 'endif'
ENDIF_ABSENT=Незакрытая директива 'ifdef' или 'ifndef' - отсутствует 'endif'
UNNECESSARY $else=Лишний 'else'
TYPE_NAME_EXPECTED=Ожидался тип
MULTILINE_STRING_CONTAINS_INCONSISTENT_INDENTS=Мультистрочная строка содержит несовместимые отступы
NON_WHITESPACE_CHARACTERS_BEFORE_CLOSING_QUOTES_OF_MULTILINE_STRING=Перед закрывающими кавычками многострочной строки не должно быть непробельных символов