[[help:service_locations|Назад: Служебные локации]] ====== Динамический код ====== ===== Лирическое вступление, поясняющее, для чего может пригодиться динамический код ===== Из-за своей линейности язык **QSP** лишён некоторых плюшек, которые есть в других языках программирования. Например, если вам нужно вывести на печать массив, вы делаете это с помощью цикла: ! массив mass создан заранее loop local i,size=0,arrsize('mass') while i Но, что если вам нужно вывести на печать десять массивов? Писать на каждый массив по циклу? Гораздо проще написать одну универсальную локацию-функцию (см. раздел [[help:organizing|"Пользовательские функции и процедуры"]]), передавать в неё название массива и выводить одним и тем же циклом на печать любой массив. И вот для этого нам потребуется использовать динамический код. (на самом деле у этой задачи есть решение и без применения оператора ''%%DYNAMIC%%'', но сейчас нас интересует именно динамический код, поэтому продолжим в рамках нужного для примера решения). Динамический код — это код, который мы не знаем заранее. Мы пишем лишь его составные части, а уже при выполнении программы этот код составляется из этих частей в нужные комбинации. Прежде всего стоит спросить себя, из чего состоит любой код? И, кажется, что ответ очевиден: код состоит из команд. Это верно, но чем по сути являются команды? А вот тут ответ очевиден не для всех: команды — это строки текста. Мы с вами прекрасно умеем работать со строками. Если не умеем, то быстренько читаем раздел [[help:strings|"Строки"]] и учимся. Строки можно склеивать, в них можно встраивать подвыражения, из них можно вырезать фрагменты. Вот и с кодом, по сути, можно делать то же самое. Так давайте запишем наш цикл в виде строки: "loop local i,size=0,arrsize('mass') while i Что мы тут видим? А видим мы, что в этой строке прописано название массива ''%%mass%%'', аж целых два раза. Название массива — это значение, которое можно помещать в перемнную, а можно извлекать из переменной. Например, мы поместили название массива в переменную ''%%$array_name%%''. Как нам вставить значение этой переменной в наш код, записанный в виде строки? Используем подвыражения: $array_name='mass' "loop local i,size=0,arrsize('<<$array_name>>') while i>[i] end" Если мы запустим получившуюся локацию, мы увидим на экране наш цикл с массивом **mass**. Если мы пропишем в переменную ''%%$array_name%%'' название другого массива, и снова запустим локацию, мы увидим на экране тот же цикл, но с другим массивом. Это значит, что наша строка текста формируется **динамически**! Получается, и код цикла формируется динамически. Нам осталось только научиться запускать на выполнение такой код, записанный в виде строки текста. И вот для этого, как раз, в **QSP** есть специальный оператор: ''%%DYNAMIC%%''. Мы просто передаём этому оператору наш записанный в виде текста цикл, и оператор ''%%DYNAMIC%%'' легко его выполняет, как обычный код QSP: $array_name='mass' dynamic "loop local i,size=0,arrsize('<<$array_name>>') while i>[i] end" Таким образом, заменяя значение в переменной ''%%$array_name%%'', мы легко выводим на печать любой массив. А чтобы нам было ещё проще, мы создаём специальную локацию-функцию, и ей, в качестве аргументов, будем передавать названия массивов, которые хотим распечатать. Назовём эту локацию ''%%print_array%%'': !# print_array dynamic "loop local i,size=0,arrsize('<<$args[0]>>') while i>[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 "Действие <>": *pl "Действие <>" end В данном случае будут созданы действия с названиями "Действие 1" ... "Действие 10". Неопытный автор ожидает, что каждое из этих действий при щелчке по нему будет выводить собственный номер. Однако, это ожидание не верно. Переменная ''%%i%%'' в данном случае получит значение **10**, и в момент выполнения действия именно это значение подставится в строку, выводимую на экран. То есть подвыражение, относящееся к коду действия, не будет раскрыто плеером, пока игрок не кликнет по действию.\\ Чтобы действительно сгенерировать действия, которые будут выводить свой правильный номер, раскрытие подвыражения должно происходить в момент создания действия. Как раз для этого и подойдёт оператор ''%%DYNAMIC%%'': loop i=1 while i<=10 step i+=1: dynamic 'act "Действие <>": *pl "Действие <>"' end В этом случае сначала раскроются подвыражения, а затем готовый код будет передан оператору ''%%DYNAMIC%%''. Таким образом сформируются дейстия с уже готовым уникальным кодом действия. * Также ''%%DYNAMIC%%'' и ''%%DYNEVAL%%'' очень удобны, когда нужно выполнить некоторый многострочный код в том месте, где невозможно разместить такой многострочный код. Например, в гиперссылках: $CODE = { if no деньги<100: addobj 'Кружка имбирного эля' кружка_эля+=1 деньги-=100 *pl "Я приобрёл куржку имбирного эля." else *pl "Мне не хватает денег на эль." end } *pl "Купить кружку имбирного эля" В этом случае переменная ''%%$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%%'' очень удобен для разного рода [[help:debugger|отладчиков]], поскольку позволяет выполнять код QSP, введённый в строку ввода безо всяких танцев с бубном. * ''%%DYNAMIC%%'' позволяет восстанавливать действия, полученные с помощью функции ''%%$CURACTS%%'', и предметы, полученные с помощью функции ''%%$CUROBJS%%'': $acts = $curacts cla dynamic $acts Нет смысла использовать динамический код, если вы можете получить тот же результат без его использования. Прежде чем всюду пихать ''%%DYNAMIC%%'' хорошенько подумайте, а не целесообразнее было бы написать этот код на отдельной локации так, чтобы не приходилось использовать ''%%DYNAMIC%%''. Динамический код сложнее читать и на него сложнее ссылаться. Он в принципе ухудшает читаемость кода. Вполне возможно вложение динамического кода в динамический код, то есть ''%%DYNAMIC%%''/''%%DYNEVAL%%'' внутри ''%%DYNAMIC%%''/''%%DYNEVAL%%'', однако это мало того, что ухудшает читаемость, так ещё и очень часто приводит к багам, которые трудно отловить. Банально, неверная закрытая кавычка внутри кода внутри кода может вызвать ошибку, выбиваемую вообще не понятно в какой строке локации. Тем не менее число вложений не ограничено. [[help:menu|Вперёд: Всплывающее меню]]