Ошибки и ограничения использования FastScript

Среда разработки приложений My Visual Database для реализации дополнительной функциональности использует FastScript — библиотеку для выполнения скриптов, созданную ООО «ФастРепортс». Кроме неоспоримых достоинств, данная библиотека имеет ряд ограничений и дефектов. Часть из них описана в документации («Руководство разработчика») , но некоторые из них не документированы и вряд ли будут исправлены (актуальная документация датирована 2005 годом).

Официальные ограничения

Ниже приведены ограничения FastScript, которые имеют значение при создании приложений в My Visual Database:

  • отсутствуют объявления типов и классов в скрипте;
  • нет записей (records);
  • нет указателей (pointers);
  • нет множеств (sets);
  • нет типа shortstrings;
  • нет безусловного перехода (GOTO).

Несмотря на отсутствие множеств, работа с ними возможна, то есть возможно использование оператора ‘IN’ – “a in [‘a’..’c’,’d’]”, а отсутствие оператора GOTO должно только порадовать истинных приверженцев структурного подхода в программировании.

Недокументированные ошибки

Локальные переменные, параметры и рекурсия

FastScript поддерживает передачу параметров-значений и параметров-переменных, однако при рекурсивном вызове процедуры или функции значение параметра-переменной не сохраняется.

Пример. Функция Recursive() вызывает саму себя столько раз, сколько указано в параметре ACount, а в переменной AResult накапливается число вызовов.

function Recursive( ACount: integer; var AResult: integer ): boolean;
begin
  AResult := AResult + 1;
  if count > 0 then
    Result := Recursive( count - 1, AResult )
  else
    Result := True;
end;Code language: Delphi (delphi)

Однако на деле в переменной AResult всегда возвращается число 1:

procedure Form1_Button1_OnClick (Sender: TObject; var Cancel: boolean);
var
  r: integer;
begin
  r := 0;
  Recursive(10,r);
  ShowMessage(r);
end;
Code language: Delphi (delphi)

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

Автозамена сочетаний символов

Если вы наберёте в редакторе последовательность " после компиляции файла script.pas в script.dcu она автоматически превратиться в символ «двойные кавычки».

Решение: проблема решается рассечением строковой последовательности на части:

s := '&q'+'uot;';Code language: Delphi (delphi)

Нельзя объявить параметр со значением по умолчанию nil

Не допускаются объявления параметра вида

procedure MyProc( AObject:TObject = nil)Code language: Delphi (delphi)

Как следствие, если в качестве параметра передаётся объект, то такой параметр не может быть необязательным.

Нельзя использовать константу при описании значения параметра по умолчанию

Не допускаются объявления параметра вида

const<br>  DEF_VAL = 1<br>procedure MyProc( AParam: integer = DEF_VAL )Code language: Delphi (delphi)

Необходимо явно указывать значение:

procedure MyProc( AParam: integer = 1 )Code language: Delphi (delphi)

Нет приведения типов

Это скорее ограничение, чем ошибка. Но факт остается фактом: явного приведения значений к нужному типу в FastScript нет.

На практике с этим сталкиваются не часто, так как для преобразования значений различных типов используются функции либо преобразование делается неявно, но в Delphi можно преобразовывать ссылки на объекты в значение Integer (и обратно), что, например, удобно для хранения целочисленных значений в массиве TStrings.Objects(), который можно использовать для создания именованного массива. Впрочем, эту задачу можно решить иначе, сохраняя в свойстве TStrings.Strings() данные в виде <Имя>=<Значение>, используя для доступа к данным свойства TStrings.Names() и TStrings.Values(). Работать это будет чуть медленней, так как потребуется преобразование строки в число.

Ошибка -1:-1

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

Найти строку с ошибкой в этом случае очень трудно: необходимо визуально просматривать сотни, а то и тысячи строк исходного кода. Лично мне для локализации этой ошибки пришлось последовательно комментировать тела процедур, так как «замыленный» глаз был просто неспособен увидеть ошибку в коде.

Объекты, рекурсия и обработка событий

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

Это разновидность скрытой ошибки, так как рекурсивно вызывать обработчики можно, но нельзя использовать значения параметров, например Sender:TObject.

Такая же ошибка возникает, если в обработчике события имеется локальная переменная, хранящая ссылку на объект. При рекурсивном вызове значение переменной перетирается!

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

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

CASE без операторов

Если написать такой вот оператор case, в ожидании того, что команды в секции else выполнятся для всех значений A, кроме указанных явно (1,2 и 3), то вас ждет сюрприз: секция else будет выполняться всегда!

case A of
1: ;
2: ;
3: ;
else
begin
  ShowMessage(A);
end;Code language: Delphi (delphi)

Решение: если добавить любую команду после значения селектора, то CASE заработает как положено.

case A of
1: B := 1;
2: C:= B * 4;
3: D := A;
else
begin
  ShowMessage(A);
end;Code language: Delphi (delphi)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *