Общее описание языка

From SunFlurry wiki
Jump to: navigation, search

Contents

Вводная информация

Встроенный язык (скрипт) системы является интерпретатором, компилирующим (переводящим) тексты, написанные по определенным правилам, в байтовый код для ускорения выполнения программы. Перевод из байтового кода в машинный код не осуществляется (таким образом сохраняя сравнительную независимость от платформы исполнения). Такой способ сродни общеизвестным языкам, типа Java или C#. Являясь императивным языком (т.е., языком, использующим инструкции), он поддерживает процедурное и объектно-ориентированное программирование (однако, в данный момент не создано возможности определения новых объектов непосредственно из кода).

Являясь также специализированным языком для поддержки работы программной среды, он обладает рядом особенностей, таких как слабая типизация (необходимая для упрощения написания текстов и адресации данных более удобным способом), многопоточность, асинхронные и прерывающие события. Компилированные тексты программы могут находиться, как в особых файлах исполнения (модулях), так и быть привязанными к визуальным объектам или метаданным (работая, к примеру, как триггеры), пунктам меню и пр. Язык обладает широкой бибилиотекой встроенных функций и объектов, позволяющих осуществлять ввод и вывод информации в разных файловых форматах, обращение и работу с базой данных, манипуляцию данными, визуальное представление данных с помощью форм и электронных таблиц.

При создании языка делался упор как на удобство программирования, уменьшения количества возможных ошибок при написании программ и полноту предоставляемого инструментария, так и на скорость исполнения и оптимизацию функций.

Иерархия текстов программ

При запуске системы, некоторые программы выполняются до того, как пользователь получает возможность управления системой или ввода информации в нее. Эти стартовые программы называются глобальными и после исполнения находятся в памяти для того, чтобы другие программы имели возможность вызывать созданные ими функции и работать с определенными ими переменными. Каждая программа подобного рода располагается в отдельном файле. Такие файлы называются модулями. Кроме глобальных модулей, любой объект системы, будь то объект метаданных или внешняя обработка и пр. (см. Конфигурация данных проекта) имеет свой набор подобных модулей. Программы хранятся не только в модулях, но и привязаны к визуальным объектам (обработчики визуальных событий), к объектам метаданных (триггеры событий метаданных), к пунктам меню (обработчики событий меню), к ячейкам и объектам электронных таблиц (однако, вызов не происходит напрямую) и в других контейнерах по усмотрению создателя конфигурации.

При инициализации и старте системы, глобальные модули исполняются последовательно по следующему принципу:

  1. Первым всегда исполняется модуль с именем Module.
  2. Последующие модули всегда исполняются в алфавитном порядке их наименований.

Любые функции или переменные, определенные в этих модулях с постфиксом Export, будут доступны во всех других модулях и функциях, вызываемых из них (см. ниже описание переменных и функций).

Система позволяет использовать многозадачность при исполнении программ, для этого при запуске программы создается один рабочий поток -- корневой поток (root thread). Для визуальных клиентов этот поток обслуживает работу пользователя при исполнении пунктов главного меню, окон, загруженных в глобальном режиме, и пр. Программы, исполняемые в данном потоке, могут создать новые потоки (к примеру, с помощью вызова LoadModule). Количество рабочих потоков в среде ограничено только ресурсами операционной системы. Вызовы нового модуля из текущего могут также не создавать новый поток, в этом случае, создается вложенный вызов модуля и предыдущий модуль остается в памяти, пока работа последующего не завершена. Во вложенном модуле доступны не только переменные и функции глобальных модулей, но и переменные и функции модуля, из которого произошел вызов (см. также Вложенность функций и модулей, уровни вложенности). Переменные созданные в одном потоке (или в корневом потоке), могут быть использованы в других потоках, блокировки доступа к переменным в многозадачной среде выполняются системой автоматически (см. также доп. информацию по блокировкам).

Построение текста программ с помощью инструкций

Любая программа в языке состоит из блоков. Каждый блок делится на некоторое количество инструкций (команд), разделенных знаком ";". Знак перевода строки после каждой инструкции необязателен, но помогает улучшить читаемость программы, позволяя, тем самым избежать ошибок:

<Начало блока 1>
Инструкция 1;
Инструкция 2;
...
<Окончание блока 1>;

<Начало блока 2>
Инструкция 1;Инструкция 2;Инструкция 3; ...
...
<Окончание блока 2>;

Блоки могут быть как объявлениями функций, так, к примеру, и наборами инструкций при выполнении какого либо условия (см. конструкцию If). После объявления начала блока не требуется знак разделителя инструкций (;), однако, его наличие не будет приводить к ошибкам. Пустые инструкции, состоящие только из знака ";" допустимы и не являются ошибкой.

Комментарии (свободный текст, не подлежащий интерпретации и компилированию) могут быть использованы в любом месте программы. Комментарии можно задать двумя способами: с помощью двух знаков "/", при этом, любой текст после этого набора знаков до окончания текущей строки будет пропущен при компиляции. Также комментарии задаются, как блок текста, начинающийся знаком "{" и заканчивающийся знаком "}". Символы перевода строки внутри этого блока будут принадлежать части комментария и пропускаются при компиляции. После начала комментария одного типа, и до его окончания, невозможно создать комментарий другого типа, так как текст внутри комментария не подлежит интерпретации. Пример использования комментариев дан ниже:

Инструкция 1;			//Комментарий 1
Инструкция 2;
//Комментарий 2
...
Инструкция 100;
//Инструкция 101;		//Инструкция не будет компилироваться (принадлежит комментарию)

{
Инструкция 102;
Инструкция 103;}		//Инструкции 102 и 103 принадлежат комментарию
Инструкция 104{Комментарии внутри инструкции допустимы};
...
{Инструкция 200;
Инструкция 201;			//Неверный комментарий,} данный текст будет считаться частью программы и вызовет ошибку компиляции

При написании программы рекомендуется каждую инструкцию помещать в отдельную строку и для каждого логического блока инструкций использовать отступ, если блок находится внутри другого блока, отступ рекомендуется увеличивать. Такой подход улучшит читаемость текста программ. Размер единицы отступа обычно совпадает с шириной знака табуляции. Последнюю можно задать в свойствах редактора текста, однако по умолчанию рекомендованное значение равно двум знакоместам. Символ табуляции в редакторе текста программ не используется, а заменяется на необходимое количество пробелов, это сделано для удобства редактирования текстов и обычно не мешает написанию программ, устраняя невидимый и неудобный в работе символ. Для добавления к строковым выражениям символа табуляции, можно использовать либо константу _TAB, либо конструкцию Chr(9). Пример использования отступов:

If a=1 Then
  Message("a равно 1");			//Инструкция внутри логического блока конструкции If имеет отступ в 2 пробела
Else
  Message("a не равно 1");		//Инструкции (эта и далее) внутри логического блока конструкции If имеет отступ в 2 пробела
  If a>0 Then
    Message("Однако, a больше нуля");	//Инструкция внутри вложенного логического блока конструкции If имеет отступ в 4 пробела
  EndIf;
EndIf;

Особые конструкции языка

Особыми называются конструкции языка, не являющиеся функциями (но могут быть магическими функциями), процедурами или частями вычисляемых выражений. Такие конструкции могут включать в себя один или более логических блоков, которые могут состоять из любого количества инструкций. Регистр не имеет значения при указании составных частей конструкций (begin и BEgin будут восприняты компилятором одинаково). Определения, используемые в этих конструкциях, являются зарезервированными словами, создание переменных или функций с зарезервированным именем запрещено (к примеру строка If:=1; вызовет ошибку при компиляции). На данный момент в языке существуют следующие конструкции и магические функции (далее по тексту <логический блок> заменяет любое количество инструкций языка, внутри квадратных скобок [...] находятся необязательные части конструкций, внутри фигурных скобок {...} перечислены две или более части конструкций, одна из которых должна обязательно присутствовать):

Конструкция объявления переменной

Var <переменная> [Export][, <переменная> [Export]];

Переменные обычно создаются автоматически при первом присваивании и удаляются при выходе за границы существования, однако при объявлении переменных с постфиксом Export, они становятся видимыми для функций и модулей более низкого уровня (вызываемых внутри данной функции или модуля). Пример см. ниже:

Var a1 Export, a2;

Function ВложеннаяФункция()
  a1:=1;				//Обращение к a1 внутри функции возможно
  a2:=2;				//При обращении к a2 создается новая переменная с границами видимости функции ВложеннаяФункция()
EndFunction

a1:=0;
a2:=0;
ВложеннаяФункция();
Message(a1);				//Выводит 1
Message(a2);				//Выводит 0, так как оригинальная переменная a2 не была изменена

Кроме того, объявление переменных можно использовать для небольшого ускорения, когда к переменной происходит очень много вызовов, так как поиск переменных, при обращении к ним, осуществляется по имени (в текущей реализации), это занимает определенное время, поэтому случаи, когда переменная была найдена в стеке сразу же, и когда она была найдена после пропуска нескольких неподходящих, занимают несколько разное время. Иными словами, переменные, объявленные позже, будут найдены при обращении быстрее. Разница во времени достаточно мала, однако в синтетическом тесте, показанном ниже, она весьма заметна:

Пример выполнения кода с результатами профилирования
Var a1,a2,a3,a4,a5,a6,a7,a8,a9,a10;

ProfilerClear();
ProfilerStart();
For i:=1 to 1000000 Do
  a1:=0;
EndDo;
For i:=1 to 1000000 Do
  a10:=0;
EndDo;
ProfilerStop();
DebugBreak;
Message("Done!");

Конструкция логического блока

Begin
  <логический блок>
End;

Конструкция имеет декоративную функцию.

Конструкция проверки выражения

If <выражение> Then
  <логический блок>
[ElseIf <выражение> Then
  <логический блок>]
[Else
  <логический блок>]
EndIf;

Конструкция служит для логического выполнения инструкций в зависимости от результатов вычисления выражений. Если выполнение выражения возвращает логическую единицу (см. ниже), будет исполнен блок, идущий после оператора Then, иначе будет осуществлена новая проверка (если используется оператор ElseIf, который может повторяться требуемое количество раз), будет произведено безусловное исполнение блока (если используется оператор Else), либо интерпретатор перейдет к последующим инструкциям программы (если используется оператор EndIf). Наличие оператора EndIf обязательно, как метки окончания конструкции.

Конструкция переключателя (case)

(планируется к реализации)

Конструкция цикла с предусловием

While <выражение> Do
  <логический блок>
EndDo;

Конструкция создает цикл, выполняя блок инструкций, пока выражение возвращает правду. Если выражение возвратит ложь при первом вызове, блок инструкций не будет выполнен ни разу.

Конструкция цикла "пока" с постусловием

Repeat
  <логический блок>
DoWhile <выражение>;

Конструкция создает цикл, выполняя блок инструкций, пока выражение возвращает правду. Проверка выражения осуществляется после выполнения блока инструкций, поэтому последний будет выполнен по крайней мере один раз.

Конструкция цикла "пока не" с постусловием

Repeat
  <логический блок>
Until <выражение>;

Конструкция создает цикл, выполняя блок инструкций до тех пор, пока выражение не возвратит правду, после чего выполнение прекратится. Проверка выражения осуществляется после выполнения блока инструкций, поэтому последний будет выполнен по крайней мере один раз.

Конструкция цикла с перечислением

For <переменная>:=<выражение> {To,Downto} <выражение> Do
  <логический блок>
EndDo;

Конструкция создает цикл, инициализируя переменную вычисленным значением первого выражения. В зависимости от использования ключевого слова To или ключевого слова DownTo:

  • (ключевое слово To) Блок инструкций выполняется до тех пор, пока значение переменной не будет больше вычисленного значения второго выражения. После выполнения блока инструкций, переменная увеличивается на единицу.
  • (ключевое слово DownTo) Блок инструкций выполняется до тех пор, пока значение переменной не будет меньше вычисленного значения второго выражения. После выполнения блока инструкций, переменная уменьшается на единицу.

Если значение переменной, при вычислении ее начального значения, сразу же будет удовлетворять условию окончания цикла, блок инструкций не будет выполнен ни разу. Такие циклы выполняются быстрее, чем циклы While или Repeat, когда необходимо выполнить цикл известное заранее число раз, так как система не вычисляет значение второго выражение после каждого оборота цикла.

Конструкция прерывания цикла

Break [<выражение>];

Конструкция прерывает выполнение любого из циклов любого уровня вложенности и продолжает его с первой инструкции вне прерываемого цикла. Каждый цикл имеет блок (набор) инструкций, выполняющийся внутри него определенное количество раз. Эти инструкции выполняются в цикле первого уровня. Если внутри цикла имеется второй цикл (любого вида, будь-то For или While, в этом случае не имеет значения), инструкции внутри его выполняются в цикле второго уровня. Вложенность циклов неограниченна. Иногда требуется прервать выполнение цикла на определенном этапе и продолжить выполнение программы, как будто цикл был завершен. Для этого существует конструкция Break. Дополнительный необязательный аргумент конструкции позволяет прервать определенное количество уровней цикла (т.е., буквально выйти из n уровня цикла на p), встречаются ситуации, когда подобный подход сильно сокращает сложность программы. Пример использования Break <Аргумент>:

Сокет:=0;
ПопыткиМин:=60*1000;					//Максимальное время, которое можно затратить на попытки соединения (60 с.)
НачВремя:=Date();
While GetPeriodMs(НачВремя)<=ПопыткиМин Do
  СрвНом:=0;
  While СрвНом<СпСервер.Size() Do			//Осуществляется проход по всем серверам из списка и попытка соединения последовательно с каждым
    СрвНом:=СрвНом+1;
    Сервер:=СпСервер.Get(СрвНом);
    Сокет:=TCPConnection.ConnectRaw(Сервер);
    If Сокет.isConnected() Then				//Когда соединение установлено успешно, продолжим работу, прервав оба цикла
      Break 2;
    EndIf;  
  EndDo;
EndDo;
...

Конструкция прерывания цикла с продолжением

Continue [<выражение>];

Конструкция прерывает выполнение любого из циклов любого уровня вложенности и продолжает его с первой инструкции самого внешнего цикла (см. описание Break для понимания терминов, типа уровня цикла). Иногда требуется прервать выполнение цикла на определенном этапе и перейти в его начало, как будто цикл совершил целый оборот. Для этого существует конструкция Continue. Дополнительный необязательный аргумент конструкции позволяет прервать определенное количество уровней цикла (т.е., буквально выйти из n уровня цикла на p) и "продолжить" выполнение на самом внешнем уровне. Таким образом Continue без аргументов просто переходит в начало цикла. Для циклов For и While конструкция переходит к условию, которое может прервать выполнение цикла, для циклов Repeat, у которых условие находится в конце цикла, будет гарантировано начато новое выполнение цикла. Пример использования Continue:

//Тб -- таблица с документами для выгрузки
Тб.Select();
While Тб.Next() Do
  If Тб.Статус=1 Then
    Continue;						//Пропускаем строки таблицы со столбиком Статус, равным 1
  EndIf;
  зДок:=Тб.Документ;					//Получаем документ из таблицы
  aTab:=зДок.LineParts("Номенклатура");
  aTab.Select();
  While aTab.Next() Do					//Производим проверку строк документа
    зН:=aTab.Номенклатура;
    If зН.флАлкогольнаяПродукция=0 Then			//Проверяем только строки с алкогольной продукцией
      Continue;						//Если это продукция другого типа, переходим к следующей строке
    EndIf;
    If aTab.Партия.Selected()=0 Then			//Во всех проверяемых строках, должен быть заполнен реквизит "Партия"
      Message("В документе "+зДок+" не указана партия для номенклатуры "+зН+" ("+зН.Code+")!","!");
      Continue 2;					//Данный документ содержит неверные строки, пропустим его и перейдем к следующему документу (в начало первого цикла While)
    EndIf;
  EndDo;

...							//Выполним необходимую работу по выгрузке документа зДок

EndDo;

Конструкция выхода с параметрами

Exit [<выражение результата>[,<выражение количества уровней>]];

Конструкция прерывает выполнение любой функции любого уровня вложенности и, выборочно изменяет значение Result (переменная значения возврата из функции). По окончании прерывания выполнение программы продолжается непосредственно после вызова функции, используя значение, которое она возвращает или игнорируя его, если функция вызывается, как обычная инструкция. При вызове любой функции, в начале выполнения ее кода, к внутреннему счетчику вложенности функции добавляется единица. Счетчик, однако, всегда равен нулю в начале загрузки нового модуля (см. LoadModule). Важная функция Exit не только в окончании выполнения функции в нужный момент, но и в возвращении ее значения. Для каждой функции возвращаемое значение хранится в переменной Result, которая доступна как для записи, так и для чтения в любой момент в пределах текущего уровня функции. Изменение этой переменной перед окончанием функции или выходом из нее возможно, но Exit <выражение> позволяет это сделать быстрее, и при этом упростить исходный код. Иногда встречаются ситуации, когда удобно выйти сразу из нескольких функций (в таких случаях, обычно в системах без возможностей, которыми обладает Exit в данной системе, создаются исключения Raise с тем, чтобы отловить их на нужном уровне, однако, такой подход может быть неудобен из-за вероятности реальных исключений), для этого и необходим второй аргумент, определяющий количество функций, из которых необходимо выйти. Нужно иметь в виду, однако, что за пределы загруженного модуля конструкция Exit выйти не сможет. В роли функции могут выступать не только обычные определяемые функции, но и контейнеры событий форм или триггеры метаданных и пр. Пример использования Exit:

//Функция возвращает 0, если заполнение выполнено не полностью, текст ошибки в стрОшибка
Function ПроверитьПолнотуЗаполнения(зК,ByRef стрОшибка)
  Function ПроверитьРеквизит(Стр,Имя)
    If IsEmpty(Стр) Then
      стрОшибка:="Не заполнен реквизит "+Имя+"!";
      Exit 0,2;
    EndIf;
  EndFunction

  стрОшибка:="";
  Result:=0;						//Мы не сможем изменить Result этого уровня из вложенной функции
  ПроверитьРеквизит(зК.Name,"наименование");
  ПроверитьРеквизит(зК.ИНН,"ИНН");
  ПроверитьРеквизит(зК.ЮрАдрес,"юридический адрес");
  ПроверитьРеквизит(зК.ФактАдрес,"фактический адрес");
  Result:=1;
EndFunction

Конструкция отлова исключения

Try
  <логический блок>
Except
  <логический блок>
EndTry;

Конструкция позволяет обнаружить и подавить возникновение исключения внутри первого блока инструкций, при возникновении исключения, будет выполнен второй блок инструкций. Если же исключение не возникло, второй блок инструкций будет пропущен. Исключение будет подавлено после выполнения второго блока инструкций, дальнейшее исполнение будет продолжаться, как будто исключительная ситуация никогда не возникала. При возникновении исключения, внутри второго блока инструкций можно использовать функции типа PopError или PeekError, чтобы выяснить, какого рода исключение было отловлено, также, при необходимости, можно вызвать новое исключение (к примеру, с помощью конструкции Raise. Выполнение второго блока инструкций также разрешается прерывать с помощью конструкций типа Break, Continue или Exit. Конструкция будет выполнена даже если пользователь прервал выполнение программы, однако, конструкция не сможет подавить исключение, созданное при попытке пользователя прервать выполнение (сразу после выполнения блока Except данное исключение будет вызвано снова, таким образом гарантируется, что рано или поздно исполнение программы будет прервано, однако, также гарантируется выполнение необходимых действий до завершения выполнения).

Конструкция гарантированного выполнения

Try
  <логический блок>
Finally
  <логический блок>
EndTry;

Конструкция позволяет выполнить второй блок инструкций вне зависимости от того, произошло ли исключение в первом блоке инструкций, также гарантируется исполнение, если в первом блоке были вызваны команды прерывания Continue, Break или команда возврата Exit. При наличии, исключение не будет подавлено после выполнения второго блока инструкций, и дальнейшее исполнение будет прервано, как будто исключение возникло сразу же за последней инструкцией второго блока (это поведение можно изменить, если вызвать функцию SuppressException внутри блока Finally). При возникновении исключения, внутри второго блока инструкций можно использовать функции типа PopError или PeekError, чтобы выяснить, какого рода исключение было отловлено, однако, при использовании PopError, информация об исключении будет удалена из стека исключений, и исключение возникнет без какой-либо информации к нему, поэтому, рекомендуется использовать только функцию PeekError. Конструкция будет выполнена даже если пользователь прервал выполнение программы. Пример использования конструкции дан ниже:

//aEl -- документ, который необходимо обработать
аПольз:="";аМаш:="";аВремя:="";
If aEl.Lock(аПольз,аМаш,аВремя)=0 Then
  Message("Невозможно заблокировать документ, так как в данный момент с ним работает пользователь "+аПольз+" на "+аМаш+" в "+аВремя+"!","!");
  Exit;
EndIf;  
Фл:=0;
Try
  aEl.Reload();						//Необходимо обновить содержимое aEl после блокирования, чтобы исключить вероятность устаревания информации
  aEl.Publish();
Finally							//Вне зависимости, произошло ли исключение в строке aEl.Publish(), необходимо разблокировать документ.
  If not isEmpty(PeekError(6)) Then
    Message("Документ "+aEl+" невозможно обработать, причина: "+PopError(6),"!");//SuppressException() подавит исключение, поэтому, мы можем удалить информацию о нем из стека исключений
    SuppressException();				//Подавить созданное исключение, Finally работает здесь частично, как Except
    Фл:=1;						//Флаг того, что исключение произошло
  EndIf;
  aEl.Unlock();
EndTry;
If Фл Then
  ...							//Блок инструкций, исполняемый, если документ невозможно было обработать
EndIf;

Синтетический пример, показывающий, что блок инструкций Finally выполняется даже при использовании прерывания исполнения или команды выхода.

For i:=1 to 10 Do
  Try
    If i>7 Then
      Exit;						//Для значения счетчика 8 будет произведен выход из цикла, цикл для значений 9 и 10 продолжен не будет
    ElseIf i>3 Then
      Continue;						//Для значений счетчика 4,5,6,7 тело цикла будет пропущено
    EndIf;

    Message("Тело цикла, счетчик="+i);

  Finally
    Message("Блок обяз. исполнения, счетчик="+i);
  EndTry;
EndDo;

Результатом исполнения будет вывод (обратите внимание, что несмотря на конструкции Continue и Exit, блок Finally все равно выполняется, при значении счетчика 8 цикл будет окончен):

Тело цикла, счетчик=1
Блок обяз. исполнения, счетчик=1
Тело цикла, счетчик=2
Блок обяз. исполнения, счетчик=2
Тело цикла, счетчик=3
Блок обяз. исполнения, счетчик=3
Блок обяз. исполнения, счетчик=4
Блок обяз. исполнения, счетчик=5
Блок обяз. исполнения, счетчик=6
Блок обяз. исполнения, счетчик=7
Блок обяз. исполнения, счетчик=8

Конструкция создания исключения

Raise [<выражение>];

Конструкция позволяет создать пользовательское исключение с описанием <выражение>. Поведение созданного исключения будет аналогичным обычному исключению. Общая информация об исключении будет представлена строкой "User defined error" (см. описание функции PopError). Иногда необходимо создать исключение, чтобы прервать выполнение после получения для этого веского основания. К примеру, если запись документа подразумевает обработку нескольких подчиненных документов и обработать их не удалось из-за того, что один из них был заблокирован другим пользователем, можно создать исключение, чтобы гарантированно прервать исполнение, как будто оно произошло на программном уровне при записи (см. пример). Raise также можно использовать, чтобы создать исключение заново внутри блока Except (к примеру: Raise PopError();).

//Мы находимся в функции OnSave, которая вызывается при записи документа (aEl), процедура должна осуществить вызов aEl.Save(); самостоятельно
//aList -- список документов, которые необходимо обработать при записи текущего (aEl)
Try
  СтрОш:="";
  For i:=1 To aList.Size() Do
    зДок:=aList.Get(i);
    аПольз:="";аМаш:="";аВремя:="";
    If зДок.Lock(аПольз,аМаш,аВремя)=0 Then
      СтрОш:="Невозможно заблокировать документ "+зДок+", так как в данный момент с ним работает пользователь "+аПольз+" на "+аМаш+" в "+аВремя+"!","!");
      Break;
    EndIf;
  EndDo;
  If СтрОш<>"" Then
    Raise СтрОш;
  EndIf;
  ...							//Обработка заблокированных документов (блок опущен для читабельности)
  aEl.Save();						//Вызов этой функции также может создать исключение, поэтому предыдущий Raise будет обработан таким же образом, 
							//как было бы обработано такое исключение
Finally
  aList.MassUnlock();
EndTry;

Конструкция объявления функции

Function <имя>([[<способ адресации> ]<имя аргумента>[=<выражение>][,..]]) [Export] {Forward,
  <логический блок>
EndFunction};

Конструкция позволяет определить функцию с именем <имя> и заданным количеством аргументов, а также закрепляет за ней логический блок инструкций. В этом блоке инструкций допускается определения вложенных функций, таким образом создается иерархия функций с разным уровнем вложения. В некоторых случаях вложенные функции позволяют упростить написание программы и сделать ее более читабельной и понятной.

Имя функции не зависит от регистра написания, при его написании могут быть использованы международные символы (к примеру 関数), но должно подчиняться следующим правилам:

  • не должно совпадать с именем другой функции, находящейся на том же уровне вложенности в блоке инструкций одной функции или одного модуля, либо функции, имеющей префикс Export, находящейся на верхних уровнях вложенности.
  • не должно совпадать с именем переменной, если она была задана до определения функции на том же самом уровне вложенности в блоке инструкций одной функции или одного модуля, либо переменной, имеющей префикс Export, находящейся на верхних уровнях вложенности.
  • не должно совпадать с определениями, используемыми в особых конструкциях (к примеру, запрещено создавать функции с именем To или Function) и вычисляемых выражениях (к примеру, And).
  • не должно содержать нелитерных и нецифровых знаков. Знаки, которые могут быть использованы в имени функций: "0".."9", "?", "A".."Z", "_", "a".."z", все символы с кодами >=128.
Аргументы функции

Функция может иметь неограниченное количество аргументов. Свойствами каждого из аргументов являются его имя, способ его адресации и наличие выражения по умолчанию. Имена аргументов подчиняются таким же правилам, как и имена функций. В момент исполнения блока инструкций функции каждый из аргументов может быть адресован по имени, как обычная переменная. Имена аргументов попадают в стек имен последними, при исполнении функции, поэтому, если какое-то имя совпадает с именем переменной с префиксом Export, более высокого уровня вложенности, при обращение к имени будет использован аргумент, а не указанная переменная. Во время вызова функции, аргументы автоматически получают префикс Export, поэтому к ним можно обращаться в функциях более низкого уровня вложенности. При этом, нужно учитывать, что аргументы функции более высокого уровня вложенности будут доступны только в функциях, вложенных в нее. К примеру, функция, вызванная из глобального модуля системы, не сможет получить доступ к аргументам, вызывающей функции, находящейся в другом модуле.

Способ адресации аргумента определяет возможность изменения внешней переменной или метода объекта из тела самой функции. Существует два способа адресации аргументов:

  • ByVal, используемый по умолчанию, когда при изменении аргументов внутри функции, внешние переменные не будут изменены (однако, нужно учесть, что, если переменная содержит объект, то аргумент будет содержать тот же самый объект и изменения, производимые в объекте при его адресации с помощью аргумента, будут также видны при его адресации с помощью внешней переменной, пример см. ниже).
  • ByRef, при задании адресации таким способом, после окончания выполнения функции, переменная, использовавшаяся при вызове функции, будет обновлена значением аргумента. Важно понимать, что обновление значения переменных не происходит при каждом изменении аргумента, а только один раз после окончания выполнения функции (пример см. ниже)

При определении аргумента можно также использовать дополнительное выражение, называемое выражением по умолчанию. Если выражение по умолчанию определено, аргумент считается необязательным, его указание при вызове функции не требуется, к примеру для функции Func с аргументами Func(a,b=1,c=2) аргументы b и с можно опустить, при пропуске только переменной b, можно осуществить вызов функции в виде, к примеру, Func(1,,3), при пропуске только переменной c, функцию можно вызвать, к примеру, Func(1,2), при пропуске же обоих аргументов функцию можно вызвать просто как Func(2). Выражение, указываемое при определении аргумента, будет использоваться для его инициализации при вызове функции без указания аргумента. Выражения могут быть как простыми константами, так как сложными выражениями с использованием математических, логических функций и даже вызовов других функций объявленных в тексте программы. В случаях сложных выражений их вычисление происходит в момент вызова функции при помещении аргументов в стек переменных до исполнения блока инструкций самой функции.

Синтетический пример, показывающий инициализацию и работу с функциями, содержащими разные типы аргументов:

var ПерА export, ПерА2 export;

Function Функ1(ByRef ПерА,ByRef ПерБ=Sin(ПерА),ПерВ)

  Function Функ2()					//Вложенная функция без аргументов
    ПерА:=1;						//Здесь адресуется аргумент Функ1, а не внешняя переменная ПерА, заданная в начале текста
    ПерА2:=2;						//Здесь адресуется внешняя переменная ПерА2, заданная с постфиксом Export
    ПерБ:=3;						//Адресуется аргумент Функ1
    ПерВ:=4;						//Адресуется аргумент Функ1
  EndFunction

  Message("Вызов осуществлен с аргументами: "+ПерА+", "+ПерБ+", "+ПерВ);
  Функ2();
EndFunction;

ПерА:=1;
Функ1(0,0,0);						//Произойдет вывод "Вызов осуществлен с аргументами: 0, 0, 0"
Message(ПерА);						//Будет выведено число 1 (функция не изменяла эту переменную)
Message(ПерА2);						//Будет выведено число 2 (функция изменила эту переменную)
Пер1:=0;Пер2:=0;Пер3:=0;
Функ1(Пер1,Пер2,Пер3);					//Произойдет вывод "Вызов осуществлен с аргументами: 0, 0, 0"
Message(Пер1);						//Будет выведено число 1 (функция изменила эту переменную, при изменении аргумента)
Message(Пер2);						//Будет выведено число 3 (функция изменила эту переменную, при изменении аргумента)
Message(Пер3);						//Будет выведено число 0 (способ адресации аргумента не позволил транслировать его изменение на внешнюю переменную)
Функ1(1,,2);						//Произойдет вывод "Вызов осуществлен с аргументами: 1, 0.841470984807896508, 2"
//Замечание: аргумент ПерБ был инициализирован значением по умолчанию Sin(ПерА) (т.е. Sin(1))

aTab:=Tab.Create("Имя,Значение");			//Создание таблицы со столбцами "Имя" и "Значение"
aTab.AddLine("Имя,Значение","Первый",0);
Пер2:=0;
Функ1(aTab.Значение,(Пер2),0);				//Произойдет вывод "Вызов осуществлен с аргументами: 0, 0, 0"
Message(Пер2);						//Будет выведено число 0, так аргументом вызова функции была не переменная, а выражение "(Пер2)"
Message(aTab.Значение);					//Будет выведено число 0
//Изменения внутри функции транслируются только на переменные, но не на свойства объектов, так как это может создавать скрытые ошибки, которые тяжело будет найти:
//  Изменение свойства, это не всегда просто сохранение значения, в случае с объектами базы данных это может быть нежелательно
Результат функции

При вызове любой функции, после создания и заполнения ее аргументов, создается также особая переменная Result, заполняемая пустым значением. Эта переменная будет возвращена как результат функции, однако, ее поведение соответствует поведению обычной переменной, ее можно изменять и использовать ее значение в любых выражениях. Если в результате выполнения функции нет необходимости, нет смысла использовать эту переменную. Значение этой переменной можно также изменить при использовании конструкции выхода Exit, если у конструкции указан первый аргумент, он будет вычислен, и полученное значение будет помещено в переменную Result. Так как Result создается при вызове любой функции, у вложенной функции нет возможности обратиться напрямую к результату функции более высокого порядка вложенности. Пример использования Result см. ниже:

Function Функ1(ПерА)
  Result:=0;
  If ПерА>1 Then
    Result:=ПерА*2;
  ElseIf ПерА<0 Then
    Exit _Pi();
  EndIf;
EndFunction;

Message(Функ1(-2));					//Возвращает 3.14159265358979325
Message(Функ1(2));					//Возвращает 4
Message(Функ1(1));					//Возвращает 0
Вложенность функций и модулей, уровни вложенности

При создании нового потока, после его вызова (к примеру, с помощью LoadModule), счетчик вложенности циклов обнуляется, а счетчик вложенности функций приравнивается единице (см. выше, к примеру, конструкции Break и Exit). Поэтому конструкция Exit не сможет завершить исполнение программ в предыдущем модуле, а конструкции Break и Continue не смогут передать управление программе, находящейся в другом модуле, даже если поток создавался из тела цикла. При вызове функции, счетчик вложенности циклов также обнуляется и, после возврата из функции, восстанавливается его предыдущее значение. Защита подобного рода помогает исключить ошибки, нахождение которых было бы затруднительно.

При объявлении функции в блоке исполнения другой функции, такая функция называется вложенной. В данной документации понятие функция более низкого уровня вложенности означает функцию, вложенную внутрь текущей, а функция более высокого уровня вложенности, функцию, в которую была вложена текущая. Вложенные функции имеют доступ к локальным переменным и своим аргументам, к аргументам функций в которые они были вложены, а также к переменным и функциям более высокого уровня вложенности, если они определены с постфиксом Export (см. аргументы функции).

Следует понимать различие между термином уровни вложенности применительно к исходным текстам программы и к вызову функций из блоков инструкций других функций в процессе исполнения. В последнем случае, функция, к примеру, может вызывать сама себя определенное количество раз, создавая рекурсию. Правила доступности и адресуемости переменных и других функций, однако, применимы для процесса исполнения таким же образом, как и для процесса компиляции.

Ниже даны правила доступности переменных, аргументов и имен функций:

Положение проверяемой функции (или модуля) Доступность из блока инструкций исполняемой
Аргументы Переменные Вложенные функции
Проверяемая функция вложена в исполняемую (с любым уровнем вложенности) Нет Нет Нет
Проверяемая функция является исполняемой Да Да Да
Исполняемая функция вложена в проверяемую (с любым уровнем вложенности). Да С постфиксом Export На том же уровне вложенности, что и исполняемая, объявлены до исполняемой
Исполняемая функция вложена в функцию (с любым уровнем вложенности), в которую также вложена проверяемая (с любым уровнем вложенности). Исполняемая функция, однако, не вложена в проверяемую (с любым уровнем вложенности). См. пример ниже. Нет Нет Нет
Исполняемая функция вложена непосредственно в проверяемый модуль. Да С постфиксом Export Объявлены до исполняемой, верхний уровень вложенности
Исполняемая функция вложена в модуль, который был вызван из проверяемого (с любым уровнем вложенности). Нет С постфиксом Export С постфиксом Export, верхний уровень вложенности
Проверяемый модуль является глобальным. Недоступно С постфиксом Export С постфиксом Export, верхний уровень вложенности
//Пример для случая №4:
Function ВерхнийУровень()
  Function Проверяемая(АргументыПр)
    Var ПеременныеПр Export;
    Function ПроверяемаяПр() Export
    EndFunction;
  EndFunction;

  Function Исполняемая()
    //АргументыПр, ПеременныеПр и функция ПроверяемаяПр() не будут доступны из этого исполняемого блока
  EndFunction;
EndFunction;
Объявление заголовков функций

Иногда в процессе написания программы из определенной функции требуется вызвать функцию, которая была описана ниже по тексту, это может происходить, при создании сложной рекурсивной конструкции, либо, чтобы избежать переносов функций внутри модуля, особенно, когда они уже сгруппированы по определенному принципу. С точки зрения исполнения, такой вызов не должен представлять проблему, так как к началу исполнения все функции в модуле определены и известны системе. Однако, что касается компиляции программных текстов, возникает проблема того, что в момент вызова, компилятор еще не знает о существовании (определении) вызываемой функции, что вызовет ошибку компиляции (такой подход позволяет также избежать определенных ошибок при написании программ). Выходом из положения, в данном случае, является возможность объявления заголовка функции заранее без ее блока описания, после чего, в нужном месте модуля, функция будет описана полностью. При объявлении заголовка функции, после перечисления ее аргументов и возможного префикса Export следует зарезервированное слово Forward, после чего объявление заголовка заканчивается. Сама функция, объявленная "в кредит" таким образом, должна присутствовать позже в том же самом модуле, иначе система вернет ошибку при компиляции. Пример использования Forward:

//Кусок кода, показывающий необходимость в сложной рекурсии. Элементы разных справочников ищутся по внешнему коду стрКод, в справочнике "СопоставленныеЭлементы"
//Функция ВыбратьЭлемент должна дать возможность пользователю визуально выбрать нужный элемент с учетом возможных родителей (см. описание справочников БД)

//Здесь описывает только заголовок рекурсивной функции
Function НайтиРодителя(ВидСпр,Префикс,стрКод,БезВыбора=0) Forward;

//Функция визуально выбирает элемент справочника ВидСпр, который и отдается, как результат
//  Если пользователь не выбрал элемент, функция возвращает 0.
//стрКод -- код в промежуточном справочнике сопоставления "СопоставленныеЭлементы", имеет вид "<КодЭлемента>@<ВидРодителя>#<КодРодителяЭлемента>@<ВидРодителяРодителя>#<КодРодителяРодителяЭлемента>..."
Function ВыбратьЭлемент(ВидСпр,стрКод)
  аРод:=0;				//Элемент родителя, если он необходим
  if Struct.Ref(ВидСпр).Parents.Count()>0 Then
    //Справочник имеет родителей, необходимо сначала осуществить выбор родителя
    аРод:=НайтиРодителя(ВидСпр,стрКод);
    If аРод=0 Then
      Exit 0;
    EndIf;    
  EndIf;

  //Вызываем функцию визуального выбора и отдаем ей в первом параметре найденного родителя или 0, если родитель не требуется
  аЭл:=SelectObject("Ref."+ВидСпр,аРод,0,"Module","SelectForm",,,"Выберите требуемый элемент справочника:",0);
  Exit ?(not isEmpty(аЭл),аЭл,0);
EndFunction

Function НайтиРодителя(ВидСпр,стрКод,БезВыбора=0)
  аЭл:=Ref.СопоставленныеЭлементы;	//Создаем объект элемента справочника СопоставленныеЭлементы
  TearStr(стрКод,"@");			//Удаляем код текущего элемента, оставляем внешний код родителя и его вид
  ВидРод:=TearStr(стрКод,"#");		//Находим вид родителя и оставляем только внешний код элемента-родителя
  If аЭл.Find("ВидСправочника,ВнешнийКод",ВидРод,стрКод) Then
    Exit аЭл.Элемент;			//Родитель уже сопоставлен, нет необходимости выбирать что-то визуально
  ElseIf БезВыбора Then
    Exit 0;				//Родитель не сопоставлен, но визуальный выбор запрещен
  EndIf;
  бЭл:=ВыбратьЭлемент(ВидРод,стрКод);	//Вызываем ВыбратьЭлемент уже для родителя
  Exit бЭл;				//Если оператор отказался от выбора возвратится 0, иначе выбранный элемент  
EndFunction


//Другая функция ниже по модулю, подбирающая возможные варианты выбора
Function ПодобратьВарианты(ВидСпр,Эл)
  СпВар:=List.Create();
  ...
  If ВидСпр="ПартииНоменклатуры" Then
    аЭл:=НайтиРодителя(ВидСпр,Trim(Эл.ВнешнийКод),1);
    If аЭл=0 Then
      Exit СпВар;
    EndIf;
    ...					//Заполняется СпВар с учетом выбранного родителя
  EndIf;
  ...
EndFunction

Использование синтаксиса массивов для доступа к значениям объектов

Система не имеет выделенной структуры массивов, так как существуют объекты (списки, таблицы, деревья), которые в полной мере могут заменить подобную структуру. Однако, доступ к элементам массивов с помощью квадратных скобок хорошо понятен, читабелен и может упростить код программы. Система использует подобный доступ к значениям избранных объектов, как будто доступ производится к элементам массивов. Так же, как и с обычными массивами, доступны как чтение, так и запись, вложенность индексов и текстовые индексы для определенных объектов. Получение значения с помощью синтаксиса массива выполняется в следующем виде: <Объект>[<Индекс 1>,...], конструкцию вызова массива можно повторять сколько угодно раз (пример, aObj[1,2]["foo",1]), изменение значения можно выполнить с помощью команды: <Объект>[<Индекс 1>,...]:=<Значение>. Если с помощью первого индекса в массиве программа получает элемент из списка значений по его индексу или наименованию, второй и последующий индексы массива будут применены к полученному элементу (и, если этот элемент не является подходящим объектом, к которому можно применить синтаксис массива, будет вызвано исключение). На данный момент синтаксис массивов применим к следующим объектам системы:

  • Для строковых значений (переменная, содержащая строку в ANSI или UTF-16 кодировках) используется один индекс, указывающий на определенный символ строки. Нумерация символов начинается с единицы. Операция получения символа, аналогична выполнению функции Mid. При изменении символа, кроме строкового значения, можно также указывать числовое значение, оно будет использовано как код присваиваемого символа, это немного быстрее и может использоваться в случае, когда строка хранит бинарные данные.
  • Список значений (List) использует один индекс массива, который может быть текстовым (в этом случае, выполняется операция аналогичная вызовам функций GetByName или SetByName), либо числовым (в этом случае, выполняется операция аналогичная вызовам функций Get или Set).
  • Таблица значений (Tab) использует один или два индекса массива. Если задан всего один индекс массива, он используется как индекс или наименование столбца для получения или изменения значения ячейки таблицы в ее текущей строке (операции <Таблица>.<Имя столбца> или <Таблица>.Get(,<Индекс столбца>)). Если задано более одного индекса массива, первый из индексов используется для указания на столбец таблицы, второй на ее строку (второй индекс может быть только числовым), т.е. выполняется операции <Таблица>.Get(<Индекс 2>,<Индекс 1>) или <Таблица>.Set(<Индекс 2>,<Индекс 1>,<Значение>).
  • Дерево значений (Tree) использует один или более индексов массива, индекс может быть текстовым (в этом случае, выполняется операция аналогичная вызовам функций GetByName или SetByName), либо числовым (в этом случае, выполняется операция аналогичная вызовам функций Get или Set). Если даны несколько индексов, все операции, кроме последней, получают не значение, а ветку (т.е., для промежуточных индексов используются операции, аналогичные функциям ByIndex или FindByName).
  • Текст (Text) использует один числовой индекс массива, который указывает на номер строки объекта текста (то есть, выполняется операция аналогичная вызовам функций GetLine или SetString).
  • Буфер (Buffer) использует один или два индекса массива. Первый индекс массива используется как позиция в буфере (первый байт буфера адресуется с нуля), второй аргумент массива, если указан, задает величину данных (доступны значения 1 (по умолчанию, байт без знака), 2 (слово без знака), 4 (двойное слово без знака), 8 или -8 (64-битное числовое значение со знаком), -1 (байт со знаком), -2 (слово со знаком), -4 (двойное слово со знаком)).
  • Запись данных (MemoryRecord) использует индекс массива для доступа к полям, являющимся массивами. Индексирование начинается с нуля.

Скорость доступа к значениям объектов с помощью синтаксиса массивов обычно не отличается от скорости доступа с помощью обычных функций. Однако, если для доступа используется несколько индексов, адресующих разные объекты, массивы будут работать несколько быстрее.

Магические функции

Магическими называются функции, нестандартная работа с аргументами которых, не позволяет создать их обычными средствами языка. Для обычных функций каждый из аргументов вычисляется до того, как будет произведено обращение к функции, тогда как магические функции работают как конструкции языка с блоками инструкций, и вычисления аргументов производятся только когда это необходимо. В отличие от конструкций языка, однако, магические функции не могут представлять самостоятельные инструкции или содержать блоки инструкций, а используются только в выражениях и могут содержать только выражения в своих аргументах.

Магическая функция логическое "и"
_And(<выражение>[,<выражение>[,...]])

Функция возвращает результат логической конъюнкции своих аргументов. Вычисление аргументов останавливается, после того как встретится первый аргумент возвративший "ложь". В отличие от конструкции (<выражение>)And(<выражение>)And..., для которой всегда выполняются все выражения, так как она может являться как логической, так и математической, _And прекращает исполнение аргументов, как только встречается "ложь", это позволяет как оптимизировать по скорости условия конструкций типа If, так и пройти каскадное условие доступности, для которого необходимо бы было использовать несколько вложенных функций If, чтобы избежать исключения. Пример разницы между And и _And см. ниже:

Function ВывестиАргумент(Арг)				//Функция возвращает переданный ей аргумент и выводит его в лог
  Message(Арг);
  Exit Арг;
EndFunction

//В лог будет выведено 1, 2 и 3. "Проверка пройдена!" выведена не будет. 
If (ВывестиАргумент(1)<2)And(ВывестиАргумент(2)<2)And(ВывестиАргумент(3)<2) Then
  Message("Проверка пройдена!");
EndIf;

//В лог будет выведено 1, 2. "Проверка пройдена!" выведена не будет. Функция ВывестиАргумент(3) не была вызвана
If _And(ВывестиАргумент(1)<2,ВывестиАргумент(2)<2,ВывестиАргумент(3)<2) Then
  Message("Проверка пройдена!");
EndIf;

...

//аОбъект -- переменная, содержащая объект неопределенного вида или строковое выражение
//Нам необходимо проверить, является ли объект элементом справочника "Номенклатура" и, если его реквизит Тип равен единице, вывести в лог его код (реквизит Code)
аОбъект:="Не объект";

//Конструкция каскадной проверки, основанная на _And, не вызовет исключений
If _And(not isEmpty(аОбъект),IsDBObject(аОбъект),аОбъект.DBName()="Номенклатура",аОбъект.Тип=1) Then
  Message(аОбъект.Code);
EndIf;

//Конструкция с аналогичным использованием And вызовет исключение при вычислении выражения аОбъект.DBName(), так как аОбъект не является объектом
If (not isEmpty(аОбъект))And(IsDBObject(аОбъект))And(аОбъект.DBName()="Номенклатура")And(аОбъект.Тип=1) Then
  Message(аОбъект.Code);
EndIf;

//Конструкция каскадной проверки, логически совпадающая с первой, однако не использующая _And, тоже не будет вызывать исключений.
//Данная конструкция, однако, выполняется медленнее чем первая и более сложна для понимания.
If (not isEmpty(аОбъект))And(IsDBObject(аОбъект)) Then
  If аОбъект.DBName()="Номенклатура" Then
      If аОбъект.Тип=1 Then
        Message(аОбъект.Code);
      EndIf;
  EndIf;
EndIf;
Магическая функция логическое "или"
_Or(<выражение>[,<выражение>[,...]])

Функция возвращает результат логической дизъюнкции своих аргументов. Вычисление аргументов останавливается, после того как встретится первый аргумент возвративший "правда". В отличие от конструкции (<выражение>)Or(<выражение>)Or..., для которой всегда выполняются все выражения, так как она может являться как логической, так и математической, _Or прекращает исполнение аргументов, как только встречается "правда", это позволяет как оптимизировать по скорости условия конструкций типа If, так и превратить несколько условий прерывания исполнения в одно. См. также пример функции _And.

Магическая функция выбора
?(<выражение>,<выражение если правда>[,<выражение если ложь>])
_If(<выражение>,<выражение если правда>[,<выражение если ложь>])

Функция вычисляет первый аргумент, и, если результатом является логическая "правда", возвращает вычисленное выражение, следующее вторым аргументом, иначе возвращает вычисленное выражение, следующее третьим аргументом, если он указан или пустое значение, если третий аргумент не указан. Функция производит вычисление второго аргумента только в случае, если первый возвратил "правду", иначе второй аргумент не будет вычислен. Тоже самое касается третьего аргумента. Функция может использоваться как для быстрой проверки типов, для того, чтобы избежать исключений, так и вместо более громоздких конструкций If. Позволяет не только уменьшить размер кода, улучшить его читаемость, но также ускорить его исполнение. Записи ? и _If равнозначны. См. также пример функции _And.

Инструкция языка

Любая программа состоит из инструкций собираемых в блоки, использующиеся внутри функций или особых конструкций языка. Инструкции в одном блоке выполняются последовательно. Инструкции разделены между собой знаком ; (точка с запятой) и могут быть двух типов:

  1. Инструкция исполнения. Инструкция представляет собой обращение к функции (функция не может быть магической), вычисляемые выражения могут быть только частью аргументов функции. Если функция возвращает какой-либо результат, он не будет использован. Общий вид инструкции: <ИмяФункции>(<Аргумент 1>,<Аргумент 2>,...);.
  2. Инструкция присваивания. Инструкция представляет собой вычисляемое выражение, значением которого обновляется значение переменной или свойства объекта. Между именем переменной и выражением используется знак присваивания :=. Использование знака равенства (=) в этом случае допускается, но не рекомендуется, так как это может ухудшить читаемость текста программы. Общий вид инструкции: <ИмяПеременнойИлиСвойства>:=<Выражение>;.

Пример использования инструкций обоих типов:

Message("12");				//Инструкция исполнения выводит в лог программы значение "12"
a:=1;					//Инструкция присваивания создает новую переменную a и присваивает ей значение 1
a:=Sin(a)*1.5-Cos(a/2)/_Pi*2;		//Инструкция присваивания изменяет значение переменной a в соответствии с результатом вычисления выражения
a:=Message("12");			//Каждая функция возвращает результат, функция Message возвращает пустой результат, который присваивается переменной а.
					//Данная инструкция, в отличие от самой первой в этом блоке, уже является инструкцией присваивания, при этом, она по-прежнему выводит строку "12"
Message("12")+1;			//Ошибочная инструкция, содержащая только выражение, компиляция будет остановлена
?(a=1,2,3);				//Ошибочная инструкция, магическая функция ? может использоваться только в выражениях, и не может быть самостоятельной инструкцией исполнения.
a:=?(a=1,2,3);				//Инструкция присваивания, с использованием магической функции в ее выражении не вызовет ошибки.
a:=a=1;					//Инструкция присваивания, результат логического выражения a=1 (0 или 1) присваивается переменной а
					//Данная инструкция показывает, почему пара ":=" при присваивании предпочтительна простому знаку "="
aTab.CurLine:=12;			//Инструкция присваивания записывает 12 в свойство CurLine объекта, задаваемого переменной aTab (в данном примере объект типа таблица)

Вычисляемые выражения и старшинство операций при вычислении

Вычисляемые выражения представляют собой математические или логические формулы с участием числовых, строковых или временных констант, заданных непосредственно в тексте программы, переменных, свойств объектов и функций, связанных между собой знаками или определениями операций и скобками. Простым примером вычисляемого выражения является математическое выражение (к примеру 1+2). Математические знаки и определения операций в основном совпадают с операциями в других языках, общее количество используемых операций невелико (см. ниже). Выражения вычисляются всегда слева направо, даже с учетом приоритета операций и скобок. Функции, используемые в выражении, всегда будут вычислены последовательно без пропусков (см. пример ниже), однако правило может не работать для функций, использующихся в аргументах магических функций. Каждая операция имеет свой приоритет. Если внутри одного уровня скобок встречаются несколько последовательных операций, связывающих разные аргументы (операнды), результат вычисления выражения будет зависеть от приоритетности операций. Ниже дается список приоритетности и список возможных операций языка:

  1. (, ) -- скобки позволяют вычислять группы операций вместе, и являются самыми приоритетными знаками операций (хотя скобки это не операции в обычном смысле, они приведены в списке для полноты).
  2. NOT -- унарная логическая и математическая операция инвертирования
  3. SHL, SHR -- бинарные операции математического сдвига
  4. AND, OR, XOR -- бинарные логические и математические операции и, или и исключающего или.
  5. *, /, \, % (MOD) -- бинарные математические операции умножения, деления, целочисленного деления и нахождения остатка от деления
  6. +, - -- бинарные математические операции сложения и вычитания, а также унарные математические операции подтверждения знака и обращения знака
  7. =, <>, <, >, <=, >= -- бинарные логические операции равно, не равно, меньше, больше, меньше или равно, больше или равно
//В данном примере показано, что вне зависимости от приоритета, функции в вычисляемых выражениях всегда вычисляются последовательно.
Function Проверка(Аргумент)
  Message(Аргумент);
  Exit Аргумент;
EndFunction;

a:=Проверка(1)+Проверка(2)*Проверка(3) shl Проверка(4);
//На экран будет выведена последовательность 1,2,3 и 4. С математической точки зрения, необходимо сначала вычислить выражение Проверка(3) shl Проверка(4), 
//  потом результат умножить на Проверка(2) и сложить с Проверка(1).

a:=((Проверка(1)=0)And(Проверка(2)=0)And(Проверка(3)=0))Or(Проверка(4)=0);
//На экран будет выведена последовательность 1,2,3 и 4. С логической точки зрения, достаточно произвести вычисление Проверка(1)=0, чтобы понять, что проверки Проверка(2)=0 и Проверка(3)=0
//  можно пропустить, получив значение "ложь", после этого нужно будет вычислить значение Проверка(4)=0.

Логические выражения

Булева функция может принимать два значения "ложь" и "правда". Любое логическое выражение оперирует и возвращает одно из двух возможных значений булевой функции. В системе, однако, нет выделенного булевого типа, вместо него используется обычный целочисленный тип. При логической проверке конструкциями If, While и пр. или магическими функциями ?, _And и пр. и везде, где должен быть указан булевый аргумент, в системе используется целочисленный. Значение этого аргумента будет являться "правдой", если его младший бит установлен, и "ложью", если его младший бит сброшен. В таком представлении любые целочисленные логические функции, типа And, Or, Xor будут также работать с логическими аргументами, надобность в дополнительном типе данных пропадает, программирование системы упрощается. Ниже даны несколько примеров работы с логическими функциями в системе:

If 1 Then
  Message("Этот текст всегда будет выведен, так как 1 соответствует логическому значению 'правда'");
EndIf;

Message(1>2);				//Выводит число 0 ("ложь")
Message(3>=2);				//Выводит число 1 ("правда")
Message((Sin(1))Or(2)Or(2>1));		//Бессмысленное выражение, смешивающее вещественные числа, целочисленную логику и булеву логику, 
					//вычисление которого, между тем, не приводит к ошибке (в лог выводится число 3).
Message(Sqrt(20) And 65535);		//При операциях с целочисленной логикой, вещественные числа приводятся к целым (отбрасывается дробная часть), это выражение выведет 4.

//В данном примере на экран выводится стиль шрифта элемента формы с именем "Поле". Свойство FontStyle отдает число, представляющее битовую маску с битами: 
//   1 -- жирный, 2 -- наклонный, 3 -- подчеркнутый, 4 -- зачеркнутый. Пример пользуется тем, что логические функции учитывают только младший бит числа.
аСтиль:=Form.ControlByName("Поле").FontStyle;
Message("Стиль элемента ""Поле"": "+?(аСтиль,"жирный ")+?(аСтиль\2,"наклонный ")+?(аСтиль\4,"подчеркнутый ")+?(аСтиль\8,"зачеркнутый "));

Константы в исходных текстах

При написании исходных текстов часто приходится использовать заранее известные значения -- числа, строки или даты. Данные значения называются константами. При компиляции текста, они превращаются в нужный тип данных и сохраняются в компилированном файле с тем, чтобы позже, использоваться при исполнении выражений. Способ задания констант в исходных текстах программы следующий:

  • Числа (вещественные или целые) записываются в стандартной математической форме, кроме десятичных констант система позволяет записывать числа в шестнадцатеричной, восьмеричной и двоичной системах исчисления. Принимаются следующие форматы записи чисел:
  1. {+,-}*[E{+,-}*] (где * заменяет любое количество знаков 0..9) -- используется для записи десятичных целых или вещественных чисел. Число может содержать только следующие знаки +,-,0..9,.,E (или e, используется для указания мантиссы вещественных чисел). Разделителем целой и вещественной части служит точка (.). Примеры: 1.23, 1000000000000, 1.73E+100.
  2. $* или 0x* (где * заменяет любое количество знаков 0..9, a..f) -- используется для записи шестнадцатеричных целых чисел. Число может содержать только следующие знаки 0..9,x,X,a..f,A..F. Ввод вещественных чисел в шестнадцатеричной нотации не допускается. Примеры: $fffaa, 0x76AB.
  3. 0o* (где * заменяет любое количество знаков 0..7) -- используется для записи восьмеричных целых чисел. Число может содержать только следующие знаки 0..7,o,O. Ввод вещественных чисел в восьмеричной нотации не допускается. Примеры: 0o7601.
  4. 0b* (где * заменяет любое количество знаков 0..1) -- используется для записи двоичных целых чисел. Число может содержать только следующие знаки 0..1,b,B. Ввод вещественных чисел в двоичной нотации не допускается. Примеры: 0b11010001.
  • Строки всегда заключены в двойные кавычки ("). Для того, чтобы добавить двойную кавычку в строку, ее нужно употребить два раза. К примеру, выражение "Строка ""в кавычках""" представляет с собой строку Строка "в кавычках". Строковая константа всегда должна быть закончена до окончания строки, если необходимо добавить в строку перевод строки, можно использовать именованную константу _NEWLINE. Однако, можно также указать в начале следующей строки символ конкатенации строковых констант |. В этом случае, компилятор продолжит набор строковой константы со следующей строки, а вместо символа | будет добавлен знак перевода строки. Пример показан ниже:
ТЗ:="Period From НачДата to КонДата;
|зБс:=Storage.Банк.БанковскийСчет;
|зЮЛ:=Storage.Банк.ЮрЛицо;
|зСмП:=Income(Сумма);
|зСмР:=Expense(Сумма);
|зСмНО:=BegTotals(Сумма);
|зСмКО:=EndTotals(Сумма);
|Condition(NOT IsEmpty(зЮЛ));";
//В данном примере создается длинная строковая константа, располагающаяся на нескольких строках, вместо знаков | будет добавлен перевод строки.
//Важно понимать, что внутри строк до их завершения невозможно использовать комментарии. Пример: 
а:="Некая строка //Данный текст не является комментарием, а будет частью строковой константы
|Другая строка";

а:="Некая строка"+_NEWLINE+ //Данный уже будет являться комментарием
"Другая строка";
  • Константы даты и времени всегда заключены в апострофы ('). Понимаются следующие форматы записи даты и времени:
  1. [Д]Д.[М]М.ГГ[ГГ][ [Ч]Ч:[М]М[:[С]С]] -- Стандартный формат записи. Примеры: '10.12.2012', '31.01.2020 12:00', '3.6.25 4:12:11'
  2. [М]М/[Д]Д/ГГ[ГГ][ [Ч]Ч:[М]М[:[С]С]] -- Американский формат, разделителем является знак /. Время по-прежнему задается в 24-х часовом формате. Примеры: '12/10/2012', '01/31/2020 12:00', '6/3/25 4:12:11'
  3. [Д]Д-[М]М-ГГ[ГГ][ [Ч]Ч:[М]М[:[С]С]] -- Стандартный формат с разделителем -. Примеры: '10-12-2012', '31-01-2020 12:00', '3-6-25 4:12:11'

При попытке использовать другие форматы в тексте исходного кода, компиляция будет остановлена с ошибкой. Если при указании даты временная часть опускается, она считается равной 00:00:00. Если при указании временной части, секундная часть опускается, она считается равной нулю секунд.

Исключения

Исключения могут возникать в моменты ошибочных ситуаций в программах, а могут быть вызваны специально, как прием прерывания выполнения, когда условия не удовлетворяют проверке и продолжение исполнения невозможно. Большинство функций способны вызвать исключение, если, к примеру, они получают в своих аргументах неожиданные или неверные значения (слишком большое число, индекс, выходящих за пределы списка или таблицы и пр.) -- такие исключения сигнализируют об ошибках в написании кода программы, возможны также исключения из-за внешних событий (к примеру, отсутствие файла или его некорректный формат) -- такие исключения невозможно предугадать, поэтому разработчик должен предусмотреть и запрограммировать реакцию программы при их возникновении. Поведением по умолчанию при возникновении исключения является остановка программы на текущей инструкции, текущий модуль, при этом, закрыт не будет (кроме ситуаций, когда исключение возникло в предопределенном событии OnOpen или в момент исполнения события формы OnOpen), управление будет отдано пользователю.

Важно понимать, что в данном тексте под исключениями понимаются внутренние события, возникающие на уровне языка. Системные исключения, сигнализирующие ошибки внутри операционной системы или внешние катастрофические события, не затрагиваются этой темой, см. статью "Исключения в программе и отладка исключений".

Разнообразные конструкции и функции системы помогают настроить работу с исключениями, как это необходимо разработчику. В данный список входят функции и конструкции, которые:

  • Изменяют поведение системы по умолчанию при возникновении исключений: конструкции Try/Except, Try/Finally (описание см. выше)
  • Вызывают или подавляют исключения: конструкция Raise (см. выше), функции SuppressException, Assert, Reraise.
  • Получают информацию по возникшим исключениям: функции PopError, PeekError.
  • Получают информацию по стеку исполнения программы: функция dbgStackTrace.

Типы данных, объекты и обращение к ним

Каждая переменная в системе содержит какой-либо объект. Объекты разделяются на сложные и простые. Простые объекты (пустое значение, числа, даты, строки) всегда копируются при копировании переменной в другую переменную (a:=b;), а также при передаче переменных в виде аргументов в функцию (т.е., изменение аргумента функции не вызовут изменение внешней переменной, даже если для аргумента использован префикс ByRef, однако, в последнем случае, изменение будет скопировано в назад переменную после окончания выполнения функции). Сложные объекты (количество которых может варьироваться в зависимости от подключения внешних библиотек, сборки системы и пр.) не хранятся непосредственно в переменных, а представлены ссылками. При копировании содержания переменной, или передаче ее в виде аргумента в тело функции, в новой переменной создается дополнительная ссылка на сложный объект (и его внутренний счетчик ссылок увеличивается на единицу). См. также статью Объекты.

Каждый сложный объект принадлежит определенному набору функций и свойств, называемому интерфейсом. Интерфейсы имеют свои имена и к ним можно обращаться, однако, обычно не несут важной функции, кроме как создание новых объектов из них (однако, есть исключения). Интерфейс можно себе представить как трафарет, а объект, как рисунок, полученный с помощью этого трафарета. Интерфейсы не содержат реальных данных, это программный набор функций, он не занимает в памяти дополнительного места. Как только создается объект, в памяти отводится определенное место под его данные (разное для разных объектов), при этом, используя этот созданный объект, можно вызывать функции его интерфейса, чтобы модифицировать его и производить манипуляцию данными с помощью его свойств. Любое обращение к внутренним возможностям объектов и интерфейсов происходит с помощью разделителя . (точка) за которым следует наименование функции или свойства. Некоторые функции или свойства объектов могут возвратить новый (или существовавший ранее) объект. В этом случае, разделитель можно применить повторно, чтобы обратиться к функциям возвращаемого объекта. Таким образом можно создавать конструкции каскадных обращений к объектам с помощью разделителей, которые могли иначе бы занять много строк текста. К примеру, строка аИмя:=Ref.тмцНоменклатура.Find("Code","000000001355").Name, начинается с обращения к интерфейсу Ref, далее созданию объекта базы данных с типом справочник тмцНоменклатура, затем для свежесозданного объекта вызывается функция Find, которая заполняет объект реальным значением из базы данных, после чего находится свойство с именем реквизита базы данных Name, оно и присваивается переменной аИмя.

Интерфейсы (и их объекты) обычно содержат следующие элементы, с помощью которых можно обращаться к ним:

  • Функция создания нового объекта. Вызывается непосредственно из интерфейса. Обычно она называется Create, но может иметь и другие названия, таких функций может быть множество, иногда она может отсутствовать. Зачастую такие функции также можно вызвать из объектов, что, в прочем, не имеет особого смысла (к примеру, aList:=List.Create(1,2,3);bList:=aList.Create(); bList создан вызовом из объекта, а не интерфейса, как это обычно бывает). Замечание: наличие функции удаления объекта не требуется, так как он удаляется автоматически системой, после того, как удалены все его ссылки.
  • Функции, вызываемые непосредственно из интерфейса (включая функции создания новых объектов). Большинство функций интерфейса могут быть вызваны только из объекта, так как им необходимы данные, которые могут ассоциироваться только с объектами, однако, существуют абстрактные функции, которые можно вызывать непосредственно из интерфейса, а есть и такие, которые работают только в совокупности с интерфейсом и не будут работать в объекте.
  • Свойства объекта. Свойства предоставляют доступ к внутренним данным объекта, в отличие от функций обычно не имеют аргументов и часто существует возможность их изменения, т.е., употребления свойства с левой стороны инструкции присваивания (к примеру, aTab.CurLine:=12, CurLine здесь является свойством объекта aTab с интерфейсом Tab). Присвоение значений функциям не имеет смысла.
  • Функции объекта. Все остальные функции, перечисленные в интерфейсе и доступные для использования в его объектах.

Компиляция и способ хранения текстов

Исходные тексты программы хранятся не только в модулях системы (отдельных текстовых файлах, специально предназначенных для хранения таких текстов), но также могут быть привязаны к данным другого вида, в последнем случае, они являются событиями или триггерами. Исполнения программ только из модулей недостаточно, чтобы удобно описать взаимодействие пользователя с пользовательским интерфейсом или некоторых объектов базы данных и основной программы, поэтому тексты программ приходится привязывать к визуальным объектам, чтобы, к примеру, выполнить какое-либо действие после того, как пользователь нажал на кнопку или изменилась выделенная строка в таблице. Также удобнее, к примеру, описать механизм отмены обработки документа в специальном месте, привязанном к объекту базы данных, в частности, это позволяет вызывать такую программу-триггер как в момент вызова программой функций Unpublish() или Mark() (без какой-либо необходимости что-то дополнительно описывать, передавать в параметрах имя процедуры обработки и пр.), так и при визуальном удалении документа из журнала, упрощая программирование системы и уменьшая число возможных ошибок. Ниже представлены разнообразные способы хранения исходных текстов в системе:

  • Модули (в т.ч. глобальные). Отдельные файлы, полностью отданные под хранение модулей.
  • Визуальные события. Тексты событий хранятся внутри особых контейнеров внутри файлов-описаний форм. События, описанные таким образом, могут быть привязаны как к элементам формы (причем каждому элементу может быть задано множество событий), так и к самой форме в общем.
  • События пунктов меню и события панели инструментов. Тексты триггеров событий хранятся внутри файлов с описанием меню и панелей инструментов. События выполняются, когда пользователь выбирает определенный пункт меню или нажимает на кнопку на панели инструментов.
  • Триггеры объектов базы данных. Тексты хранятся внутри файла метаданных и вызываются в моменты создания, записи или обработки объектов.

Для увеличения скорости исполнения все тексты (включая события и триггеры) до передачи на сервер базы данных подвергаются компиляции. Компиляция это необратимое преобразование текстового файла программы в бинарный файл, не являющийся файлом машинного кода, но более понятный и удобный для исполнения машине, чем текстовый файл. Компилированные файлы невозможно в полной мере превратить назад в тот вид, какими они были до компиляции (при компиляции, к примеру, теряются комментарии, возможно меняются имена переменных и пр.). Поэтому, такие файлы хранятся только на сервере, тогда как разработчик может продолжать производить требуемые изменения в тестах проекта. Компиляция выполняется автоматически программой разработки Студия, при этом в компиляции участвуют и подвергаются обновлению на сервере только те файлы, в которых производились изменения.

Предопределенные события

В некоторых случаях, более удобно описать какое-либо событие внутри модуля, нежели в контейнере обработчике событий. Иногда это связано с тем, что такое событие будет вызвано по умолчанию, и, если его разместить в глобальном модуле, можно изменить поведение всего проекта при наступлении какого-либо события в любом его месте, после чего для определенных форм это поведение можно модифицировать в обработчиках событий. Иногда же, такой способ является единственно возможным (к примеру, файлы электронных таблиц на данный момент не подразумевают наличия своего хранилища событий, поэтому, события, связанные со свободными окнами электронных таблиц вызываются только из модуля, из которого эти таблицы были созданы (данный модуль будет оставаться в памяти системы, до того, как будет закрыта последняя таблица); однако, для электронной таблицы, помещенной на форму, как ее элемент, уже можно задать свои события из редактора форм.

С учетом внешних событий или событий, задаваемых подключаемыми модулями, количество типов предопределенных событий в системе не ограничено. Однако основные события, описанные на уровне системы, поддаются полному описанию. Для получения информации см. статью Предопределенные события.