Каталог решений - Hello, Executor! Познакомимся с языком 1С:Исполнитель и сразу попытаемся его усовершенствовать

Hello, Executor! Познакомимся с языком 1С:Исполнитель и сразу попытаемся его усовершенствовать

Hello, Executor! Познакомимся с языком 1С:Исполнитель и сразу попытаемся его усовершенствовать

В наличии

На прошлой неделе произошел долгожданный громкий релиз в мире 1С: вышла бета версия 1С:Исполнителя. И пусть не оправдались все ожидания после анонса, пусть язык еще сырой, что-то сделать на нем очень интересно. Посмотрим, что это такое, и даже произведем небольшую модернизацию языка (Velocity-pad Costilization).

Категория:

Описание

В прошедшие дни в профильных чатах кипели страсти по поводу нового языка: что это вообще такое, зачем он нужен, зачем было изобретать что-то новое, когда уже есть проверенные скриптовые языки, ведь он не подходит ни 1Сникам, ни админам… Я не буду вступать в эту полемику, я сам пока еще плохо понимаю его нишу, и пригодится ли он мне. Но мне интересно рассмотреть 1С:Исполнителя именно как новый язык: какой у него синтаксис, какие есть фишки. И надеяться, что это перейдет в «старший» язык 1С:Предприятия.

 

Первое знакомство

Все материалы для работы с новым инструментом находятся по адресу https://releases.1c.ru/project/Executor. Там вы найдете:

  • сам дистрибутив языка,
  • отдельную IDE на основе Eclipse по типу EDT,
  • плагин для VS Code (его можно установить и через сам редактор),
  • различную документацию.

Что же сделать, когда все установлено? Конечно же, написать «Hello World»! Вот как он выглядит на двух языках. Я буду везде дальше приводить примеры на русском и английском для сравнения.

 

метод Скрипт()
    Консоль.Записать("Ну, здравствуй, душеприказчик!")
;

 

method Script()
    Console.Write("Well hello executor!")
;

 

Хоть пример и очень короткий, в нем можно увидеть сразу много особенностей Исполнителя:

  1. Здесь это сразу не понятно, но язык регистрозависимый. Ключевые слова должны быть с маленькой буквы, все остальные слова – в CamelCase. Не в том регистре напишете имя переменной, что-либо другое, все, это будет ошибкой. Наверное, подстава для людей, расслабленных 1С и пишущих как бог на душу положит. Я всегда старюсь придерживаться стиля, поэтому каких-то проблем в связи с этим не заметил.
  2. Больше нет процедур и функций, только ключевое слово метод(method). Конечно, разделение по смыслу в языке есть, но об этом позже.
  3. Основным методом, точкой входа в скрипт является метод с именем Скрипт(Script). Если выполнить файл sbsl без допоплнительных параметров, вызовется именно этот метод.
  4. Для вывода используется Консоль.Записать. Я очень давно не работал с консольными языками, поэтому вспомнились школьные, студенческие годы с Basic, Pascal, readln, writeln… Здесь нет интерфейса программы, она запускается в командной строке.
  5. Точка с запятой в конце не просто случайно на следующую строку съехала. В Исполнителе не надо ставить «;» в конце каждой строки. Символ этот служит для завершения блока. Т.е. вместо КонецЕсли, КонецЦикла, все этого, теперь просто «;». Решение, которое взбесило многих после анонса, в том числе и меня. Однако начал непосредственно писать код, и это уже не кажется таким диким. Больше проблем, когда «;» в конце строчки постоянно ставишь. Но если говорить о старшем языке, я бы оставил ключевое слово конец(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
;

 

Разберем строки, отмеченные цифрами:

  1. Объявляем новую функцию с параметрами. Напротив каждого параметра стоит тип. Потому как Исполнитель – это строго типизированный язык. Вот такое вот смелое решение для скриптового языка. Не знаю, как здесь, но в старшем языке это было бы круто сделать, хоть и сложно. После скобок с параметрами тоже указан тип, значит, это функция, она возвращает строку. Третий параметр необязательный, считаем, что если множественное число для предмета не прописано, то просто добавляем «s» к единственному. Однако зачем делать значением «-», а не просто пустую строку, спросите вы? Тут дело в вызове через командную строку. Необходимо передать все параметры в команде вызова, а если написать пустую строку, то это, считай, и параметр не передан. В общем, заморочка, может, что-то доделают в будущих версиях.
  2. Определяем массив цифр. Во-первых, «А чт эт за слв в нчл стрк?» — спросите вы… Теперь каждая переменная должна объявляться с ключевым словом. знч(val) для объектов, которые не будут перезаписываться, пер(var) – в другом случае. Английские названия здесь выглядят приятней. Дальше инициализация массива. Теперь это можно сделать в одну строку, е-ху! Очень удобная классная штука, хочу такую в платформу.
  3. Формируем слово, описывающее число. Здесь я применил тернарный оператор: условие, что если истина, что если ложь. Не совсем привычно после обычного 1Совского, надо пробовать на вкус. Все выражение поместил в скобки для выделения, можно и без них. По смыслу, это если число меньше 10, пишем прописью, иначе просто цифрами.
  4. Здесь я делаю просто объявление строковой переменной для представления самого предмета. Т.к. язык строготипизированный, то и каждая переменная должна иметь тип, что здесь и указывается. В переменных выше это не делалось, т.к. они сразу инициализируются значением, тогда тип можно не указывать. Зачем вообще я просто объявляю переменную, скажу чуть позже.
  5. Вот мы встретились с условным оператором если(if). Отличается он от знакомого нами тем, что слова тогда уже нет, ну и заканчивается тоже той самой «;». Еще изменились условные операторы. Вместо <> пришло !=, вместо = пришло ==. Не знаю, зачем это сделали, но ОК, запомнить можно.
  6. Возвращаем два получившихся слова через пробел. Здесь мы видим, так называемую, интерполяцию строк. Забудьте о всех этих конкатенациях с переменными, о всяких подстановках в шаблон, теперь можно просто вставлять переменные в строки через символ «%»! Круто, отличная тема, очень хочется себе «в продакшн». Так можно обращаться не только к переменным, но целые выражения в строках писать. И вернусь к переменной СловоПредмета. Зачем я вообще ее объявлял, ведь дальше она в обоих ветках условия присваивалась? А здесь мы встречаемся с областью видимости переменных. То, что произошло внутри условия, должно остаться внутри условия! Если объявить переменную где-то внутри блока, она не будет доступна за его рамками. Наверное, правильная вещь, но тоже нам, обычным 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
;

 

Посмотрим, что здесь происходит:

  1. Метод принимает в качестве параметров путь вызываемого скрипта, имя метода, если вызывается не метод Скрипт, а также массив параметров, если такие нужны. Последний необязателен, поэтому задан пустым массивом по умолчанию.
  2. В данной строке вам нужно будет написать путь расположения своего Исполнителя. Вообще, можно было заморочиться, и определять его автоматически, т.к. есть методы для работы с переменными среды, а там можно взять PATH и найти там нужную папку, но для первой версии и так сойдет. Кстати, можете заметить, что все слеши в строке двойные. Это потому, что слеш спецсимвол, и так вводится именно он сам.
  3. Инициализируем массив аргументов для вызова Исполнителя.
  4. Добавляем в аргументы параметры вызова нужного скрипта. Здесь используется метод ДобавитьВсе(AddAll), который добавляет к массиву другой массив. Его мы тут же собрали в этой строчке. Удобно, опять же. Еще можно увидеть, что кавычка теперь записывается как \.
  5. И здесь можно порадоваться синтаксису. Переменные даже примитивных типов представляют собой полноценные объекты, у которых есть методы. Поэтому вместо ПустаяСтрока(ИмяМетода) пишется более элегантное ИмяМетода.Пустое(). Или, например, вместо СтрНайти(МояСтрока… теперь МояСтрока.Найти(
  6. Ну и в конце просто докидываем в аргументы массив параметров, а не пробегаемся по каждому в цикле.
  7. В этой строке сама «магия» метода. Мы можем создавать и запускать процессы ОС. Здесь мы запускаем Исполнитель для вызова метода «вложенного» скрипта.
  8. Метод может быть просто процедурой в терминах 1С, выполнить нужные действия без какой-либо обратной связи, но что если нужно получить результат? Для этого написана данная часть кода. Мы можем забирать данные из консоли вызываемого приложения. Т.е. если что-то было выведено в консоль в скрипте или метод возвращает значение, все это попадает сюда. Сам цикл построен немного странно, но это я взял из документации, возможно, стоит будет его переписать.
  9. Будем собирать выводимые значения в результат построчно, если их несколько. Здесь, во-первых, можно увидеть синтаксический сахар в виде +=. Вместо ВотТакаяДлиннаяПеременная = ВотТакаяДлиннаяПеременная + Другая можно писать ВотТакаяДлиннаяПеременная += Другая. Почему-то операцию ++ не ввели, но и это уже очень хорошо. И еще в самой строке можно увидеть символ новой строки. В итоге получается вот такой короткий код добавления новой строки в многострочную переменную.
  10. Возвращаем обрезанную через аналог СокрЛП строку, без лишних пропусков в начале и в конце.

 

Вот так, передав имя скрипта, имя метода, массив параметров, мы вызываем другой скрипт. Но, допустим, для наглядности мы захотим передать параметры не просто массивом, а вместе с именами. Сделаем второй метод.

 

метод ВызватьМетодСкрипта(ПутьСкрипта: Строка, ИмяМетода = "", Параметры = {:}): Строка //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. Во-первых, мы написали метод с точно таким же именем. И это в том же модуле. Как же такое возможно? Это перегрузка методов. Можно объявлять методы с одинаковыми именами, но разными типами или количеством параметров. Что у нас как раз здесь и есть. Последний параметр – это Соответствие, состоящее из пар ключ – значение. Через {:} задается пустое соответствие.
  2. Здесь мы видим обход коллекции для каждого, и добавления в массив значений из соответствия. Т.е. имена переданных параметров и не важны, важен их порядок.
  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"))
;

 

Попробуем все варианты метода

  1. Сразу сохраним путь используемого скрипта.
  2. Вызываем как обычный метод и складываем результат с заданной строкой.
  3. Так задается соответствие из ключей и значений. Кто работал с 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

has been added to your cart:
Оформление заказа