BSL-контексты и глобальные методы: руководство разработчика
Этот документ — практическая инструкция по добавлению в OneScript новых BSL-контекстов (классов), методов и свойств, а также глобальных методов. Здесь собраны готовые сниппеты, чек-лист и ссылки на ключевые места в исходниках.
См. также docs/developer_docs.md — карта компонентов и «куда лезть» при доработках.
- Что такое BSL-контекст
- Добавление нового BSL-класса (контекста)
- Добавление свойства
- Добавление метода
- Создание глобального контекста и глобальных методов
- Регистрация библиотек и package-loader.os
- i18n для API (двуязычные имена)
- Депрекейшен и предупреждения
- Тестирование (C# и BSL)
- Документация (OneScriptDocumenter)
- Безопасность
- Чек-лист готовности
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/*.
- База глобальных контекстов: GlobalContextBase — src/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. Дополнительная ручная регистрация при этом не требуется.
[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 в поле, а не пересоздавать его в геттере.
// Функция, возвращающая удвоенное значение
[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
-
Поведенческие сценарии лежат в каталоге 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).
- Не выполняйте произвольный код, поступивший «снаружи». Если контекст исполняет BSL, который пришёл от пользователя, делайте это только через явные точки расширения (IBslProcess.Run/Eval), осознавая, какие глобальные имена и контексты доступны.
- При работе с файловой системой и сетью валидируйте пути и URL, проверяйте кодировки и таймауты. Для долгих операций предусматривайте отмену.
- Не логируйте секреты (пароли, токены, заголовки Authorization) в открытом виде.
- Не доверяйте внешним десериализаторам: для JSON/XML используйте уже существующие в OneScript.StandardLibrary обёртки и не добавляйте отключение защит «по умолчанию».
- Если ваш контекст представляет собой обёртку поверх системного API, явно указывайте в XML-документации, какие побочные эффекты возможны (запись на диск, запуск процессов, сетевые вызовы).
- При добавлении опасных по умолчанию операций предусматривайте явный «согласный» флаг в API, а не включайте их «втихую».
Перед открытием Pull Request убедитесь, что выполнены пункты:
- У всех публичных элементов API заданы оба имени — русское и английское.
- Класс/метод/свойство снабжены XML-комментариями (<summary>, <param>, <returns>).
- Параметры с разумными значениями по умолчанию указаны через дефолты C#, а не через перегрузки-обёртки.
- Если переименовываете существующее имя — добавлен DeprecatedName со старым псевдонимом.
- Добавлены модульные тесты на C# и/или приёмочные тесты на BSL.
- Все тесты проходят локально (dotnet msbuild Build.csproj /t:Test).
- Соблюдены требования CODESTYLE.md.
- Проверено, что новый контекст корректно отображается в справке (/t:BuildDocumentation), если он публичный.
- Описание изменений в PR содержит мотивацию и ссылки на тесты/issue.