Перейти к основному содержимому

Назад: Служебные локации

Динамический код

Лирическое вступление, поясняющее, для чего может пригодиться динамический код

Из-за своей линейности язык QSP лишён некоторых плюшек, которые есть в других языках программирования. Например, если вам нужно вывести на печать массив, вы делаете это с помощью цикла:

! массив mass создан заранее
loop local i,size=0,arrsize('mass') while i<size step i+=1:
*pl mass[i]
end

Но, что если вам нужно вывести на печать десять массивов? Писать на каждый массив по циклу?

Гораздо проще написать одну универсальную локацию-функцию (см. раздел "Пользовательские функции и процедуры"), передавать в неё название массива и выводить одним и тем же циклом на печать любой массив.

И вот для этого нам потребуется использовать динамический код.

(на самом деле у этой задачи есть решение и без применения оператора DYNAMIC, но сейчас нас интересует именно динамический код, поэтому продолжим в рамках нужного для примера решения).

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

Прежде всего стоит спросить себя, из чего состоит любой код? И, кажется, что ответ очевиден: код состоит из команд. Это верно, но чем по сути являются команды? А вот тут ответ очевиден не для всех: команды — это строки текста.

Мы с вами прекрасно умеем работать со строками. Если не умеем, то быстренько читаем раздел "Строки" и учимся.

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

"loop local i,size=0,arrsize('mass') while i<size step i+=1:
*pl mass[i]
end"

Что мы тут видим? А видим мы, что в этой строке прописано название массива mass, аж целых два раза. Название массива — это значение, которое можно помещать в перемнную, а можно извлекать из переменной.

Например, мы поместили название массива в переменную $array_name. Как нам вставить значение этой переменной в наш код, записанный в виде строки? Используем подвыражения:

$array_name='mass'
"loop local i,size=0,arrsize('<<$array_name>>') while i<size step i+=1:
*pl <<$array_name>>[i]
end"

Если мы запустим получившуюся локацию, мы увидим на экране наш цикл с массивом mass. Если мы пропишем в переменную $array_name название другого массива, и снова запустим локацию, мы увидим на экране тот же цикл, но с другим массивом. Это значит, что наша строка текста формируется динамически!

Получается, и код цикла формируется динамически. Нам осталось только научиться запускать на выполнение такой код, записанный в виде строки текста.

И вот для этого, как раз, в QSP есть специальный оператор: DYNAMIC. Мы просто передаём этому оператору наш записанный в виде текста цикл, и оператор DYNAMIC легко его выполняет, как обычный код QSP:

$array_name='mass'
dynamic "loop local i,size=0,arrsize('<<$array_name>>') while i<size step i+=1:
*pl <<$array_name>>[i]
end"

Таким образом, заменяя значение в переменной $array_name, мы легко выводим на печать любой массив. А чтобы нам было ещё проще, мы создаём специальную локацию-функцию, и ей, в качестве аргументов, будем передавать названия массивов, которые хотим распечатать. Назовём эту локацию print_array:

!# print_array
dynamic "loop local i,size=0,arrsize('<<$args[0]>>') while i<size step i+=1:
*pl <<$args[0]>>[i]
end"

И теперь, как мы выведем на печать любой, какой хотим, массив? Проще простого:

@print_array('mass')
@print_array('$unit_name')
@print_array('unit_count')

Вот так возможность работы с динамическим кодом позволяет нам сделать наш код QSP более гибким и легко читаемым.

DYNAMIC

DYNAMIC — выполняет код, переданный в виде строки текста. Общая запись:

DYNAMIC [$код],[аргумент 0],[аргумент 1], ... ,[аргумент 18]

, где [$код] — это обычный код QSP, записанный в виде текста. Выполнение такого кода аналогично выполнению кода оператора GOSUB. Аргументы [аргумент 0], [аргумент 1] и т.д. могут использоваться внутри [$код], их значения автоматически помещаются в ячейки массива args: в ARGS[0], ARGS[1], и т.д. соответственно. Внутри выполняемого кода DYNAMIC используется свой собственный массив ARGS, его значения не пересекаются со значениями ARGS на локации, из которой DYNAMIC был вызван. После выполнения кода, переданного оператору DYNAMIC, продолжается выполнение кода со следующей команды после DYNAMIC.

Примеры:

! простые вызовы кода, записанного в виде текста
dynamic '$a="string<<$b>>"'
dynamic '$a'
dynamic 'if $a="string":''text!'''
! вызов кода с передачей в него аргументов
dynamic "
*pl $args[0]
addobj $args[1]
",'Вы взяли вилку.','Вилка'

Нижеследующая информация справедлива и для функции DYNEVAL (см. ниже).

Важно! Если код задан с помощью одинарных ('') или двойных ("") кавычек, подвыражения вычисляются сразу:

$args[0]='qwerty'
$code = '
*pl "<<$args[0]>>"
*pl $args[0]
'

dynamic $code,'asdfg'

В этом случае при задании переменной $code будет вычислено подвыражение, поэтому когда DYNAMIC выполнит код, первой строкой выведется 'qwerty', второй строкой выведется 'asdfg'.

Фигурные скобки - третий вид кавычек используемый специально для написания динамического кода. Здесь поддерживается вложенность скобок, а подвыражения не вычисляются:

$args[0]='qwerty'
$code = {
*pl "<<$args[0]>>"
*pl $args[0]
}

dynamic $code,'asdfg'

В этом случае будут выведены две строки 'asdfg'. Так как подвыражение не вычисляется на момент присвоения кода переменной $code, но зато будет вычислено уже при непосредственном выполнении кода оператором DYNAMIC.

DYNEVAL

DYNEVAL — выполняет код, переданный в виде строки текста, и возвращает результат, если он есть. Общая запись:

$DYNEVAL([$код],[аргумент 0],[аргумент 1], ... ,[аргумент 18])
DYNEVAL([$код],[аргумент 0],[аргумент 1], ... ,[аргумент 18])

, где [$код] — это обычный код QSP, записанный в виде текста. Выполнение такого кода аналогично выполнению кода функции FUNC. Аргументы [аргумент 0], [аргумент 1] и т.д. могут использоваться внутри [$код], их значения автоматически помещаются в массив ARGS, в ячейки ARGS[0], ARGS[1], и т.д. соответственно. Внутри выполняемого кода DYNEVAL используется свой собственный массив ARGS, его значения не пересекаются со значениями ARGS на локации, из которой DYNEVAL была вызвана. После выполнения кода, переданного функции DYNEVAL, продолжается вычисление выражения, в котором расположена функция DYNEVAL.

Чтобы DYNEVAL возвращала результат, необходимо внутри [$код] присвоить этот результат переменной RESULT.

Примеры:

dyneval('result = 3+4')
*pl $dyneval('$result = $mid("abcd",2,1)+"qwerty"')
проход = dyneval("result = ($args[0] <> 'текст')", 'строка')
  • Чтобы DYNEVAL вернула строковое значение, результат должен быть записан в $RESULT.
  • Чтобы DYNEVAL вернула числовое значение, результат должен быть записан в RESULT.
  • Чтобы DYNEVAL вернула несколько значений, результат нужно записать в %RESULT как кортеж значений.
  • %RESULT, $RESULT и RESULT — это одна и та же переменная, но с разными типами данных. Следует помнить, что новая запись значения затирает предыдущее, какого бы типа данных не было это значение.

Если при выполнении DYNEVAL она не возвращает значения (RESULT не инициализируется), и является единственным элементом выражения, передаваемого неявному оператору, плеер ничего не выведет на экран. Т.е. DYNEVAL будет работать, как DYNAMIC. Пример:

! неявный оператор выведет на экран 123:
123
! код в dyneval выполнится, но на экране
! мы ничего не увидим:
dyneval("code = 123 + 890")
! неявный оператор выведет на экран 1013:
code

Области применения DYNAMIC и DYNEVAL

DYNAMIC и DYNEVAL следует использовать в нескольких случаях:

  • Когда нет иного способа получить необходимый функционал. Как пример, очень распространённый случай генерации действий с помощью цикла:
    loop i=1 while i<=10 step i+=1:
    act "Действие <<i>>": *pl "Действие <<i>>"
    end
    В данном случае будут созданы действия с названиями "Действие 1" ... "Действие 10". Неопытный автор ожидает, что каждое из этих действий при щелчке по нему будет выводить собственный номер. Однако, это ожидание не верно. Переменная i в данном случае получит значение 10, и в момент выполнения действия именно это значение подставится в строку, выводимую на экран. То есть подвыражение, относящееся к коду действия, не будет раскрыто плеером, пока игрок не кликнет по действию.
    Чтобы действительно сгенерировать действия, которые будут выводить свой правильный номер, раскрытие подвыражения должно происходить в момент создания действия. Как раз для этого и подойдёт оператор DYNAMIC:
    loop i=1 while i<=10 step i+=1:
    dynamic 'act "Действие <<i>>": *pl "Действие <<i>>"'
    end
    В этом случае сначала раскроются подвыражения, а затем готовый код будет передан оператору DYNAMIC. Таким образом сформируются дейстия с уже готовым уникальным кодом действия.
  • Также DYNAMIC и DYNEVAL очень удобны, когда нужно выполнить некоторый многострочный код в том месте, где невозможно разместить такой многострочный код. Например, в гиперссылках:
    $CODE={
    if no деньги<100:
    addobj 'Кружка имбирного эля'
    кружка_эля+=1
    деньги-=100
    *pl "Я приобрёл куржку имбирного эля."
    else
    *pl "Мне не хватает денег на эль."
    end
    }
    *pl "<a href='exec:dynamic $CODE'>Купить кружку имбирного эля</a>"
    В этом случае переменная $CODE обязательно должна быть глобальной, иначе ссылка её не "увидит". Однако, если необходимо использовать такой код в условиях или циклах, вполне можно обойтись и локальной переменной, чтобы код не висел в памяти:
    ! этот код не имеет практической пользы. Просто для примера.
    local $code = {
    if args[0]+args[1]+args[2]+args[3]=0:
    result=1
    elseif args[0]+args[1]+args[2]+args[3]=4:
    result=1
    else:
    result=0
    end
    }
    loop while dyneval($code,a,b,c,d):
    a = rand(0,1)
    b = rand(0,1)
    c = rand(0,1)
    d = rand(0,1)
    end
    В своём роде это способ создавать анонимные функции в QSP.
  • Ещё DYNAMIC очень удобен для разного рода отладчиков, поскольку позволяет выполнять код QSP, введённый в строку ввода безо всяких танцев с бубном.
  • DYNAMIC позволяет восстанавливать действия, полученные с помощью функции $CURACTS, и предметы, полученные с помощью функции $CUROBJS:
    $acts = $curacts
    cla
    dynamic $acts

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

Вполне возможно вложение динамического кода в динамический код, то есть DYNAMIC/DYNEVAL внутри DYNAMIC/DYNEVAL, однако это мало того, что ухудшает читаемость, так ещё и очень часто приводит к багам, которые трудно отловить. Банально, неверная закрытая кавычка внутри кода внутри кода может вызвать ошибку, выбиваемую вообще не понятно в какой строке локации. Тем не менее число вложений не ограничено.

Вперёд: Всплывающее меню