diff --git a/.cursor/agents/coder.md b/.cursor/agents/coder.md new file mode 100644 index 000000000..8e2381233 --- /dev/null +++ b/.cursor/agents/coder.md @@ -0,0 +1,7 @@ +--- +name: coder +model: claude-4.6-sonnet-medium +description: Use this agent to implement coding tasks by provided specs only when it's explicitly specified +--- + +Ты профессиональный разработчик ПО высокого уровня. Тебе поступает на вход файл со спецификацией задачи. Реализуй эту задачу. \ No newline at end of file diff --git a/.cursor/agents/planner.md b/.cursor/agents/planner.md new file mode 100644 index 000000000..ba817997c --- /dev/null +++ b/.cursor/agents/planner.md @@ -0,0 +1,25 @@ +--- +name: planner +model: gemini-3-flash +description: Use this agent only when explicitly specified to sequentially run coding subagents when you have numbered list of tasks +readonly: true +--- + +На вход тебе подается каталог, в котором лежат файлы .md со списком задач и файл main-task со спецификацией задачи в целом. +Файлы подзадач пронумерованы префиксом вида 01, 02 и т.д. + +Твоя задача - запустить субагент сoder, передав ему очередную задачу по порядку, дождаться изменений от субагента и запустить новый субагент, уже со следующей задачей из перечня. Ты сам не должен писать код, ты находишься в Ask Mode, твоя задача запускать субагент coder, передавая ему очередную задачу (файл с описанием) + +И так до тех пор, пока перечень задач не будет выполнен целиком. + +Если какая-то задача не может быть выполнена и субагент выдает ошибку или вопрос - прерывай цикл выполнения, задай мне вопрос или сообщи текст ошибки и жди дальнейших инструкций. + +Итого, твои действия: + +1. изучить конечную цель (файл main-task) +2. взять файл с описанием задачи 01-* и сформулировать задачу агенту coder по одному и только одному файлу плана. +3. запустить агент coder, передав ему весь нужный контекст +4. получить ответ от агента coder и если он выполнил задачу успешно, перейти к следующему файлу с задачей (02-*) +5. если агент не смог выполнить задачу - прервать цикл +6. продолжать брать следующие по порядку задачи и снова запускать агент coder, передавая ему только одну задачу из файлов-планов по порядку +7. когда все файлы задач будут реализованы - завершиться. \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 29652043f..96a141791 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -9,3 +9,5 @@ dotnet msbuild Build.csproj /t:"CleanAll;MakeFDD;GatherLibrary;ComposeDistributi ```sh dotnet oscript.dll tests/testrunner.os -runAll tests ``` + +ВСЕГДА проверяй изменения перед коммитом. Не коммить бинарные файлы (exe, ospx, и другие) если тебя об этом явно не попросили. diff --git a/Jenkinsfile b/Jenkinsfile index 43640c1b8..706ed496d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,8 +4,8 @@ pipeline { agent none environment { - VersionPrefix = '2.0.0' - VersionSuffix = 'rc.11'+"+${BUILD_NUMBER}" + VersionPrefix = '2.0.2' + VersionSuffix = 'rc.1'+"+${BUILD_NUMBER}" outputEnc = '65001' } @@ -240,6 +240,29 @@ pipeline { } } + stage ('Publishing latest') { + when { + anyOf { + branch 'release/latest'; + } + } + agent { label 'master' } + options { skipDefaultCheckout() } + + steps { + cleanWs() + checkout scm // чтобы получить файл release-notes + unstash 'dist' + unstash 'vsix' + + // Положит описание для сайта + publishReleaseNotes('latest') + + // Положит файлы дистрибутива в целевую папку + publishRelease('latest', true) + } + } + stage ('Publishing artifacts to clouds') { when { anyOf { @@ -247,13 +270,13 @@ pipeline { branch 'release/preview'; } } - + agent { label 'windows' } steps{ - + unstash 'buildResults' - + withCredentials([string(credentialsId: 'NuGetToken', variable: 'NUGET_TOKEN')]) { bat "chcp $outputEnc > nul\r\n\"${tool 'MSBuild'}\" Build.csproj /t:PublishNuget /p:NugetToken=$NUGET_TOKEN" } @@ -266,7 +289,7 @@ pipeline { agent { label 'linux' } when { anyOf { - branch 'release/latest' + branch 'release/lts' expression { return env.TAG_NAME && env.TAG_NAME.startsWith('v1.') } @@ -274,7 +297,7 @@ pipeline { } steps { script { - def codename = env.TAG_NAME ? env.TAG_NAME : 'latest' + def codename = env.TAG_NAME ? env.TAG_NAME : 'lts' publishDockerImage('v1', codename) } } @@ -285,14 +308,20 @@ pipeline { when { anyOf { branch 'develop' - expression { - return env.TAG_NAME && env.TAG_NAME.startsWith('v2.') - } + branch 'release/latest' } } steps { script { - def codename = env.TAG_NAME ? env.TAG_NAME : 'dev' + def codename = '' + if (env.VersionSuffix != null && !env.VersionSuffix.isEmpty()) { + codename = 'dev' + } + else + { + codename = fullVersionNumber() + } + publishDockerImage('v2', codename) } } @@ -302,6 +331,20 @@ pipeline { } } +def fullVersionNumber() { + def version = env.VersionPrefix + if (env.VersionSuffix != null && !env.VersionSuffix.isEmpty()) + { + version = version + "-${env.VersionSuffix}" + } + + return version +} + +def underscoredVersion() { + return fullVersionNumber().replaceAll("\\.", "_") +} + def publishRelease(codename, isNumbered) { dir('targetContent') { sh """ @@ -317,7 +360,8 @@ def publishRelease(codename, isNumbered) { """.stripIndent() if (isNumbered) { - def version="${env.VersionPrefix}-${env.VersionSuffix}".replaceAll("\\.", "_") + + def version = underscoredVersion() sh """ TARGET="/var/www/oscript.io/download/versions/${version}/" @@ -330,7 +374,7 @@ def publishRelease(codename, isNumbered) { def publishReleaseNotes(codename) { dir('markdownContent') { - def version="${env.VersionPrefix}-${env.VersionSuffix}".replaceAll("\\.", "_") + def version=underscoredVersion() def targetDir='/var/www/oscript.io/markdown/versions' sh """ diff --git a/README-EN.md b/README-EN.md index 5b1dac8a4..531111fc8 100644 --- a/README-EN.md +++ b/README-EN.md @@ -34,28 +34,23 @@ The OneScript distribution already includes a set of the most commonly used pack ### Linux ### -- (interactively) download the required package from the [official website](https://oscript.io) or installer from the [Releases](https://github.com/EvilBeaver/OneScript/releases) section and install it. +- Download the ZIP archive for Linux from the [Releases](https://github.com/EvilBeaver/OneScript/releases) section or from the [official website](https://oscript.io). +- Extract the archive to a convenient directory. +- Set executable permissions: + ```bash + chmod +x oscript + ``` ### MacOS ### -There is no interactive installer, but the engine can be installed from the command line: - -- install [homebrew](https://brew.sh/index_ru) -- install mono with the command `brew install mono` -- download [ovm](https://github.com/oscript-library/ovm/releases). Download the `ovm.exe` file (despite the .exe extension it will work on MacOS) -- run the command `mono path/to/ovm.exe install stable` - - *Tip: To correctly specify the path to `ovm.exe`, drag the `ovm.exe` file into the terminal. Example of a full command: `mono Users/username/Downloads/ovm.exe install stable`* -- run the command `mono path/to/ovm.exe use stable` -- restart the terminal - -#### Additional configuration for Self-Contained distribution (does not require dotnet installation) - -``` -chmod +x ./oscript -xattr -d com.apple.quarantine *.dylib oscript -codesign -s - ./oscript -``` +- Download the ZIP archive for macOS (x64 or arm64) from the [Releases](https://github.com/EvilBeaver/OneScript/releases) section or from the [official website](https://oscript.io). +- Extract the archive to a convenient directory. +- Perform additional configuration to remove quarantine and sign the binary: + ```bash + chmod +x ./oscript + xattr -d com.apple.quarantine *.dylib oscript + codesign -s - ./oscript + ``` # Manual local build diff --git a/README.md b/README.md index 98fab370d..959253f91 100644 --- a/README.md +++ b/README.md @@ -34,28 +34,23 @@ OneScript позволяет создавать и выполнять текст ### Linux ### -- (интерактивно) скачать нужный пакет [официального сайта](https://oscript.io) или установщик из раздела [Releases](https://github.com/EvilBeaver/OneScript/releases) и установить его. +- Скачать ZIP-архив для Linux со [страницы релизов](https://github.com/EvilBeaver/OneScript/releases) или с [официального сайта](https://oscript.io). +- Распаковать архив в удобный каталог. +- Установить права на выполнение: + ```bash + chmod +x oscript + ``` ### MacOS ### -Интерактивного установщика нет, но движок можно установить из командной строки: - -- установить [homebrew](https://brew.sh/index_ru) -- установить mono командой `brew install mono` -- скачать [ovm](https://github.com/oscript-library/ovm/releases). Скачать файл `ovm.exe` (несмотря на расширение .exe он сработает на MacOS) -- выполнить команду `mono path/to/ovm.exe install stable` - - *Совет: Чтобы корректно указать путь до `ovm.exe` перенесите мышкой файл `ovm.exe` в терминал. Пример полной команды: `mono Users/username/Downloads/ovm.exe install stable`* -- выполнить команду `mono path/to/ovm.exe use stable` -- перезапустить терминал - -#### Донастройка Self-Contained варианта поставки (не требующего инсталляции dotnet) - -``` -chmod +x ./oscript -xattr -d com.apple.quarantine *.dylib oscript -codesign -s - ./oscript -``` +- Скачать ZIP-архив для macOS (x64 или arm64) со [страницы релизов](https://github.com/EvilBeaver/OneScript/releases) или с [официального сайта](https://oscript.io). +- Распаковать архив в удобный каталог. +- Выполнить донастройку для снятия карантина и подписи: + ```bash + chmod +x ./oscript + xattr -d com.apple.quarantine *.dylib oscript + codesign -s - ./oscript + ``` # Ручная локальная сборка diff --git a/docs/developer_docs.md b/docs/developer_docs.md index 1541add6e..b3c38febe 100644 --- a/docs/developer_docs.md +++ b/docs/developer_docs.md @@ -120,7 +120,7 @@ OneScript.Core — система типов и контекстная моде - LibraryLoader.cs — package-loader.os, подключение .os модулей/классов/макетов; FileSystemDependencyResolver.cs — поиск библиотек, цикл обработки, защита от циклических зависимостей. - Extensions/EngineBuilderExtensions.cs — UseSystemConfigFile/UseEnvironmentVariableConfig/UseEntrypointConfigFile; UseImports/UseFileSystemLibraries/UseNativeRuntime/UseEventHandlers. - Жизненный цикл: - 1) Читает настройки из системного `oscript.cfg`, env, и файла `oscript.cfg` рядом с entrypoint. + 1) Читает настройки из системного `oscript.cfg`, файла `oscript.cfg` рядом с entrypoint, и переменной окружения OSCRIPT_CONFIG (в порядке возрастания приоритета, то есть переменная окружения перезаписывает все предыдущие настройки). 2) Инициализация HostedScriptEngine → глобальные объекты → процесс → компиляция/исполнение модуля. 3) Загрузка библиотек: default или кастомный package-loader.os, последующая регистрация символов и компиляция задержанных модулей. diff --git a/install/release-notes.md b/install/release-notes.md index 1cbadb956..8eeb7d412 100644 --- a/install/release-notes.md +++ b/install/release-notes.md @@ -1,19 +1,15 @@ -# Версия 2.0.0-rc.10 +# Версия 2.0.1 -## Исправления ошибок, обнаруженных в 2.0.0-rc.10 +## Исправление ошибок -* Изменено поведение по умолчанию для опции lang.explicitImports под отладкой. Теперь неявные импорты не считаются критичной ошибкой -* #1627: Исправлена ошибка несрабатывания отладчика на коротких скриптах +* #1646 Ошибка метода ЗаполнитьЗначенияСвойств на ФиксированнойСтруктуре +* #1647 Исправлена вставка ключа Null в соответствие +* #1649 Исправлена обработка символов '\0' в каталогах WebDAV +* #1654 В методе можно было объявлять параметры, не разделяя их запятой +* #1652 Исправлена обработка аннотаций для списка переменных, объявленных через запятую +* #1657 Исправлен приоритет активации источников в конфиге -## Исправление текущих ошибок +## Рефакторинг и оптимизация -* #1580, #1626: Улучшено соответствие поведения ТаблицыЗначений поведению 1С -* Исправлена медленная вставка строк в индексированную таблицу значений -* - -## Новые возможности - -* Доступ к сырому сетевому потоку при вызове http-метода. Позволяет организовать http-streaming -* Использование аннотаций внутри параметров аннотаций - -Большое спасибо @Mr-Rm, @dmpas, @asosnoviy, @nixel2007, @Bayselonarrend за участие в этом релизе! \ No newline at end of file +* В парсере применен приоритет операторов вместо рекурсивного спуска (улучшена скорость компиляции) +* Загрузчик библиотек теперь разделяет ситуацию ненайденных библиотек и тех, которые не удается загрузить (улучшена диагностика ненайденных библиотек) diff --git a/src/OneScript.Core/Contexts/ClassBuilder.cs b/src/OneScript.Core/Contexts/ClassBuilder.cs index 88cf838bb..3b7839c83 100644 --- a/src/OneScript.Core/Contexts/ClassBuilder.cs +++ b/src/OneScript.Core/Contexts/ClassBuilder.cs @@ -111,9 +111,10 @@ private static bool MarkedAsContextMethod(MemberInfo member, bool includeDepreca .Any(x => includeDeprecations || (x as ContextMethodAttribute)?.IsDeprecated == false); } - private static bool MarkedAsContextProperty(MemberInfo member, bool includeDeprecations = false) + private static bool MarkedAsContextProperty(PropertyInfo member, bool includeDeprecations = false) { - return member.GetCustomAttributes(typeof(ContextPropertyAttribute), false).Any(); + return member.GetCustomAttributes(typeof(ContextPropertyAttribute), false) + .Any(x => includeDeprecations || (x as ContextPropertyAttribute)?.IsDeprecated == false); } public ClassBuilder ExportConstructor(MethodInfo info) diff --git a/src/OneScript.Core/Execution/ForbiddenBslProcess.cs b/src/OneScript.Core/Execution/ForbiddenBslProcess.cs index b0fc36f6b..73fb76973 100644 --- a/src/OneScript.Core/Execution/ForbiddenBslProcess.cs +++ b/src/OneScript.Core/Execution/ForbiddenBslProcess.cs @@ -20,7 +20,7 @@ namespace OneScript.Execution /// public class ForbiddenBslProcess : IBslProcess { - public static IBslProcess Instance = new ForbiddenBslProcess(); + public static readonly IBslProcess Instance = new ForbiddenBslProcess(); private ForbiddenBslProcess() {} diff --git a/src/OneScript.Language/IdentifiersTrie.cs b/src/OneScript.Language/IdentifiersTrie.cs index df4b68a19..5f014ba6f 100644 --- a/src/OneScript.Language/IdentifiersTrie.cs +++ b/src/OneScript.Language/IdentifiersTrie.cs @@ -17,26 +17,20 @@ public class IdentifiersTrie : IDictionary private class TrieNode { - public char charL; - public char charU; - public TrieNode sibl; - public TrieNode next; + internal char charL; + internal char charU; + internal TrieNode sibl; + internal TrieNode next; - private T _value; + internal T value; - public T Value - { - get => _value; - set - { - HasValue = true; - _value = value; - } - } - - public bool HasValue { get; private set; } - - public TrieNode Find(char ch) + internal bool hasValue; + + internal TrieNode() { } + internal TrieNode(char ch) + { charL = char.ToLower(ch); charU = char.ToUpper(ch); } + + internal TrieNode Find(char ch) { var node = sibl; while (node != null) @@ -47,47 +41,46 @@ public TrieNode Find(char ch) } return null; } - - } - + } + public void Add(string str, T val) { var node = _root; + TrieNode key = node; foreach (char ch in str) { - var key = node.Find(ch); - if (key == null) + if (node == null) { - key = new TrieNode + node = new TrieNode(ch); + key.next = node; + key = node; + node = null; + } + else + { + TrieNode last = node; + key = node; + while (key != null && key.charL != ch && key.charU != ch) + { + last = key; + key = key.sibl; + } + if (key == null) { - charL = char.ToLower(ch), - charU = char.ToUpper(ch), - Value = default(T), - sibl = node.sibl - }; - node.sibl = key; - key.next = new TrieNode(); + key = new TrieNode(ch); + last.sibl = key; + } + node = key.next; } - node = key.next; } - node.Value = val; + key.value = val; + key.hasValue = true; } - public bool ContainsKey(string key) - { - var node = _root; - foreach (char ch in key) - { - var keyNode = node.Find(ch); - if (keyNode == null) - { - return false; - } - node = keyNode.next; - } - - return node.next == null && node.HasValue; + public bool ContainsKey(string str) + { + return TryGetValue(str, out _); } public bool Remove(string key) @@ -96,22 +89,10 @@ public bool Remove(string key) } public T Get(string str) - { - var node = _root; - foreach (char ch in str) - { - TrieNode key = node.Find(ch); - if (key == null) - throw new KeyNotFoundException(); - - node = key.next; - } - - if (!node.HasValue) - throw new KeyNotFoundException(); - - return node.Value; - } + { + return TryGetValue(str, out var value) ? value + : throw new KeyNotFoundException(); + } public T this[string index] { @@ -124,27 +105,34 @@ public T this[string index] public bool TryGetValue(string str, out T value) { - var node = _root; + TrieNode key = _root; + var node = key.sibl; foreach (char ch in str) - { - var key = node.Find(ch); - if (key == null) - { - value = default; - return false; - } - + { + while (node != null && node.charL != ch && node.charU != ch) + { + node = node.sibl; + } + if (node == null) + { + value = default; + return false; + } + + key = node; node = key.next; } - if (!node.HasValue) - { - value = default; - return false; + if (key.hasValue) + { + value = key.value; + return true; + } + else + { + value = default; + return false; } - - value = node.Value; - return true; } public IEnumerator> GetEnumerator() diff --git a/src/OneScript.Language/LanguageDef.cs b/src/OneScript.Language/LanguageDef.cs index 1583db1b0..b32456fbd 100644 --- a/src/OneScript.Language/LanguageDef.cs +++ b/src/OneScript.Language/LanguageDef.cs @@ -5,10 +5,10 @@ This Source Code Form is subject to the terms of the at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ +using OneScript.Language.LexicalAnalysis; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using OneScript.Language.LexicalAnalysis; namespace OneScript.Language { @@ -21,14 +21,20 @@ public static class LanguageDef private static readonly IdentifiersTrie _stringToToken = new IdentifiersTrie(); - private static readonly IdentifiersTrie _undefined = new IdentifiersTrie(); - private static readonly IdentifiersTrie _booleans = new IdentifiersTrie(); - private static readonly IdentifiersTrie _logicalOp = new IdentifiersTrie(); - - private static readonly IdentifiersTrie _preprocImport = new IdentifiersTrie(); - const int BUILTINS_INDEX = (int)Token.ByValParam; + public enum WordType + { + Undefined, + Boolean, + Logical, + Null, + Preproc, + None + }; + + private static readonly IdentifiersTrie _specwords = new IdentifiersTrie(); + static LanguageDef() { _priority.Add(Token.Plus, 5); @@ -52,21 +58,26 @@ static LanguageDef() #region constants - _undefined.Add("Undefined", true); - _undefined.Add("Неопределено", true); + _specwords.Add("Undefined", WordType.Undefined); + _specwords.Add("Неопределено", WordType.Undefined); + + _specwords.Add("True", WordType.Boolean); + _specwords.Add("False", WordType.Boolean); + _specwords.Add("Истина", WordType.Boolean); + _specwords.Add("Ложь", WordType.Boolean); - _booleans.Add("True", true); - _booleans.Add("False", true); - _booleans.Add("Истина", true); - _booleans.Add("Ложь", true); + _specwords.Add("And", WordType.Logical); + _specwords.Add("Or", WordType.Logical); + _specwords.Add("Not", WordType.Logical); - _logicalOp.Add("And", true); - _logicalOp.Add("Or", true); - _logicalOp.Add("Not", true); + _specwords.Add("И", WordType.Logical); + _specwords.Add("ИЛИ", WordType.Logical); + _specwords.Add("НЕ", WordType.Logical); - _logicalOp.Add("И", true); - _logicalOp.Add("ИЛИ", true); - _logicalOp.Add("НЕ", true); + _specwords.Add("NULL", WordType.Null); + + _specwords.Add("Использовать", WordType.Preproc); + _specwords.Add("Use", WordType.Preproc); #endregion @@ -216,8 +227,6 @@ static LanguageDef() #endregion - _preprocImport.Add("Использовать", true); - _preprocImport.Add("Use", true); } private static void AddToken(Token token, string name) @@ -247,6 +256,7 @@ public static string GetTokenName(Token token) return Enum.GetName(typeof(Token), token); } + public static string GetTokenAlias(Token token) { if (_keywords.TryGetValue(token,out var strings)) @@ -257,11 +267,9 @@ public static string GetTokenAlias(Token token) return Enum.GetName(typeof(Token), token); } - public static Token GetToken(string tokText) { - Token result; - if (_stringToToken.TryGetValue(tokText, out result)) + if (_stringToToken.TryGetValue(tokText, out Token result)) { return result; } @@ -274,7 +282,31 @@ public static Token GetToken(string tokText) public static int GetPriority(Token op) { return _priority[op]; - } + } + + public static int GetBinaryPriority(Token op) + { + return IsBinaryOperator(op) ? _priority[op] : -1; + } + + public static int GetUnaryPriority(Token op) + { + return op switch + { + Token.OpenPar => MAX_OPERATION_PRIORITY + 1, + + Token.Not => _priority[op], + Token.UnaryMinus => _priority[op], + Token.UnaryPlus => _priority[op], + + Token.Minus => _priority[Token.UnaryMinus], + Token.Plus => _priority[Token.UnaryPlus], + + _ => MAX_OPERATION_PRIORITY + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBuiltInFunction(Token token) @@ -284,22 +316,28 @@ public static bool IsBuiltInFunction(Token token) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBinaryOperator(Token token) - { - return token == Token.Plus - || token == Token.Minus - || token == Token.Multiply - || token == Token.Division - || token == Token.Modulo - || token == Token.And - || token == Token.Or - || token == Token.LessThan - || token == Token.LessOrEqual - || token == Token.MoreThan - || token == Token.MoreOrEqual - || token == Token.Equal - || token == Token.NotEqual; - } - + { + switch (token) + { + case Token.Plus: + case Token.Minus: + case Token.Multiply: + case Token.Division: + case Token.Modulo: + case Token.Equal: + case Token.LessThan: + case Token.LessOrEqual: + case Token.MoreThan: + case Token.MoreOrEqual: + case Token.NotEqual: + case Token.And: + case Token.Or: + return true; + default: + return false; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsLogicalBinaryOperator(Token token) { @@ -315,24 +353,42 @@ public static bool IsUnaryOperator(Token token) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsLiteral(in Lexem lex) { - return lex.Type == LexemType.StringLiteral - || lex.Type == LexemType.NumberLiteral - || lex.Type == LexemType.BooleanLiteral - || lex.Type == LexemType.DateLiteral - || lex.Type == LexemType.UndefinedLiteral - || lex.Type == LexemType.NullLiteral; + switch (lex.Type) + { + case LexemType.StringLiteral: + case LexemType.NumberLiteral: + case LexemType.BooleanLiteral: + case LexemType.DateLiteral: + case LexemType.UndefinedLiteral: + case LexemType.NullLiteral: + return true; + default: + return false; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsValidPropertyName(in Lexem lex) { - return lex.Type == LexemType.Identifier - || lex.Type == LexemType.BooleanLiteral - || lex.Type == LexemType.NullLiteral - || lex.Type == LexemType.UndefinedLiteral - || lex.Token == Token.And - || lex.Token == Token.Or - || lex.Token == Token.Not; + switch (lex.Type) + { + case LexemType.Identifier: + case LexemType.BooleanLiteral: + case LexemType.NullLiteral: + case LexemType.UndefinedLiteral: + return true; + + default: + switch (lex.Token) + { + case Token.And: + case Token.Or: + case Token.Not: + return true; + default: + return false; + } + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -394,29 +450,40 @@ public static bool IsBeginOfStatement(Token token) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsEndOfBlockToken(Token token) + { + switch (token) + { + case Token.EndIf: + case Token.EndProcedure: + case Token.EndFunction: + case Token.Else: + case Token.EndLoop: + case Token.EndTry: + case Token.EndOfText: + case Token.ElseIf: + case Token.Exception: + return true; + default: + return false; + } + } + + + public static WordType GetWordType(string value) { - return token == Token.EndIf - || token == Token.EndProcedure - || token == Token.EndFunction - || token == Token.Else - || token == Token.EndLoop - || token == Token.EndTry - || token == Token.EndOfText - || token == Token.ElseIf - || token == Token.Exception - ; + return _specwords.TryGetValue(value, out var wordType)? wordType : WordType.None; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBooleanLiteralString(string value) { - return _booleans.TryGetValue(value, out var nodeIsFilled) && nodeIsFilled; + return _specwords.TryGetValue(value, out var wordType) && wordType == WordType.Boolean; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsUndefinedString(string value) { - return _undefined.TryGetValue(value, out var nodeIsFilled) && nodeIsFilled; + return _specwords.TryGetValue(value, out var wordType) && wordType == WordType.Undefined; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -428,13 +495,13 @@ public static bool IsNullString(string value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsLogicalOperatorString(string content) { - return _logicalOp.TryGetValue(content, out var nodeIsFilled) && nodeIsFilled; + return _specwords.TryGetValue(content, out var wordType) && wordType == WordType.Logical; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsImportDirective(string value) { - return _preprocImport.TryGetValue(value, out var nodeIsFilled) && nodeIsFilled; + return _specwords.TryGetValue(value, out var wordType) && wordType == WordType.Preproc; } } } diff --git a/src/OneScript.Language/LexicalAnalysis/SourceCodeIterator.cs b/src/OneScript.Language/LexicalAnalysis/SourceCodeIterator.cs index d82d9e2c9..720ebf271 100644 --- a/src/OneScript.Language/LexicalAnalysis/SourceCodeIterator.cs +++ b/src/OneScript.Language/LexicalAnalysis/SourceCodeIterator.cs @@ -18,6 +18,7 @@ public class SourceCodeIterator : ISourceCodeIndexer private int _lineCounter; private int _index; private int _startPosition; + private int _codeLength; private List _lineBounds; private bool _onNewLine; @@ -44,6 +45,7 @@ internal SourceCodeIterator() private void InitOnString(string code) { _code = code; + _codeLength = code.Length; int cap = code.Length < 512 ? 32 : 512; _lineBounds = new List(cap); _index = OUT_OF_TEXT; @@ -68,18 +70,8 @@ private void InitOnString(string code) public int CurrentLine => _lineCounter; public int CurrentColumn - { - get - { - if (_startPosition == OUT_OF_TEXT) - { - return OUT_OF_TEXT; - } - - int start = GetLineBound(CurrentLine); - return _index - start + 1; - } - } + => _startPosition == OUT_OF_TEXT ? OUT_OF_TEXT : _index - _lineBounds[^1] + 1; + // CurrentLine's start is last in _lineBounds public char CurrentSymbol => _currentSymbol; @@ -87,14 +79,13 @@ public int CurrentColumn public bool MoveNext() { _index++; - if (_index < _code.Length) + if (_index < _codeLength) { _currentSymbol = _code[_index]; if (_currentSymbol == '\n') { _lineCounter++; - if (_index < _code.Length) - _lineBounds.Add(_index + 1); + _lineBounds.Add(_index + 1); } return true; @@ -109,7 +100,7 @@ public bool MoveNext() public char PeekNext() { char result = '\0'; - if (_index + 1 < _code.Length) + if (_index + 1 < _codeLength) { result = _code[_index + 1]; } @@ -155,14 +146,32 @@ public bool SkipSpaces() } } - if (_index >= _code.Length) + if (_index >= _codeLength) { return false; } return true; + } + + public char ReadNextChar() + { + while (Char.IsWhiteSpace(_currentSymbol)) + { + if (_currentSymbol == '\n') + { + _onNewLine = true; + } + if (!MoveNext()) + { + break; + } + } + + return _currentSymbol; } + public string ReadToLineEnd() { while (_currentSymbol != '\n' && MoveNext()) @@ -180,6 +189,9 @@ public string ReadToLineEnd() public string GetCodeLine(int lineNumber) { int start = GetLineBound(lineNumber); + if (start >= _code.Length) + return String.Empty; + int end = _code.IndexOf('\n', start); if (end >= 0) { @@ -200,7 +212,7 @@ public ReadOnlyMemory GetContentSpan() { int len; - if (_startPosition == _index && _startPosition < _code.Length) + if (_startPosition == _index && _startPosition < _codeLength) { len = 1; } diff --git a/src/OneScript.Language/LexicalAnalysis/StringLexerState.cs b/src/OneScript.Language/LexicalAnalysis/StringLexerState.cs index 3afe4c4df..b9163315d 100644 --- a/src/OneScript.Language/LexicalAnalysis/StringLexerState.cs +++ b/src/OneScript.Language/LexicalAnalysis/StringLexerState.cs @@ -1,97 +1,96 @@ -/*---------------------------------------------------------- -This Source Code Form is subject to the terms of the -Mozilla Public License, v.2.0. If a copy of the MPL -was not distributed with this file, You can obtain one -at http://mozilla.org/MPL/2.0/. -----------------------------------------------------------*/ - -using System.Text; - -namespace OneScript.Language.LexicalAnalysis -{ - public class StringLexerState : LexerState - { - private void SkipSpacesAndComments(SourceCodeIterator iterator) - { - while (true) - { /* Пропускаем все пробелы и комментарии */ - iterator.SkipSpaces(); - - if (iterator.CurrentSymbol == '/') - { - if (!iterator.MoveNext()) - throw CreateExceptionOnCurrentLine("Некорректный символ", iterator); - - if (iterator.CurrentSymbol != '/') - throw CreateExceptionOnCurrentLine("Некорректный символ", iterator); - - do - { - if (!iterator.MoveNext()) - break; - - } while (iterator.CurrentSymbol != '\n'); - - } - else - break; - } - } - - public override Lexem ReadNextLexem(SourceCodeIterator iterator) - { +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using System.Text; + +namespace OneScript.Language.LexicalAnalysis +{ + public class StringLexerState : LexerState + { + private void SkipSpacesAndComments(SourceCodeIterator iterator) + { + while (true) + { /* Пропускаем все пробелы и комментарии */ + if (iterator.ReadNextChar() == '/') + { + if (!iterator.MoveNext()) + throw CreateExceptionOnCurrentLine("Некорректный символ", iterator); + + if (iterator.CurrentSymbol != '/') + throw CreateExceptionOnCurrentLine("Некорректный символ", iterator); + + do + { + if (!iterator.MoveNext()) + break; + + } while (iterator.CurrentSymbol != '\n'); + + } + else + break; + } + } + + public override Lexem ReadNextLexem(SourceCodeIterator iterator) + { StringBuilder contentBuilder = new StringBuilder(); - - while (iterator.MoveNext()) - { - var cs = iterator.CurrentSymbol; - - if (cs == SpecialChars.StringQuote) - { - if (iterator.MoveNext()) - { - if (iterator.CurrentSymbol == SpecialChars.StringQuote) - { - /* Двойная кавычка */ - contentBuilder.Append("\""); - continue; - } - - /* Завершение строки */ - SkipSpacesAndComments(iterator); - - if (iterator.CurrentSymbol == SpecialChars.StringQuote) - { - /* Сразу же началась новая строка */ - contentBuilder.Append('\n'); - continue; - } - } - - var lex = new Lexem - { - Type = LexemType.StringLiteral, - Content = contentBuilder.ToString() - }; - return lex; - } - - if (cs == '\n') - { - iterator.MoveNext(); - SkipSpacesAndComments(iterator); - - if (iterator.CurrentSymbol != '|') - throw CreateExceptionOnCurrentLine("Некорректный строковый литерал!", iterator); - - contentBuilder.Append('\n'); - } - else if(cs != '\r') - contentBuilder.Append(cs); - - } - - throw CreateExceptionOnCurrentLine("Незавершённый строковой интервал!", iterator); - } - } -} + + while (iterator.MoveNext()) + { + var cs = iterator.CurrentSymbol; + + if (cs == SpecialChars.StringQuote) + { + if (iterator.MoveNext()) + { + if (iterator.CurrentSymbol == SpecialChars.StringQuote) + { + /* Двойная кавычка */ + contentBuilder.Append('"'); + + continue; + } + + /* Завершение строки */ + SkipSpacesAndComments(iterator); + + if (iterator.CurrentSymbol == SpecialChars.StringQuote) + { + /* Сразу же началась новая строка */ + contentBuilder.Append('\n'); + + continue; + } + } + + return new Lexem + { + Type = LexemType.StringLiteral, + Content = contentBuilder.ToString() + }; + } + + if (cs == '\n') + { + iterator.MoveNext(); + SkipSpacesAndComments(iterator); + + if (iterator.CurrentSymbol != '|') + throw CreateExceptionOnCurrentLine("Некорректный строковый литерал", iterator); + + contentBuilder.Append('\n'); + } + else if (cs != '\r') + contentBuilder.Append(cs); + + } + + throw CreateExceptionOnCurrentLine("Незавершённый строковый литерал", iterator); + } + } +} diff --git a/src/OneScript.Language/LexicalAnalysis/Token.cs b/src/OneScript.Language/LexicalAnalysis/Token.cs index aca7738f1..681fdf324 100644 --- a/src/OneScript.Language/LexicalAnalysis/Token.cs +++ b/src/OneScript.Language/LexicalAnalysis/Token.cs @@ -43,13 +43,15 @@ public enum Token RemoveHandler, Async, Await, - Goto, - - // operators + Goto, + + // operators + UnaryPlus, + UnaryMinus, + // binary begin + // recommend to be in continuous block Plus, Minus, - UnaryPlus, - UnaryMinus, Multiply, Division, Modulo, @@ -61,6 +63,7 @@ public enum Token NotEqual, And, Or, + // binary end Not, Dot, OpenPar, diff --git a/src/OneScript.Language/LexicalAnalysis/WordLexerState.cs b/src/OneScript.Language/LexicalAnalysis/WordLexerState.cs index c9249ca46..3fa944d63 100644 --- a/src/OneScript.Language/LexicalAnalysis/WordLexerState.cs +++ b/src/OneScript.Language/LexicalAnalysis/WordLexerState.cs @@ -11,91 +11,78 @@ public class WordLexerState : LexerState { public override Lexem ReadNextLexem(SourceCodeIterator iterator) { - bool isEndOfText = false; - char cs = '\0'; - int start = iterator.Position; - int currentLine = iterator.CurrentLine; - int currentColumn = iterator.CurrentColumn; - while (true) - { - if (!isEndOfText) - { - cs = iterator.CurrentSymbol; - } - - if (SpecialChars.IsDelimiter(cs) || isEndOfText) - { - var content = iterator.GetContents(); + var location = new CodeRange(iterator.CurrentLine, iterator.CurrentColumn); - Lexem lex; - - if (LanguageDef.IsLogicalOperatorString(content)) - { - lex = new Lexem() - { - Type = LexemType.Operator, - Token = LanguageDef.GetToken(content), - Content = content, - Location = new CodeRange(currentLine, currentColumn) - }; - } - else if (LanguageDef.IsBooleanLiteralString(content)) - { - lex = new Lexem() - { - Type = LexemType.BooleanLiteral, - Content = content, - Location = new CodeRange(currentLine, currentColumn) - }; - } - else if (LanguageDef.IsUndefinedString(content)) - { - lex = new Lexem() - { - Type = LexemType.UndefinedLiteral, - Content = content, - Location = new CodeRange(currentLine, currentColumn) - }; - - } - else if (LanguageDef.IsNullString(content)) - { - lex = new Lexem() - { - Type = LexemType.NullLiteral, - Content = content, - Location = new CodeRange(currentLine, currentColumn) - }; - - } - else - { - lex = new Lexem() - { - Type = LexemType.Identifier, - Content = content, - Token = LanguageDef.GetToken(content), - Location = new CodeRange(currentLine, currentColumn) - }; - - if (LanguageDef.IsBuiltInFunction(lex.Token)) - { - iterator.SkipSpaces(); - if (iterator.CurrentSymbol != '(') - { - lex.Token = Token.NotAToken; - } - } - } - - return lex; - } - - if (!iterator.MoveNext()) - { - isEndOfText = true; - } + do + { + if (SpecialChars.IsDelimiter(iterator.CurrentSymbol)) + break; } + while (iterator.MoveNext()); + + var content = iterator.GetContents(); + Lexem lex; + + switch (LanguageDef.GetWordType(content)) + { + case LanguageDef.WordType.Logical: + lex = new Lexem() + { + Type = LexemType.Operator, + Token = LanguageDef.GetToken(content), + Content = content, + Location = location + }; + break; + + case LanguageDef.WordType.Boolean: + lex = new Lexem() + { + Type = LexemType.BooleanLiteral, + Content = content, + Location = location + }; + break; + + case LanguageDef.WordType.Undefined: + lex = new Lexem() + { + Type = LexemType.UndefinedLiteral, + Content = content, + Location = location + }; + break; + + case LanguageDef.WordType.Null: + lex = new Lexem() + { + Type = LexemType.NullLiteral, + Content = content, + Location = location + }; + break; + + default: + var tok = LanguageDef.GetToken(content); + if (LanguageDef.IsBuiltInFunction(tok)) + { + if (iterator.ReadNextChar() != '(') + { + tok = Token.NotAToken; + } + } + + lex = new Lexem() + { + Type = LexemType.Identifier, + Content = content, + Token = tok, + Location = location + }; + break; + } + + return lex; } } } diff --git a/src/OneScript.Language/ScriptException.cs b/src/OneScript.Language/ScriptException.cs index 89048f73b..329285557 100644 --- a/src/OneScript.Language/ScriptException.cs +++ b/src/OneScript.Language/ScriptException.cs @@ -98,7 +98,11 @@ public override string Message { var sb = new StringBuilder(MessageWithoutCodeFragment); sb.AppendLine(); - var codeLine = Code?.Replace('\t', ' ').TrimEnd(); + var codeLine = Code?.Replace('\t', ' ')?.TrimEnd() ?? String.Empty; + if (ColumnNumber > codeLine.Length) + { + ColumnNumber = codeLine.Length; + } if (ColumnNumber != ErrorPositionInfo.OUT_OF_TEXT) { diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/BinaryOperationNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/BinaryOperationNode.cs index f4cebbfbf..2582f31f0 100644 --- a/src/OneScript.Language/SyntaxAnalysis/AstNodes/BinaryOperationNode.cs +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/BinaryOperationNode.cs @@ -17,5 +17,13 @@ public BinaryOperationNode(Lexem operation) : base(NodeKind.BinaryOperation, ope { Operation = operation.Token; } + + public BinaryOperationNode(BslSyntaxNode firstArg, BslSyntaxNode secondArg, Lexem operation) + : base(NodeKind.BinaryOperation, operation) + { + Operation = operation.Token; + AddChild(firstArg); + AddChild(secondArg); + } } } \ No newline at end of file diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/ConditionNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/ConditionNode.cs index 7e44d8112..86a41d118 100644 --- a/src/OneScript.Language/SyntaxAnalysis/AstNodes/ConditionNode.cs +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/ConditionNode.cs @@ -26,7 +26,7 @@ public ConditionNode(Lexem startLexem) : base(NodeKind.Condition, startLexem) public IEnumerable GetAlternatives() { if(_alternativesStart == 0) - return new BslSyntaxNode[0]; + return System.Array.Empty(); return Children .Skip(_alternativesStart) diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/LineMarkerNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/LineMarkerNode.cs index d67c85d4e..3399bc17f 100644 --- a/src/OneScript.Language/SyntaxAnalysis/AstNodes/LineMarkerNode.cs +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/LineMarkerNode.cs @@ -18,6 +18,6 @@ public LineMarkerNode(CodeRange location, NodeKind kind) Kind = kind; } - public override IReadOnlyList Children => new BslSyntaxNode[0]; + public override IReadOnlyList Children => System.Array.Empty(); } } \ No newline at end of file diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodNode.cs index 07aa269a3..7cea61576 100644 --- a/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodNode.cs +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodNode.cs @@ -30,7 +30,7 @@ public MethodNode() : base(NodeKind.Method) public IReadOnlyList VariableDefinitions() { if (VariableSection == default) - return new VariableDefinitionNode[0]; + return System.Array.Empty(); return VariableSection.Children .Cast() diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodSignatureNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodSignatureNode.cs index 1272785b4..cc245493d 100644 --- a/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodSignatureNode.cs +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/MethodSignatureNode.cs @@ -28,7 +28,7 @@ public IEnumerable GetParameters() { var paramList = Children.FirstOrDefault(x => x.Kind == NodeKind.MethodParameters); if (paramList == default) - return new MethodParameterNode[0]; + return System.Array.Empty(); return ((NonTerminalNode) paramList).Children.Cast(); } diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/TerminalNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/TerminalNode.cs index a57fbe4cc..09fe2996f 100644 --- a/src/OneScript.Language/SyntaxAnalysis/AstNodes/TerminalNode.cs +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/TerminalNode.cs @@ -26,8 +26,6 @@ public TerminalNode(NodeKind kind, Lexem lexem) Location = lexem.Location; } - public override IReadOnlyList Children => EmptyChildren; - - private static readonly BslSyntaxNode[] EmptyChildren = new BslSyntaxNode[0]; + public override IReadOnlyList Children => System.Array.Empty(); } } \ No newline at end of file diff --git a/src/OneScript.Language/SyntaxAnalysis/AstNodes/UnaryOperationNode.cs b/src/OneScript.Language/SyntaxAnalysis/AstNodes/UnaryOperationNode.cs index 05e9b80cd..d55774cf1 100644 --- a/src/OneScript.Language/SyntaxAnalysis/AstNodes/UnaryOperationNode.cs +++ b/src/OneScript.Language/SyntaxAnalysis/AstNodes/UnaryOperationNode.cs @@ -16,6 +16,12 @@ public class UnaryOperationNode : NonTerminalNode public UnaryOperationNode(Lexem operation) : base(NodeKind.UnaryOperation, operation) { Operation = operation.Token; - } + } + + public UnaryOperationNode(BslSyntaxNode arg, Lexem operation) : base(NodeKind.UnaryOperation, operation) + { + Operation = operation.Token; + AddChild(arg); + } } } \ No newline at end of file diff --git a/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs b/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs index 7c2b352da..1a0c6f8cf 100644 --- a/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs +++ b/src/OneScript.Language/SyntaxAnalysis/BslSyntaxWalker.cs @@ -324,7 +324,7 @@ protected virtual void VisitTernaryOperation(BslSyntaxNode node) protected virtual void VisitModuleBody(BslSyntaxNode codeBlock) { - if(codeBlock.Children.Count > 0) + if(codeBlock.Children.Count != 0) VisitCodeBlock(codeBlock.Children[0]); } diff --git a/src/OneScript.Language/SyntaxAnalysis/ConditionalDirectiveHandler.cs b/src/OneScript.Language/SyntaxAnalysis/ConditionalDirectiveHandler.cs index d84e8febc..5a5572dde 100644 --- a/src/OneScript.Language/SyntaxAnalysis/ConditionalDirectiveHandler.cs +++ b/src/OneScript.Language/SyntaxAnalysis/ConditionalDirectiveHandler.cs @@ -297,7 +297,7 @@ private void MarkAsSolved() private void PopBlock() { - if (_blocks.Count > 0) + if (_blocks.Count != 0) _blocks.Pop(); else AddError(LocalizedErrors.DirectiveIsMissing("Если")); diff --git a/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs b/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs index 7f6b980ca..ad0839376 100644 --- a/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs +++ b/src/OneScript.Language/SyntaxAnalysis/DefaultBslParser.cs @@ -21,8 +21,8 @@ public class DefaultBslParser private readonly ILexer _lexer; private readonly PreprocessorHandlers _preprocessorHandlers; - private Lexem _lastExtractedLexem; - + private Lexem _lastExtractedLexem; + private bool _inMethodScope; private bool _isMethodsDefined; private bool _isStatementsDefined; @@ -32,8 +32,8 @@ public class DefaultBslParser private readonly Stack _tokenStack = new Stack(); private bool _isInLoopScope; - private bool _enableException; - + private bool _enableException; + private readonly List _annotations = new List(); public DefaultBslParser( @@ -47,21 +47,21 @@ public DefaultBslParser( _nodeContext = new ParserContext(); } - private IErrorSink ErrorSink { get; } - - public IEnumerable Errors => ErrorSink.Errors ?? new CodeError[0]; - + private IErrorSink ErrorSink { get; } + + public IEnumerable Errors => ErrorSink.Errors ?? Array.Empty(); + public BslSyntaxNode ParseStatefulModule() { - ModuleNode node; - + ModuleNode node; + _preprocessorHandlers.OnModuleEnter(); - NextLexem(); - + NextLexem(); + + node = new ModuleNode(_lexer.Iterator.Source, _lastExtractedLexem); + PushContext(node); try - { - node = new ModuleNode(_lexer.Iterator.Source, _lastExtractedLexem); - PushContext(node); + { ParseModuleSections(); } finally @@ -69,8 +69,8 @@ public BslSyntaxNode ParseStatefulModule() PopContext(); } - _preprocessorHandlers.OnModuleLeave(); - + _preprocessorHandlers.OnModuleLeave(); + return node; } @@ -81,11 +81,9 @@ public BslSyntaxNode ParseCodeBatch(bool allowReturns = false) PushContext(node); try { - if (allowReturns) - { - _inMethodScope = true; - _isInFunctionScope = true; - } + _inMethodScope = allowReturns; + _isInFunctionScope = allowReturns; + BuildModuleBody(); } finally @@ -107,8 +105,8 @@ public BslSyntaxNode ParseExpression() return module; } - private void PushContext(NonTerminalNode node) => _nodeContext.PushContext(node); - + private void PushContext(NonTerminalNode node) => _nodeContext.PushContext(node); + private NonTerminalNode PopContext() => _nodeContext.PopContext(); private NonTerminalNode CurrentParent => _nodeContext.CurrentParent; @@ -116,14 +114,14 @@ public BslSyntaxNode ParseExpression() private void ParseModuleAnnotation() { if (_lastExtractedLexem.Type != LexemType.PreprocessorDirective) - return; - + return; + var annotationParser = _preprocessorHandlers .Slice(x => x is ModuleAnnotationDirectiveHandler) .Cast() - .ToList(); - - if (!annotationParser.Any()) + .ToList(); + + if (annotationParser.Count == 0) return; while (_lastExtractedLexem.Type == LexemType.PreprocessorDirective) @@ -133,7 +131,7 @@ private void ParseModuleAnnotation() foreach (var handler in annotationParser) { handled = handler.ParseAnnotation(ref _lastExtractedLexem, _lexer, _nodeContext); - if(handled) + if (handled) break; } @@ -141,8 +139,8 @@ private void ParseModuleAnnotation() { AddError(LocalizedErrors.DirectiveNotSupported(directive)); } - } - + } + foreach (var handler in annotationParser) { handler.OnModuleLeave(); @@ -152,19 +150,18 @@ private void ParseModuleAnnotation() private void ParseModuleSections() { ParseModuleAnnotation(); - BuildVariableSection(); - BuildMethodsSection(); + BuildVariablesSection(); + BuildMethodsSection(); + if (_annotations.Count != 0) + { + AddError(LocalizedErrors.AnnotationNotAllowed()); + } BuildModuleBody(); - - if (_annotations.Count != 0) - { - AddError(LocalizedErrors.UnexpectedEof()); - } - } - + } + #region Variables - - private void BuildVariableSection() + + private void BuildVariablesSection() { if (_lastExtractedLexem.Token != Token.VarDef && _lastExtractedLexem.Type != LexemType.Annotation) { @@ -179,97 +176,99 @@ private void BuildVariableSection() { while (true) { - BuildAnnotations(); - if (_lastExtractedLexem.Token == Token.VarDef) - { - if (!hasVars) - { - hasVars = true; - parent.AddChild(allVarsSection); - } - - BuildVariableDefinition(); - } - else - { - break; - } + BuildAnnotations(); + + if (_lastExtractedLexem.Token != Token.VarDef) + break; + + if (!hasVars) + { + hasVars = true; + parent.AddChild(allVarsSection); + } + + BuildVariablesDefinition(); } } finally { PopContext(); - } - } - - private void BuildVariableDefinition() - { + } + + } + + private void BuildVariablesDefinition() + { + if (_inMethodScope) + { + if (_isStatementsDefined) + { + AddError(LocalizedErrors.LateVarDefinition()); + return; + } + } + else if (_isMethodsDefined) + { + AddError(LocalizedErrors.LateVarDefinition()); + return; + } + while (true) { - var variable = _nodeContext.AddChild(new VariableDefinitionNode(_lastExtractedLexem)); - - ApplyAnnotations(variable); - - NextLexem(); - - if (IsUserSymbol(_lastExtractedLexem)) - { - if (_inMethodScope) - { - if (_isStatementsDefined) - { - AddError(LocalizedErrors.LateVarDefinition()); - return; - } - } - else - { - if (_isMethodsDefined) - { - AddError(LocalizedErrors.LateVarDefinition()); - return; - } - } - - var symbolicName = _lastExtractedLexem.Content; - CreateChild(variable, NodeKind.Identifier, _lastExtractedLexem); - - NextLexem(); - if (_lastExtractedLexem.Token == Token.Export) - { - if (_inMethodScope) - { - AddError(LocalizedErrors.ExportedLocalVar(symbolicName)); - break; - } - CreateChild(variable, NodeKind.ExportFlag, _lastExtractedLexem); - NextLexem(); - } - - if (_lastExtractedLexem.Token == Token.Comma) - { - continue; - } - - if (_lastExtractedLexem.Token == Token.Semicolon) - { - NextLexem(); - } - else - { - AddError(LocalizedErrors.SemicolonExpected()); - } - - } - else - { - AddError(LocalizedErrors.IdentifierExpected()); + NextLexem(); // skip opening VarDef or Comma + + if (!IsUserSymbol(_lastExtractedLexem)) + { + if(_lastExtractedLexem.Type == LexemType.Annotation) + AddError(LocalizedErrors.AnnotationNotAllowed()); + else + AddError(LocalizedErrors.IdentifierExpected()); + return; + } + + BuildVariable(); + + if (_lastExtractedLexem.Token == Token.Semicolon) + { + break; + } + + if (_lastExtractedLexem.Token != Token.Comma) + { + AddError(LocalizedErrors.SemicolonExpected()); + return; + } + } + + NextLexem(); // skip Semicolon + _annotations.Clear(); + } + + private void BuildVariable() + { + var variable = _nodeContext.AddChild(new VariableDefinitionNode(_lastExtractedLexem)); + if (!_inMethodScope) + foreach (var astNode in _annotations) + { + variable.AddChild(astNode); } - - break; - } - } - + + var symbolicName = _lastExtractedLexem.Content; + CreateChild(variable, NodeKind.Identifier, _lastExtractedLexem); + + NextLexem(); + if (_lastExtractedLexem.Token == Token.Export) + { + if (_inMethodScope) + { + AddError(LocalizedErrors.ExportedLocalVar(symbolicName)); + return; + } + CreateChild(variable, NodeKind.ExportFlag, _lastExtractedLexem); + NextLexem(); + } + } + private void ApplyAnnotations(AnnotatableNode annotatable) { foreach (var astNode in _annotations) @@ -279,16 +278,14 @@ private void ApplyAnnotations(AnnotatableNode annotatable) _annotations.Clear(); } - #endregion + #endregion #region Methods private void BuildMethodsSection() { - if (_lastExtractedLexem.Type != LexemType.Annotation - && _lastExtractedLexem.Token != Token.Procedure - && _lastExtractedLexem.Token != Token.Function - && _lastExtractedLexem.Token != Token.Async) + if (_lastExtractedLexem.Type != LexemType.Annotation + && !IsStartOfMethod(_lastExtractedLexem)) { return; } @@ -303,21 +300,17 @@ private void BuildMethodsSection() while (true) { BuildAnnotations(); - if (IsStartOfMethod(_lastExtractedLexem)) - { - if (!sectionExist) - { - sectionExist = true; - _isMethodsDefined = true; - parent.AddChild(allMethodsSection); - } - - BuildMethod(); - } - else - { - break; - } + if (!IsStartOfMethod(_lastExtractedLexem)) + break; + + if (!sectionExist) + { + sectionExist = true; + _isMethodsDefined = true; + parent.AddChild(allMethodsSection); + } + + BuildMethod(); } } finally @@ -329,14 +322,14 @@ private void BuildMethodsSection() private static bool IsStartOfMethod(in Lexem lex) { return lex.Token == Token.Async || lex.Token == Token.Procedure || lex.Token == Token.Function; - } - + } + private void BuildMethod() { Debug.Assert(IsStartOfMethod(_lastExtractedLexem)); - var method = _nodeContext.AddChild(new MethodNode()); - + var method = _nodeContext.AddChild(new MethodNode()); + ApplyAnnotations(method); PushContext(method); if (_lastExtractedLexem.Token == Token.Async) @@ -344,8 +337,8 @@ private void BuildMethod() method.IsAsync = true; _isInAsyncMethod = true; NextLexem(); - } - + } + try { BuildMethodSignature(); @@ -370,7 +363,7 @@ private void BuildMethodVariablesSection() { // для корректной перемотки вперед в случае ошибок в секции переменных PushStructureToken(_isInFunctionScope ? Token.EndFunction : Token.EndProcedure); - BuildVariableSection(); + BuildVariablesSection(); } finally { @@ -380,8 +373,8 @@ private void BuildMethodVariablesSection() private void BuildMethodBody() { - var body = CreateChild(CurrentParent, NodeKind.CodeBatch, _lastExtractedLexem); - PushContext((NonTerminalNode)body); + var body = _nodeContext.AddChild(new CodeBatchNode(_lastExtractedLexem)); + PushContext(body); try { BuildCodeBatch(_isInFunctionScope ? Token.EndFunction : Token.EndProcedure); @@ -389,8 +382,8 @@ private void BuildMethodBody() finally { PopContext(); - } - + } + CreateChild(CurrentParent, NodeKind.BlockEnd, _lastExtractedLexem); NextLexem(); } @@ -399,7 +392,7 @@ private void BuildMethodSignature() { var signature = _nodeContext.AddChild(new MethodSignatureNode(_lastExtractedLexem)); var isFunction = _lastExtractedLexem.Token == Token.Function; - CreateChild(signature, isFunction? NodeKind.Function : NodeKind.Procedure, _lastExtractedLexem); + CreateChild(signature, isFunction ? NodeKind.Function : NodeKind.Procedure, _lastExtractedLexem); _isInFunctionScope = isFunction; NextLexem(); if (!IsUserSymbol(_lastExtractedLexem)) @@ -426,53 +419,59 @@ private void BuildMethodParameters(MethodSignatureNode signature) } var paramList = new NonTerminalNode(NodeKind.MethodParameters, _lastExtractedLexem); - signature.AddChild(paramList); - + signature.AddChild(paramList); + NextLexem(); // ( - var expectParameter = false; - while (_lastExtractedLexem.Token != Token.ClosePar) - { - BuildAnnotations(); - var param = new MethodParameterNode(); - paramList.AddChild(param); - ApplyAnnotations(param); - // [Знач] Identifier [= Literal],... - if (_lastExtractedLexem.Token == Token.ByValParam) - { - CreateChild(param, NodeKind.ByValModifier, _lastExtractedLexem); - NextLexem(); - } - - if (!IsUserSymbol(_lastExtractedLexem)) - { - AddError(LocalizedErrors.IdentifierExpected()); - return; - } - CreateChild(param, NodeKind.Identifier, _lastExtractedLexem); - NextLexem(); - if (_lastExtractedLexem.Token == Token.Equal) - { - NextLexem(); - if(!BuildDefaultParameterValue(param, NodeKind.ParameterDefaultValue)) - return; - } - - expectParameter = false; - if (_lastExtractedLexem.Token == Token.Comma) - { - NextLexem(); - expectParameter = true; - } - } - - if (expectParameter) - { - AddError(LocalizedErrors.IdentifierExpected(), false); - } - + if (_lastExtractedLexem.Token != Token.ClosePar) + while (true) + { + BuildMethodParameter(paramList); + + if (_lastExtractedLexem.Token == Token.ClosePar) + { + break; + } + + if (_lastExtractedLexem.Token == Token.Comma) + { + NextLexem(); + } + else + { + AddError(LocalizedErrors.TokenExpected(Token.ClosePar)); + return; + } + } + NextLexem(); // ) + } + private void BuildMethodParameter(NonTerminalNode paramList) + { + BuildAnnotations(); + var param = new MethodParameterNode(); + paramList.AddChild(param); + ApplyAnnotations(param); + // [Знач] Identifier [= Literal],... + if (_lastExtractedLexem.Token == Token.ByValParam) + { + CreateChild(param, NodeKind.ByValModifier, _lastExtractedLexem); + NextLexem(); + } + + if (!IsUserSymbol(_lastExtractedLexem)) + { + AddError(LocalizedErrors.IdentifierExpected()); + return; + } + CreateChild(param, NodeKind.Identifier, _lastExtractedLexem); + NextLexem(); + if (_lastExtractedLexem.Token == Token.Equal) + { + NextLexem(); + BuildDefaultParameterValue(param, NodeKind.ParameterDefaultValue); + } } private bool BuildDefaultParameterValue(NonTerminalNode param, NodeKind nodeKind) @@ -487,12 +486,11 @@ private bool BuildDefaultParameterValue(NonTerminalNode param, NodeKind nodeKind if (LanguageDef.IsLiteral(_lastExtractedLexem)) { - string literalText = _lastExtractedLexem.Content; if (hasSign) { if (_lastExtractedLexem.Type == LexemType.NumberLiteral && signIsMinus) { - literalText = '-' + literalText; + _lastExtractedLexem.Content = '-' + _lastExtractedLexem.Content; } else if (_lastExtractedLexem.Type == LexemType.StringLiteral || _lastExtractedLexem.Type == LexemType.DateLiteral) @@ -502,7 +500,6 @@ private bool BuildDefaultParameterValue(NonTerminalNode param, NodeKind nodeKind } } - _lastExtractedLexem.Content = literalText; CreateChild(param, nodeKind, _lastExtractedLexem); NextLexem(); } @@ -513,15 +510,15 @@ private bool BuildDefaultParameterValue(NonTerminalNode param, NodeKind nodeKind } return true; - } - + } + #endregion - + private void BuildModuleBody() { if (!_lexer.Iterator.MoveToContent()) - return; - + return; + var moduleBody = new NonTerminalNode(NodeKind.ModuleBody, _lastExtractedLexem); var node = moduleBody.AddNode(new CodeBatchNode(_lastExtractedLexem)); PushContext(node); @@ -534,17 +531,24 @@ private void BuildModuleBody() PopContext(); } CurrentParent.AddChild(moduleBody); - } - + } + #region Annotations private void BuildAnnotations() { while (_lastExtractedLexem.Type == LexemType.Annotation) - { + { + if (_inMethodScope) + { + AddError(LocalizedErrors.AnnotationNotAllowed()); + return; + } + var node = BuildAnnotationDefinition(); _annotations.Add(node); } } + private AnnotationNode BuildAnnotationDefinition() { var node = new AnnotationNode(NodeKind.Annotation, _lastExtractedLexem); NextLexem(); @@ -552,30 +556,37 @@ private AnnotationNode BuildAnnotationDefinition() { return node; } - private void BuildAnnotationParameters(AnnotationNode annotation) { if (_lastExtractedLexem.Token != Token.OpenPar) return; NextLexem(); - - while (_lastExtractedLexem.Token != Token.EndOfText) + + if (_lastExtractedLexem.Token != Token.ClosePar) + while (true) { - if (_lastExtractedLexem.Token == Token.ClosePar) - { - NextLexem(); - break; - } - - BuildAnnotationParameter(annotation); + BuildAnnotationParameter(annotation); + + if (_lastExtractedLexem.Token == Token.ClosePar) + { + break; + } + if (_lastExtractedLexem.Token == Token.Comma) { NextLexem(); } - } - } - + else + { + AddError(LocalizedErrors.TokenExpected(Token.ClosePar), false); + return; + } + } + + NextLexem(); // ) + } + private void BuildAnnotationParameter(AnnotationNode annotation) { bool success = true; @@ -639,7 +650,10 @@ private void BuildCodeBatch(params Token[] endTokens) if (_lastExtractedLexem.Type != LexemType.Identifier && _lastExtractedLexem.Token != Token.EndOfText) { - AddError(LocalizedErrors.UnexpectedOperation()); + if (_lastExtractedLexem.Type == LexemType.Annotation) + AddError(LocalizedErrors.AnnotationNotAllowed()); + else + AddError(LocalizedErrors.UnexpectedOperation()); continue; } @@ -661,7 +675,7 @@ private void BuildCodeBatch(params Token[] endTokens) private void DefineLabel(Lexem label) { var node = new LabelNode(label); - _nodeContext.AddChild(node); + CurrentParent.AddChild(node); NextLexem(); } @@ -732,8 +746,7 @@ private void BuildComplexStructureStatement() } else { - var expected = _tokenStack.Peek(); - AddError(LocalizedErrors.TokenExpected(expected)); + AddError(LocalizedErrors.TokenExpected(_tokenStack.Peek())); } break; } @@ -741,9 +754,9 @@ private void BuildComplexStructureStatement() private void BuildGlobalCallAwaitOperator() { - Debug.Assert(_lastExtractedLexem.Token == Token.Await); - - _nodeContext.AddChild(TerminalNode()); + Debug.Assert(_lastExtractedLexem.Token == Token.Await); + + CurrentParent.AddChild(TerminalNode()); } @@ -757,9 +770,7 @@ private BslSyntaxNode BuildExpressionAwaitOperator(Lexem lexem) if (argument != default) { CheckAsyncMethod(); - var awaitOperator = new UnaryOperationNode(lexem); - awaitOperator.AddChild(argument); - return awaitOperator; + return new UnaryOperationNode(argument, lexem); } else if (!_isInAsyncMethod) { @@ -768,8 +779,7 @@ private BslSyntaxNode BuildExpressionAwaitOperator(Lexem lexem) } else { - AddError(LocalizedErrors.ExpressionSyntax()); - return new ErrorTerminalNode(_lastExtractedLexem); + return CreateError(LocalizedErrors.ExpressionSyntax()); } } @@ -781,12 +791,13 @@ private void BuildGotoOperator() if (_lastExtractedLexem.Type != LexemType.LabelRef) { AddError(LocalizedErrors.LabelNameExpected()); + return; } gotoNode.AddChild(new LabelNode(_lastExtractedLexem)); NextLexem(); - _nodeContext.AddChild(gotoNode); + CurrentParent.AddChild(gotoNode); } private void CheckAsyncMethod() @@ -845,8 +856,9 @@ private void BuildWhileStatement() var loopNode = _nodeContext.AddChild(new WhileLoopNode(_lastExtractedLexem)); NextLexem(); BuildExpressionUpTo(loopNode, Token.Loop); - var body = CreateChild(loopNode, NodeKind.CodeBatch, _lastExtractedLexem); - PushContext((NonTerminalNode)body); + var body = loopNode.AddNode(new CodeBatchNode(_lastExtractedLexem)); + + PushContext(body); var loopState = _isInLoopScope; try { @@ -863,8 +875,7 @@ private void BuildWhileStatement() } private void BuildForStatement() - { - var lexem = _lastExtractedLexem; + { NextLexem(); NodeKind loopKind; @@ -904,8 +915,8 @@ private void BuildCountableForStatement(NonTerminalNode loopNode) AddError(LocalizedErrors.IdentifierExpected()); BuildBatchWithContext(loopNode, Token.EndLoop); return; - } - + } + var counter = _lastExtractedLexem; if (!NextExpected(Token.Equal)) { @@ -924,8 +935,8 @@ private void BuildCountableForStatement(NonTerminalNode loopNode) var limit = new NonTerminalNode(NodeKind.ForLimit, _lastExtractedLexem); BuildExpressionUpTo(limit, Token.Loop); - loopNode.AddChild(limit); - + loopNode.AddChild(limit); + BuildBatchWithContext(loopNode, Token.EndLoop); CreateChild(loopNode, NodeKind.BlockEnd, _lastExtractedLexem); @@ -1072,10 +1083,9 @@ private void BuildEventHandlerOperation(Token token) NextLexem(); var source = BuildExpressionUpTo(node, Token.Comma); - if (source == null) - return; - if ((source.Kind != NodeKind.DereferenceOperation || !_lastDereferenceIsWritable) && source.Kind != NodeKind.IndexAccess) + if ((source.Kind != NodeKind.DereferenceOperation || !_lastDereferenceIsWritable) + && source.Kind != NodeKind.IndexAccess) { AddError(LocalizedErrors.WrongEventName()); return; @@ -1140,7 +1150,7 @@ private BslSyntaxNode BuildGlobalCall(Lexem identifier) private BslSyntaxNode CallOrVariable(Lexem identifier) { - var target = NodeBuilder.CreateNode(NodeKind.Identifier, identifier); + BslSyntaxNode target = new TerminalNode(NodeKind.Identifier, identifier); if (_lastExtractedLexem.Token != Token.OpenPar) { _lastDereferenceIsWritable = true; // одиночный идентификатор @@ -1153,7 +1163,7 @@ private BslSyntaxNode CallOrVariable(Lexem identifier) return BuildDereference(target); } - private BslSyntaxNode BuildCall(BslSyntaxNode target, NodeKind callKind) + private CallNode BuildCall(BslSyntaxNode target, NodeKind callKind) { var callNode = new CallNode(callKind, _lastExtractedLexem); callNode.AddChild(target); @@ -1169,60 +1179,49 @@ private void BuildCallParameters(NonTerminalNode callNode) try { NextLexem(); // съели открывающую скобку - WalkCallArguments(node); - + BuildCallArguments(node); NextLexem(); // съели закрывающую скобку } finally { PopStructureToken(); } - } - - private int WalkCallArguments(NonTerminalNode node) - { - int argCount = 0; - while (_lastExtractedLexem.Token != Token.ClosePar) - { - BuildCallArgument(node); - argCount++; - } - - if (_lastExtractedLexem.Token != Token.ClosePar) - { - AddError(LocalizedErrors.TokenExpected(Token.ClosePar)); - argCount = -1; - } - - return argCount; - } - - private void BuildCallArgument(NonTerminalNode argsList) - { - if (_lastExtractedLexem.Token == Token.Comma) - { - CreateChild(argsList, NodeKind.CallArgument, _lastExtractedLexem); - - BuildLastDefaultArg(argsList); - } - else if (_lastExtractedLexem.Token != Token.ClosePar) + } + + private void BuildCallArguments(NonTerminalNode node) + { + if (_lastExtractedLexem.Token != Token.ClosePar) + while (true) + { + BuildOptionalCallArgument(node); + + if (_lastExtractedLexem.Token == Token.ClosePar) + { + break; + } + + if (_lastExtractedLexem.Token == Token.Comma) + { + NextLexem(); + } + else + { + AddError(LocalizedErrors.TokenExpected(Token.ClosePar)); + return; + } + } + } + + private void BuildOptionalCallArgument(NonTerminalNode argsList) + { + var arg = argsList.AddNode(new NonTerminalNode(NodeKind.CallArgument, _lastExtractedLexem)); + if (_lastExtractedLexem.Token == Token.Comma + || _lastExtractedLexem.Token == Token.ClosePar) { - var node = argsList.AddNode(new NonTerminalNode(NodeKind.CallArgument, _lastExtractedLexem)); - BuildOptionalExpression(node, Token.Comma); - if (_lastExtractedLexem.Token == Token.Comma) - { - BuildLastDefaultArg(argsList); - } - } - } - - private void BuildLastDefaultArg(NonTerminalNode argsList) - { - NextLexem(); - if (_lastExtractedLexem.Token == Token.ClosePar) - { - CreateChild(argsList, NodeKind.CallArgument, _lastExtractedLexem); - } + return; + } + + arg.AddNode( BuildExpression(0) ); } #endregion @@ -1233,134 +1232,68 @@ private BslSyntaxNode BuildExpression(NonTerminalNode parent, Token stopToken) { if (_lastExtractedLexem.Token == stopToken) { - AddError(LocalizedErrors.ExpressionExpected()); - return default; + return CreateError(LocalizedErrors.ExpressionExpected()); } - var op = BuildOrExpression(); + var op = BuildExpression(0); parent.AddChild(op); return op; } - - private BslSyntaxNode BuildOrExpression() - { - var firstArg = BuildAndExpression(); - while (_lastExtractedLexem.Token == Token.Or) - { - var operationLexem = _lastExtractedLexem; - NextLexem(); - var secondArg = BuildAndExpression(); - firstArg = MakeBinaryOperationNode(firstArg, secondArg, operationLexem); - } - return firstArg; - } - - private BslSyntaxNode BuildAndExpression() + private BslSyntaxNode BuildExpression(int prio) { - var firstArg = BuildNotExpression(); - while (_lastExtractedLexem.Token == Token.And) + var firstArg = BuildPrimaryExpression(); + while (LanguageDef.GetBinaryPriority(_lastExtractedLexem.Token) > prio) { var operationLexem = _lastExtractedLexem; NextLexem(); - var secondArg = BuildNotExpression(); - firstArg = MakeBinaryOperationNode(firstArg, secondArg, operationLexem); - } + var secondArg = BuildExpression(LanguageDef.GetBinaryPriority(operationLexem.Token)); - return firstArg; - } - - private BslSyntaxNode BuildNotExpression() - { - if (_lastExtractedLexem.Token == Token.Not) - { - var operation = _lastExtractedLexem; - NextLexem(); - var op = new UnaryOperationNode(operation); - var argument = BuildLogicalComparison(); - op.AddChild(argument); - return op; - } - - return BuildLogicalComparison(); - } - - private BslSyntaxNode BuildLogicalComparison() - { - var firstArg = BuildAdditionExpression(); - while (_lastExtractedLexem.Token == Token.Equal || - _lastExtractedLexem.Token == Token.MoreThan || - _lastExtractedLexem.Token == Token.LessThan || - _lastExtractedLexem.Token == Token.MoreOrEqual || - _lastExtractedLexem.Token == Token.LessOrEqual || - _lastExtractedLexem.Token == Token.NotEqual) - { - var operationLexem = _lastExtractedLexem; - NextLexem(); - var secondArg = BuildAdditionExpression(); - firstArg = MakeBinaryOperationNode(firstArg, secondArg, operationLexem); + firstArg = new BinaryOperationNode(firstArg, secondArg, operationLexem); } return firstArg; - } - - private BslSyntaxNode BuildAdditionExpression() + } + + private BslSyntaxNode BuildPrimaryExpression() { - var firstArg = BuildMultiplyExpression(); - while (_lastExtractedLexem.Token == Token.Plus || - _lastExtractedLexem.Token == Token.Minus) - { - var operationLexem = _lastExtractedLexem; - NextLexem(); - var secondArg = BuildMultiplyExpression(); - firstArg = MakeBinaryOperationNode(firstArg, secondArg, operationLexem); - } - - return firstArg; - } - - private BslSyntaxNode BuildMultiplyExpression() - { - var firstArg = BuildUnaryMathExpression(); - while (_lastExtractedLexem.Token == Token.Multiply || - _lastExtractedLexem.Token == Token.Division || - _lastExtractedLexem.Token == Token.Modulo) - { - var operationLexem = _lastExtractedLexem; - NextLexem(); - var secondArg = BuildUnaryMathExpression(); - firstArg = MakeBinaryOperationNode(firstArg, secondArg, operationLexem); - } - - return firstArg; - } - - private BslSyntaxNode BuildUnaryMathExpression() - { - if (_lastExtractedLexem.Token == Token.Plus) - _lastExtractedLexem.Token = Token.UnaryPlus; - else if (_lastExtractedLexem.Token == Token.Minus) - _lastExtractedLexem.Token = Token.UnaryMinus; - else - return BuildParenthesis(); - - // Можно оптимизировать численный литерал до константы - var operation = _lastExtractedLexem; - NextLexem(); - if (_lastExtractedLexem.Type == LexemType.NumberLiteral) - { - if (operation.Token == Token.UnaryMinus) - _lastExtractedLexem.Content = '-' + _lastExtractedLexem.Content; - - return TerminalNode(); - } - - var op = new UnaryOperationNode(operation); - var argument = BuildParenthesis(); - op.AddChild(argument); - return op; - } - + if (_lastExtractedLexem.Token == Token.OpenPar) + { + return BuildParenthesis(); + } + + var operation = _lastExtractedLexem; + var prio = LanguageDef.GetUnaryPriority(operation.Token); + + if (prio == LanguageDef.MAX_OPERATION_PRIORITY) + { + return TerminalNode(); + } + + NextLexem(); + + if (operation.Token == Token.Plus) + operation.Token = Token.UnaryPlus; + else if (operation.Token == Token.Minus) + { + operation.Token = Token.UnaryMinus; + if (_lastExtractedLexem.Type == LexemType.NumberLiteral) //TODO:move it to lexer + { + _lastExtractedLexem.Content = '-' + _lastExtractedLexem.Content; + return TerminalNode(); + } + } + + if (LanguageDef.GetUnaryPriority(_lastExtractedLexem.Token) <= prio) + { + return CreateError(LocalizedErrors.ExpressionSyntax()); + } + + var arg = BuildExpression(prio); + return new UnaryOperationNode(arg, operation); + } + + private BslSyntaxNode BuildExpressionUpTo(NonTerminalNode parent, Token stopToken) { var node = BuildExpression(parent, stopToken); @@ -1372,14 +1305,12 @@ private BslSyntaxNode BuildExpressionUpTo(NonTerminalNode parent, Token stopToke { if (_lastExtractedLexem.Token == Token.EndOfText) { - AddError(LocalizedErrors.UnexpectedEof()); + return CreateError(LocalizedErrors.UnexpectedEof()); } else { - AddError(LocalizedErrors.TokenExpected(stopToken), false); + return CreateError(LocalizedErrors.TokenExpected(stopToken), false); } - - node = default; } return node; @@ -1392,36 +1323,23 @@ private void BuildOptionalExpression(NonTerminalNode parent, Token stopToken) return; } - var op = BuildOrExpression(); + var op = BuildExpression(0); parent.AddChild(op); } #region Operators - private static BslSyntaxNode MakeBinaryOperationNode(BslSyntaxNode firstArg, BslSyntaxNode secondArg, in Lexem lexem) - { - var node = new BinaryOperationNode(lexem); - node.AddChild(firstArg); - node.AddChild(secondArg); - return node; - } - private BslSyntaxNode BuildParenthesis() - { - if (_lastExtractedLexem.Token == Token.OpenPar) - { - NextLexem(); - var expr = BuildOrExpression(); - if (_lastExtractedLexem.Token != Token.ClosePar) - { - AddError(LocalizedErrors.TokenExpected(Token.ClosePar)); - } - NextLexem(); - - return BuildDereference(expr); - } - - return TerminalNode(); + { + NextLexem(); + var expr = BuildExpression(0); + if (_lastExtractedLexem.Token != Token.ClosePar) + { + return CreateError(LocalizedErrors.TokenExpected(Token.ClosePar)); + } + NextLexem(); + + return BuildDereference(expr); } #endregion @@ -1431,9 +1349,9 @@ private BslSyntaxNode TerminalNode() BslSyntaxNode node = SelectTerminalNode(_lastExtractedLexem, true); if (node == default) { - AddError(LocalizedErrors.ExpressionSyntax()); + return CreateError(LocalizedErrors.ExpressionSyntax()); } - + return node; } @@ -1442,58 +1360,50 @@ private BslSyntaxNode SelectTerminalNode(in Lexem currentLexem, bool supportAwai BslSyntaxNode node = default; if (LanguageDef.IsLiteral(currentLexem)) { - node = NodeBuilder.CreateNode(NodeKind.Constant, currentLexem); + node = new TerminalNode(NodeKind.Constant, currentLexem); NextLexem(); } else if (LanguageDef.IsUserSymbol(currentLexem)) { node = BuildGlobalCall(currentLexem); } - else if(currentLexem.Token == Token.NewObject) + else if (currentLexem.Token == Token.NewObject) { node = BuildNewObjectCreation(); } - else if (currentLexem.Token == Token.Question) - { - node = BuildQuestionOperator(); - } else if (LanguageDef.IsBuiltInFunction(currentLexem.Token)) { node = BuildGlobalCall(currentLexem); } + else if (currentLexem.Token == Token.Question) + { + node = BuildQuestionOperator(); + } else if (supportAwait && currentLexem.Token == Token.Await) { node = BuildExpressionAwaitOperator(currentLexem); } - + return node; } private BslSyntaxNode BuildQuestionOperator() { var node = new NonTerminalNode(NodeKind.TernaryOperator, _lastExtractedLexem); - if(!NextExpected(Token.OpenPar)) - AddError(LocalizedErrors.TokenExpected(Token.OpenPar)); + if (!NextExpected(Token.OpenPar)) + return CreateError(LocalizedErrors.TokenExpected(Token.OpenPar)); + + NextLexem(); if (!TryParseNode(() => { - NextLexem(); - BuildExpression(node, Token.Comma); - NextLexem(); - BuildExpression(node, Token.Comma); - NextLexem(); - BuildExpression(node, Token.ClosePar); + BuildExpressionUpTo(node, Token.Comma); + BuildExpressionUpTo(node, Token.Comma); + BuildExpressionUpTo(node, Token.ClosePar); })) { - return default; - } - - if (_lastExtractedLexem.Token != Token.ClosePar) - { - AddError(LocalizedErrors.TokenExpected(Token.ClosePar)); - return default; + return CreateError(LocalizedErrors.ExpressionSyntax()); } - NextLexem(); return BuildDereference(node); } @@ -1508,15 +1418,14 @@ private BslSyntaxNode BuildDereference(BslSyntaxNode target) NextLexem(); if (!LanguageDef.IsValidPropertyName(_lastExtractedLexem)) { - AddError(LocalizedErrors.IdentifierExpected()); - return default; + return CreateError(LocalizedErrors.IdentifierExpected()); } var identifier = _lastExtractedLexem; NextLexem(); if (_lastExtractedLexem.Token == Token.OpenPar) { - var ident = NodeBuilder.CreateNode(NodeKind.Identifier, identifier); + var ident = new TerminalNode(NodeKind.Identifier, identifier); var call = BuildCall(ident, NodeKind.MethodCall); dotNode.AddChild(call); } @@ -1540,10 +1449,9 @@ private BslSyntaxNode BuildIndexerAccess(BslSyntaxNode target) node.AddChild(target); NextLexem(); var expression = BuildExpression(node, Token.CloseBracket); - if (expression == default) + if (expression.Kind == NodeKind.Unknown) { - AddError(LocalizedErrors.ExpressionSyntax()); - return default; + return CreateError(LocalizedErrors.ExpressionSyntax()); } NextLexem(); _lastDereferenceIsWritable = true; @@ -1568,8 +1476,7 @@ private BslSyntaxNode BuildNewObjectCreation() } else { - AddError(LocalizedErrors.IdentifierExpected()); - node = default; + return CreateError(LocalizedErrors.IdentifierExpected()); } return BuildDereference(node); @@ -1596,7 +1503,7 @@ private void NewObjectDynamicConstructor(NonTerminalNode node) // есть аргументы после имени NextLexem(); } - WalkCallArguments(callArgs); + BuildCallArguments(callArgs); node.AddChild(callArgs); NextLexem(); } @@ -1650,7 +1557,7 @@ private void AddError(CodeError err, bool doFastForward = true) if (doFastForward) { - if (_tokenStack.Count > 0) + if (_tokenStack.Count != 0) SkipToNextStatement(_tokenStack.Peek()); else SkipToNextStatement(); @@ -1660,6 +1567,13 @@ private void AddError(CodeError err, bool doFastForward = true) throw new InternalParseException(err); } + private ErrorTerminalNode CreateError(CodeError error, bool doFastForward = true) + { + var lexem = _lastExtractedLexem; + AddError(error, doFastForward); + return new ErrorTerminalNode(lexem); + } + private bool IsUserSymbol(in Lexem lex) { return LanguageDef.IsUserSymbol(in lex) || (!_isInAsyncMethod && lex.Token == Token.Await); @@ -1676,11 +1590,10 @@ private Token[] PopStructureToken() return tok; } - private BslSyntaxNode CreateChild(NonTerminalNode parent, NodeKind kind, in Lexem lex) + private static void CreateChild(NonTerminalNode parent, NodeKind kind, in Lexem lex) { var child = NodeBuilder.CreateNode(kind, lex); parent.AddChild(child); - return child; } private bool TryParseNode(Action action) diff --git a/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs b/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs index 809d3e695..a6e23d267 100644 --- a/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs +++ b/src/OneScript.Language/SyntaxAnalysis/LocalizedErrors.cs @@ -111,6 +111,9 @@ public static CodeError WrongHandlerName() => public static CodeError UnexpectedSymbol(char c) => Create($"Неизвестный символ {c}", $"Unexpected character {c}"); + public static CodeError AnnotationNotAllowed() => + Create("Аннотация неприменима в данном месте", "Annotation is not allowed here"); + public static CodeError DirectiveNotSupported(string directive) => Create($"Директива {directive} не разрешена в данном месте", $"Directive {directive} is not supported here"); diff --git a/src/OneScript.Native/Compiler/MethodCompiler.cs b/src/OneScript.Native/Compiler/MethodCompiler.cs index 3237a29f7..190643ebf 100644 --- a/src/OneScript.Native/Compiler/MethodCompiler.cs +++ b/src/OneScript.Native/Compiler/MethodCompiler.cs @@ -713,7 +713,7 @@ protected override void VisitIfNode(ConditionNode node) { stack.Push(elif); } - else if (stack.Count > 0) + else if (stack.Count != 0) { var cond = stack.Pop(); @@ -726,7 +726,7 @@ protected override void VisitIfNode(ConditionNode node) } } - while (stack.Count > 0) + while (stack.Count != 0) { var elseIfNode = stack.Pop(); VisitElseIfNode(elseIfNode); @@ -1147,7 +1147,7 @@ protected override void VisitObjectProcedureCall(BslSyntaxNode node) private IEnumerable PrepareDynamicCallArguments(BslSyntaxNode argList) { return argList.Children.Select(passedArg => - passedArg.Children.Count > 0 + passedArg.Children.Count != 0 ? ConvertToExpressionTree(passedArg.Children[0]) : Expression.Constant(BslSkippedParameterValue.Instance)); } @@ -1414,7 +1414,7 @@ private List PrepareCallArguments(BslSyntaxNode argList, ParameterIn } var parameters = argList.Children.Select(passedArg => - passedArg.Children.Count > 0 + passedArg.Children.Count != 0 ? ConvertToExpressionTree(passedArg.Children[0]) : null).ToArray(); @@ -1523,7 +1523,7 @@ protected override void VisitNewObjectCreation(NewObjectNode node) if (node.ConstructorArguments != default) { parameters = node.ConstructorArguments.Children.Select(passedArg => - passedArg.Children.Count > 0 ? + passedArg.Children.Count != 0 ? ConvertToExpressionTree(passedArg.Children[0]) : Expression.Default(typeof(BslValue))).ToArray(); } diff --git a/src/OneScript.Native/Compiler/StatementBlocksWriter.cs b/src/OneScript.Native/Compiler/StatementBlocksWriter.cs index f781f1a08..6bf68f434 100644 --- a/src/OneScript.Native/Compiler/StatementBlocksWriter.cs +++ b/src/OneScript.Native/Compiler/StatementBlocksWriter.cs @@ -18,7 +18,7 @@ public class StatementBlocksWriter public void EnterBlock(JumpInformationRecord newJumpStates) { - var current = _blocks.Count > 0 ? GetCurrentBlock() : null; + var current = _blocks.Count != 0 ? GetCurrentBlock() : null; if (current != null) { newJumpStates.MethodReturn ??= current.MethodReturn; diff --git a/src/OneScript.StandardLibrary/Collections/FixedStructureImpl.cs b/src/OneScript.StandardLibrary/Collections/FixedStructureImpl.cs index 146d71eba..a208576d9 100644 --- a/src/OneScript.StandardLibrary/Collections/FixedStructureImpl.cs +++ b/src/OneScript.StandardLibrary/Collections/FixedStructureImpl.cs @@ -85,6 +85,11 @@ public override BslPropertyInfo GetPropertyInfo(int propertyNumber) .Build(); } + public override string GetPropName(int propNum) + { + return _structure.GetPropName(propNum); + } + public override void CallAsProcedure(int methodNumber, IValue[] arguments, IBslProcess process) { var binding = _methods.GetCallableDelegate(methodNumber); diff --git a/src/OneScript.StandardLibrary/Collections/Indexes/CollectionIndex.cs b/src/OneScript.StandardLibrary/Collections/Indexes/CollectionIndex.cs index 71fb5c62b..73f13347e 100644 --- a/src/OneScript.StandardLibrary/Collections/Indexes/CollectionIndex.cs +++ b/src/OneScript.StandardLibrary/Collections/Indexes/CollectionIndex.cs @@ -42,7 +42,7 @@ public CollectionIndex(IIndexCollectionSource source, IEnumerable fields internal bool CanBeUsedFor(IEnumerable searchFields) { - return _fields.Count > 0 && _fields.All(f => searchFields.Contains(f)); + return _fields.Count != 0 && _fields.All(f => searchFields.Contains(f)); } private CollectionIndexKey IndexKey(PropertyNameIndexAccessor source) diff --git a/src/OneScript.StandardLibrary/CustomLineFeedStreamReader.cs b/src/OneScript.StandardLibrary/CustomLineFeedStreamReader.cs index b6933543c..f087c5d26 100644 --- a/src/OneScript.StandardLibrary/CustomLineFeedStreamReader.cs +++ b/src/OneScript.StandardLibrary/CustomLineFeedStreamReader.cs @@ -56,7 +56,7 @@ public int Read () _buffer.Dequeue (); UpdateCharQueue (); - if (_buffer.Count > 0 && _buffer.Peek () == '\n') { + if (_buffer.Count != 0 && _buffer.Peek () == '\n') { _buffer.Dequeue (); UpdateCharQueue (); } diff --git a/src/OneScript.StandardLibrary/FileContext.cs b/src/OneScript.StandardLibrary/FileContext.cs index 739a5da8a..9ba9f0abd 100644 --- a/src/OneScript.StandardLibrary/FileContext.cs +++ b/src/OneScript.StandardLibrary/FileContext.cs @@ -24,6 +24,10 @@ public class FileContext : AutoContext public FileContext(string name) { + // Strip null characters that can be added by Windows WebDAV client + // to maintain compatibility with 1.x behavior + name = PathHelper.StripNullCharacters(name); + if (String.IsNullOrWhiteSpace(name)) { _name = ""; diff --git a/src/OneScript.StandardLibrary/FileOperations.cs b/src/OneScript.StandardLibrary/FileOperations.cs index a3ddb95c5..7d6188851 100644 --- a/src/OneScript.StandardLibrary/FileOperations.cs +++ b/src/OneScript.StandardLibrary/FileOperations.cs @@ -138,6 +138,11 @@ public string GetTempFilename(string ext = null) [ContextMethod("НайтиФайлы", "FindFiles")] public ArrayImpl FindFiles(string dir, string mask = null, bool recursive = false) { + // Strip null characters that can be added by Windows WebDAV client + // to maintain compatibility with 1.x behavior + dir = PathHelper.StripNullCharacters(dir); + mask = PathHelper.StripNullCharacters(mask); + if (mask == null) { // fix 225, 227, 228 diff --git a/src/OneScript.StandardLibrary/PathHelper.cs b/src/OneScript.StandardLibrary/PathHelper.cs new file mode 100644 index 000000000..444b3cbf7 --- /dev/null +++ b/src/OneScript.StandardLibrary/PathHelper.cs @@ -0,0 +1,32 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +namespace OneScript.StandardLibrary +{ + /// + /// Utility methods for working with file paths + /// + internal static class PathHelper + { + /// + /// Strips trailing null characters from a path string. + /// This is needed because Windows WebDAV client can add null characters to the end of paths, + /// which causes ArgumentException in System.IO methods. + /// Only trailing null characters are removed to avoid masking potential security issues + /// with null characters in the middle of paths (e.g., "file.txt\0.exe"). + /// + /// Path that may contain trailing null characters + /// Path with trailing null characters removed, or null if input was null + public static string StripNullCharacters(string path) + { + if (path == null) + return null; + + return path.TrimEnd('\0'); + } + } +} diff --git a/src/OneScript.StandardLibrary/Tasks/BackgroundTasksManager.cs b/src/OneScript.StandardLibrary/Tasks/BackgroundTasksManager.cs index 0a875dc84..9999dd86c 100644 --- a/src/OneScript.StandardLibrary/Tasks/BackgroundTasksManager.cs +++ b/src/OneScript.StandardLibrary/Tasks/BackgroundTasksManager.cs @@ -120,7 +120,7 @@ public void WaitCompletionOfTasks() var failedTasks = _tasks.Where(x => x.State == TaskStateEnum.CompletedWithErrors) .ToList(); - if (failedTasks.Any()) + if (failedTasks.Count != 0) { throw new ParametrizedRuntimeException( Locale.NStr("ru = 'Задания завершились с ошибками';en = 'Tasks are completed with errors'"), diff --git a/src/ScriptEngine.HostedScript/CfgFileConfigProvider.cs b/src/ScriptEngine.HostedScript/CfgFileConfigProvider.cs index 68e14bf92..18ee7a842 100644 --- a/src/ScriptEngine.HostedScript/CfgFileConfigProvider.cs +++ b/src/ScriptEngine.HostedScript/CfgFileConfigProvider.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -8,7 +8,7 @@ This Source Code Form is subject to the terms of the using System; using System.Collections.Generic; using System.IO; -using System.Linq; +using ScriptEngine.Hosting; namespace ScriptEngine.HostedScript { @@ -20,10 +20,17 @@ public class CfgFileConfigProvider : IConfigProvider public bool Required { get; set; } - public Func> GetProvider() + public string SourceId => FilePath; + + public IReadOnlyDictionary Load() + { + return (IReadOnlyDictionary)ReadConfigFile(FilePath); + } + + public string ResolveRelativePath(string path) { - var localCopy = FilePath; - return () => ReadConfigFile(localCopy); + var confDir = Path.GetDirectoryName(FilePath); + return Path.Combine(confDir, path); } private IDictionary ReadConfigFile(string configPath) @@ -58,30 +65,7 @@ private IDictionary ReadConfigFile(string configPath) } } - ExpandRelativePaths(conf, configPath); - return conf; } - - private static void ExpandRelativePaths(IDictionary conf, string configFile) - { - string sysDir = null; - conf.TryGetValue(OneScriptLibraryOptions.SYSTEM_LIBRARY_DIR, out sysDir); - - var confDir = System.IO.Path.GetDirectoryName(configFile); - if (sysDir != null && !System.IO.Path.IsPathRooted(sysDir)) - { - sysDir = System.IO.Path.GetFullPath(System.IO.Path.Combine(confDir, sysDir)); - conf[OneScriptLibraryOptions.SYSTEM_LIBRARY_DIR] = sysDir; - } - - string additionals; - if (conf.TryGetValue(OneScriptLibraryOptions.ADDITIONAL_LIBRARIES, out additionals)) - { - var fullPaths = additionals.Split(new[]{";"}, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Path.GetFullPath(Path.Combine(confDir, x))); - conf[OneScriptLibraryOptions.ADDITIONAL_LIBRARIES] = string.Join(";",fullPaths); - } - } } -} \ No newline at end of file +} diff --git a/src/ScriptEngine.HostedScript/EngineConfigProvider.cs b/src/ScriptEngine.HostedScript/EngineConfigProvider.cs deleted file mode 100644 index abb2f8e32..000000000 --- a/src/ScriptEngine.HostedScript/EngineConfigProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -/*---------------------------------------------------------- -This Source Code Form is subject to the terms of the -Mozilla Public License, v.2.0. If a copy of the MPL -was not distributed with this file, You can obtain one -at http://mozilla.org/MPL/2.0/. -----------------------------------------------------------*/ - -using ScriptEngine.Hosting; - -namespace ScriptEngine.HostedScript -{ - public class EngineConfigProvider - { - private readonly ConfigurationProviders _providers; - KeyValueConfig _currentConfig; - - public EngineConfigProvider(ConfigurationProviders providers) - { - _providers = providers; - UpdateConfig(); - } - - private void UpdateConfig() - { - _currentConfig = _providers.CreateConfig(); - } - - public KeyValueConfig ReadConfig() - { - UpdateConfig(); - return _currentConfig; - } - - } -} diff --git a/src/ScriptEngine.HostedScript/EnvironmentVariableConfigProvider.cs b/src/ScriptEngine.HostedScript/EnvironmentVariableConfigProvider.cs new file mode 100644 index 000000000..b9f3c6ff2 --- /dev/null +++ b/src/ScriptEngine.HostedScript/EnvironmentVariableConfigProvider.cs @@ -0,0 +1,41 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using System; +using System.Collections.Generic; +using OneScript.Commons; +using ScriptEngine.Hosting; + +namespace ScriptEngine.HostedScript +{ + public class EnvironmentVariableConfigProvider : IConfigProvider + { + private readonly string _variableName; + + public EnvironmentVariableConfigProvider(string variableName) + { + _variableName = variableName; + } + + public string SourceId => _variableName; + + public IReadOnlyDictionary Load() + { + var envValue = Environment.GetEnvironmentVariable(_variableName); + if (string.IsNullOrEmpty(envValue)) + return new Dictionary(); + + var paramList = new FormatParametersList(envValue); + return paramList.ToDictionary(); + } + + public string ResolveRelativePath(string path) + { + return path; + } + } +} diff --git a/src/ScriptEngine.HostedScript/Extensions/EngineBuilderExtensions.cs b/src/ScriptEngine.HostedScript/Extensions/EngineBuilderExtensions.cs index 9cfbdaeb9..185612566 100644 --- a/src/ScriptEngine.HostedScript/Extensions/EngineBuilderExtensions.cs +++ b/src/ScriptEngine.HostedScript/Extensions/EngineBuilderExtensions.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -25,7 +25,7 @@ public static ConfigurationProviders UseConfigFile(this ConfigurationProviders p FilePath = configFile, Required = required }; - providers.Add(reader.GetProvider()); + providers.Add(reader); } return providers; @@ -60,16 +60,8 @@ public static ConfigurationProviders UseEntrypointConfigFile(this ConfigurationP public static ConfigurationProviders UseEnvironmentVariableConfig(this ConfigurationProviders providers, string varName) { - var env = System.Environment.GetEnvironmentVariable(varName); - if(env == null) - return providers; - - var reader = new FormatStringConfigProvider - { - ValuesString = env - }; - - providers.Add(reader.GetProvider()); + var reader = new EnvironmentVariableConfigProvider(varName); + providers.Add(reader); return providers; } @@ -127,4 +119,4 @@ public static IEngineBuilder UseEventHandlers(this IEngineBuilder builder) return builder; } } -} \ No newline at end of file +} diff --git a/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs b/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs index abf504ca8..f5fd6b45b 100644 --- a/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs +++ b/src/ScriptEngine.HostedScript/FileSystemDependencyResolver.cs @@ -42,6 +42,27 @@ private enum ProcessingState Processed } + private enum LoadStatus + { + NotFound, // Каталог библиотеки не существует + Empty, // Каталог найден, но библиотека не содержит исполняемых файлов + Success // Библиотека успешно загружена + } + + private class LoadResult + { + public PackageInfo Package { get; set; } + public LoadStatus Status { get; set; } + + public static LoadResult NotFound() => new LoadResult { Status = LoadStatus.NotFound }; + public static LoadResult Empty() => new LoadResult { Status = LoadStatus.Empty }; + public static LoadResult Success(PackageInfo package) => new LoadResult + { + Status = LoadStatus.Success, + Package = package + }; + } + #endregion public FileSystemDependencyResolver() @@ -82,17 +103,25 @@ public PackageInfo Resolve(SourceCode module, string libraryName, IBslProcess pr { bool quoted = PrepareQuoted(ref libraryName); - var lib = quoted ? + var result = quoted ? LoadByRelativePath(module, libraryName, process) : LoadByName(libraryName, process); - if (lib == null) + if (result.Status == LoadStatus.NotFound) + { throw new CompilerException($"Библиотека не найдена: '{libraryName}'"); + } + else if (result.Status == LoadStatus.Empty) + { + throw new CompilerException( + $"Библиотека '{libraryName}' найдена, но не содержит исполняемых файлов.\n" + + "Для получения подробной информации установите переменную окружения OS_LIBRARY_LOADER_TRACE=1"); + } - return lib; + return result.Package; } - private PackageInfo LoadByName(string libraryName, IBslProcess process) + private LoadResult LoadByName(string libraryName, IBslProcess process) { foreach (var path in SearchDirectories) { @@ -100,16 +129,20 @@ private PackageInfo LoadByName(string libraryName, IBslProcess process) continue; var libraryPath = Path.Combine(path, libraryName); - var loadAttempt = LoadByPath(libraryPath, process); - if (loadAttempt != null) - return loadAttempt; + var result = LoadByPath(libraryPath, process); + + // Если библиотека найдена (успешно загружена или пуста), сразу возвращаем + // Не ищем дальше, так как это более приоритетный путь + if (result.Status != LoadStatus.NotFound) + return result; } + // Если в SearchDirectories ничего не нашли, проверяем rootPath var rootPath = Path.Combine(LibraryRoot, libraryName); return LoadByPath(rootPath, process); } - private PackageInfo LoadByRelativePath(SourceCode module, string libraryPath, IBslProcess process) + private LoadResult LoadByRelativePath(SourceCode module, string libraryPath, IBslProcess process) { string realPath; @@ -197,11 +230,16 @@ private bool PrepareQuoted(ref string value) return quoted; } - private PackageInfo LoadByPath(string libraryPath, IBslProcess process) + private LoadResult LoadByPath(string libraryPath, IBslProcess process) { - return Directory.Exists(libraryPath) ? - LoadLibraryInternal(libraryPath, process) : - null; + if (!Directory.Exists(libraryPath)) + return LoadResult.NotFound(); + + var package = LoadLibraryInternal(libraryPath, process); + + return package == null + ? LoadResult.Empty() + : LoadResult.Success(package); } private PackageInfo LoadLibraryInternal(string libraryPath, IBslProcess process) diff --git a/src/ScriptEngine.HostedScript/FormatStringConfigProvider.cs b/src/ScriptEngine.HostedScript/FormatStringConfigProvider.cs deleted file mode 100644 index e1ce9f389..000000000 --- a/src/ScriptEngine.HostedScript/FormatStringConfigProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -/*---------------------------------------------------------- -This Source Code Form is subject to the terms of the -Mozilla Public License, v.2.0. If a copy of the MPL -was not distributed with this file, You can obtain one -at http://mozilla.org/MPL/2.0/. -----------------------------------------------------------*/ - -using System; -using System.Collections.Generic; -using OneScript.Commons; - -namespace ScriptEngine.HostedScript -{ - public class FormatStringConfigProvider : IConfigProvider - { - public string ValuesString { get; set; } - - public Func> GetProvider() - { - var localCopy = ValuesString; - return () => - { - var paramList = new FormatParametersList(localCopy); - return paramList.ToDictionary(); - }; - } - } -} \ No newline at end of file diff --git a/src/ScriptEngine.HostedScript/LibraryLoader.cs b/src/ScriptEngine.HostedScript/LibraryLoader.cs index adc76a06b..7a5cea07d 100644 --- a/src/ScriptEngine.HostedScript/LibraryLoader.cs +++ b/src/ScriptEngine.HostedScript/LibraryLoader.cs @@ -279,7 +279,9 @@ private IExecutableModule CompileFile(string path, string ownerPackageId, IBslPr } private static Lazy TraceEnabled = - new Lazy(() => System.Environment.GetEnvironmentVariable("OS_LRE_TRACE") == "1"); + new Lazy(() => + System.Environment.GetEnvironmentVariable("OS_LIBRARY_LOADER_TRACE") == "1" || + System.Environment.GetEnvironmentVariable("OS_LRE_TRACE") == "1"); // для обратной совместимости public static void TraceLoadLibrary(string message) { diff --git a/src/ScriptEngine.HostedScript/OneScriptLibraryOptions.cs b/src/ScriptEngine.HostedScript/OneScriptLibraryOptions.cs index 370977ba5..f407c1cc0 100644 --- a/src/ScriptEngine.HostedScript/OneScriptLibraryOptions.cs +++ b/src/ScriptEngine.HostedScript/OneScriptLibraryOptions.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the ----------------------------------------------------------*/ using System.Collections.Generic; +using System.Linq; using ScriptEngine.Hosting; namespace ScriptEngine.HostedScript @@ -17,18 +18,12 @@ public class OneScriptLibraryOptions : OneScriptCoreOptions public OneScriptLibraryOptions(KeyValueConfig config) : base(config) { - SystemLibraryDir = config[SYSTEM_LIBRARY_DIR]; - - var additionalDirsList = config[ADDITIONAL_LIBRARIES]; - if (additionalDirsList != null) - { - var addDirs = additionalDirsList.Split(';'); - AdditionalLibraries = new List(addDirs); - } + SystemLibraryDir = config.GetEntry(SYSTEM_LIBRARY_DIR)?.ResolvePath(); + AdditionalLibraries = config.GetEntry(ADDITIONAL_LIBRARIES)?.ResolvePathList(';').ToList(); } public string SystemLibraryDir { get; set; } public IEnumerable AdditionalLibraries { get; set; } } -} \ No newline at end of file +} diff --git a/src/ScriptEngine.HostedScript/SystemConfigAccessor.cs b/src/ScriptEngine.HostedScript/SystemConfigAccessor.cs index 619e6f6f6..996ee71fa 100644 --- a/src/ScriptEngine.HostedScript/SystemConfigAccessor.cs +++ b/src/ScriptEngine.HostedScript/SystemConfigAccessor.cs @@ -15,13 +15,11 @@ namespace ScriptEngine.HostedScript.Library [GlobalContext(Category = "Работа с настройками системы")] public class SystemConfigAccessor : GlobalContextBase { - private KeyValueConfig _config; - private readonly ConfigurationProviders _providers; + private readonly EngineConfiguration _activeConfig; - public SystemConfigAccessor(ConfigurationProviders providers) + public SystemConfigAccessor(EngineConfiguration activeConfig) { - _providers = providers; - Refresh(); + _activeConfig = activeConfig; } /// @@ -30,7 +28,7 @@ public SystemConfigAccessor(ConfigurationProviders providers) [ContextMethod("ОбновитьНастройкиСистемы", "RefreshSystemConfig")] public void Refresh() { - _config = _providers.CreateConfig(); + _activeConfig.Reload(); } /// @@ -41,21 +39,16 @@ public void Refresh() [ContextMethod("ПолучитьЗначениеСистемнойНастройки", "GetSystemOptionValue")] public IValue GetSystemOptionValue(string optionKey) { - string value = null; - if (_config != null) - { - value = _config[optionKey]; - } - - if (value != null) - return ValueFactory.Create(value); + var cfg = _activeConfig.GetConfig(); - return ValueFactory.Create(); + var value = cfg[optionKey]; + + return value != null ? ValueFactory.Create(value) : ValueFactory.Create(); } - public static IAttachableContext CreateInstance(ConfigurationProviders providers) + public static IAttachableContext CreateInstance(EngineConfiguration configHolder) { - return new SystemConfigAccessor(providers); + return new SystemConfigAccessor(configHolder); } } } diff --git a/src/ScriptEngine/Compiler/ModuleDumpWriter.cs b/src/ScriptEngine/Compiler/ModuleDumpWriter.cs index ea402b1f2..198b13e62 100644 --- a/src/ScriptEngine/Compiler/ModuleDumpWriter.cs +++ b/src/ScriptEngine/Compiler/ModuleDumpWriter.cs @@ -129,6 +129,13 @@ private void WriteImage(TextWriter output, StackRuntimeModule module) output.WriteLine( $"{i,-3}:type: {item.SystemType.Alias}, val: {item}"); } + output.WriteLine(".identifiers"); + for (int i = 0; i < module.Identifiers.Count; i++) + { + var item = module.Identifiers[i]; + output.WriteLine( + $"{i,-3}: {item}"); + } output.WriteLine(".code"); for (int i = 0; i < module.Code.Count; i++) { diff --git a/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs b/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs index 5e2200f49..3edae66b7 100644 --- a/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs +++ b/src/ScriptEngine/Compiler/StackMachineCodeGenerator.cs @@ -12,7 +12,6 @@ This Source Code Form is subject to the terms of the using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; using OneScript.Compilation; using OneScript.Compilation.Binding; using OneScript.Contexts; @@ -126,7 +125,7 @@ private void HandleImportClause(AnnotationNode node) private void CheckForwardedDeclarations() { - if (_forwardedMethods.Count > 0) + if (_forwardedMethods.Count != 0) { foreach (var item in _forwardedMethods) { @@ -477,7 +476,7 @@ protected override void VisitContinueNode(LineMarkerNode node) protected override void VisitReturnNode(BslSyntaxNode node) { - if (node.Children.Count > 0) + if (node.Children.Count != 0) { VisitExpression(node.Children[0]); AddCommand(OperationCode.MakeRawValue); @@ -489,7 +488,7 @@ protected override void VisitReturnNode(BslSyntaxNode node) protected override void VisitRaiseNode(BslSyntaxNode node) { int arg = -1; - if (node.Children.Any()) + if (node.Children.Count != 0) { VisitExpression(node.Children[0]); arg = 0; @@ -728,24 +727,17 @@ private void ResolveObjectMethod(BslSyntaxNode callNode, bool asFunction) PushCallArguments(args); - var cDef = new ConstDefinition(); - cDef.Type = DataType.String; - cDef.Presentation = name.GetIdentifier(); - int lastIdentifierConst = GetConstNumber(cDef); + int lastIdentifierIndex = GetIdentNumber(name.GetIdentifier()); if (asFunction) - AddCommand(OperationCode.ResolveMethodFunc, lastIdentifierConst); + AddCommand(OperationCode.ResolveMethodFunc, lastIdentifierIndex); else - AddCommand(OperationCode.ResolveMethodProc, lastIdentifierConst); + AddCommand(OperationCode.ResolveMethodProc, lastIdentifierIndex); } private void ResolveProperty(string identifier) { - var cDef = new ConstDefinition(); - cDef.Type = DataType.String; - cDef.Presentation = identifier; - var identifierConstIndex = GetConstNumber(cDef); - AddCommand(OperationCode.ResolveProp, identifierConstIndex); + AddCommand(OperationCode.ResolveProp, GetIdentNumber(identifier)); } private int PushVariable(TerminalNode node) @@ -863,11 +855,13 @@ private void GlobalCall(CallNode call, bool asFunction) else { // can be defined later - var forwarded = new ForwardedMethodDecl(); - forwarded.identifier = identifier; - forwarded.asFunction = asFunction; - forwarded.location = identifierNode.Location; - forwarded.factArguments = argList; + var forwarded = new ForwardedMethodDecl + { + identifier = identifier, + asFunction = asFunction, + location = identifierNode.Location, + factArguments = argList + }; PushCallArguments(call.ArgumentList); @@ -885,17 +879,17 @@ private void PushCallArguments(BslSyntaxNode argList) private void PushArgumentsList(BslSyntaxNode argList) { - for (int i = 0; i < argList.Children.Count; i++) + var arguments = argList.Children; + for (int i = 0; i < arguments.Count; i++) { - var passedArg = argList.Children[i]; - VisitCallArgument(passedArg); + VisitCallArgument(arguments[i]); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void VisitCallArgument(BslSyntaxNode passedArg) { - if (passedArg.Children.Count > 0) + if (passedArg.Children.Count != 0) { VisitExpression(passedArg.Children[0]); } @@ -1056,7 +1050,7 @@ private void MakeNewObjectDynamic(NewObjectNode node) var argsPassed = node.ConstructorArguments.Children.Count; if (argsPassed == 1) { - PushArgumentsList(node.ConstructorArguments); + VisitCallArgument(node.ConstructorArguments.Children[0]); ; } else if (argsPassed > 1) { @@ -1068,21 +1062,17 @@ private void MakeNewObjectDynamic(NewObjectNode node) private void MakeNewObjectStatic(NewObjectNode node) { - var cDef = new ConstDefinition() - { - Type = DataType.String, - Presentation = node.TypeNameNode.GetIdentifier() - }; - AddCommand(OperationCode.PushConst, GetConstNumber(cDef)); - - var callArgs = 0; if (node.ConstructorArguments != default) { - PushArgumentsList(node.ConstructorArguments); - callArgs = node.ConstructorArguments.Children.Count; + PushCallArguments(node.ConstructorArguments); + } + else + { + AddCommand(OperationCode.ArgNum, 0); } - AddCommand(OperationCode.NewInstance, callArgs); + var idNum = GetIdentNumber(node.TypeNameNode.GetIdentifier()); + AddCommand(OperationCode.NewInstance, idNum); } private void ExitTryBlocks() @@ -1094,7 +1084,7 @@ private void ExitTryBlocks() private void PushTryNesting() { - if (_nestedLoops.Count > 0) + if (_nestedLoops.Count != 0) { _nestedLoops.Peek().tryNesting++; } @@ -1102,7 +1092,7 @@ private void PushTryNesting() private void PopTryNesting() { - if (_nestedLoops.Count > 0) + if (_nestedLoops.Count != 0) { _nestedLoops.Peek().tryNesting--; } @@ -1328,6 +1318,18 @@ private int GetConstNumber(in ConstDefinition cDef) return idx; } + private int GetIdentNumber(string ident) + { + var idx = _module.Identifiers.IndexOf(ident); + if (idx < 0) + { + idx = _module.Identifiers.Count; + _module.Identifiers.Add(ident); + } + return idx; + } + + private int GetMethodRefNumber(in SymbolBinding methodBinding) { var descriptor = _ctx.GetBinding(methodBinding.ScopeNumber); diff --git a/src/ScriptEngine/Hosting/ConfigurationProviders.cs b/src/ScriptEngine/Hosting/ConfigurationProviders.cs index f5b63219a..9dae5ec81 100644 --- a/src/ScriptEngine/Hosting/ConfigurationProviders.cs +++ b/src/ScriptEngine/Hosting/ConfigurationProviders.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -7,16 +7,17 @@ This Source Code Form is subject to the terms of the using System; using System.Collections.Generic; +using System.Linq; namespace ScriptEngine.Hosting { public class ConfigurationProviders { - private List>> _providers = new List>>(); + private readonly List _providers = new List(); - public void Add(Func> configGetter) + public void Add(IConfigProvider source) { - _providers.Add(configGetter); + _providers.Add(source); } public KeyValueConfig CreateConfig() @@ -24,10 +25,14 @@ public KeyValueConfig CreateConfig() var cfg = new KeyValueConfig(); foreach (var provider in _providers) { - cfg.Merge(provider()); + var values = provider.Load(); + if (values != null && values.Count > 0) + { + cfg.Merge((IDictionary)values, provider); + } } return cfg; } } -} \ No newline at end of file +} diff --git a/src/ScriptEngine/Hosting/ConfigurationValue.cs b/src/ScriptEngine/Hosting/ConfigurationValue.cs new file mode 100644 index 000000000..e8765f9a9 --- /dev/null +++ b/src/ScriptEngine/Hosting/ConfigurationValue.cs @@ -0,0 +1,50 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace ScriptEngine.Hosting +{ + public class ConfigurationValue + { + public string RawValue { get; } + public IConfigProvider Source { get; } + + public ConfigurationValue(string rawValue, IConfigProvider source) + { + RawValue = rawValue; + Source = source; + } + + /// + /// Разрешает значение как путь относительно источника конфигурации + /// + public string ResolvePath() + { + return string.IsNullOrEmpty(RawValue) ? RawValue : Source.ResolveRelativePath(RawValue.Trim()); + } + + /// + /// Разрешает значение как список путей + /// + public IEnumerable ResolvePathList(char separator = ';') + { + if (string.IsNullOrEmpty(RawValue)) + return Enumerable.Empty(); + + return RawValue.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries) + .Select(path => + { + var trimmed = path.Trim(); + return Source.ResolveRelativePath(trimmed); + }); + } + } +} diff --git a/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs b/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs index 47a03759d..37225644d 100644 --- a/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs +++ b/src/ScriptEngine/Hosting/EngineBuilderExtensions.cs @@ -73,10 +73,11 @@ public static IEngineBuilder SetDefaultOptions(this IEngineBuilder builder) return new PreprocessorHandlers(providers); }); + services.RegisterSingleton(); services.Register(sp => { - var providers = sp.Resolve(); - return providers.CreateConfig(); + var holder = sp.Resolve(); + return holder.GetConfig(); }); services.Register(); diff --git a/src/ScriptEngine/Hosting/EngineConfiguration.cs b/src/ScriptEngine/Hosting/EngineConfiguration.cs new file mode 100644 index 000000000..46b80daf9 --- /dev/null +++ b/src/ScriptEngine/Hosting/EngineConfiguration.cs @@ -0,0 +1,43 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +namespace ScriptEngine.Hosting +{ + /// + /// Предназначен для хранения стабильного конфига + /// который не изменяется и не вычитывает провайдеры при каждом обращении к опциям. + /// + public class EngineConfiguration + { + private KeyValueConfig _config; + private readonly ConfigurationProviders _providers; + + private readonly object _refreshLock = new object(); + + public EngineConfiguration(ConfigurationProviders providers) + { + _providers = providers; + _config = _providers.CreateConfig(); + } + + public KeyValueConfig GetConfig() + { + lock (_refreshLock) + { + return _config; + } + } + + public void Reload() + { + lock (_refreshLock) + { + _config = _providers.CreateConfig(); + } + } + } +} \ No newline at end of file diff --git a/src/ScriptEngine.HostedScript/IConfigProvider.cs b/src/ScriptEngine/Hosting/IConfigProvider.cs similarity index 59% rename from src/ScriptEngine.HostedScript/IConfigProvider.cs rename to src/ScriptEngine/Hosting/IConfigProvider.cs index cd07f0b7e..1b1271d48 100644 --- a/src/ScriptEngine.HostedScript/IConfigProvider.cs +++ b/src/ScriptEngine/Hosting/IConfigProvider.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -8,10 +8,14 @@ This Source Code Form is subject to the terms of the using System; using System.Collections.Generic; -namespace ScriptEngine.HostedScript +namespace ScriptEngine.Hosting { public interface IConfigProvider { - Func> GetProvider(); + string SourceId { get; } + + IReadOnlyDictionary Load(); + + string ResolveRelativePath(string path); } -} \ No newline at end of file +} diff --git a/src/ScriptEngine/Hosting/KeyValueConfig.cs b/src/ScriptEngine/Hosting/KeyValueConfig.cs index e754902ae..909d35fcb 100644 --- a/src/ScriptEngine/Hosting/KeyValueConfig.cs +++ b/src/ScriptEngine/Hosting/KeyValueConfig.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -12,43 +12,34 @@ namespace ScriptEngine.Hosting { public class KeyValueConfig { - private readonly Dictionary _values = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly Dictionary _values = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - public KeyValueConfig() - { - } - - public KeyValueConfig(IDictionary source) - { - Merge(source); - } - - public void Merge(IDictionary source) + public void Merge(IDictionary source, IConfigProvider sourceProvider) { foreach (var keyValue in source) { - this[keyValue.Key] = keyValue.Value; + if (string.IsNullOrWhiteSpace(keyValue.Key)) + throw BadKeyException(keyValue.Key); + + _values[keyValue.Key] = new ConfigurationValue(keyValue.Value, sourceProvider); } } - public string this[string key] + public ConfigurationValue GetEntry(string key) { - get - { - if (string.IsNullOrWhiteSpace(key)) - throw BadKeyException(key); + if (string.IsNullOrWhiteSpace(key)) + throw BadKeyException(key); - _values.TryGetValue(key, out var value); - - return value; + _values.TryGetValue(key, out var value); + return value; + } - } - private set + public string this[string key] + { + get { - if (String.IsNullOrWhiteSpace(key)) - throw BadKeyException(key); - - _values[key] = value; + var entry = GetEntry(key); + return entry?.RawValue; } } diff --git a/src/ScriptEngine/Machine/GenericIValueComparer.cs b/src/ScriptEngine/Machine/GenericIValueComparer.cs index 5d913a5dd..e8c53da8a 100644 --- a/src/ScriptEngine/Machine/GenericIValueComparer.cs +++ b/src/ScriptEngine/Machine/GenericIValueComparer.cs @@ -21,8 +21,8 @@ public class GenericIValueComparer : IEqualityComparer, IComparerorderedTypes = new List { BasicTypes.Undefined, BasicTypes.Null, BasicTypes.Boolean, - BasicTypes.Number, BasicTypes.String, BasicTypes.Date, BasicTypes.Type }; - + BasicTypes.Number, BasicTypes.String, BasicTypes.Date, BasicTypes.Type }; + private const int INDEX_OF_TYPE = 6; public GenericIValueComparer() @@ -47,6 +47,9 @@ public int GetHashCode(IValue obj) if (obj is BslUndefinedValue) return obj.GetHashCode(); + if (obj is BslNullValue) + return obj.GetHashCode(); + try { CLR_obj = ContextValuesMarshaller.ConvertToClrObject(obj); @@ -56,6 +59,9 @@ public int GetHashCode(IValue obj) CLR_obj = obj; } + if (CLR_obj == null) + return 0; + return CLR_obj.GetHashCode(); } @@ -67,8 +73,8 @@ private int CompareAsStrings(IValue x, IValue y) private int CompareByPresentations(IValue x, IValue y) { return ((BslValue)x).ToString(_process).CompareTo(((BslValue)y).ToString(_process)); - } - + } + /// /// Сравнение переменных разных типов. Правила сравнения соответствуют 1С v8.3.27: /// Переменные типа "Тип" следуют за всеми прочими; @@ -82,7 +88,7 @@ private int CompareByTypes(IValue x, IValue y) var iy = orderedTypes.IndexOf(y.SystemType); if (ix >= 0) - if (iy >= 0) + if (iy >= 0) return ix - iy; else return ix == INDEX_OF_TYPE ? 1 : -1; diff --git a/src/ScriptEngine/Machine/MachineInstance.cs b/src/ScriptEngine/Machine/MachineInstance.cs index 992dbb292..eb5d73bf1 100644 --- a/src/ScriptEngine/Machine/MachineInstance.cs +++ b/src/ScriptEngine/Machine/MachineInstance.cs @@ -680,8 +680,8 @@ private void PushConst(int arg) { _operationStack.Push(_module.Constants[arg]); NextInstruction(); - } - + } + private void PushBool(int arg) { _operationStack.Push(BslBooleanValue.Create(arg == 1)); @@ -1011,7 +1011,7 @@ private void ResolveProp(int arg) var objIValue = _operationStack.Pop(); var context = objIValue.AsObject(); - var propName = _module.Constants[arg].ToString(_process); + var propName = _module.Identifiers[arg]; var propNum = context.GetPropertyNumber(propName); var propReference = Variable.CreateContextPropertyReference(context, propNum, "stackvar"); @@ -1048,7 +1048,7 @@ private void PrepareContextCallArguments(int arg, out IRuntimeContextInstance co var objIValue = _operationStack.Pop(); context = objIValue.AsObject(); - var methodName = _module.Constants[arg].ToString(_process); + var methodName = _module.Identifiers[arg]; methodId = context.GetMethodNumber(methodName); if (context.DynamicMethodSignatures) @@ -1133,7 +1133,7 @@ private void Return(int arg) if (_currentFrame.DiscardReturnValue) _operationStack.Pop(); - while(_exceptionsStack.Count > 0 && _exceptionsStack.Peek().HandlerFrame == _currentFrame) + while(_exceptionsStack.Count != 0 && _exceptionsStack.Peek().HandlerFrame == _currentFrame) { _exceptionsStack.Pop(); } @@ -1180,8 +1180,9 @@ private void Inc(int arg) NextInstruction(); } - private void NewInstance(int argCount) - { + private void NewInstance(int arg) + { + int argCount = (int)_operationStack.Pop().AsNumber(); IValue[] argValues = new IValue[argCount]; // fact args for (int i = argCount - 1; i >= 0; i--) @@ -1189,26 +1190,10 @@ private void NewInstance(int argCount) var argValue = _operationStack.Pop(); if(!argValue.IsSkippedArgument()) argValues[i] = RawValue(argValue); - } - - var typeName = PopRawBslValue().ToString(_process); - if (!_typeManager.TryGetType(typeName, out var type)) - { - throw RuntimeException.TypeIsNotDefined(typeName); - } - - // TODO убрать cast после рефакторинга ITypeFactory - var factory = (TypeFactory)_typeManager.GetFactoryFor(type); - var context = new TypeActivationContext - { - TypeName = typeName, - TypeManager = _typeManager, - Services = _process.Services, - CurrentProcess = _process - }; - - var instance = (IValue)factory.Activate(context, argValues); - _operationStack.Push(instance); + } + + var typeName = _module.Identifiers[arg]; + _operationStack.Push(CreateInstance(typeName, argValues)); NextInstruction(); } @@ -1262,7 +1247,7 @@ private void BeginTry(int exceptBlockAddress) private void EndTry(int arg) { - if (_exceptionsStack.Count > 0) + if (_exceptionsStack.Count != 0) { var jmpInfo = _exceptionsStack.Peek(); if (jmpInfo.HandlerFrame == _currentFrame && arg == jmpInfo.HandlerAddress) @@ -1385,7 +1370,7 @@ private void Execute(int arg) { var code = PopRawBslValue().ToString(_process); var module = CompileCached(code, CompileExecutionBatchModule); - if (!module.Methods.Any()) + if (module.Methods.Count == 0) { NextInstruction(); return; @@ -2395,13 +2380,19 @@ private void NewFunc(int argCount) else argValues = Array.Empty(); } - + var typeName = PopRawBslValue().ToString(_process); + _operationStack.Push(CreateInstance(typeName, argValues)); + NextInstruction(); + } + + private IValue CreateInstance(string typeName, IValue[] args) + { if (!_typeManager.TryGetType(typeName, out var type)) { throw RuntimeException.TypeIsNotDefined(typeName); } - + // TODO убрать cast после рефакторинга ITypeFactory var factory = (TypeFactory)_typeManager.GetFactoryFor(type); var context = new TypeActivationContext @@ -2410,17 +2401,14 @@ private void NewFunc(int argCount) TypeManager = _typeManager, Services = _process.Services, CurrentProcess = _process - }; - - var instance = factory.Activate(context, argValues); - _operationStack.Push(instance); - NextInstruction(); + }; + return factory.Activate(context, args); } #endregion - + #endregion - + private StackRuntimeModule CompileExpressionModule(string expression) { var entryId = CurrentCodeEntry().ToString(); diff --git a/src/ScriptEngine/Machine/StackRuntimeModule.cs b/src/ScriptEngine/Machine/StackRuntimeModule.cs index 6f570b49d..ea4d02803 100644 --- a/src/ScriptEngine/Machine/StackRuntimeModule.cs +++ b/src/ScriptEngine/Machine/StackRuntimeModule.cs @@ -26,10 +26,12 @@ public StackRuntimeModule(Type ownerType) public int EntryMethodIndex { get; set; } = -1; public List Constants { get; } = new List(); + + internal List Identifiers { get; } = new List(); - internal IList VariableRefs { get; } = new List(); + internal List VariableRefs { get; } = new List(); - internal IList MethodRefs { get; } = new List(); + internal List MethodRefs { get; } = new List(); #region IExecutableModule members @@ -52,7 +54,7 @@ public BslScriptMethodInfo ModuleBody public IList Methods { get; } = new List(); - public IList Code { get; } = new List(512); + public List Code { get; } = new List(512); public SourceCode Source { get; set; } diff --git a/src/Tests/OneScript.Core.Tests/ExplicitImportsTest.cs b/src/Tests/OneScript.Core.Tests/ExplicitImportsTest.cs index 1e6b79482..586c4e0a4 100644 --- a/src/Tests/OneScript.Core.Tests/ExplicitImportsTest.cs +++ b/src/Tests/OneScript.Core.Tests/ExplicitImportsTest.cs @@ -216,10 +216,10 @@ private IRuntimeContextInstance CompileUserScript( .Create() .SetupConfiguration(p => { - p.Add(() => new Dictionary + p.Add(new DictionaryConfigProvider(new Dictionary { { "lang.explicitImports", configValue } - }); + })); }) .SetDefaultOptions(); @@ -314,6 +314,27 @@ public PackageInfo Resolve(SourceCode module, string libraryName, IBslProcess pr return null; } } + + private class DictionaryConfigProvider : IConfigProvider + { + private readonly Dictionary _config; + public string SourceId => "dictionary"; + + public DictionaryConfigProvider(Dictionary config) + { + _config = config; + } + + public IReadOnlyDictionary Load() + { + return _config; + } + + public string ResolveRelativePath(string path) + { + return path; + } + } } } diff --git a/src/Tests/OneScript.Language.Tests/ParserTests.cs b/src/Tests/OneScript.Language.Tests/ParserTests.cs index 23fef46f2..92d498423 100644 --- a/src/Tests/OneScript.Language.Tests/ParserTests.cs +++ b/src/Tests/OneScript.Language.Tests/ParserTests.cs @@ -168,6 +168,53 @@ public void CheckBuild_Of_AnnotationAsValue() .NextChildIs(NodeKind.Annotation); } + [Fact] + public void Check_AnnotationNotAllowed_InList() + { + var code = @" + &Аннотация + Перем Пер1, &Анн Пер2;"; + + CatchParsingError(code, err => err.First().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] + public void Check_AnnotationNotAllowed_InMethod() + { + var code = @" + Процедура Процедура1() + &Аннотация + Возврат + КонецПроцедуры"; + + CatchParsingError(code, err => err.Single().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] + public void Check_AnnotationNotAllowed_BeforeMethodsEnd() + { + var code = @" + Процедура Процедура1() + Ч = 0; + &Аннотация + КонецПроцедуры"; + + CatchParsingError(code, err => err.Single().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] + public void Check_AnnotationNotAllowed_InModuleBody() + { + var code = @" + Процедура Процедура1() + КонецПроцедуры + &Аннотация + Ч = 0"; + + CatchParsingError(code, err => err.Single().ErrorId.Should().Be("AnnotationNotAllowed")); + } + + [Fact] public void Check_Method_Parameters() { @@ -1243,9 +1290,38 @@ public void Check_Semicolon_Between_Statements() Ф = 2 КонецЕсли"; + CatchParsingError(code); + } + + [Fact] + public void Check_Question_Operator_Delimiters() + { + var code = @"Ф = ?(Истина? 1 ; 2);"; + + CatchParsingError(code); + } + + [Fact] + public void Check_Method_Definition_Delimiters() + { + var code = @"Процедура Проц1(арг1 арг2) + КонецПроцедуры"; + CatchParsingError(code); } - + + [Fact] + public void Check_Method_Call_Delimiters() + { + var code = @"Процедура Проц1(арг1, арг2) + КонецПроцедуры + Проц1(""1"" 2)"; + + CatchParsingError(code); + } + + + [Fact] public void TestLocalExportVar() { @@ -1254,12 +1330,7 @@ public void TestLocalExportVar() Перем Переменная Экспорт; КонецПроцедуры"; - CatchParsingError(code, err => - { - var errors = err.ToArray(); - errors.Should().HaveCount(1); - errors[0].Description.Should().Contain("Локальная переменная не может быть экспортирована"); - }); + CatchParsingError(code, err => err.First().ErrorId.Should().Be("ExportedLocalVar")); } [Fact] diff --git a/src/VSCode.DebugAdapter/AttachOptions.cs b/src/VSCode.DebugAdapter/AttachOptions.cs new file mode 100644 index 000000000..cd9c0c57f --- /dev/null +++ b/src/VSCode.DebugAdapter/AttachOptions.cs @@ -0,0 +1,15 @@ +// /*---------------------------------------------------------- +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v.2.0. If a copy of the MPL +// was not distributed with this file, You can obtain one +// at http://mozilla.org/MPL/2.0/. +// ----------------------------------------------------------*/ + +namespace VSCode.DebugAdapter +{ + internal class AttachOptions + { + public int DebugPort { get; set; } = 2801; + public WorkspaceMapper PathsMapping { get; set; } + } +} \ No newline at end of file diff --git a/src/VSCode.DebugAdapter/ConsoleProcess.cs b/src/VSCode.DebugAdapter/ConsoleProcess.cs index a1a740278..99444187e 100644 --- a/src/VSCode.DebugAdapter/ConsoleProcess.cs +++ b/src/VSCode.DebugAdapter/ConsoleProcess.cs @@ -5,10 +5,12 @@ This Source Code Form is subject to the terms of the at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using Newtonsoft.Json.Linq; +using EvilBeaver.DAP.Dto.Requests; +using EvilBeaver.DAP.Dto.Serialization; using Serilog; namespace VSCode.DebugAdapter @@ -30,10 +32,15 @@ public ConsoleProcess(PathHandlingStrategy pathHandling) : base(pathHandling) public string RuntimeArguments { get; set; } public IDictionary Environment { get; set; } = new Dictionary(); + + public override void Init(LaunchRequestArguments args) + { + var options = args.DeserializeAdditionalProperties(); + InitInternal(options); + } - protected override void InitInternal(JObject args) + private void InitInternal(ConsoleLaunchOptions options) { - var options = args.ToObject(); if (options.Program == null) { throw new InvalidDebugeeOptionsException(1001, "Property 'program' is missing or empty."); diff --git a/src/VSCode.DebugAdapter/DebugSession.cs b/src/VSCode.DebugAdapter/DebugSession.cs deleted file mode 100644 index ff119d2d1..000000000 --- a/src/VSCode.DebugAdapter/DebugSession.cs +++ /dev/null @@ -1,502 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using VSCode.DebugAdapter; - -namespace VSCodeDebug -{ - // ---- Types ------------------------------------------------------------------------- - - public class Message { - public int id { get; } - public string format { get; } - public dynamic variables { get; } - public dynamic showUser { get; } - public dynamic sendTelemetry { get; } - - public Message(int id, string format, dynamic variables = null, bool user = true, bool telemetry = false) { - this.id = id; - this.format = format; - this.variables = variables; - this.showUser = user; - this.sendTelemetry = telemetry; - } - } - - public class StackFrame - { - public int id { get; } - public Source source { get; } - public int line { get; } - public int column { get; } - public string name { get; } - - public StackFrame(int id, string name, Source source, int line, int column) { - this.id = id; - this.name = name; - this.source = source; - this.line = line; - this.column = column; - } - } - - public class Scope - { - public string name { get; } - public int variablesReference { get; } - public bool expensive { get; } - - public Scope(string name, int variablesReference, bool expensive = false) { - this.name = name; - this.variablesReference = variablesReference; - this.expensive = expensive; - } - } - - public class Variable - { - public string name { get; } - public string value { get; } - public string type { get; } - public int variablesReference { get; } - - public Variable(string name, string value, string type, int variablesReference = 0) { - this.name = name; - this.value = value; - this.type = type; - this.variablesReference = variablesReference; - } - } - - public class Thread - { - public int id { get; } - public string name { get; } - - public Thread(int id, string name) { - this.id = id; - if (name == null || name.Length == 0) { - this.name = string.Format("Thread #{0}", id); - } - else { - this.name = name; - } - } - } - - public class Source - { - public string name { get; } - public string path { get; } - public int sourceReference { get; } - - public string origin { get; set; } - - public string presentationHint { get; set; } - - public Source(string name, string path, int sourceReference = 0) { - this.name = name; - this.path = path; - this.sourceReference = sourceReference; - } - - public Source(string path, int sourceReference = 0) { - this.name = Path.GetFileName(path); - this.path = path; - this.sourceReference = sourceReference; - } - } - - public class Breakpoint - { - public bool verified { get; } - public int line { get; } - - public Breakpoint(bool verified) - { - this.verified = verified; - line = 0; - } - - public Breakpoint(bool verified, int line) { - this.verified = verified; - this.line = line; - } - } - - // ---- Events ------------------------------------------------------------------------- - - public class InitializedEvent : Event - { - public InitializedEvent() - : base("initialized") { } - } - - public class StoppedEvent : Event - { - public StoppedEvent(int tid, string reasn, string txt = null) - : base("stopped", new { - threadId = tid, - reason = reasn, - text = txt - }) { } - } - - public class ExitedEvent : Event - { - public ExitedEvent(int exCode) - : base("exited", new { exitCode = exCode } ) { } - } - - public class TerminatedEvent : Event - { - public TerminatedEvent() - : base("terminated") { } - } - - public class ThreadEvent : Event - { - public ThreadEvent(string reasn, int tid) - : base("thread", new { - reason = reasn, - threadId = tid - }) { } - } - - public class OutputEvent : Event - { - public OutputEvent(string cat, string outpt) - : base("output", new { - category = cat, - output = outpt - }) { } - } - - // ---- Response ------------------------------------------------------------------------- - - public class Capabilities : ResponseBody { - - public bool supportsConfigurationDoneRequest; - public bool supportsFunctionBreakpoints; - public bool supportsConditionalBreakpoints; - public bool supportsEvaluateForHovers; - public bool supportsExceptionFilterOptions; - public dynamic[] exceptionBreakpointFilters; - public bool supportTerminateDebuggee; - } - - public class ErrorResponseBody : ResponseBody { - - public Message error { get; } - - public ErrorResponseBody(Message error) { - this.error = error; - } - } - - public class StackTraceResponseBody : ResponseBody - { - public StackFrame[] stackFrames { get; } - - public StackTraceResponseBody(IEnumerable frames = null) { - if (frames == null) - stackFrames = new StackFrame[0]; - else - stackFrames = frames.ToArray(); - } - } - - public class ScopesResponseBody : ResponseBody - { - public Scope[] scopes { get; } - - public ScopesResponseBody(IList scps = null) { - if (scps == null) - scopes = new Scope[0]; - else - scopes = scps.ToArray(); - } - } - - public class VariablesResponseBody : ResponseBody - { - public Variable[] variables { get; } - - public VariablesResponseBody(IList vars = null) { - if (vars == null) - variables = new Variable[0]; - else - variables = vars.ToArray(); - } - } - - public class ThreadsResponseBody : ResponseBody - { - public Thread[] threads { get; } - - public ThreadsResponseBody(List vars = null) { - if (vars == null) - threads = new Thread[0]; - else - threads = vars.ToArray(); - } - } - - public class EvaluateResponseBody : ResponseBody - { - public string result { get; } - - public string type { get; set; } - - public int variablesReference { get; } - - - public EvaluateResponseBody(string value, int reff = 0) { - result = value; - variablesReference = reff; - } - } - - public class SetBreakpointsResponseBody : ResponseBody - { - public Breakpoint[] breakpoints { get; } - - public SetBreakpointsResponseBody(List bpts = null) { - if (bpts == null) - breakpoints = Array.Empty(); - else - breakpoints = bpts.ToArray(); - } - } - - public class SetExceptionBreakpointsResponseBody : ResponseBody - { - public Breakpoint[] breakpoints { get; } - - public SetExceptionBreakpointsResponseBody(List bpts = null) - { - if (bpts == null) - breakpoints = Array.Empty(); - else - breakpoints = bpts.ToArray(); - } - } - - // ---- The Session -------------------------------------------------------- - - public abstract class DebugSession : ProtocolServer - { - private bool _clientLinesStartAt1 = true; - private bool _clientPathsAreUri = true; - - public PathHandlingStrategy PathStrategy { get; } - - public DebugSession(bool debuggerLinesStartAt1, bool debuggerPathsAreURI = false) - { - PathStrategy = new PathHandlingStrategy - { - DebuggerLinesStartAt1 = debuggerLinesStartAt1, - DebuggerPathsAreUri = debuggerPathsAreURI - }; - } - - public void SendResponse(Response response, dynamic body = null) - { - if (body != null) { - response.SetBody(body); - } - SendMessage(response); - } - - public void SendErrorResponse(Response response, int id, string format, dynamic arguments = null, bool user = true, bool telemetry = false) - { - var msg = new Message(id, format, arguments, user, telemetry); - var message = Utilities.ExpandVariables(msg.format, msg.variables); - response.SetErrorBody(message, new ErrorResponseBody(msg)); - SendMessage(response); - } - - protected override void DispatchRequest(string command, dynamic args, Response response) - { - if (args == null) { - args = new { }; - } - - try { - switch (command) { - - case "initialize": - if (args.linesStartAt1 != null) { - _clientLinesStartAt1 = (bool)args.linesStartAt1; - } - var pathFormat = (string)args.pathFormat; - if (pathFormat != null) { - switch (pathFormat) { - case "uri": - _clientPathsAreUri = true; - break; - case "path": - _clientPathsAreUri = false; - break; - default: - SendErrorResponse(response, 1015, "initialize: bad value '{_format}' for pathFormat", new { _format = pathFormat }); - return; - } - } - - SetPathStrategy(); - Initialize(response, args); - break; - - case "launch": - Launch(response, args); - break; - - case "attach": - Attach(response, args); - break; - - case "disconnect": - Disconnect(response, args); - break; - - case "next": - Next(response, args); - break; - - case "continue": - Continue(response, args); - break; - - case "stepIn": - StepIn(response, args); - break; - - case "stepOut": - StepOut(response, args); - break; - - case "pause": - Pause(response, args); - break; - - case "stackTrace": - StackTrace(response, args); - break; - - case "scopes": - Scopes(response, args); - break; - - case "variables": - Variables(response, args); - break; - - case "source": - Source(response, args); - break; - - case "threads": - Threads(response, args); - break; - - case "setBreakpoints": - SetBreakpoints(response, args); - break; - - case "setFunctionBreakpoints": - SetFunctionBreakpoints(response, args); - break; - - case "setExceptionBreakpoints": - SetExceptionBreakpoints(response, args); - break; - - case "evaluate": - Evaluate(response, args); - break; - case "configurationDone": - ConfigurationDone(response, args); - break; - - default: - SendErrorResponse(response, 1014, "unrecognized request: {_request}", new { _request = command }); - break; - } - } - catch (Exception e) { - OnRequestError(e); - SendErrorResponse(response, 1104, "error while processing request '{_request}' (exception: {_exception})", new { _request = command, _exception = e.Message }); - } - - if (command == "disconnect") { - Stop(); - } - } - - private void SetPathStrategy() - { - PathStrategy.ClientLinesStartAt1 = _clientLinesStartAt1; - PathStrategy.ClientPathsAreUri = _clientPathsAreUri; - } - - protected string ConvertClientPathToDebugger(string path) - { - return PathStrategy.ConvertClientPathToDebugger(path); - } - - public abstract void ConfigurationDone(Response response, dynamic args); - - public abstract void Initialize(Response response, dynamic args); - - public abstract void Launch(Response response, dynamic arguments); - - public abstract void Attach(Response response, dynamic arguments); - - public abstract void Disconnect(Response response, dynamic arguments); - - public virtual void SetFunctionBreakpoints(Response response, dynamic arguments) - { - } - - public virtual void SetExceptionBreakpoints(Response response, dynamic arguments) - { - } - - protected virtual void OnRequestError(Exception e) - { - } - - public abstract void SetBreakpoints(Response response, dynamic arguments); - - public abstract void Continue(Response response, dynamic arguments); - - public abstract void Next(Response response, dynamic arguments); - - public abstract void StepIn(Response response, dynamic arguments); - - public abstract void StepOut(Response response, dynamic arguments); - - public abstract void Pause(Response response, dynamic arguments); - - public abstract void StackTrace(Response response, dynamic arguments); - - public abstract void Scopes(Response response, dynamic arguments); - - public abstract void Variables(Response response, dynamic arguments); - - public virtual void Source(Response response, dynamic arguments) - { - SendErrorResponse(response, 1020, "Source not supported"); - } - - public abstract void Threads(Response response, dynamic arguments); - - public abstract void Evaluate(Response response, dynamic arguments); - } -} diff --git a/src/VSCode.DebugAdapter/DebugeeProcess.cs b/src/VSCode.DebugAdapter/DebugeeProcess.cs index e27ba8738..b651b5bca 100644 --- a/src/VSCode.DebugAdapter/DebugeeProcess.cs +++ b/src/VSCode.DebugAdapter/DebugeeProcess.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -11,6 +11,7 @@ This Source Code Form is subject to the terms of the using System.Linq; using System.Runtime.InteropServices; using System.Text; +using EvilBeaver.DAP.Dto.Requests; using Newtonsoft.Json.Linq; using Serilog; using VSCode.DebugAdapter.Transport; @@ -118,22 +119,19 @@ public void InitAttached() } - public void Init(JObject args) + public abstract void Init(LaunchRequestArguments args); + + public void InitPathsMapper(AttachRequestArguments args) { - InitInternal(args); - } - - public void InitPathsMapper(JObject args) - { - if (args == null) + if (!args.AdditionalData?.ContainsKey("pathsMapping") ?? false) { PathsMapper = null; return; } - + try { - var mappingToken = args["pathsMapping"]; + var mappingToken = args.AdditionalData?["pathsMapping"]; if (mappingToken == null || mappingToken.Type == JTokenType.Null) { PathsMapper = null; @@ -151,8 +149,6 @@ public void InitPathsMapper(JObject args) protected abstract Process CreateProcess(); - protected abstract void InitInternal(JObject args); - protected string ConvertClientPathToDebugger(string clientPath) { return _strategy.ConvertClientPathToDebugger(clientPath); diff --git a/src/VSCode.DebugAdapter/OneScriptDebugAdapter.cs b/src/VSCode.DebugAdapter/OneScriptDebugAdapter.cs new file mode 100644 index 000000000..e98313f71 --- /dev/null +++ b/src/VSCode.DebugAdapter/OneScriptDebugAdapter.cs @@ -0,0 +1,513 @@ +// /*---------------------------------------------------------- +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v.2.0. If a copy of the MPL +// was not distributed with this file, You can obtain one +// at http://mozilla.org/MPL/2.0/. +// ----------------------------------------------------------*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EvilBeaver.DAP.Dto.Events; +using EvilBeaver.DAP.Dto.Requests; +using EvilBeaver.DAP.Dto.Serialization; +using EvilBeaver.DAP.Dto.Types; +using EvilBeaver.DAP.Server; +using Microsoft.Extensions.Logging; +using VSCode.DebugAdapter.Transport; + +namespace VSCode.DebugAdapter +{ + internal class OneScriptDebugAdapter : DebugAdapterBase + { + private DebugeeProcess _debuggee; + private readonly ThreadStateContainer _threadState = new ThreadStateContainer(); + + private ILogger Log { get; } + + public OneScriptDebugAdapter(ILogger logger) + { + Log = logger; + } + + protected override async Task OnInitializeAsync(InitializeRequest request, CancellationToken ct) + { + var pathStrategy = new PathHandlingStrategy + { + ClientLinesStartAt1 = Client.LinesStartAt1, + ClientPathsAreUri = Client.PathFormat == "uri", + DebuggerLinesStartAt1 = true, + DebuggerPathsAreUri = false + }; + + _debuggee = DebugeeFactory.CreateProcess(Client.AdapterId, pathStrategy); + Log.LogDebug("Debuggee created"); + + await EventsChannel.SendEventAsync(new InitializedEvent(), ct); + + return new InitializeResponse() + { + Body = new Capabilities + { + SupportsConditionalBreakpoints = true, + SupportsFunctionBreakpoints = false, + SupportsConfigurationDoneRequest = true, + SupportsExceptionFilterOptions = true, + ExceptionBreakpointFilters = new [] + { + new ExceptionBreakpointsFilter + { + Filter = "uncaught", + Label = "Необработанные исключения", + Description = "Остановка при возникновении необработанного исключения", + SupportsCondition = true, + ConditionDescription = "Искомая подстрока текста исключения" + }, + new ExceptionBreakpointsFilter + { + Filter = "all", + Label = "Все исключения", + Description = "Остановка при возникновении любого исключения", + SupportsCondition = true, + ConditionDescription = "Искомая подстрока текста исключения" + } + }, + SupportsEvaluateForHovers = true, + SupportTerminateDebuggee = true + } + }; + } + + public override Task ConfigurationDoneAsync(ConfigurationDoneRequest request, CancellationToken ct) + { + if (_debuggee == null) + { + Log.LogDebug("Config Done. Process is not started"); + return Task.FromResult(new ConfigurationDoneResponse()); + } + + Log.LogDebug("Config Done. Process is started, sending Execute"); + _debuggee.BeginExecution(-1); + + return Task.FromResult(new ConfigurationDoneResponse()); + } + + public override Task DisconnectAsync(DisconnectRequest request, CancellationToken ct) + { + Log.LogDebug("Disconnect requested, terminate={Terminate}", request.Arguments?.TerminateDebuggee); + bool terminateDebuggee = request.Arguments?.TerminateDebuggee == true; + + _debuggee?.HandleDisconnect(terminateDebuggee); + + return Task.FromResult(new DisconnectResponse()); + } + + public override Task ContinueAsync(ContinueRequest request, CancellationToken ct) + { + _debuggee.BeginExecution(-1); + return Task.FromResult(new ContinueResponse()); + } + + public override Task NextAsync(NextRequest request, CancellationToken ct) + { + lock (_debuggee) + { + if (!_debuggee.HasExited) + _debuggee.Next(request.Arguments.ThreadId); + } + return Task.FromResult(new NextResponse()); + } + + public override Task StepInAsync(StepInRequest request, CancellationToken ct) + { + lock (_debuggee) + { + if (!_debuggee.HasExited) + _debuggee.StepIn(request.Arguments.ThreadId); + } + return Task.FromResult(new StepInResponse()); + } + + public override Task StepOutAsync(StepOutRequest request, CancellationToken ct) + { + lock (_debuggee) + { + if (!_debuggee.HasExited) + _debuggee.StepOut(request.Arguments.ThreadId); + } + return Task.FromResult(new StepOutResponse()); + } + + public override Task ThreadsAsync(ThreadsRequest request, CancellationToken ct) + { + var processThreads = _debuggee.GetThreads(); + var threads = new EvilBeaver.DAP.Dto.Types.Thread[processThreads.Length]; + for (int i = 0; i < processThreads.Length; i++) + { + threads[i] = new EvilBeaver.DAP.Dto.Types.Thread + { + Id = processThreads[i], + Name = $"Thread {processThreads[i]}" + }; + } + + return Task.FromResult(new ThreadsResponse + { + Body = new ThreadsResponseBody { Threads = threads } + }); + } + + public override Task StackTraceAsync(StackTraceRequest request, CancellationToken ct) + { + var args = request.Arguments; + var firstFrameIdx = args.StartFrame ?? 0; + var limit = args.Levels ?? 0; + var threadId = args.ThreadId; + + var processFrames = _debuggee.GetStackTrace(threadId, firstFrameIdx, limit); + var frames = new EvilBeaver.DAP.Dto.Types.StackFrame[processFrames.Length]; + for (int i = 0; i < processFrames.Length; i++) + { + frames[i] = new EvilBeaver.DAP.Dto.Types.StackFrame + { + Id = _threadState.RegisterFrame(processFrames[i]), + Name = processFrames[i].MethodName, + Source = processFrames[i].GetSource(), + Line = processFrames[i].LineNumber, + Column = 0 + }; + } + + return Task.FromResult(new StackTraceResponse + { + Body = new StackTraceResponseBody + { + StackFrames = frames, + TotalFrames = frames.Length + } + }); + } + + public override Task ScopesAsync(ScopesRequest request, CancellationToken ct) + { + int frameId = request.Arguments.FrameId; + var frame = _threadState.GetFrameById(frameId); + if (frame == null) + { + throw new ErrorResponseException("No active stackframe"); + } + + var scopes = new List(); + + var localProvider = new LocalScopeProvider(frame.ThreadId, frame.Index); + var localHandle = _threadState.RegisterVariablesProvider(localProvider); + scopes.Add(new Scope + { + Name = "Локальные переменные", + VariablesReference = localHandle + }); + + if (_debuggee.ProtocolVersion >= ProtocolVersions.Version4) + { + var moduleProvider = new ModuleScopeProvider(frame.ThreadId, frame.Index); + var moduleHandle = _threadState.RegisterVariablesProvider(moduleProvider); + scopes.Add(new Scope + { + Name = "Переменные модуля", + VariablesReference = moduleHandle + }); + } + + return Task.FromResult(new ScopesResponse + { + Body = new ScopesResponseBody { Scopes = scopes.ToArray() } + }); + } + + public override Task VariablesAsync(VariablesRequest request, CancellationToken ct) + { + int varsHandle = request.Arguments.VariablesReference; + var provider = _threadState.GetVariablesProviderById(varsHandle); + if (provider == null) + { + throw new ErrorResponseException("Invalid variables reference"); + } + + var variables = _debuggee.FetchVariables(provider); + var responseArray = new Variable[variables.Length]; + + for (int i = 0; i < responseArray.Length; i++) + { + var variable = variables[i]; + int childHandle = 0; + + if (variable.IsStructured) + { + var childProvider = provider.CreateChildProvider(i); + childHandle = _threadState.RegisterVariablesProvider(childProvider); + } + + responseArray[i] = new Variable + { + Name = variable.Name, + Value = variable.Presentation, + Type = variable.TypeName, + VariablesReference = childHandle + }; + } + + return Task.FromResult(new VariablesResponse + { + Body = new VariablesResponseBody { Variables = responseArray } + }); + } + + public override Task EvaluateAsync(EvaluateRequest request, CancellationToken ct) + { + var args = request.Arguments; + int frameId = args.FrameId ?? 0; + var frame = _threadState.GetFrameById(frameId); + if (frame == null) + { + throw new ErrorResponseException("No active stackframe"); + } + + var expression = args.Expression; + var context = args.Context; + + int id = 0; + OneScript.DebugProtocol.Variable evalResult; + try + { + evalResult = _debuggee.Evaluate(frame, expression); + if (evalResult.IsStructured) + { + var provider = new EvaluatedExpressionProvider(expression, frame.ThreadId, frame.Index); + id = _threadState.RegisterVariablesProvider(provider); + } + } + catch (Exception e) + { + evalResult = new OneScript.DebugProtocol.Variable() { Presentation = e.Message, Name = "$evalFault" }; + } + + if (evalResult.Name.Equals("$evalFault") && "hover".Equals(context)) + { + evalResult.Presentation = $"err: {expression}"; + } + + return Task.FromResult(new EvaluateResponse + { + Body = new EvaluateResponseBody + { + Result = evalResult.Presentation, + Type = evalResult.TypeName, + VariablesReference = id + } + }); + } + + public override Task SetExceptionBreakpointsAsync(SetExceptionBreakpointsRequest request, CancellationToken ct) + { + var args = request.Arguments; + var filters = new List<(string Id, string Condition)>(); + var acceptedFilters = new List(); + + if (args.Filters != null) + { + foreach (var filter in args.Filters) + { + filters.Add((filter, "")); + acceptedFilters.Add(new Breakpoint { Verified = true }); + } + } + + if (args.FilterOptions != null) + { + foreach (var filterOption in args.FilterOptions) + { + filters.Add((filterOption.FilterId, filterOption.Condition ?? "")); + acceptedFilters.Add(new Breakpoint { Verified = true }); + } + } + + _debuggee.SetExceptionsBreakpoints(filters.ToArray()); + + return Task.FromResult(new SetExceptionBreakpointsResponse + { + Body = new SetExceptionBreakpointsResponseBody + { + Breakpoints = acceptedFilters.ToArray() + } + }); + } + + public override Task AttachAsync(AttachRequest request, CancellationToken ct) + { + var options = request.Arguments.DeserializeAdditionalProperties(); + + _debuggee.DebugPort = options.DebugPort; + _debuggee.PathsMapper = options.PathsMapping; + + SubscribeForDebuggeeProcessEvents(); + + DebugClientFactory debugClientFactory; + try + { + debugClientFactory = ConnectDebugServer(); + } + catch (Exception e) + { + Log.LogError(e, "Can't connect debuggee"); + throw new ErrorResponseException("Can't connect: " + e.ToString()); + } + + _debuggee.SetClient(debugClientFactory.CreateDebugClient()); + try + { + _debuggee.InitAttached(); + } + catch (Exception e) + { + Log.LogError(e, "Attach failed"); + throw new ErrorResponseException("Attach failed: " + e.ToString()); + } + + return Task.FromResult(new AttachResponse()); + } + + public override Task SetBreakpointsAsync(SetBreakpointsRequest request, CancellationToken ct) + { + if (request.Arguments.SourceModified == true) + { + throw new ErrorResponseException("Нельзя установить точку останова на модифицированный файл."); + } + + Debug.Assert(request.Arguments.Source.Path != null, "request.Arguments.Source.Path != null"); + var path = ToNativePath(request.Arguments.Source.Path); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + // vscode иногда передает путь, где диск - маленькая буква + path = Utilities.NormalizeDriveLetter(path); + } + + var useConditions = _debuggee.ProtocolVersion >= ProtocolVersions.Version2; + + Debug.Assert(request.Arguments.Breakpoints != null, "request.Arguments.Breakpoints != null"); + var breaks = request.Arguments.Breakpoints + .Select(srcBreakpoint => new OneScript.DebugProtocol.Breakpoint + { + Line = srcBreakpoint.Line, + Source = path, + Condition = useConditions ? srcBreakpoint.Condition ?? string.Empty : string.Empty + }).ToList(); + + var confirmedBreaks = _debuggee.SetBreakpoints(breaks); + var confirmedDapBreaks = new List(confirmedBreaks.Length); + confirmedDapBreaks.AddRange(confirmedBreaks + .Select(t => new Breakpoint + { + Line = t.Line, + Verified = true + }) + ); + + return Task.FromResult(new SetBreakpointsResponse + { + Body = new SetBreakpointsResponseBody + { + Breakpoints = confirmedDapBreaks.ToArray(), + } + }); + } + + public override async Task LaunchAsync(LaunchRequest request, CancellationToken ct) + { + try + { + Log.LogDebug("Initializing process settings"); + + _debuggee.Init(request.Arguments); + } + catch (InvalidDebugeeOptionsException e) + { + Log.LogError(e, "Wrong options received {ErrorCode}: {Message}", e.ErrorCode, e.Message); + throw new ErrorResponseException(e.Message); + } + + SubscribeForDebuggeeProcessEvents(); + + try + { + Log.LogDebug("Starting debuggee"); + _debuggee.Start(); + Log.LogInformation("Debuggee started"); + } + catch (Exception e) + { + Log.LogError(e, "Can't launch debuggee"); + throw new ErrorResponseException($"Can't launch debuggee ({e.Message})."); + } + + DebugClientFactory debugClientFactory; + try + { + debugClientFactory = ConnectDebugServer(); + } + catch (Exception e) + { + _debuggee.Kill(); + await EventsChannel.SendEventAsync(new TerminatedEvent(), ct); + Log.LogError(e, "Can't connect to debug server"); + throw new ErrorResponseException("Can't connect: " + e.ToString()); + } + + _debuggee.SetClient(debugClientFactory.CreateDebugClient()); + + return new LaunchResponse(); + } + + private void SubscribeForDebuggeeProcessEvents() + { + _debuggee.OutputReceived += (s, e) => + { + Log.LogDebug("Output received {Output}", e.Content); + + if (string.IsNullOrEmpty(e.Content)) + return; + + var data = e.Content; + if (data[data.Length - 1] != '\n') + { + data += '\n'; + } + + EventsChannel.SendEventAsync(new OutputEvent + { + Body = new OutputEventBody + { + Category = e.Category, + Output = data + } + }); + }; + + _debuggee.ProcessExited += (s, e) => + { + Log.LogInformation("Debuggee has exited"); + EventsChannel.SendEventAsync(new TerminatedEvent()); + }; + } + + private DebugClientFactory ConnectDebugServer() + { + var tcpConnection = ConnectionFactory.Connect(_debuggee.DebugPort); + var listener = new OscriptDebugEventsListener(EventsChannel, _threadState); + return new DebugClientFactory(tcpConnection, listener); + } + } +} \ No newline at end of file diff --git a/src/VSCode.DebugAdapter/OscriptDebugEventsListener.cs b/src/VSCode.DebugAdapter/OscriptDebugEventsListener.cs index 1fa55e419..d583596db 100644 --- a/src/VSCode.DebugAdapter/OscriptDebugEventsListener.cs +++ b/src/VSCode.DebugAdapter/OscriptDebugEventsListener.cs @@ -1,25 +1,26 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ using System.Runtime.CompilerServices; +using EvilBeaver.DAP.Dto.Events; +using EvilBeaver.DAP.Server; using OneScript.DebugProtocol; using Serilog; -using VSCodeDebug; namespace VSCode.DebugAdapter { public class OscriptDebugEventsListener : IDebugEventListener { - private readonly DebugSession _session; + private readonly IClientChannel _channel; private readonly ThreadStateContainer _threadState; private readonly ILogger Log = Serilog.Log.ForContext(); - public OscriptDebugEventsListener(DebugSession session, ThreadStateContainer threadState) + public OscriptDebugEventsListener(IClientChannel channel, ThreadStateContainer threadState) { - _session = session; + _channel = channel; _threadState = threadState; } @@ -27,7 +28,15 @@ public void ThreadStopped(int threadId, ThreadStopReason reason) { LogEventOccured(); _threadState.Reset(); - _session.SendEvent(new StoppedEvent(threadId, reason.ToString())); + _channel.SendEventAsync(new StoppedEvent + { + Body = new StoppedEventBody + { + ThreadId = threadId, + Reason = reason.ToString(), + AllThreadsStopped = true + } + }); } public void ThreadStoppedEx(int threadId, ThreadStopReason reason, string errorMessage) @@ -38,13 +47,27 @@ public void ThreadStoppedEx(int threadId, ThreadStopReason reason, string errorM if (!string.IsNullOrEmpty(errorMessage)) SendOutput("stderr", errorMessage); - _session.SendEvent(new StoppedEvent(threadId, reason.ToString())); + _channel.SendEventAsync(new StoppedEvent + { + Body = new StoppedEventBody + { + ThreadId = threadId, + Reason = reason.ToString(), + AllThreadsStopped = true + } + }); } public void ProcessExited(int exitCode) { LogEventOccured(); - _session.SendEvent(new ExitedEvent(exitCode)); + _channel.SendEventAsync(new ExitedEvent + { + Body = new ExitedEventBody + { + ExitCode = exitCode + } + }); } private void SendOutput(string category, string data) @@ -55,7 +78,14 @@ private void SendOutput(string category, string data) { data += '\n'; } - _session.SendEvent(new OutputEvent(category, data)); + _channel.SendEventAsync(new OutputEvent + { + Body = new OutputEventBody + { + Category = category, + Output = data + } + }); } } diff --git a/src/VSCode.DebugAdapter/OscriptDebugSession.cs b/src/VSCode.DebugAdapter/OscriptDebugSession.cs deleted file mode 100644 index 664e4a3e4..000000000 --- a/src/VSCode.DebugAdapter/OscriptDebugSession.cs +++ /dev/null @@ -1,524 +0,0 @@ -/*---------------------------------------------------------- -This Source Code Form is subject to the terms of the -Mozilla Public License, v.2.0. If a copy of the MPL -was not distributed with this file, You can obtain one -at http://mozilla.org/MPL/2.0/. -----------------------------------------------------------*/ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using OneScript.DebugProtocol; -using Serilog; -using VSCode.DebugAdapter.Transport; -using VSCodeDebug; - - -namespace VSCode.DebugAdapter -{ - internal class OscriptDebugSession : DebugSession - { - private DebugeeProcess _debuggee; - private bool _startupPerformed = false; - private ThreadStateContainer _threadState = new ThreadStateContainer(); - - private readonly ILogger Log = Serilog.Log.ForContext(); - - public OscriptDebugSession() : base(true, false) - { - } - - private string AdapterID { get; set; } - - public override void Initialize(Response response, dynamic args) - { - LogCommandReceived(); - AdapterID = (string) args.adapterID; - - _debuggee = DebugeeFactory.CreateProcess(AdapterID, PathStrategy); - - SendResponse(response, new Capabilities - { - supportsConditionalBreakpoints = true, - supportsFunctionBreakpoints = false, - supportsConfigurationDoneRequest = true, - supportsExceptionFilterOptions = true, - exceptionBreakpointFilters = new dynamic[] - { - new - { - filter = "uncaught", - label = "Необработанные исключения", - description = "Остановка при возникновении необработанного исключения", - supportsCondition = true, - conditionDescription = "Искомая подстрока текста исключения" - }, - new - { - filter = "all", - label = "Все исключения", - description = "Остановка при возникновении любого исключения", - supportsCondition = true, - conditionDescription = "Искомая подстрока текста исключения" - } - }, - supportsEvaluateForHovers = true, - supportTerminateDebuggee = true - }); - - SendEvent(new InitializedEvent()); - } - - public override void Launch(Response response, dynamic args) - { - LogCommandReceived(); - try - { - Log.Debug("Initializing process settings"); - _debuggee.Init(args); - } - catch (InvalidDebugeeOptionsException e) - { - Log.Error(e, "Wrong options received {ErrorCode}: {Message}", e.ErrorCode, e.Message); - SendErrorResponse(response, e.ErrorCode, e.Message); - return; - } - - SubscribeForDebuggeeProcessEvents(); - - try - { - Log.Verbose("Starting debuggee"); - _debuggee.Start(); - Log.Information("Debuggee started"); - } - catch (Exception e) - { - Log.Error(e, "Can't launch debuggee"); - SendErrorResponse(response, 3012, "Can't launch debugee ({reason}).", new { reason = e.Message }); - return; - } - - DebugClientFactory debugClientFactory; - try - { - debugClientFactory = ConnectDebugServer(); - } - catch (Exception e) - { - _debuggee.Kill(); - SendEvent(new TerminatedEvent()); - Log.Error(e, "Can't connect to debug server"); - SendErrorResponse(response, 4550, "Can't connect: " + e.ToString()); - return; - } - - _debuggee.SetClient(debugClientFactory.CreateDebugClient()); - - SendResponse(response); - } - - private void SubscribeForDebuggeeProcessEvents() - { - _debuggee.OutputReceived += (s, e) => - { - Log.Debug("Output received {Output}", e.Content); - SendOutput(e.Category, e.Content); - }; - - _debuggee.ProcessExited += (s, e) => - { - Log.Information("Debuggee has exited"); - SendEvent(new TerminatedEvent()); - }; - } - - public override void Attach(Response response, dynamic arguments) - { - LogCommandReceived(); - SubscribeForDebuggeeProcessEvents(); - - _debuggee.DebugPort = GetFromContainer(arguments, "debugPort", 2801); - _debuggee.InitPathsMapper(arguments); - - DebugClientFactory debugClientFactory; - try - { - debugClientFactory = ConnectDebugServer(); - } - catch (Exception e) - { - Log.Error(e, "Can't connect debuggee"); - SendErrorResponse(response, 4550, "Can't connect: " + e.ToString()); - return; - } - - _debuggee.SetClient(debugClientFactory.CreateDebugClient()); - try - { - _debuggee.InitAttached(); - } - catch (Exception e) - { - Log.Error(e, "Attach failed"); - SendErrorResponse(response, 4550, "Attach failed: " + e.ToString()); - return; - } - - SendResponse(response); - } - - private DebugClientFactory ConnectDebugServer() - { - var tcpConnection = ConnectionFactory.Connect(_debuggee.DebugPort); - var listener = new OscriptDebugEventsListener(this, _threadState); - return new DebugClientFactory(tcpConnection, listener); - } - - public override void Disconnect(Response response, dynamic arguments) - { - LogCommandReceived(new { Terminate = arguments.terminateDebuggee }); - bool terminateDebuggee = arguments.terminateDebuggee == true; - - _debuggee.HandleDisconnect(terminateDebuggee); - SendResponse(response); - } - - public override void SetExceptionBreakpoints(Response response, dynamic arguments) - { - LogCommandReceived(); - var acceptedFilters = new List(); - var filters = new List<(string Id, string Condition)>(); - - foreach(var filter in arguments.filters) - { - filters.Add((filter, "")); - acceptedFilters.Add(new VSCodeDebug.Breakpoint(true)); - } - - foreach (var filterOption in arguments.filterOptions) - { - filters.Add((filterOption.filterId, filterOption.condition ?? "")); - acceptedFilters.Add(new VSCodeDebug.Breakpoint(true)); - } - - _debuggee.SetExceptionsBreakpoints(filters.ToArray()); - - SendResponse(response, new SetExceptionBreakpointsResponseBody(acceptedFilters)); - } - - public override void SetBreakpoints(Response response, dynamic arguments) - { - LogCommandReceived(); - - if ((bool)arguments.sourceModified) - { - if (_startupPerformed) - { - SendErrorResponse(response, 1102, "Нельзя установить точку останова на модифицированный файл."); - return; - } - SendResponse(response, new SetBreakpointsResponseBody()); - return; - } - - var path = (string) arguments.source.path; - path = ConvertClientPathToDebugger(path); - if (Environment.OSVersion.Platform == PlatformID.Win32NT) - { - // vscode иногда передает путь, где диск - маленькая буква - path = NormalizeDriveLetter(path); - } - - var breaks = new List(); - - var useConditions = _debuggee.ProtocolVersion >= ProtocolVersions.Version2; - - foreach (var srcBreakpoint in arguments.breakpoints) - { - var bpt = new OneScript.DebugProtocol.Breakpoint - { - Line = (int)srcBreakpoint.line, - Source = path, - Condition = useConditions ? srcBreakpoint.condition ?? string.Empty : string.Empty - }; - breaks.Add(bpt); - } - - if(breaks.Count == 0) // в целях сохранения интерфейса WCF придется сделать костыль на перех. период - { - var bpt = new OneScript.DebugProtocol.Breakpoint - { - Line = 0, - Source = path - }; - breaks.Add(bpt); - } - - var confirmedBreaks = _debuggee.SetBreakpoints(breaks); - var confirmedBreaksVSCode = new List(confirmedBreaks.Length); - for (int i = 0; i < confirmedBreaks.Length; i++) - { - confirmedBreaksVSCode.Add(new VSCodeDebug.Breakpoint(true, confirmedBreaks[i].Line)); - } - - SendResponse(response, new SetBreakpointsResponseBody(confirmedBreaksVSCode)); - } - - private string NormalizeDriveLetter(string path) - { - if (Path.IsPathRooted(path)) - return path[0].ToString().ToUpperInvariant() + path.Substring(1); - else - return path; - - } - - public override void ConfigurationDone(Response response, dynamic args) - { - if (_debuggee == null) - { - Log.Debug("Config Done. Process is not started"); - SendResponse(response); - return; - } - Log.Debug("Config Done. Process is started, sending Execute"); - _debuggee.BeginExecution(-1); - _startupPerformed = true; - SendResponse(response); - } - - public override void Continue(Response response, dynamic arguments) - { - LogCommandReceived(); - SendResponse(response); - _debuggee.BeginExecution(-1); - } - - public override void Next(Response response, dynamic arguments) - { - LogCommandReceived(); - SendResponse(response); - lock (_debuggee) - { - if (!_debuggee.HasExited) - { - _debuggee.Next((int)arguments.threadId); - } - } - - } - - public override void StepIn(Response response, dynamic arguments) - { - LogCommandReceived(); - SendResponse(response); - lock (_debuggee) - { - if (!_debuggee.HasExited) - { - _debuggee.StepIn((int)arguments.threadId); - } - } - } - - public override void StepOut(Response response, dynamic arguments) - { - LogCommandReceived(); - SendResponse(response); - lock (_debuggee) - { - if (!_debuggee.HasExited) - { - _debuggee.StepOut((int)arguments.threadId); - } - } - } - - public override void Pause(Response response, dynamic arguments) - { - LogCommandReceived(); - throw new NotImplementedException(); - } - - public override void StackTrace(Response response, dynamic arguments) - { - LogCommandReceived(); - var firstFrameIdx = (int?)arguments.startFrame ?? 0; - var limit = (int?) arguments.levels ?? 0; - var threadId = (int) arguments.threadId; - var processFrames = _debuggee.GetStackTrace(threadId, firstFrameIdx, limit); - var frames = new VSCodeDebug.StackFrame[processFrames.Length]; - for (int i = 0; i < processFrames.Length; i++) - { - frames[i] = new VSCodeDebug.StackFrame( - _threadState.RegisterFrame(processFrames[i]), - processFrames[i].MethodName, - processFrames[i].GetSource(), - processFrames[i].LineNumber, 0); - } - - SendResponse(response, new StackTraceResponseBody(frames)); - } - - public override void Scopes(Response response, dynamic arguments) - { - LogCommandReceived(); - int frameId = GetFromContainer(arguments, "frameId", 0); - var frame = _threadState.GetFrameById(frameId); - if (frame == null) - { - SendErrorResponse(response, 10001, "No active stackframe"); - return; - } - - var scopes = new List(); - - // Scope 1: Локальные переменные - var localProvider = new LocalScopeProvider(frame.ThreadId, frame.Index); - var localHandle = _threadState.RegisterVariablesProvider(localProvider); - scopes.Add(new Scope("Локальные переменные", localHandle)); - - // Scope 2: Переменные модуля (начиная с протокола версии 4) - if (_debuggee.ProtocolVersion >= ProtocolVersions.Version4) - { - var moduleProvider = new ModuleScopeProvider(frame.ThreadId, frame.Index); - var moduleHandle = _threadState.RegisterVariablesProvider(moduleProvider); - scopes.Add(new Scope("Переменные модуля", moduleHandle)); - } - - SendResponse(response, new ScopesResponseBody(scopes.ToArray())); - } - - public override void Variables(Response response, dynamic arguments) - { - LogCommandReceived(); - int varsHandle = GetFromContainer(arguments, "variablesReference", 0); - var provider = _threadState.GetVariablesProviderById(varsHandle); - if (provider == null) - { - SendErrorResponse(response, 10001, "Invalid variables reference"); - return; - } - - // Получаем переменные через провайдер - var variables = _debuggee.FetchVariables(provider); - var responseArray = new VSCodeDebug.Variable[variables.Length]; - - for (int i = 0; i < responseArray.Length; i++) - { - var variable = variables[i]; - int childHandle = 0; - - if (variable.IsStructured) - { - var childProvider = provider.CreateChildProvider(i); - childHandle = _threadState.RegisterVariablesProvider(childProvider); - } - - responseArray[i] = new VSCodeDebug.Variable( - variable.Name, - variable.Presentation, - variable.TypeName, - childHandle); - } - - SendResponse(response, new VariablesResponseBody(responseArray)); - } - - public override void Threads(Response response, dynamic arguments) - { - LogCommandReceived(); - var threads = new List(); - var processThreads = _debuggee.GetThreads(); - for (int i = 0; i < processThreads.Length; i++) - { - threads.Add(new VSCodeDebug.Thread(processThreads[i], $"Thread {processThreads[i]}")); - } - - SendResponse(response, new ThreadsResponseBody(threads)); - } - - public override void Evaluate(Response response, dynamic arguments) - { - LogCommandReceived(); - // expression, frameId, context - int frameId = GetFromContainer(arguments, "frameId", 0); - var frame = _threadState.GetFrameById(frameId); - if (frame == null) - { - SendErrorResponse(response, 10001, "No active stackframe"); - return; - } - - var expression = (string) arguments.expression; - var context = (string) arguments.context; - - Log.Debug("Evaluate {Expression} in {Context}", expression, context); - - int id = 0; - OneScript.DebugProtocol.Variable evalResult; - try - { - evalResult = _debuggee.Evaluate(frame, expression); - - if (evalResult.IsStructured) - { - var provider = new EvaluatedExpressionProvider(expression, frame.ThreadId, frame.Index); - id = _threadState.RegisterVariablesProvider(provider); - } - } - catch (Exception e) - { - evalResult = new OneScript.DebugProtocol.Variable() { Presentation = e.Message, Name = "$evalFault" }; - } - - if (evalResult.Name.Equals("$evalFault") && context.Equals("hover")) - { - evalResult.Presentation = $"err: {expression}"; - } - - var protResult = new EvaluateResponseBody(evalResult.Presentation, id) {type = evalResult.TypeName}; - SendResponse(response, protResult); - } - - - private void SendOutput(string category, string data) - { - if (!String.IsNullOrEmpty(data)) - { - if (data[data.Length - 1] != '\n') - { - data += '\n'; - } - SendEvent(new OutputEvent(category, data)); - } - } - - private static T GetFromContainer(dynamic container, string propertyName, T defaultValue = default) - { - try - { - return (T)container[propertyName]; - } - catch (Exception) - { - // ignore and return default value - } - return defaultValue; - } - - protected override void OnRequestError(Exception e) - { - Log.Error(e, "Unhandled request processing error"); - } - - private void LogCommandReceived(dynamic args = null, [CallerMemberName] string commandName = "") - { - if (args == null) - Log.Debug("Command received {Command}", commandName); - else - Log.Debug("Command received {Command}: {@Args}", commandName, args); - } - } -} diff --git a/src/VSCode.DebugAdapter/Program.cs b/src/VSCode.DebugAdapter/Program.cs index da026d34d..9cae5a4e7 100644 --- a/src/VSCode.DebugAdapter/Program.cs +++ b/src/VSCode.DebugAdapter/Program.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -9,7 +9,10 @@ This Source Code Form is subject to the terms of the using System.IO; using System.Linq; using System.Net.Sockets; +using EvilBeaver.DAP.Server; +using EvilBeaver.DAP.Server.Transport; using Serilog; +using Serilog.Extensions.Logging; namespace VSCode.DebugAdapter { @@ -47,13 +50,15 @@ private static void StartSession(Stream input, Stream output) .WriteTo.File(file, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj} ({SourceContext}){NewLine}{Exception}") .CreateLogger(); } - - var session = new OscriptDebugSession(); + + var factory = new SerilogLoggerFactory(Log.Logger); + var adapter = new OneScriptDebugAdapter(factory.CreateLogger("OneScriptDebugAdapter")); + var dapServer = new DapServer(new BufferedTransport(input, output), adapter, factory); try { Log.Logger.Information("Starting debug adapter"); - session.Start(input, output); + dapServer.RunAsync(System.Threading.CancellationToken.None).GetAwaiter().GetResult(); } catch (Exception e) { diff --git a/src/VSCode.DebugAdapter/Protocol.cs b/src/VSCode.DebugAdapter/Protocol.cs deleted file mode 100644 index fad6cd03b..000000000 --- a/src/VSCode.DebugAdapter/Protocol.cs +++ /dev/null @@ -1,270 +0,0 @@ -/*---------------------------------------------------------- -This Source Code Form is subject to the terms of the -Mozilla Public License, v.2.0. If a copy of the MPL -was not distributed with this file, You can obtain one -at http://mozilla.org/MPL/2.0/. -----------------------------------------------------------*/ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -using System; -using System.Text; -using System.IO; -using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Serilog; - -namespace VSCodeDebug -{ - public class ProtocolMessage - { - public int seq; - public string type { get; } - - public ProtocolMessage(string typ) { - type = typ; - } - - public ProtocolMessage(string typ, int sq) { - type = typ; - seq = sq; - } - } - - public class Request : ProtocolMessage - { - public string command; - public dynamic arguments; - - public Request(int id, string cmd, dynamic arg) : base("request", id) { - command = cmd; - arguments = arg; - } - } - - /* - * subclasses of ResponseBody are serialized as the body of a response. - * Don't change their instance variables since that will break the debug protocol. - */ - public class ResponseBody { - // empty - } - - public class Response : ProtocolMessage - { - public bool success { get; private set; } - public string message { get; private set; } - public int request_seq { get; } - public string command { get; } - public ResponseBody body { get; private set; } - - public Response(Request req) : base("response") { - success = true; - request_seq = req.seq; - command = req.command; - } - - public void SetBody(ResponseBody bdy) { - success = true; - body = bdy; - } - - public void SetErrorBody(string msg, ResponseBody bdy = null) { - success = false; - message = msg; - body = bdy; - } - } - - public class Event : ProtocolMessage - { - [JsonProperty(PropertyName = "event")] - public string eventType { get; } - public dynamic body { get; } - - public Event(string type, dynamic bdy = null) : base("event") { - eventType = type; - body = bdy; - } - } - - /* - * The ProtocolServer can be used to implement a server that uses the VSCode debug protocol. - */ - public abstract class ProtocolServer - { - protected const int BUFFER_SIZE = 4096; - protected const string TWO_CRLF = "\r\n\r\n"; - protected static readonly Regex CONTENT_LENGTH_MATCHER = new Regex(@"Content-Length: (\d+)"); - - protected static readonly Encoding Encoding = System.Text.Encoding.UTF8; - - private int _sequenceNumber = 1; - - private Stream _outputStream; - - private ByteBuffer _rawData = new ByteBuffer(); - private int _bodyLength = -1; - - private bool _stopRequested; - - - public void Start(Stream inputStream, Stream outputStream) - { - _outputStream = outputStream; - - byte[] buffer = new byte[BUFFER_SIZE]; - - _stopRequested = false; - while (!_stopRequested) { - var read = inputStream.Read(buffer, 0, buffer.Length); - - if (read == 0) { - // end of stream - break; - } - - if (read > 0) { - _rawData.Append(buffer, read); - ProcessData(); - } - } - } - - public void Stop() - { - _stopRequested = true; - } - - public void SendEvent(Event e) - { - SendMessage(e); - } - - protected abstract void DispatchRequest(string command, dynamic args, Response response); - - // ---- private ------------------------------------------------------------------------ - - private void ProcessData() - { - while (true) { - if (_bodyLength >= 0) { - if (_rawData.Length >= _bodyLength) { - var buf = _rawData.RemoveFirst(_bodyLength); - - _bodyLength = -1; - - Dispatch(Encoding.GetString(buf)); - - continue; // there may be more complete messages to process - } - } - else { - string s = _rawData.GetString(Encoding); - var idx = s.IndexOf(TWO_CRLF); - if (idx != -1) { - Match m = CONTENT_LENGTH_MATCHER.Match(s); - if (m.Success && m.Groups.Count == 2) { - _bodyLength = Convert.ToInt32(m.Groups[1].ToString()); - - _rawData.RemoveFirst(idx + TWO_CRLF.Length); - - continue; // try to handle a complete message - } - } - } - break; - } - } - - private void Dispatch(string req) - { - var request = JsonConvert.DeserializeObject(req); - if (request != null && request.type == "request") { - Log.Verbose("Got {Command} with args {Args}", request.command, JsonConvert.SerializeObject(request.arguments)); - - var response = new Response(request); - - DispatchRequest(request.command, request.arguments, response); - - SendMessage(response); - } - } - - protected void SendMessage(ProtocolMessage message) - { - message.seq = _sequenceNumber++; - - if (message.type == "response") { - Log.Verbose("Response {Response}", JsonConvert.SerializeObject(message)); - } - if (message.type == "event") { - Event e = (Event)message; - Log.Verbose("Event {EventType} with args {Args}", e.eventType, JsonConvert.SerializeObject(e.body)); - } - - var data = ConvertToBytes(message); - try { - _outputStream.Write(data, 0, data.Length); - _outputStream.Flush(); - } - catch (Exception) { - // ignore - } - } - - private static byte[] ConvertToBytes(ProtocolMessage request) - { - var asJson = JsonConvert.SerializeObject(request); - byte[] jsonBytes = Encoding.GetBytes(asJson); - - string header = string.Format("Content-Length: {0}{1}", jsonBytes.Length, TWO_CRLF); - byte[] headerBytes = Encoding.GetBytes(header); - - byte[] data = new byte[headerBytes.Length + jsonBytes.Length]; - System.Buffer.BlockCopy(headerBytes, 0, data, 0, headerBytes.Length); - System.Buffer.BlockCopy(jsonBytes, 0, data, headerBytes.Length, jsonBytes.Length); - - return data; - } - } - - //-------------------------------------------------------------------------------------- - - class ByteBuffer - { - private byte[] _buffer; - - public ByteBuffer() { - _buffer = new byte[0]; - } - - public int Length { - get { return _buffer.Length; } - } - - public string GetString(Encoding enc) - { - return enc.GetString(_buffer); - } - - public void Append(byte[] b, int length) - { - byte[] newBuffer = new byte[_buffer.Length + length]; - System.Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _buffer.Length); - System.Buffer.BlockCopy(b, 0, newBuffer, _buffer.Length, length); - _buffer = newBuffer; - } - - public byte[] RemoveFirst(int n) - { - byte[] b = new byte[n]; - System.Buffer.BlockCopy(_buffer, 0, b, 0, n); - byte[] newBuffer = new byte[_buffer.Length - n]; - System.Buffer.BlockCopy(_buffer, n, newBuffer, 0, _buffer.Length - n); - _buffer = newBuffer; - return b; - } - } -} diff --git a/src/VSCode.DebugAdapter/ProtocolExtensions.cs b/src/VSCode.DebugAdapter/ProtocolExtensions.cs index 21fc5fbfb..c013b2bcd 100644 --- a/src/VSCode.DebugAdapter/ProtocolExtensions.cs +++ b/src/VSCode.DebugAdapter/ProtocolExtensions.cs @@ -5,7 +5,8 @@ This Source Code Form is subject to the terms of the at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ -using VSCodeDebug; +using System.IO; +using EvilBeaver.DAP.Dto.Types; using StackFrame = OneScript.DebugProtocol.StackFrame; namespace VSCode.DebugAdapter @@ -23,14 +24,19 @@ public static Source GetSource(this StackFrame frame) { if (frame.IsStringModule()) { - return new Source(frame.Source, null) + return new Source { - origin = frame.Source, - presentationHint = "deemphasize" + Name = frame.Source, + Origin = frame.Source, + PresentationHint = "deemphasize" }; } - return new Source(frame.Source); + return new Source + { + Name = Path.GetFileName(frame.Source), + Path = frame.Source + }; } } -} \ No newline at end of file +} diff --git a/src/VSCode.DebugAdapter/ServerProcess.cs b/src/VSCode.DebugAdapter/ServerProcess.cs index 844fea9d6..644bf20aa 100644 --- a/src/VSCode.DebugAdapter/ServerProcess.cs +++ b/src/VSCode.DebugAdapter/ServerProcess.cs @@ -8,7 +8,8 @@ This Source Code Form is subject to the terms of the using System.Collections.Generic; using System.Diagnostics; using System.IO; -using Newtonsoft.Json.Linq; +using EvilBeaver.DAP.Dto.Requests; +using EvilBeaver.DAP.Dto.Serialization; namespace VSCode.DebugAdapter { @@ -51,10 +52,13 @@ protected override Process CreateProcess() return process; } - protected override void InitInternal(JObject args) + public override void Init(LaunchRequestArguments args) + { + InitInternal(args.DeserializeAdditionalProperties()); + } + + private void InitInternal(WebLaunchOptions options) { - var options = args.ToObject(); - // validate argument 'cwd' var workingDirectory = options.AppDir; if (workingDirectory != null) diff --git a/src/VSCode.DebugAdapter/Utilities.cs b/src/VSCode.DebugAdapter/Utilities.cs index a98912f4b..e628ceb27 100644 --- a/src/VSCode.DebugAdapter/Utilities.cs +++ b/src/VSCode.DebugAdapter/Utilities.cs @@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the ----------------------------------------------------------*/ using System; using System.Collections.Generic; +using System.IO; using System.Reflection; using System.Text; using System.Text.RegularExpressions; @@ -66,5 +67,14 @@ public static Encoding GetEncodingFromOptions(string optionsValue) return Encoding.GetEncoding(optionsValue); } + + public static string NormalizeDriveLetter(string path) + { + if (Path.IsPathRooted(path)) + return path[0].ToString().ToUpperInvariant() + path.Substring(1); + else + return path; + + } } } diff --git a/src/VSCode.DebugAdapter/VSCode.DebugAdapter.csproj b/src/VSCode.DebugAdapter/VSCode.DebugAdapter.csproj index 7ca054e8a..d342b399d 100644 --- a/src/VSCode.DebugAdapter/VSCode.DebugAdapter.csproj +++ b/src/VSCode.DebugAdapter/VSCode.DebugAdapter.csproj @@ -22,9 +22,10 @@ + - + @@ -43,4 +44,9 @@ + + + + + \ No newline at end of file diff --git a/src/VSCode.DebugAdapter/WorkspaceMapper.cs b/src/VSCode.DebugAdapter/WorkspaceMapper.cs index e9d60018a..d16590507 100644 --- a/src/VSCode.DebugAdapter/WorkspaceMapper.cs +++ b/src/VSCode.DebugAdapter/WorkspaceMapper.cs @@ -12,7 +12,6 @@ namespace VSCode.DebugAdapter { public class WorkspaceMapper { - private Workspace _localWorkspace; private Workspace _remoteWorkspace; diff --git a/src/VSCode.DebugAdapter/package.json b/src/VSCode.DebugAdapter/package.json index 931c390c5..a0938e592 100644 --- a/src/VSCode.DebugAdapter/package.json +++ b/src/VSCode.DebugAdapter/package.json @@ -1,7 +1,7 @@ { "name": "oscript-debug", "displayName": "OneScript Debug (BSL)", - "version": "1.0.0", + "version": "1.1.0", "publisher": "EvilBeaver", "description": "Visual Studio Code debugger extension for OneScript (BSL)", "icon": "images/logo-dbg.png", diff --git a/src/oscommon.targets b/src/oscommon.targets index d47770e3a..019db5bd6 100644 --- a/src/oscommon.targets +++ b/src/oscommon.targets @@ -26,7 +26,6 @@ 2.0.0 $(VersionPrefix).$(BuildNumber) $(VersionPrefix)-$(VersionSuffix) - $(VersionPrefix)+$(BuildNumber) 1C (BSL) language runtime Copyright (c) 2021 EvilBeaver diff --git a/src/oscript/ConsoleHostBuilder.cs b/src/oscript/ConsoleHostBuilder.cs index 8554a70f4..675179567 100644 --- a/src/oscript/ConsoleHostBuilder.cs +++ b/src/oscript/ConsoleHostBuilder.cs @@ -1,4 +1,4 @@ -/*---------------------------------------------------------- +/*---------------------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v.2.0. If a copy of the MPL was not distributed with this file, You can obtain one @@ -21,8 +21,8 @@ public static IEngineBuilder Create(string codePath) .SetupConfiguration(p => { p.UseSystemConfigFile() - .UseEnvironmentVariableConfig("OSCRIPT_CONFIG") - .UseEntrypointConfigFile(codePath); + .UseEntrypointConfigFile(codePath) + .UseEnvironmentVariableConfig("OSCRIPT_CONFIG"); }); BuildUpWithIoC(builder); diff --git a/src/oscript/ShowUsageBehavior.cs b/src/oscript/ShowUsageBehavior.cs index 5b5baba3a..ee1c26a57 100644 --- a/src/oscript/ShowUsageBehavior.cs +++ b/src/oscript/ShowUsageBehavior.cs @@ -14,22 +14,37 @@ public override int Execute() Output.WriteLine($"1Script Execution Engine. Version {Program.GetVersion()}"); Output.WriteLine(); Output.WriteLine("Usage:"); + Output.WriteLine(" oscript.exe [options] [script_arguments...]"); + Output.WriteLine(" oscript.exe [mode_options] [script_arguments...]"); Output.WriteLine(); - Output.WriteLine("I. Script execution: oscript.exe [script arguments..]"); - Output.WriteLine(); - Output.WriteLine("II. Special mode: oscript.exe [script arguments..]"); - Output.WriteLine("Mode can be one of these:"); - Output.WriteLine($" {"-measure",-12}measures execution time"); - Output.WriteLine($" {"-compile",-12}shows compiled module without execution"); - Output.WriteLine($" {"-check [-env=]",-12}provides syntax check"); - Output.WriteLine($" {"-check -cgi",-12}provides syntax check in CGI-mode"); - Output.WriteLine($" {"-version",-12}output version string"); + + const int modeWidth = -18; + const int subOptionWidth = -14; + + Output.WriteLine("Modes:"); + Output.WriteLine($" {"-measure",modeWidth} Measures script execution time."); + Output.WriteLine($" {"-compile",modeWidth} Shows compiled module without execution."); + Output.WriteLine($" {"-check",modeWidth} Provides syntax check."); + Output.WriteLine($" {"",modeWidth} Options:"); + Output.WriteLine($" {"",modeWidth} {"-cgi",subOptionWidth} Syntax check in CGI-mode."); + Output.WriteLine($" {"",modeWidth} {"-env=",subOptionWidth} Path to entrypoint file for context."); + + Output.WriteLine($" {"-debug",modeWidth} Runs script in debug mode."); + Output.WriteLine($" {"",modeWidth} Options:"); + Output.WriteLine($" {"",modeWidth} {"-port=",subOptionWidth} Debugger port (default is 2801)."); + Output.WriteLine($" {"",modeWidth} {"-noWait",subOptionWidth} Do not wait for debugger connection."); + + Output.WriteLine($" {"-version, -v",modeWidth} Output version string."); Output.WriteLine(); - Output.WriteLine(" -encoding= set output encoding"); - Output.WriteLine(" -codestat= write code statistics"); + + Output.WriteLine("Options:"); + Output.WriteLine($" {"-encoding=",modeWidth} Set output encoding (e.g. utf-8)."); + Output.WriteLine($" {"-codestat=",modeWidth} Write code execution statistics to file."); Output.WriteLine(); - Output.WriteLine("III. Run as CGI application: oscript.exe -cgi [script arguments..]"); - Output.WriteLine(" Runs as CGI application under HTTP-server (Apache/Nginx/IIS/etc...)"); + + Output.WriteLine("CGI Mode:"); + Output.WriteLine(" oscript.exe -cgi [script_arguments...]"); + Output.WriteLine(" Runs as CGI application under HTTP-server."); return 0; } diff --git a/src/oscript/Web/Multipart/BinaryStreamStack.cs b/src/oscript/Web/Multipart/BinaryStreamStack.cs index eac9dbb10..bb07961cc 100644 --- a/src/oscript/Web/Multipart/BinaryStreamStack.cs +++ b/src/oscript/Web/Multipart/BinaryStreamStack.cs @@ -1,404 +1,404 @@ -// -------------------------------------------------------------------------------------------------------------------- -// -// Copyright (c) 2013 Jake Woods -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software -// and associated documentation files (the "Software"), to deal in the Software without restriction, -// including without limitation the rights to use, copy, modify, merge, publish, distribute, -// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software -// is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies -// or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR -// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// -// Jake Woods -// -// Provides character based and byte based stream-like read operations over multiple -// streams and provides methods to add data to the front of the buffer. -// -// -------------------------------------------------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace HttpMultipartParser -{ - /// - /// Provides character based and byte based stream-like read operations over multiple - /// streams and provides methods to add data to the front of the buffer. - /// - internal class BinaryStreamStack - { - #region Fields - - /// - /// Holds the streams to read from, the stream on the top of the - /// stack will be read first. - /// - private readonly Stack streams = new Stack(); - - #endregion - - #region Constructors and Destructors - - /// - /// Initializes a new instance of the class with the default - /// encoding of UTF8. - /// - public BinaryStreamStack() - : this(Encoding.UTF8) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The encoding to use for character based operations. - /// - public BinaryStreamStack(Encoding encoding) - { - CurrentEncoding = encoding; - } - - #endregion - - #region Public Properties - - /// - /// Gets or sets the current encoding. - /// - public Encoding CurrentEncoding { get; set; } - - #endregion - - #region Public Methods and Operators - - /// - /// Returns true if there is any data left to read. - /// - /// - /// True or false. - /// - public bool HasData() - { - return streams.Any(); - } - - /// - /// Returns the reader on the top of the stack but does not remove it. - /// - /// - /// The . - /// - public BinaryReader Peek() - { - return streams.Peek(); - } - - /// - /// Returns the reader on the top of the stack and removes it - /// - /// - /// The . - /// - public BinaryReader Pop() - { - return streams.Pop(); - } - - /// - /// Pushes data to the front of the stack. The most recently pushed data will - /// be read first. - /// - /// - /// The data to add to the stack. - /// - public void Push(byte[] data) - { - streams.Push(new BinaryReader(new MemoryStream(data), CurrentEncoding)); - } - - /// - /// Reads a single byte as an integer from the stack. Returns -1 if no - /// data is left to read. - /// - /// - /// The that was read. - /// - public int Read() - { - BinaryReader top = streams.Peek(); - - int value; - while ((value = top.Read()) == -1) - { - top.Dispose(); - streams.Pop(); - - if (!streams.Any()) - { - return -1; - } - - top = streams.Peek(); - } - - return value; - } - - /// - /// Reads the specified number of bytes from the stack, starting from a specified point in the byte array. - /// - /// - /// The buffer to read data into. - /// - /// - /// The index of buffer to start reading into. - /// - /// - /// The number of bytes to read into the buffer. - /// - /// - /// The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, - /// or it might be zero if the end of the stream is reached. - /// - public int Read(byte[] buffer, int index, int count) - { - if (!HasData()) - { - return 0; - } - - // Read through all the stream untill we exhaust them - // or untill count is satisfied - int amountRead = 0; - BinaryReader top = streams.Peek(); - while (amountRead < count && streams.Any()) - { - int read = top.Read(buffer, index + amountRead, count - amountRead); - if (read == 0) - { - if ((top = NextStream()) == null) - { - return amountRead; - } - } - else - { - amountRead += read; - } - } - - return amountRead; - } - - /// - /// Reads the specified number of characters from the stack, starting from a specified point in the byte array. - /// - /// - /// The buffer to read data into. - /// - /// - /// The index of buffer to start reading into. - /// - /// - /// The number of characters to read into the buffer. - /// - /// - /// The number of characters read into buffer. This might be less than the number of bytes requested if that many bytes are not available, - /// or it might be zero if the end of the stream is reached. - /// - public int Read(char[] buffer, int index, int count) - { - if (!HasData()) - { - return 0; - } - - // Read through all the stream untill we exhaust them - // or untill count is satisfied - int amountRead = 0; - BinaryReader top = streams.Peek(); - while (amountRead < count && streams.Any()) - { - int read = top.Read(buffer, index + amountRead, count - amountRead); - if (read == 0) - { - if ((top = NextStream()) == null) - { - return amountRead; - } - } - else - { - amountRead += read; - } - } - - return amountRead; - } - - /// - /// Reads the specified number of characters from the stack, starting from a specified point in the byte array. - /// - /// - /// A byte array containing all the data up to but not including the next newline in the stack. - /// - public byte[] ReadByteLine() - { - bool dummy; - return ReadByteLine(out dummy); - } - - /// - /// Reads a line from the stack delimited by the newline for this platform. The newline - /// characters will not be included in the stream - /// - /// - /// This will be set to true if we did not end on a newline but instead found the end of - /// our data. - /// - /// - /// The containing the line. - /// - public byte[] ReadByteLine(out bool hitStreamEnd) - { - hitStreamEnd = false; - if (!HasData()) - { - // No streams, no data! - return null; - } - - // This is horribly inefficient, consider profiling here if - // it becomes an issue. - BinaryReader top = streams.Peek(); - byte[] ignore = CurrentEncoding.GetBytes(new[] {'\r'}); - byte[] search = CurrentEncoding.GetBytes(new[] {'\n'}); - int searchPos = 0; - var builder = new MemoryStream(); - - while (true) - { - // First we need to read a byte from one of the streams - var bytes = new byte[search.Length]; - int amountRead = top.Read(bytes, 0, bytes.Length); - while (amountRead == 0) - { - streams.Pop(); - if (!streams.Any()) - { - hitStreamEnd = true; - return builder.ToArray(); - } - - top.Dispose(); - top = streams.Peek(); - amountRead = top.Read(bytes, 0, bytes.Length); - } - - // Now we've got some bytes, we need to check it against the search array. - foreach (byte b in bytes) - { - if (ignore.Contains(b)) - { - continue; - } - - if (b == search[searchPos]) - { - searchPos += 1; - } - else - { - // We only want to append the information if it's - // not part of the newline sequence - if (searchPos != 0) - { - byte[] append = search.Take(searchPos).ToArray(); - builder.Write(append, 0, append.Length); - } - - builder.Write(new[] {b}, 0, 1); - searchPos = 0; - } - - // Finally if we've found our string - if (searchPos == search.Length) - { - return builder.ToArray(); - } - } - } - } - - /// - /// Reads a line from the stack delimited by the newline for this platform. The newline - /// characters will not be included in the stream - /// - /// - /// The containing the line. - /// - public string ReadLine() - { - bool dummy; - return ReadLine(out dummy); - } - - /// - /// Reads a line from the stack delimited by the newline for this platform. The newline - /// characters will not be included in the stream - /// - /// - /// This will be set to true if we did not end on a newline but instead found the end of - /// our data. - /// - /// - /// The containing the line. - /// - public string ReadLine(out bool hitStreamEnd) - { - bool foundEnd; - byte[] result = ReadByteLine(out foundEnd); - hitStreamEnd = foundEnd; - - if (result == null) - { - return null; - } - - return CurrentEncoding.GetString(result); - } - - #endregion - - #region Methods - - /// - /// Removes the current reader from the stack and ensures it is correctly - /// destroyed and then returns the next available reader. If no reader - /// is available this method returns null. - /// - /// - /// The next reader. - /// - private BinaryReader NextStream() - { - BinaryReader top = streams.Pop(); - top.Dispose(); - - return streams.Any() ? streams.Peek() : null; - } - - #endregion - } +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2013 Jake Woods +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software +// and associated documentation files (the "Software"), to deal in the Software without restriction, +// including without limitation the rights to use, copy, modify, merge, publish, distribute, +// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies +// or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Jake Woods +// +// Provides character based and byte based stream-like read operations over multiple +// streams and provides methods to add data to the front of the buffer. +// +// -------------------------------------------------------------------------------------------------------------------- + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace HttpMultipartParser +{ + /// + /// Provides character based and byte based stream-like read operations over multiple + /// streams and provides methods to add data to the front of the buffer. + /// + internal class BinaryStreamStack + { + #region Fields + + /// + /// Holds the streams to read from, the stream on the top of the + /// stack will be read first. + /// + private readonly Stack streams = new Stack(); + + #endregion + + #region Constructors and Destructors + + /// + /// Initializes a new instance of the class with the default + /// encoding of UTF8. + /// + public BinaryStreamStack() + : this(Encoding.UTF8) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The encoding to use for character based operations. + /// + public BinaryStreamStack(Encoding encoding) + { + CurrentEncoding = encoding; + } + + #endregion + + #region Public Properties + + /// + /// Gets or sets the current encoding. + /// + public Encoding CurrentEncoding { get; set; } + + #endregion + + #region Public Methods and Operators + + /// + /// Returns true if there is any data left to read. + /// + /// + /// True or false. + /// + public bool HasData() + { + return streams.Count != 0; + } + + /// + /// Returns the reader on the top of the stack but does not remove it. + /// + /// + /// The . + /// + public BinaryReader Peek() + { + return streams.Peek(); + } + + /// + /// Returns the reader on the top of the stack and removes it + /// + /// + /// The . + /// + public BinaryReader Pop() + { + return streams.Pop(); + } + + /// + /// Pushes data to the front of the stack. The most recently pushed data will + /// be read first. + /// + /// + /// The data to add to the stack. + /// + public void Push(byte[] data) + { + streams.Push(new BinaryReader(new MemoryStream(data), CurrentEncoding)); + } + + /// + /// Reads a single byte as an integer from the stack. Returns -1 if no + /// data is left to read. + /// + /// + /// The that was read. + /// + public int Read() + { + BinaryReader top = streams.Peek(); + + int value; + while ((value = top.Read()) == -1) + { + top.Dispose(); + streams.Pop(); + + if (streams.Count == 0) + { + return -1; + } + + top = streams.Peek(); + } + + return value; + } + + /// + /// Reads the specified number of bytes from the stack, starting from a specified point in the byte array. + /// + /// + /// The buffer to read data into. + /// + /// + /// The index of buffer to start reading into. + /// + /// + /// The number of bytes to read into the buffer. + /// + /// + /// The number of bytes read into buffer. This might be less than the number of bytes requested if that many bytes are not available, + /// or it might be zero if the end of the stream is reached. + /// + public int Read(byte[] buffer, int index, int count) + { + if (!HasData()) + { + return 0; + } + + // Read through all the stream untill we exhaust them + // or untill count is satisfied + int amountRead = 0; + BinaryReader top = streams.Peek(); + while (amountRead < count && streams.Count != 0) + { + int read = top.Read(buffer, index + amountRead, count - amountRead); + if (read == 0) + { + if ((top = NextStream()) == null) + { + return amountRead; + } + } + else + { + amountRead += read; + } + } + + return amountRead; + } + + /// + /// Reads the specified number of characters from the stack, starting from a specified point in the byte array. + /// + /// + /// The buffer to read data into. + /// + /// + /// The index of buffer to start reading into. + /// + /// + /// The number of characters to read into the buffer. + /// + /// + /// The number of characters read into buffer. This might be less than the number of bytes requested if that many bytes are not available, + /// or it might be zero if the end of the stream is reached. + /// + public int Read(char[] buffer, int index, int count) + { + if (!HasData()) + { + return 0; + } + + // Read through all the stream untill we exhaust them + // or untill count is satisfied + int amountRead = 0; + BinaryReader top = streams.Peek(); + while (amountRead < count && streams.Count != 0) + { + int read = top.Read(buffer, index + amountRead, count - amountRead); + if (read == 0) + { + if ((top = NextStream()) == null) + { + return amountRead; + } + } + else + { + amountRead += read; + } + } + + return amountRead; + } + + /// + /// Reads the specified number of characters from the stack, starting from a specified point in the byte array. + /// + /// + /// A byte array containing all the data up to but not including the next newline in the stack. + /// + public byte[] ReadByteLine() + { + bool dummy; + return ReadByteLine(out dummy); + } + + /// + /// Reads a line from the stack delimited by the newline for this platform. The newline + /// characters will not be included in the stream + /// + /// + /// This will be set to true if we did not end on a newline but instead found the end of + /// our data. + /// + /// + /// The containing the line. + /// + public byte[] ReadByteLine(out bool hitStreamEnd) + { + hitStreamEnd = false; + if (!HasData()) + { + // No streams, no data! + return null; + } + + // This is horribly inefficient, consider profiling here if + // it becomes an issue. + BinaryReader top = streams.Peek(); + byte[] ignore = CurrentEncoding.GetBytes(new[] {'\r'}); + byte[] search = CurrentEncoding.GetBytes(new[] {'\n'}); + int searchPos = 0; + var builder = new MemoryStream(); + + while (true) + { + // First we need to read a byte from one of the streams + var bytes = new byte[search.Length]; + int amountRead = top.Read(bytes, 0, bytes.Length); + while (amountRead == 0) + { + streams.Pop(); + if (streams.Count == 0) + { + hitStreamEnd = true; + return builder.ToArray(); + } + + top.Dispose(); + top = streams.Peek(); + amountRead = top.Read(bytes, 0, bytes.Length); + } + + // Now we've got some bytes, we need to check it against the search array. + foreach (byte b in bytes) + { + if (ignore.Contains(b)) + { + continue; + } + + if (b == search[searchPos]) + { + searchPos += 1; + } + else + { + // We only want to append the information if it's + // not part of the newline sequence + if (searchPos != 0) + { + byte[] append = search.Take(searchPos).ToArray(); + builder.Write(append, 0, append.Length); + } + + builder.Write(new[] {b}, 0, 1); + searchPos = 0; + } + + // Finally if we've found our string + if (searchPos == search.Length) + { + return builder.ToArray(); + } + } + } + } + + /// + /// Reads a line from the stack delimited by the newline for this platform. The newline + /// characters will not be included in the stream + /// + /// + /// The containing the line. + /// + public string ReadLine() + { + bool dummy; + return ReadLine(out dummy); + } + + /// + /// Reads a line from the stack delimited by the newline for this platform. The newline + /// characters will not be included in the stream + /// + /// + /// This will be set to true if we did not end on a newline but instead found the end of + /// our data. + /// + /// + /// The containing the line. + /// + public string ReadLine(out bool hitStreamEnd) + { + bool foundEnd; + byte[] result = ReadByteLine(out foundEnd); + hitStreamEnd = foundEnd; + + if (result == null) + { + return null; + } + + return CurrentEncoding.GetString(result); + } + + #endregion + + #region Methods + + /// + /// Removes the current reader from the stack and ensures it is correctly + /// destroyed and then returns the next available reader. If no reader + /// is available this method returns null. + /// + /// + /// The next reader. + /// + private BinaryReader NextStream() + { + BinaryReader top = streams.Pop(); + top.Dispose(); + + return streams.Count != 0 ? streams.Peek() : null; + } + + #endregion + } } \ No newline at end of file diff --git a/tests/annotations.os b/tests/annotations.os index a095cf5c5..b18a75c20 100644 --- a/tests/annotations.os +++ b/tests/annotations.os @@ -15,6 +15,7 @@ ВсеТесты.Добавить("ТестДолжен_ПроверитьАннотацииПолейЗагрузитьСценарийИзСтроки"); ВсеТесты.Добавить("ТестДолжен_ПроверитьАннотациюКакЗначениеПараметраАннотации"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьПолучениеАннотацийСпискаПеременных"); Возврат ВсеТесты; @@ -225,3 +226,29 @@ КонецЕсли; КонецПроцедуры + +Процедура ТестДолжен_ПроверитьПолучениеАннотацийСпискаПеременных() Экспорт + + ТекстСценария = "&Аннотация1 + |Перем Поле1 Экспорт, Поле2; + |Перем Поле3; + |"; + + Сценарий = ЗагрузитьСценарийИзСтроки(ТекстСценария); + + Рефлектор = Новый Рефлектор; + ТаблицаСвойств = Рефлектор.ПолучитьТаблицуСвойств(Сценарий, Истина); + + юТест.ПроверитьРавенство(ТаблицаСвойств.Количество(), 3, "Ожидали, что в таблице свойств будет 3 свойства"); + + юТест.ПроверитьРавенство(ТаблицаСвойств[0].Аннотации.Количество(), 1, "Ожидали, что Поле1 имеет 1 аннотацию"); + ИмяАннотации = ТаблицаСвойств[0].Аннотации[0].Имя; + юТест.ПроверитьРавенство(ИмяАннотации, "Аннотация1", "Ожидали, что Поле1 имеет аннотацию Аннотация1"); + + юТест.ПроверитьРавенство(ТаблицаСвойств[1].Аннотации.Количество(), 1, "Ожидали, что Поле2 имеет 1 аннотацию"); + ИмяАннотации = ТаблицаСвойств[1].Аннотации[0].Имя; + юТест.ПроверитьРавенство(ИмяАннотации, "Аннотация1", "Ожидали, что Поле2 имеет аннотацию Аннотация1"); + + юТест.ПроверитьРавенство(ТаблицаСвойств[2].Аннотации.Количество(), 0, "Ожидали, что Поле3 не имеет аннотаций"); + +КонецПроцедуры diff --git a/tests/collections.os b/tests/collections.os index cb73fe601..516e533b5 100644 --- a/tests/collections.os +++ b/tests/collections.os @@ -7,6 +7,14 @@ ВсеТесты = Новый Массив; ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючНеопределеноВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючNullВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючБулевоВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючЧислоВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючСтрокаВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючДатаВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючУникальныйИдентификаторВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьКлючТипВСоответствии"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьРазличныеКлючиВСоответствии"); Возврат ВсеТесты; КонецФункции @@ -18,4 +26,124 @@ юТест.ПроверитьРавенство(1, Соотв.Количество()); юТест.ПроверитьРавенство(Истина, Соотв[Неопределено]); +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьКлючNullВСоответствии() Экспорт + + Соотв = Новый Соответствие; + Соотв.Вставить(Null, "Значение для Null"); + юТест.ПроверитьРавенство(1, Соотв.Количество()); + юТест.ПроверитьРавенство("Значение для Null", Соотв[Null]); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьКлючБулевоВСоответствии() Экспорт + + Соотв = Новый Соответствие; + Соотв.Вставить(Истина, "Значение для Истина"); + Соотв.Вставить(Ложь, "Значение для Ложь"); + + юТест.ПроверитьРавенство(2, Соотв.Количество()); + юТест.ПроверитьРавенство("Значение для Истина", Соотв[Истина]); + юТест.ПроверитьРавенство("Значение для Ложь", Соотв[Ложь]); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьКлючЧислоВСоответствии() Экспорт + + Соотв = Новый Соответствие; + Соотв.Вставить(42, "Значение для 42"); + Соотв.Вставить(0, "Значение для 0"); + Соотв.Вставить(-100, "Значение для -100"); + Соотв.Вставить(3.14, "Значение для 3.14"); + + юТест.ПроверитьРавенство(4, Соотв.Количество()); + юТест.ПроверитьРавенство("Значение для 42", Соотв[42]); + юТест.ПроверитьРавенство("Значение для 0", Соотв[0]); + юТест.ПроверитьРавенство("Значение для -100", Соотв[-100]); + юТест.ПроверитьРавенство("Значение для 3.14", Соотв[3.14]); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьКлючСтрокаВСоответствии() Экспорт + + Соотв = Новый Соответствие; + Соотв.Вставить("Ключ1", "Значение1"); + Соотв.Вставить("Ключ2", "Значение2"); + Соотв.Вставить("", "Значение для пустой строки"); + + юТест.ПроверитьРавенство(3, Соотв.Количество()); + юТест.ПроверитьРавенство("Значение1", Соотв["Ключ1"]); + юТест.ПроверитьРавенство("Значение2", Соотв["Ключ2"]); + юТест.ПроверитьРавенство("Значение для пустой строки", Соотв[""]); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьКлючДатаВСоответствии() Экспорт + + Соотв = Новый Соответствие; + Дата1 = '20151231'; + Дата2 = '20200101'; + + Соотв.Вставить(Дата1, "Значение для Дата1"); + Соотв.Вставить(Дата2, "Значение для Дата2"); + + юТест.ПроверитьРавенство(2, Соотв.Количество()); + юТест.ПроверитьРавенство("Значение для Дата1", Соотв[Дата1]); + юТест.ПроверитьРавенство("Значение для Дата2", Соотв[Дата2]); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьКлючУникальныйИдентификаторВСоответствии() Экспорт + + Соотв = Новый Соответствие; + УИД1 = Новый УникальныйИдентификатор; + УИД2 = Новый УникальныйИдентификатор; + + Соотв.Вставить(УИД1, "Значение для УИД1"); + Соотв.Вставить(УИД2, "Значение для УИД2"); + + юТест.ПроверитьРавенство(2, Соотв.Количество()); + юТест.ПроверитьРавенство("Значение для УИД1", Соотв[УИД1]); + юТест.ПроверитьРавенство("Значение для УИД2", Соотв[УИД2]); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьКлючТипВСоответствии() Экспорт + + Соотв = Новый Соответствие; + Тип1 = Тип("Строка"); + Тип2 = Тип("Число"); + + Соотв.Вставить(Тип1, "Значение для Тип Строка"); + Соотв.Вставить(Тип2, "Значение для Тип Число"); + + юТест.ПроверитьРавенство(2, Соотв.Количество()); + юТест.ПроверитьРавенство("Значение для Тип Строка", Соотв[Тип1]); + юТест.ПроверитьРавенство("Значение для Тип Число", Соотв[Тип2]); + +КонецПроцедуры + +Процедура ТестДолжен_ПроверитьРазличныеКлючиВСоответствии() Экспорт + + // Проверка, что различные типы значений могут быть одновременно ключами + Соотв = Новый Соответствие; + + Соотв[Неопределено] = "Неопределено"; + Соотв[Null] = "Null"; + Соотв[Истина] = "Истина"; + Соотв[42] = "Число"; + Соотв["Текст"] = "Строка"; + Соотв['20240101'] = "Дата"; + Соотв[Тип("Строка")] = "Тип"; + + юТест.ПроверитьРавенство(7, Соотв.Количество()); + юТест.ПроверитьРавенство("Неопределено", Соотв[Неопределено]); + юТест.ПроверитьРавенство("Null", Соотв[Null]); + юТест.ПроверитьРавенство("Истина", Соотв[Истина]); + юТест.ПроверитьРавенство("Число", Соотв[42]); + юТест.ПроверитьРавенство("Строка", Соотв["Текст"]); + юТест.ПроверитьРавенство("Дата", Соотв['20240101']); + юТест.ПроверитьРавенство("Тип", Соотв[Тип("Строка")]); + КонецПроцедуры \ No newline at end of file diff --git a/tests/config.os b/tests/config.os new file mode 100644 index 000000000..e3d33f62e --- /dev/null +++ b/tests/config.os @@ -0,0 +1,129 @@ +Перем юТест; + +Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт + + юТест = ЮнитТестирование; + + ВсеТесты = Новый Массив; + ВсеТесты.Добавить("ТестДолженПроверитьПорядокПрименения_ПеременнаяОкруженияИмеетВысшийПриоритет"); + ВсеТесты.Добавить("ТестДолженПроверитьЧтениеЗначенияПоУмолчанию"); + ВсеТесты.Добавить("ТестДолженПроверитьПереопределениеНесколькихПараметровОдновременно"); + + Возврат ВсеТесты; + +КонецФункции + +Процедура ТестДолженПроверитьПорядокПрименения_ПеременнаяОкруженияИмеетВысшийПриоритет() Экспорт + + // Получаем исходное значение языка системы + ИсходноеЗначениеЯзыка = ПолучитьЗначениеСистемнойНастройки("SystemLanguage"); + + // Определяем тестовое значение, отличное от текущего + Если ИсходноеЗначениеЯзыка = "ru" Тогда + ТестовоеЗначение = "en"; + Иначе + ТестовоеЗначение = "ru"; + КонецЕсли; + + // Устанавливаем переменную окружения с новым значением + УстановитьПеременнуюСреды("OSCRIPT_CONFIG", "SystemLanguage=" + ТестовоеЗначение); + + Попытка + // Перечитываем конфигурацию + ОбновитьНастройкиСистемы(); + + // Проверяем, что значение изменилось + НовоеЗначениеЯзыка = ПолучитьЗначениеСистемнойНастройки("SystemLanguage"); + + юТест.ПроверитьРавенство(ТестовоеЗначение, НовоеЗначениеЯзыка, + "Переменная окружения OSCRIPT_CONFIG должна иметь наивысший приоритет"); + + Исключение + // Очищаем переменную окружения даже в случае ошибки + УстановитьПеременнуюСреды("OSCRIPT_CONFIG", ""); + ВызватьИсключение; + КонецПопытки; + + // Очищаем переменную окружения и возвращаем исходное состояние + УстановитьПеременнуюСреды("OSCRIPT_CONFIG", ""); + ОбновитьНастройкиСистемы(); + + // Проверяем, что значение вернулось к исходному + ВосстановленноеЗначение = ПолучитьЗначениеСистемнойНастройки("SystemLanguage"); + юТест.ПроверитьРавенство(ИсходноеЗначениеЯзыка, ВосстановленноеЗначение, + "После очистки переменной окружения значение должно вернуться к исходному"); + +КонецПроцедуры + +Процедура ТестДолженПроверитьЧтениеЗначенияПоУмолчанию() Экспорт + + // Проверяем, что можем прочитать существующий параметр + ЗначениеКодировки = ПолучитьЗначениеСистемнойНастройки("encoding.script"); + + // Значение может быть Неопределено, если не задано в конфигурации + // Это нормальное поведение + юТест.ПроверитьИстину( + ЗначениеКодировки = Неопределено ИЛИ ТипЗнч(ЗначениеКодировки) = Тип("Строка"), + "Значение encoding.script должно быть либо строкой, либо Неопределено"); + + // Проверяем чтение несуществующего параметра + НесуществующийПараметр = ПолучитьЗначениеСистемнойНастройки("несуществующий.параметр.test"); + + юТест.ПроверитьРавенство(Неопределено, НесуществующийПараметр, + "Несуществующий параметр должен возвращать Неопределено"); + +КонецПроцедуры + +Процедура ТестДолженПроверитьПереопределениеНесколькихПараметровОдновременно() Экспорт + + // Получаем исходные значения + ИсходныйЯзык = ПолучитьЗначениеСистемнойНастройки("SystemLanguage"); + ИсходнаяКодировка = ПолучитьЗначениеСистемнойНастройки("encoding.script"); + + // Устанавливаем несколько параметров через точку с запятой + Если ИсходныйЯзык = "ru" Тогда + НовыйЯзык = "en"; + Иначе + НовыйЯзык = "ru"; + КонецЕсли; + + НоваяКодировка = "utf-8"; + + КонфигурационнаяСтрока = "SystemLanguage=" + НовыйЯзык + ";encoding.script=" + НоваяКодировка; + УстановитьПеременнуюСреды("OSCRIPT_CONFIG", КонфигурационнаяСтрока); + + Попытка + // Перечитываем конфигурацию + ОбновитьНастройкиСистемы(); + + // Проверяем, что оба значения изменились + ЗначениеЯзыка = ПолучитьЗначениеСистемнойНастройки("SystemLanguage"); + ЗначениеКодировки = ПолучитьЗначениеСистемнойНастройки("encoding.script"); + + юТест.ПроверитьРавенство(НовыйЯзык, ЗначениеЯзыка, + "Язык системы должен измениться на " + НовыйЯзык); + + юТест.ПроверитьРавенство(НоваяКодировка, ЗначениеКодировки, + "Кодировка должна измениться на " + НоваяКодировка); + + Исключение + // Очищаем переменную окружения даже в случае ошибки + УстановитьПеременнуюСреды("OSCRIPT_CONFIG", ""); + ВызватьИсключение; + КонецПопытки; + + // Очищаем переменную окружения + УстановитьПеременнуюСреды("OSCRIPT_CONFIG", ""); + ОбновитьНастройкиСистемы(); + + // Проверяем, что значения вернулись (или стали Неопределено) + ВосстановленныйЯзык = ПолучитьЗначениеСистемнойНастройки("SystemLanguage"); + ВосстановленнаяКодировка = ПолучитьЗначениеСистемнойНастройки("encoding.script"); + + юТест.ПроверитьРавенство(ИсходныйЯзык, ВосстановленныйЯзык, + "Язык системы должен вернуться к исходному значению"); + + юТест.ПроверитьРавенство(ИсходнаяКодировка, ВосстановленнаяКодировка, + "Кодировка должна вернуться к исходному значению"); + +КонецПроцедуры diff --git a/tests/fixed-structure.os b/tests/fixed-structure.os index 19e00dac9..0374d357f 100644 --- a/tests/fixed-structure.os +++ b/tests/fixed-structure.os @@ -24,6 +24,7 @@ ВсеТесты.Добавить("ТестДолжен_ПроверитьМетодСвойство"); ВсеТесты.Добавить("ТестДолжен_ПроверитьОтсутствиеМетодаВставить"); ВсеТесты.Добавить("ТестДолжен_СоздатьСтруктуруПоФиксированнойСтруктуре"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьЗаполнитьЗначенияСвойствИзФиксированнойСтруктуры"); Возврат ВсеТесты; КонецФункции @@ -165,3 +166,17 @@ Ожидаем.Что(Структура.Ключ2, "значения элементов совпадут").Равно(ФиксированнаяСтруктура.Ключ2); КонецПроцедуры + +Процедура ТестДолжен_ПроверитьЗаполнитьЗначенияСвойствИзФиксированнойСтруктуры() Экспорт + + Структура1 = Новый ФиксированнаяСтруктура("тест1,тест2,тест3", "тест", Истина); + Структура2 = Новый Структура(Структура1); + + // Проверяем, что метод ЗаполнитьЗначенияСвойств работает с ФиксированнаяСтруктура + ЗаполнитьЗначенияСвойств(Структура2, Структура1); + + Ожидаем.Что(Структура2.тест1, "значения свойства тест1 совпадут").Равно(Структура1.тест1); + Ожидаем.Что(Структура2.тест2, "значения свойства тест2 совпадут").Равно(Структура1.тест2); + Ожидаем.Что(Структура2.тест3, "значения свойства тест3 совпадут").Равно(Структура1.тест3); + +КонецПроцедуры diff --git a/tests/global-funcs.os b/tests/global-funcs.os index 159433ae9..2acfd4bef 100644 --- a/tests/global-funcs.os +++ b/tests/global-funcs.os @@ -14,7 +14,8 @@ Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт Если мАдресРесурса = Неопределено Тогда - ПодключитьСценарий("./tests/http.os", "http_тест"); + ПутьКHttpТесту = ОбъединитьПути(ТекущийСценарий().Каталог, "http.os"); + ПодключитьСценарий(ПутьКHttpТесту, "http_тест"); мАдресРесурса = Новый http_тест().ВыбратьДоступныйХост(); КонецЕсли; diff --git a/tests/null-character-handling.os b/tests/null-character-handling.os new file mode 100644 index 000000000..631f7cc7c --- /dev/null +++ b/tests/null-character-handling.os @@ -0,0 +1,110 @@ +Перем юТест; + +Функция Версия() Экспорт + Возврат "0.1"; +КонецФункции + +Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт + + юТест = ЮнитТестирование; + + ВсеТесты = Новый Массив; + + ВсеТесты.Добавить("ТестДолжен_СоздатьФайлСНулевымСимволом"); + ВсеТесты.Добавить("ТестДолжен_ВыполнитьНайтиФайлыСНулевымСимволом"); + ВсеТесты.Добавить("ТестДолжен_ПолучитьСвойстваФайлаСНулевымСимволом"); + + Возврат ВсеТесты; +КонецФункции + +Процедура ТестДолжен_СоздатьФайлСНулевымСимволом() Экспорт + + ВремФайл = ПолучитьИмяВременногоФайла("txt"); + ЗаписьТекста = Новый ЗаписьТекста(ВремФайл); + ЗаписьТекста.ЗаписатьСтроку("тестовое содержимое"); + ЗаписьТекста.Закрыть(); + + Попытка + // Добавляем нулевой символ в путь, как это делает Windows WebDAV клиент + ПутьСНулевымСимволом = ВремФайл + Символ(0); + Файл = Новый Файл(ПутьСНулевымСимволом); + + // Проверяем, что файл доступен и не выбрасывает исключение + юТест.ПроверитьИстину(Файл.Существует(), "Файл должен существовать"); + юТест.ПроверитьРавенство("txt", Прав(Файл.Расширение, 3), "Расширение должно быть txt"); + + Исключение + УдалитьФайлы(ВремФайл); + ВызватьИсключение; + КонецПопытки; + + УдалитьФайлы(ВремФайл); + +КонецПроцедуры + +Процедура ТестДолжен_ВыполнитьНайтиФайлыСНулевымСимволом() Экспорт + + ВремКаталог = КаталогВременныхФайлов(); + ИмяТестовогоФайла = "test_null_char_" + Формат(ТекущаяДата(), "ДФ=yyyyMMddHHmmss") + ".txt"; + ВремФайл = ОбъединитьПути(ВремКаталог, ИмяТестовогоФайла); + + ЗаписьТекста = Новый ЗаписьТекста(ВремФайл); + ЗаписьТекста.ЗаписатьСтроку("тестовое содержимое"); + ЗаписьТекста.Закрыть(); + + Попытка + // Добавляем нулевой символ в путь к каталогу + ПутьКаталогаСНулевымСимволом = ВремКаталог + Символ(0); + + // Проверяем, что НайтиФайлы не выбрасывает исключение + НайденныеФайлы = НайтиФайлы(ПутьКаталогаСНулевымСимволом, ИмяТестовогоФайла); + + // Проверяем, что файл был найден + юТест.ПроверитьРавенство(1, НайденныеФайлы.Количество(), "Должен быть найден один файл"); + юТест.ПроверитьРавенство(ИмяТестовогоФайла, НайденныеФайлы[0].Имя, "Имя файла должно совпадать"); + + Исключение + УдалитьФайлы(ВремФайл); + ВызватьИсключение; + КонецПопытки; + + УдалитьФайлы(ВремФайл); + +КонецПроцедуры + +Процедура ТестДолжен_ПолучитьСвойстваФайлаСНулевымСимволом() Экспорт + + ВремФайл = ПолучитьИмяВременногоФайла("txt"); + ЗаписьТекста = Новый ЗаписьТекста(ВремФайл); + ЗаписьТекста.ЗаписатьСтроку("тестовое содержимое"); + ЗаписьТекста.Закрыть(); + + Попытка + // Добавляем нулевой символ в конец пути + ПутьСНулевымСимволом = ВремФайл + Символ(0); + Файл = Новый Файл(ПутьСНулевымСимволом); + + // Проверяем, что все свойства доступны без исключений + ПолноеИмя = Файл.ПолноеИмя; + юТест.ПроверитьНеравенство("", ПолноеИмя, "ПолноеИмя не должно быть пустым"); + + Имя = Файл.Имя; + юТест.ПроверитьНеравенство("", Имя, "Имя не должно быть пустым"); + + Путь = Файл.Путь; + юТест.ПроверитьНеравенство("", Путь, "Путь не должен быть пустым"); + + Расширение = Файл.Расширение; + юТест.ПроверитьРавенство(".txt", Расширение, "Расширение должно быть .txt"); + + Размер = Файл.Размер(); + юТест.ПроверитьБольше(Размер, 0, "Размер должен быть больше 0"); + + Исключение + УдалитьФайлы(ВремФайл); + ВызватьИсключение; + КонецПопытки; + + УдалитьФайлы(ВремФайл); + +КонецПроцедуры diff --git a/tests/process.os b/tests/process.os index b65cc25c8..44da651b5 100644 --- a/tests/process.os +++ b/tests/process.os @@ -144,22 +144,29 @@ "1Script Execution Engine. Version " + СИ.Версия + " | |Usage: + | oscript.exe [options] [script_arguments...] + | oscript.exe [mode_options] [script_arguments...] | - |I. Script execution: oscript.exe [script arguments..] + |Modes: + | -measure Measures script execution time. + | -compile Shows compiled module without execution. + | -check Provides syntax check. + | Options: + | -cgi Syntax check in CGI-mode. + | -env= Path to entrypoint file for context. + | -debug Runs script in debug mode. + | Options: + | -port= Debugger port (default is 2801). + | -noWait Do not wait for debugger connection. + | -version, -v Output version string. | - |II. Special mode: oscript.exe [script arguments..] - |Mode can be one of these: - | -measure measures execution time - | -compile shows compiled module without execution - | -check [-env=]provides syntax check - | -check -cgi provides syntax check in CGI-mode - | -version output version string + |Options: + | -encoding= Set output encoding (e.g. utf-8). + | -codestat= Write code execution statistics to file. | - | -encoding= set output encoding - | -codestat= write code statistics - | - |III. Run as CGI application: oscript.exe -cgi [script arguments..] - | Runs as CGI application under HTTP-server (Apache/Nginx/IIS/etc...)"; + |CGI Mode: + | oscript.exe -cgi [script_arguments...] + | Runs as CGI application under HTTP-server."; Возврат НормализоватьПереводыСтрок(Текст);