В прошедшие дни в профильных чатах кипели страсти по поводу нового языка: что это вообще такое, зачем он нужен, зачем было изобретать что-то новое, когда уже есть проверенные скриптовые языки, ведь он не подходит ни 1Сникам, ни админам… Я не буду вступать в эту полемику, я сам пока еще плохо понимаю его нишу, и пригодится ли он мне. Но мне интересно рассмотреть 1С:Исполнителя именно как новый язык: какой у него синтаксис, какие есть фишки. И надеяться, что это перейдет в «старший» язык 1С:Предприятия.
Первое знакомство
Все материалы для работы с новым инструментом находятся по адресу https://releases.1c.ru/project/Executor. Там вы найдете:
- сам дистрибутив языка,
- отдельную IDE на основе Eclipse по типу EDT,
- плагин для VS Code (его можно установить и через сам редактор),
- различную документацию.
Что же сделать, когда все установлено? Конечно же, написать «Hello World»! Вот как он выглядит на двух языках. Я буду везде дальше приводить примеры на русском и английском для сравнения.
метод Скрипт()
Консоль.Записать("Ну, здравствуй, душеприказчик!")
;
method Script()
Console.Write("Well hello executor!")
;
Хоть пример и очень короткий, в нем можно увидеть сразу много особенностей Исполнителя:
- Здесь это сразу не понятно, но язык регистрозависимый. Ключевые слова должны быть с маленькой буквы, все остальные слова – в CamelCase. Не в том регистре напишете имя переменной, что-либо другое, все, это будет ошибкой. Наверное, подстава для людей, расслабленных 1С и пишущих как бог на душу положит. Я всегда старюсь придерживаться стиля, поэтому каких-то проблем в связи с этим не заметил.
- Больше нет процедур и функций, только ключевое слово метод(method). Конечно, разделение по смыслу в языке есть, но об этом позже.
- Основным методом, точкой входа в скрипт является метод с именем Скрипт(Script). Если выполнить файл sbsl без допоплнительных параметров, вызовется именно этот метод.
- Для вывода используется Консоль.Записать. Я очень давно не работал с консольными языками, поэтому вспомнились школьные, студенческие годы с Basic, Pascal, readln, writeln… Здесь нет интерфейса программы, она запускается в командной строке.
- Точка с запятой в конце не просто случайно на следующую строку съехала. В Исполнителе не надо ставить «;» в конце каждой строки. Символ этот служит для завершения блока. Т.е. вместо КонецЕсли, КонецЦикла, все этого, теперь просто «;». Решение, которое взбесило многих после анонса, в том числе и меня. Однако начал непосредственно писать код, и это уже не кажется таким диким. Больше проблем, когда «;» в конце строчки постоянно ставишь. Но если говорить о старшем языке, я бы оставил ключевое слово конец(end), все же солидней как-то.
Запускается скрипт в командной строке, если у вас Java 11, следующим образом:
“%ПапкаИсполнителя%\executor_j11.cmd” –s “%ПапкаСкрипта%\ИмяСкрипта.sbsl” –m ИмяМетода
После имени самого исполнителя пишется имя файла скрипта через –s и имя метода через –m. Если имя метода не указать, запустится метод с именем Скрипт(Script).
Что-нибудь посерьезнее
Но давайте напишем скрипт, который делает хоть что-то осмысленное. И заодно узнаем больше фишек языка. Сделаем функцию, которая по переданному числу и названию предмета на английском, возвращает фразу. Например, передали 5 и «apple», а она в ответ «five apples». Вот код получившегося метода.
метод СклонениеСЧислом(Количество: Число, Единственное: Строка, Множественное = "-"): Строка //1
знч Цифры = ["zero","one","two","three","four","five","six","seven","eight","nine"] //2
знч СловоЧисла = (Количество < 10 ? Цифры[Количество] : Строка(Количество)) //3
пер СловоПредмета: Строка //4
если Количество != 1 //5
СловоПредмета = (Множественное == "-" ? Единственное + "s" : Множественное)
иначе
СловоПредмета = Единственное
;
возврат "%СловоЧисла %СловоПредмета" //6
;
method DeclensionWithNumber(Count: Number, Singular: String, Plural = "-"): String //1
val Digits = ["zero","one","two","three","four","five","six","seven","eight","nine"] //2
val CountWord = (Count < 10 ? Digits[Count] : String(Count)) //3
var ObjectWord: String //4
if Count != 1 //5
ObjectWord = (Plural == "-" ? Singular + "s" : Plural)
else
ObjectWord = Singular
;
return "%CountWord %ObjectWord" //6
;
Разберем строки, отмеченные цифрами:
- Объявляем новую функцию с параметрами. Напротив каждого параметра стоит тип. Потому как Исполнитель – это строго типизированный язык. Вот такое вот смелое решение для скриптового языка. Не знаю, как здесь, но в старшем языке это было бы круто сделать, хоть и сложно. После скобок с параметрами тоже указан тип, значит, это функция, она возвращает строку. Третий параметр необязательный, считаем, что если множественное число для предмета не прописано, то просто добавляем «s» к единственному. Однако зачем делать значением «-», а не просто пустую строку, спросите вы? Тут дело в вызове через командную строку. Необходимо передать все параметры в команде вызова, а если написать пустую строку, то это, считай, и параметр не передан. В общем, заморочка, может, что-то доделают в будущих версиях.
- Определяем массив цифр. Во-первых, «А чт эт за слв в нчл стрк?» - спросите вы… Теперь каждая переменная должна объявляться с ключевым словом. знч(val) для объектов, которые не будут перезаписываться, пер(var) – в другом случае. Английские названия здесь выглядят приятней. Дальше инициализация массива. Теперь это можно сделать в одну строку, е-ху! Очень удобная классная штука, хочу такую в платформу.
- Формируем слово, описывающее число. Здесь я применил тернарный оператор: условие, что если истина, что если ложь. Не совсем привычно после обычного 1Совского, надо пробовать на вкус. Все выражение поместил в скобки для выделения, можно и без них. По смыслу, это если число меньше 10, пишем прописью, иначе просто цифрами.
- Здесь я делаю просто объявление строковой переменной для представления самого предмета. Т.к. язык строготипизированный, то и каждая переменная должна иметь тип, что здесь и указывается. В переменных выше это не делалось, т.к. они сразу инициализируются значением, тогда тип можно не указывать. Зачем вообще я просто объявляю переменную, скажу чуть позже.
- Вот мы встретились с условным оператором если(if). Отличается он от знакомого нами тем, что слова тогда уже нет, ну и заканчивается тоже той самой «;». Еще изменились условные операторы. Вместо <> пришло !=, вместо = пришло ==. Не знаю, зачем это сделали, но ОК, запомнить можно.
- Возвращаем два получившихся слова через пробел. Здесь мы видим, так называемую, интерполяцию строк. Забудьте о всех этих конкатенациях с переменными, о всяких подстановках в шаблон, теперь можно просто вставлять переменные в строки через символ «%»! Круто, отличная тема, очень хочется себе «в продакшн». Так можно обращаться не только к переменным, но целые выражения в строках писать. И вернусь к переменной СловоПредмета. Зачем я вообще ее объявлял, ведь дальше она в обоих ветках условия присваивалась? А здесь мы встречаемся с областью видимости переменных. То, что произошло внутри условия, должно остаться внутри условия! Если объявить переменную где-то внутри блока, она не будет доступна за его рамками. Наверное, правильная вещь, но тоже нам, обычным 1Сникам, не привычная.
Сам вызов функции будет выглядеть так:
метод Скрипт()
Консоль.Записать(СклонениеСЧислом(42, "table"))
Консоль.Записать(СклонениеСЧислом(5, "mouse", "mice"))
;
method Script()
Console.Write(DeclensionWithNumber(42, "table"))
Console.Write(DeclensionWithNumber(5, "mouse", "mice"))
;
И в консоли мы увидим:
42 tables
five mice
А теперь нечто совсем иное
В анонсе Исполнителя на Зазеркалье было рассказано про иерархию типов (т.е. классов), про контракты (т.е. интерфейсы), про множественное наследование… Я обрадовался: «ООП, любимое, пришло в 1С, ну наконец-то, заживем!». По факту все оказалось не так радужно. Иерархия типов есть, но только для использования, из своих типов можно создавать только Структуры и Перечисления, без методов.
И даже нет возможности вызывать методы одного скрипта из другого. Зарезервированы ключевые слова импорт и экспорт, но они сейчас ничего не делают. Но это уж как-то совсем обидно, подумал я, и решил провести небольшую костылизацию, сделать на коленке механизм, который позволит вызывать методы других скриптов. Будет это происходит через вызов Исполнителем самого себя. Итак, вот решение.
метод ВызватьМетодСкрипта(ПутьСкрипта: Строка, ИмяМетода = "", Параметры = []): Строка //1
знч ПутьИсполнителя = "F:\\1C\\Executor\\bin\\executor_j11.cmd" //2
знч Аргументы = новый Массив() //3
Аргументы.ДобавитьВсе(["-s", "\"%ПутьСкрипта\""]) //4
если не ИмяМетода.Пусто() //5
Аргументы.ДобавитьВсе(["-m", ИмяМетода])
;
Аргументы.ДобавитьВсе(Параметры) //6
пер Результат = ""
знч Процесс = новый ПроцессОс(ПутьИсполнителя, Аргументы) //7
Процесс.Запустить()
пока Истина
знч Вывод = Процесс.ПотокВывода.ПрочитатьКакТекст() //8
если Вывод.Пусто()
прервать
;
Результат += "\н%Вывод" //9
;
возврат Результат.Сократить() //10
;
method CallScriptMethod(ScriptPath: String, MethodName = "", Params = []): String //1
val ExecutorPath = "F:\\1C\\Executor\\bin\\executor_j11.cmd" //2
val Args = new Array() //3
Args.AddAll(["-s", "\"%ScriptPath\""]) //4
if not MethodName.IsEmpty() //5
Args.AddAll(["-m", MethodName])
;
Args.AddAll(Params) //6
var Result = ""
val Process = new OsProcess(ExecutorPath, Args) //7
Process.Start()
while True
val Output = Process.OutputStream.ReadAsText() //8
if Output.IsEmpty()
break
;
Result += "\n%Output" //9
;
return Result.Trim() //10
;
Посмотрим, что здесь происходит:
- Метод принимает в качестве параметров путь вызываемого скрипта, имя метода, если вызывается не метод Скрипт, а также массив параметров, если такие нужны. Последний необязателен, поэтому задан пустым массивом по умолчанию.
- В данной строке вам нужно будет написать путь расположения своего Исполнителя. Вообще, можно было заморочиться, и определять его автоматически, т.к. есть методы для работы с переменными среды, а там можно взять PATH и найти там нужную папку, но для первой версии и так сойдет. Кстати, можете заметить, что все слеши в строке двойные. Это потому, что слеш спецсимвол, и так вводится именно он сам.
- Инициализируем массив аргументов для вызова Исполнителя.
- Добавляем в аргументы параметры вызова нужного скрипта. Здесь используется метод ДобавитьВсе(AddAll), который добавляет к массиву другой массив. Его мы тут же собрали в этой строчке. Удобно, опять же. Еще можно увидеть, что кавычка теперь записывается как \”.
- И здесь можно порадоваться синтаксису. Переменные даже примитивных типов представляют собой полноценные объекты, у которых есть методы. Поэтому вместо ПустаяСтрока(ИмяМетода) пишется более элегантное ИмяМетода.Пустое(). Или, например, вместо СтрНайти(МояСтрока… теперь МояСтрока.Найти(…
- Ну и в конце просто докидываем в аргументы массив параметров, а не пробегаемся по каждому в цикле.
- В этой строке сама «магия» метода. Мы можем создавать и запускать процессы ОС. Здесь мы запускаем Исполнитель для вызова метода «вложенного» скрипта.
- Метод может быть просто процедурой в терминах 1С, выполнить нужные действия без какой-либо обратной связи, но что если нужно получить результат? Для этого написана данная часть кода. Мы можем забирать данные из консоли вызываемого приложения. Т.е. если что-то было выведено в консоль в скрипте или метод возвращает значение, все это попадает сюда. Сам цикл построен немного странно, но это я взял из документации, возможно, стоит будет его переписать.
- Будем собирать выводимые значения в результат построчно, если их несколько. Здесь, во-первых, можно увидеть синтаксический сахар в виде +=. Вместо ВотТакаяДлиннаяПеременная = ВотТакаяДлиннаяПеременная + Другая можно писать ВотТакаяДлиннаяПеременная += Другая. Почему-то операцию ++ не ввели, но и это уже очень хорошо. И еще в самой строке можно увидеть символ новой строки. В итоге получается вот такой короткий код добавления новой строки в многострочную переменную.
- Возвращаем обрезанную через аналог СокрЛП строку, без лишних пропусков в начале и в конце.
Вот так, передав имя скрипта, имя метода, массив параметров, мы вызываем другой скрипт. Но, допустим, для наглядности мы захотим передать параметры не просто массивом, а вместе с именами. Сделаем второй метод.
метод ВызватьМетодСкрипта(ПутьСкрипта: Строка, ИмяМетода = "", Параметры = {:}): Строка //1
знч МассивПарам = новый Массив()
для Парам из Параметры //2
МассивПарам.Добавить(Парам.Значение)
;
возврат ВызватьМетодСкрипта(ПутьСкрипта, ИмяМетода, МассивПарам) //3
;
method CallScriptMethod(ScriptPath: String, MethodName = "", Params = {:}): String //1
val ParamsArray = new Array()
for Param in Params //2
ParamsArray.Add(Param.Value)
;
return CallScriptMethod(ScriptPath, MethodName, ParamsArray) //3
;
Что у нас здесь:
- Во-первых, мы написали метод с точно таким же именем. И это в том же модуле. Как же такое возможно? Это перегрузка методов. Можно объявлять методы с одинаковыми именами, но разными типами или количеством параметров. Что у нас как раз здесь и есть. Последний параметр – это Соответствие, состоящее из пар ключ – значение. Через {:} задается пустое соответствие.
- Здесь мы видим обход коллекции для каждого, и добавления в массив значений из соответствия. Т.е. имена переданных параметров и не важны, важен их порядок.
- Ну а дальше мы вызываем уже написанный выше метод с массивом параметров.
И напишем еще третий вариант метода, когда параметры можно перечислить просто через запятую. Ограничимся для примера тремя параметрами.
метод ВызватьМетодСкрипта(
ПутьСкрипта: Строка,
ИмяМетода = "",
Параметр1: Строка|Число|Булево|? = Неопределено, //1
Параметр2: Строка|Число|Булево|? = Неопределено,
Параметр3: Строка|Число|Булево|? = Неопределено): Строка
знч МассивПарам = новый Массив()
если Параметр1 != Неопределено
МассивПарам.Добавить(Параметр1)
;
если Параметр2 != Неопределено
МассивПарам.Добавить(Параметр2)
;
если Параметр3 != Неопределено
МассивПарам.Добавить(Параметр3)
;
возврат ВызватьМетодСкрипта(ПутьСкрипта, ИмяМетода, МассивПарам)
;
method CallScriptMethod(
ScriptPath: String,
MethodName = "",
Param1: String|Number|Boolean|? = Undefined, //1
Param2: String|Number|Boolean|? = Undefined,
Param3: String|Number|Boolean|? = Undefined): String
val ParamsArray = new Array()
if Param1 != Undefined
ParamsArray.Add(Param1)
;
if Param2 != Undefined
ParamsArray.Add(Param2)
;
if Param3 != Undefined
ParamsArray.Add(Param3)
;
return CallScriptMethod(ScriptPath, MethodName, ParamsArray)
;
В общем, тут мы не увидим уже ничего для нас нового, кроме объявления типов переменных. В вызываемый скрипт мы можем передать значения трех типов: Строка, Число, Булево. Значит параметр является переменной составного типа, что и указывается перечислением типов через черту. Еще в конце указан знак вопроса, что означает, что параметр также может иметь значение Неопределено(Undefined), его мы и присваиваем по умолчанию.
И теперь проверим этот механизм. Создадим основной метод.
метод Скрипт()
знч ПутьСкрипта = "F:\\1C\\Executor\\SingularPluralRus.sbsl" //1
Консоль.Записать("I have " + ВызватьМетодСкрипта(ПутьСкрипта, "СклонениеСЧислом", [4, "apple", "-"])) //2
знч Параметры = {"Количество": 6, "Единственное": "orange", "Множественное": "-"} //3
Консоль.Записать("You have " + ВызватьМетодСкрипта(ПутьСкрипта, "СклонениеСЧислом", Параметры))
Консоль.Записать("He has " + ВызватьМетодСкрипта(ПутьСкрипта, "СклонениеСЧислом", 25, "cherry", "cherries"))
;
method Script()
val ScripthPath = "F:\\1C\\Executor\\SingularPluralEng.sbsl" //1
Console.Write("I have " + CallScriptMethod(ScripthPath, "DeclensionWithNumber", [4, "apple", "-"])) //2
val Params = {"Count": 6, "Singular": "orange", "Plural": "-"} //3
Console.Write("You have " + CallScriptMethod(ScripthPath, "DeclensionWithNumber", Params))
Console.Write("He has " + CallScriptMethod(ScripthPath, "DeclensionWithNumber", 25, "cherry", "cherries"))
;
Попробуем все варианты метода
- Сразу сохраним путь используемого скрипта.
- Вызываем как обычный метод и складываем результат с заданной строкой.
- Так задается соответствие из ключей и значений. Кто работал с JSON, тому будет знакома эта запись (так же, как и для массива).
Если выполним этот скрипт, в консоли выведется:
I have four apples
You have six oranges
He has 25 cherries
Конечно, данное решение пока далеко от совершенства. Чтобы использовать его, нужно в скрипт, из которого вы будете делать вызовы, вставить эти методы, от одного до трех. Ну, хоть к вызываемым скриптам требований нет. Нормально передаются только примитивные типы, в первую очередь, строки. Если хочется передачи более сложных данных, нужно думать о сериализации.
Расстраивает быстродействие. Вот эти строчки, что были выше, у меня выводятся с интервалом в 1- 2 секунды. Получается, под каждый процесс снова заводится Ява Машина, как-то так, в этой теме я плаваю. Но это отправная точка, а вообще будем надеяться, что в будущих версиях разработчики добавят нормальное взаимодействие скриптов.
О языках и средах
Я разрабатывал и в специальной IDE для Исполнителя, и в VS Code с плагином. В обоих средах есть и автодополнение, и контекстная подсказка, с этим все хорошо. Конечно, где-то есть мелкие недочеты, но это особенности беты. В итоге могу сказать, что пока мне больше нравится разрабатывать именно в VSC. Такой скриптовый язык не требует сложной IDE, все необходимое есть в VSC, к тому же он стильный модный и молодежный.
Если говорить про язык, то мне больше понравился код на английском. Он смотрится органичней, нет странных русских сокращений. Ну и не надо переключать раскладку. Хотя, кстати, в IDE об этом позаботились. Чтобы набирать специальные символы типа “[“, “]”, нужно нажать этот символ с зажатой клавишей Alt.
Ну и вот для сравнения скриншоты одного и того же кода в IDE на русском, и в VSC на английском. Мне больше нравится второй вариант. А вам?
Заключение
Хоть я и описал много фишек языка, которые меня порадовали, кое-что интересное не попало в обзор. Например, создание собственных структур данных и перечислений, которые потом можно использовать во всем скрипте. Или типизированные исключения, с возможностью создания собственных типов, для более продвинутой обработки исключений. В общем, очень много чего, что хотелось бы увидеть в языке платформы.
Конечно, язык еще довольно «слаб», у него скудная стандартная библиотека, и вообще область применения туманна, но мне нравится, что 1С создает что-то новое, делает смелые шаги и не дает нам скучать.
Если интересно, добавляйтесь в группу по 1С:Исполнителю в Telegram: https://t.me/executor1c
Репозиторий со скриптами из статьи: https://github.com/KonstantinHeinrich/Call-1C-Executor-Scripts-Methods