Skip to content

Latest commit

 

History

History
288 lines (218 loc) · 21.1 KB

File metadata and controls

288 lines (218 loc) · 21.1 KB

BSL-контексты и глобальные методы: руководство разработчика

Этот документ — практическая инструкция по добавлению в OneScript новых BSL-контекстов (классов), методов и свойств, а также глобальных методов. Здесь собраны готовые сниппеты, чек-лист и ссылки на ключевые места в исходниках.

См. также docs/developer_docs.md — карта компонентов и «куда лезть» при доработках.

Содержание

  1. Что такое BSL-контекст
  2. Добавление нового BSL-класса (контекста)
  3. Добавление свойства
  4. Добавление метода
  5. Создание глобального контекста и глобальных методов
  6. Регистрация библиотек и package-loader.os
  7. i18n для API (двуязычные имена)
  8. Депрекейшен и предупреждения
  9. Тестирование (C# и BSL)
  10. Документация (OneScriptDocumenter)
  11. Безопасность
  12. Чек-лист готовности

1. Что такое BSL-контекст

  • Контекст — это .NET-класс, методы и свойства которого доступны из BSL. Экземпляр контекста может создаваться оператором Новый (класс-контекст) или предоставляться глобально (глобальный контекст).
  • Отражение и метаданные описываются атрибутами:
    • [ContextClass("РусИмя", "EngName")] — класс-контекст;
    • [ContextMethod("РусИмя", "EngName")] — метод (процедура/функция);
    • [ContextProperty("РусИмя", "EngName", CanRead = true, CanWrite = false, ...)] — свойство;
    • [GlobalContext(...)] — глобальный контекст;
    • [ScriptConstructor] — фабричный метод для создания объектов через Новый.
  • Двуязычные имена обязательны: все элементы публичного API должны иметь пару имён Рус/Eng.

Где в коде смотреть:

  • Атрибуты и метаданные: src/OneScript.Core/Contexts/*.
  • Базовые помощники контекстов: src/ScriptEngine/Machine/Contexts/*.
  • База глобальных контекстов: GlobalContextBasesrc/ScriptEngine/Machine/Contexts/GlobalContextBase.cs.

2. Добавление нового BSL-класса (контекста)

Минимальный шаблон

using OneScript.Contexts;            // ContextClass, ContextMethod, ContextProperty, ScriptConstructor
using OneScript.Execution;           // IBslProcess
using OneScript.Types;               // TypeActivationContext
using ScriptEngine.Machine;          // IValue, ValueFactory
using ScriptEngine.Machine.Contexts; // AutoContext<T>

[ContextClass("ПримерКласс", "SampleClass")]
public class SampleClass : AutoContext<SampleClass>
{
    // Конструктор для BSL: Новый ПримерКласс()
    [ScriptConstructor(Name = "Без параметров")]
    public static SampleClass Ctor(TypeActivationContext ctx)
        => new SampleClass();

    // Свойство только для чтения
    [ContextProperty("Версия", "Version", CanWrite = false)]
    public IValue Version => ValueFactory.Create("1.0");

    // Процедура с доступом к bsl-процессу (можно запускать BSL-код из C#)
    [ContextMethod("Сообщить", "Message")]
    public void Message(IBslProcess process, IValue text)
    {
        // вызов bsl-метода в том же стеке вызовов, что и у переданного процесса
        process.Run(/*...*/);
    }

    // Функция с возвратом значения
    [ContextMethod("Сложить", "Add")]
    public IValue Add(IValue a, IValue b)
    {
        var sum = a.AsNumber() + b.AsNumber();
        return ValueFactory.Create(sum);
    }
}

Комментарии к шаблону

  • Наследуемся от AutoContext<T> — это стандартная база для классов-контекстов.
  • [ScriptConstructor] — статический фабричный метод, возможно принимающий TypeActivationContext. Можно объявить несколько перегрузок для разных сигнатур конструктора.
  • IBslProcess можно внедрять первым параметром метода, чтобы получить доступ к сервисам/окружению выполнения.
  • Возвраты:
    • Процедура — метод без возвращаемого значения (void).
    • Функция — возвращает IValue или конвертируемый тип C# (см. ContextValuesMarshaller).

Регистрация в движке

При старте ContextDiscoverer (src/ScriptEngine/Machine/Contexts/ContextDiscoverer.cs) сканирует подключённые сборки и автоматически регистрирует все классы, помеченные атрибутами ContextClass, GlobalContext и EnumerationType. Дополнительная ручная регистрация при этом не требуется.

3. Добавление свойства

[ContextProperty("Порог", "Threshold", CanRead = true, CanWrite = true)]
public IValue Threshold
{
    get => ValueFactory.Create(_threshold);
    set => _threshold = value.AsNumber();
}

private decimal _threshold = 0m;

Заметки:

  • CanRead/CanWrite управляют доступностью геттера и сеттера из BSL. Если параметры не указаны, используется наличие стандартных get/set у свойства.
  • Маршаллинг значения свойства автоматический через ContextValuesMarshaller.
  • Для значений, которые часто читаются и не меняются, имеет смысл вычислять и кешировать IValue в поле, а не пересоздавать его в геттере.

4. Добавление метода

// Функция, возвращающая удвоенное значение
[ContextMethod("УдвоитьЧисло", "DoubleNumber")]
public int DoubleNumber(int number)
{
    return number * 2;
}

Значения параметров и результат метода будут автоматически сконвертированы из типов C# в типы BSL.

Заметки:

  • Передача аргумента по ссылке — используйте тип IVariable в сигнатуре метода. В него можно присвоить новое значение через .Value.
  • Передача по значению — используйте типы C# напрямую, если они поддерживаются маршаллером, или IValue.
  • Необязательные параметры — задавайте через значения по умолчанию C# (int count = 0, string mode = null). Эти значения будут видны вызывающему коду BSL.
  • Перегрузки — поддерживаются: можно объявить несколько методов с одним и тем же ContextMethod-именем, но с разными сигнатурами.

5. Создание глобального контекста и глобальных методов

Глобальный контекст

using OneScript.Contexts;            // GlobalContext, ContextMethod, IAttachableContext
using ScriptEngine.Machine;          // IValue, ValueFactory
using ScriptEngine.Machine.Contexts; // GlobalContextBase<T>

[GlobalContext(Category = "Мои функции")]
public class MyGlobals : GlobalContextBase<MyGlobals>
{
    // Фабрика экземпляра для внедрения в глобальную область
    public static IAttachableContext CreateInstance() => new MyGlobals();

    [ContextMethod("МояФункция", "MyFunc")]
    public IValue MyFunc(IValue x)
    {
        return ValueFactory.Create(x.ToString().Length);
    }
}

Заметки:

  • По умолчанию глобальные контексты регистрируются автоматически (ManualRegistration = false). Достаточно, чтобы сборка с контекстом была подключена к окружению.
  • При необходимости можно внедрить контекст вручную через HostedScriptEngine.InjectObject или IRuntimeEnvironment.InjectObject.

Добавление метода в существующий глобальный контекст

  • Например, в StandardGlobalContext (src/OneScript.StandardLibrary/StandardGlobalContext.cs): добавьте [ContextMethod] в соответствующий класс и реализуйте логику.
  • Внимание: изменение публичного API стандартной библиотеки требует обсуждения с мэйнтейнерами.

6. Регистрация библиотек и package-loader.os

  • HostedScript ищет библиотеку и вызывает package-loader.os (дефолтный или кастомный из самой библиотеки).
  • Основные операции загрузчика (см. src/ScriptEngine.HostedScript/LibraryLoader.cs):
    • ДобавитьКласс/AddClass("path", "ИмяКласса") — регистрирует новый BSL-тип;
    • ДобавитьМодуль/AddModule("path", "ИмяМодуля") — подключает модуль как глобальный;
    • ДобавитьМакет/AddTemplate — регистрирует шаблон.
  • Для отладки разрешения зависимостей полезно посмотреть FileSystemDependencyResolver.cs — порядок поиска и защита от циклических зависимостей.

7. i18n для API (двуязычные имена)

  • Каждый публичный элемент API (класс, метод, свойство, перечисление, элемент перечисления) должен иметь две формы имени — русскую и английскую — задаваемые в атрибутах:

    [ContextClass("ИмяНаРусском", "EnglishName")]
    [ContextMethod("ВыполнитьДействие", "Perform")]
    [ContextProperty("Размер", "Size")]
  • Имена должны быть согласованы с уже существующим API: смотрите OneScript.StandardLibrary и сгенерированный справочник (см. раздел Документация).

  • Имена параметров и текст исключений переводить не нужно — они остаются на одном языке (как правило, на русском, в соответствии с привычным стилем 1С).

  • Категории глобальных контекстов (GlobalContext(Category = "...")) тоже желательно делать осмысленными — они используются в подсказках и группировках в редакторах.

8. Депрекейшен и предупреждения

Для обозначения устаревших имён, классов, методов и свойств используется атрибут DeprecatedNameAttribute (src/OneScript.Core/Contexts/DeprecatedNameAttribute.cs):

[ContextMethod("НоваяФункция", "NewFunc")]
[DeprecatedName("СтараяФункция")]
[DeprecatedName("OldFunc")]
public IValue NewFunc() { /* ... */ }
  • Аргумент name — устаревший псевдоним. При обращении к нему из BSL вызов всё ещё работает, но в системный лог пишется предупреждение.
  • Параметр throwOnUse: true превращает использование устаревшего имени в ошибку выполнения. Применяется, когда нужно жёстко удалить старый псевдоним.
  • Атрибут можно ставить и на сам элемент ([DeprecatedName("OldName")] рядом с [ContextMethod]), и на отдельные перечисления/типы.
  • Для классов, которые сами по себе устарели, реализуйте интерфейс ISupportsDeprecation (src/OneScript.Core/Contexts/ISupportsDeprecation.cs) — возвращайте IsDeprecated = true. Тогда система сможет логировать факт использования такого типа.

Изменение и удаление публичных имён без депрекейшена считается ломающим изменением — старайтесь сначала пройти этап с предупреждением.

9. Тестирование (C# и BSL)

В проекте принято покрывать новые контексты двумя слоями тестов.

Модульные тесты (xUnit/NUnit)

  • Тесты лежат в src/Tests/* (xUnit/NUnit).

  • Для контекстов из OneScript.StandardLibrary подходит проект src/Tests/OneScript.StandardLibrary.Tests. Там же есть пример проверки атрибута DeprecatedName (ObsoleteEnumTest.cs).

  • Для проверки кода, который должен компилироваться, используйте инфраструктуру из src/Tests/OneScript.Dynamic.Tests (CompilerTestBase, CompileHelper).

  • Запуск: dotnet test в каталоге соответствующего проекта или одной командой:

    dotnet msbuild Build.csproj /t:UnitTests

Приёмочные тесты на BSL

  • Поведенческие сценарии лежат в каталоге tests/*.os и совместимы с форматом xUnitFor1C.

  • Для нового контекста создайте файл tests/<ваш-контекст>.os со списком тестов. Минимальный шаблон:

    Перем юТест;
    
    Функция ПолучитьСписокТестов(ЮнитТестирование) Экспорт
        юТест = ЮнитТестирование;
        ВсеТесты = Новый Массив;
        ВсеТесты.Добавить("ТестДолжен_СоздатьЭкземпляр");
        Возврат ВсеТесты;
    КонецФункции
    
    Процедура ТестДолжен_СоздатьЭкземпляр() Экспорт
        Объект = Новый ПримерКласс();
        юТест.ПроверитьРавенство("1.0", Объект.Версия);
    КонецПроцедуры
  • Запуск всего набора тестов через свежесобранный oscript (см. README.md, раздел «Тестирование»):

    rem Windows
    dotnet build src/oscript/oscript.csproj
    tests\run-bsl-tests.cmd src\oscript\bin\Debug\net8.0\oscript.exe
    # Linux/macOS
    dotnet build src/oscript/oscript.csproj
    tests/run-bsl-tests.sh src/oscript/bin/Debug/net8.0/oscript
  • Запуск одного теста: <путь к oscript> tests/testrunner.os -run tests/<имя файла>.os.

10. Документация (OneScriptDocumenter)

  • Утилита OneScriptDocumenter (см. src/OneScriptDocumenter) формирует справку по платформе из XML-документации сборок и оглавления default_toc.json.
  • Чтобы ваш контекст попал в справку:
    • включите для проекта генерацию XML-документации (это сделано во всех публичных проектах через oscommon.targets);
    • снабдите класс/методы/свойства XML-комментариями <summary>, <param>, <returns>, <example> — они извлекаются документатором;
    • при необходимости — отредактируйте оглавление src/OneScriptDocumenter/default_toc.json, чтобы новый раздел появился в нужном месте.
  • Сгенерировать локально: dotnet msbuild Build.csproj /t:BuildDocumentation. Результат окажется в built/docs/ (markdown + json).

11. Безопасность

  • Не выполняйте произвольный код, поступивший «снаружи». Если контекст исполняет BSL, который пришёл от пользователя, делайте это только через явные точки расширения (IBslProcess.Run/Eval), осознавая, какие глобальные имена и контексты доступны.
  • При работе с файловой системой и сетью валидируйте пути и URL, проверяйте кодировки и таймауты. Для долгих операций предусматривайте отмену.
  • Не логируйте секреты (пароли, токены, заголовки Authorization) в открытом виде.
  • Не доверяйте внешним десериализаторам: для JSON/XML используйте уже существующие в OneScript.StandardLibrary обёртки и не добавляйте отключение защит «по умолчанию».
  • Если ваш контекст представляет собой обёртку поверх системного API, явно указывайте в XML-документации, какие побочные эффекты возможны (запись на диск, запуск процессов, сетевые вызовы).
  • При добавлении опасных по умолчанию операций предусматривайте явный «согласный» флаг в API, а не включайте их «втихую».

12. Чек-лист готовности

Перед открытием Pull Request убедитесь, что выполнены пункты:

  • У всех публичных элементов API заданы оба имени — русское и английское.
  • Класс/метод/свойство снабжены XML-комментариями (<summary>, <param>, <returns>).
  • Параметры с разумными значениями по умолчанию указаны через дефолты C#, а не через перегрузки-обёртки.
  • Если переименовываете существующее имя — добавлен DeprecatedName со старым псевдонимом.
  • Добавлены модульные тесты на C# и/или приёмочные тесты на BSL.
  • Все тесты проходят локально (dotnet msbuild Build.csproj /t:Test).
  • Соблюдены требования CODESTYLE.md.
  • Проверено, что новый контекст корректно отображается в справке (/t:BuildDocumentation), если он публичный.
  • Описание изменений в PR содержит мотивацию и ссылки на тесты/issue.