Пользовательские операторы
Пользовательские операторы (Creator-Defined Statements, CDS) позволяют вам добавлять свои собственные операторы в скриптовый язык Ren'Py. Это даёт возможность добавлять функциональность, которая не поддерживается текущим синтаксисом.
CDS могут быть более гибкими, чем их эквивалент на чистом Python.
Например, случайный выбор реплики диалога:
label introduction:
python:
greetings = ['Hello.', 'Welcome.', 'Can I help you?']
greeting = renpy.random.choice(greetings)
"[greeting]"
Парсер Ren'Py не знает заранее, что происходит в блоке python или как его следует выполнять. Он ничего не делает с этим кодом до момента выполнения и вызывает ошибку, если возникает исключение.
Использование CDS позволяет:
Проверять корректность разобранного синтаксиса (например, проверять, что элементы в списке, отправленном в renpy.random.choice, содержат корректный текст).
Игнорировать некорректные данные при выполнении (для некритичных функций часто лучше пропустить выполнение, чем выбрасывать исключение).
Предсказывать Displayable-объекты (если функция их использует).
Предоставлять вам дополнительную информацию во время Lint (если во время выполнения ошибка была проигнорирована, вы можете получить здесь отчёт).
Например, то же поведение, но написанное в виде CDS:
python early:
def parse_random(lexer):
subblock_lexer = lexer.subblock_lexer()
choices = []
while subblock_lexer.advance():
with subblock_lexer.catch_error():
statement = subblock_lexer.renpy_statement()
choices.append(statement)
return choices
def next_random(choices):
return renpy.random.choice(choices)
def lint_random(parsed_object):
for i in parsed_object:
check = renpy.check_text_tags(i.block[0].what)
if check:
renpy.error(check)
renpy.register_statement(
name="random",
block=True,
parse=parse_random,
next=next_random,
lint=lint_random,
)
random теперь доступен как оператор:
label introduction:
random:
"Hello."
"Welcome."
"Can I help you?"
Использование CDS не гарантирует успешного выполнения, но чем лучше вы напишете код своего оператора, тем лучше Ren'Py сможет "понять", чего вы от него ожидаете.
Использование
Пользовательские операторы (CDS) должны соответствовать следующим правилам:
Они должны быть определены в блоке
python early.Их нельзя использовать в том же файле, в котором они определены.
Файл, содержащий CDS, должен быть загружен раньше любого файла, который его использует. (Поскольку Ren'Py загружает файлы в порядке сортировки их путей по стандарту Юникод, обычно имеет смысл добавлять префикс, такой как 01 или другое небольшое число, к имени любого файла, содержащего CDS. См. Фаза парсинга скриптов для получения дополнительной информации о порядке загрузки файлов в Ren'Py, включая особенности для директорий game/libs/ и game/mods/.)
Пользовательские операторы регистрируются с помощью функции renpy.register_statement(). Эта функция принимает другие функции, которые выполняют операции над содержимым CDS.
Например, новый оператор с именем line, который позволяет указывать строки текста без кавычек.
line e "These quotes will show up," Eileen said, "and don't need to be backslashed."
Функции парсинга будет отправлено содержимое после лексического анализа для разбора. Функция выполнения должна производить операцию над разобранным содержимым. Функция lint должна сообщать о любых ошибках в разобранном содержимом.
python early:
def parse_smartline(lexer):
who = lexer.simple_expression()
what = lexer.rest()
return (who, what)
def execute_smartline(parsed_object):
who, what = parsed_object
renpy.say(eval(who), what)
def lint_smartline(parsed_object):
who, what = parsed_object
try:
eval(who)
except Exception:
renpy.error("Character not defined: {}".format(who))
tte = renpy.check_text_tags(what)
if tte:
renpy.error(tte)
renpy.register_statement(
"line",
parse=parse_smartline,
execute=execute_smartline,
lint=lint_smartline,
)
Справочник по API
- renpy.register_statement(name, parse=None, lint=None, execute=None, predict=None, next=None, scry=None, block=False, init=False, translatable=False, execute_init=None, init_priority=0, label=None, warp=None, translation_strings=None, force_begin_rollback=False, post_execute=None, post_label=None, predict_all=True, predict_next=None, execute_default=None, reachable=None)
Регистрирует пользовательскую инструкцию.
- name
Это либо список имен, разделенных пробелами, с которых начинается инструкция, либо пустая строка для определения новой инструкции по умолчанию (инструкция по умолчанию заменит инструкцию say).
- block
Может быть одним из:
False, чтобы указать, что инструкция не ожидает блока.
True, чтобы указать, что инструкция ожидает блок и будет его анализировать (парсить).
"possible", чтобы указать, что инструкция может принимать блок, а может и не принимать.
"script", чтобы указать, что блок должен интерпретироваться как блок инструкций на языке скриптов Ren'Py. См. next для информации о том, как реализовать управление потоком с помощью этого.
"script-possible" рассматривается как "script", если блок присутствует, и как False в противном случае.
"atl", чтобы указать, что блок должен интерпретироваться как ATL- преобразование. Он передается как дополнительный аргумент в execute.
"atl-possible" рассматривается как "atl", если блок присутствует, и как False в противном случае.
- parse
Это функция, принимающая объект Lexer. Эта функция должна проанализировать инструкцию и вернуть объект. Этот объект передается как аргумент всем остальным функциям.
- lint
Вызывается для проверки инструкции. Ей передается один аргумент, объект, возвращенный из parse. Она должна вызывать renpy.error для сообщения об ошибках.
- execute
Это функция, которая вызывается при выполнении инструкции. Ей передается один аргумент, объект, возвращенный из parse. Если есть ATL-блок, именованный аргумент atl передается с ATL-преобразованием.
- execute_init
Это функция, которая вызывается во время инициализации (init time), с приоритетом 0. Ей передается один аргумент, объект, возвращенный из parse.
- predict
Это функция, которая вызывается для предсказания изображений, используемых инструкцией. Ей передается один аргумент, объект, возвращенный из parse. Она должна возвращать список displayable-объектов, используемых инструкцией.
- next
Это функция, которая вызывается для определения следующей инструкции.
Если block не равен "script", ей передается один аргумент, объект, возвращенный из функции parse. Если block равен "script", передается дополнительный аргумент — объект, который именует первую инструкцию в блоке.
Функция должна возвращать либо строку, указывающую на метку для перехода, либо второй аргумент для передачи управления в блок, либо None для продолжения выполнения инструкции, следующей за этой. Она также может возвращать результат
Lexer.renpy_statement()илиLexer.renpy_block()при вызове в функции parse.- label
Это функция, которая вызывается для определения метки этой инструкции. Если она возвращает строку, эта строка используется как метка инструкции, к которой можно обращаться и переходить так же, как к любой другой метке.
- warp
Это функция, которая вызывается для определения, должна ли эта инструкция выполняться во время 'прокрутки' (warping). Если функция существует и возвращает true, она выполняется во время 'прокрутки', в противном случае инструкция не выполняется во время 'прокрутки'.
- scry
Используется внутренне Ren'Py.
- init
True, если эта инструкция должна выполняться во время инициализации (init-time). (Если инструкция еще не находится внутри init блока, она автоматически помещается в init блок.)
Скорее всего, это вам не понадобится, если у вас есть функция execute_init, так как обертывание инструкции в init блок приведет к одновременному вызову функций execute_init и execute.
- translatable
Если установлено в true, инструкция будет включена в блок перевода, обычно это блок, содержащий следующую инструкцию say. Это может быть установлено в true только для однострочных инструкций. Используется для инструкций вроде
nvl clearиvoice, которые могут потребовать изменений вместе с диалогом.- init_priority
Целое число, определяющее приоритет инициализации init блока, созданного init и функцией execute_init. Также может быть функцией, которая вызывается с проанализированным блоком и должна возвращать целое число.
- translation_strings
Функция, которая вызывается с проанализированным блоком. Ожидается, что она вернет список строк или кортежей вида (номер строки, строка), которые затем помечаются как доступные для перевода.
- force_begin_rollback
Должно быть установлено в true для инструкций, которые могут вызвать прекращение быстрой перемотки (fast skip), подобно
menuилиcall screen.- post_execute
Функция, которая выполняется как часть следующей инструкции после этой. (Добавление функции post_execute изменяет содержимое RPYC-файла, что означает необходимость принудительной компиляции (Force Compile).)
- post_label
Это функция, которая вызывается для определения метки инструкции post execute. Если она возвращает строку, эта строка используется как метка инструкции, к которой можно обращаться и переходить как к любой другой метке. Это можно использовать для создания уникальной точки возврата.
- predict_all
Если True, то предсказываются все под-блоки (sub-parses) этой инструкции и следующей за ней.
- predict_next
Вызывается с одним аргументом, меткой инструкции, которая будет выполняться после этой.
Должна вызываться для предсказания инструкций, которые могут быть выполнены после этой. Ожидается, что она вернет список меток или объектов SubParse. Не вызывается, если predict_all равно true.
- execute_default
Это функция, которая вызывается в то же время, когда выполняются инструкции по умолчанию — после фазы инициализации, но до начала игры; при загрузке сохранения; после отката; перед lint; и, возможно, в другие моменты.
Вызывается с одним аргументом, объектом, возвращенным из parse.
- reachable
Это функция, которая вызывается, чтобы позволить этой инструкции настраивать свое участие в анализе достижимости lint'а.
По умолчанию пользовательский блок инструкции, под-блоки, созданные с помощью Lexer.renpy_block(), и следующая инструкция считаются достижимыми, если достижима сама инструкция. Инструкция также достижима, если у нее есть функция label.
Это можно настроить, предоставив функцию reachable. Это функция, которая принимает пять аргументов (в дальнейшем "метка" может быть строкой или непрозрачным объектом (opaque object)):
Объект, возвращенный функцией parse.
Логическое значение, которое true, если инструкция достижима.
Метка инструкции.
Метка следующей инструкции, или None, если следующей инструкции нет.
Если block установлен в "script", метка первой инструкции в блоке, или None, если блока нет.
Ожидается, что она вернет множество, которое может содержать:
Метку или объект subparse инструкции, которая достижима.
True, чтобы указать, что об этой инструкции не следует сообщать lint'у, но она не является изначально достижимой. (Она станет достижимой, если будет сообщено о ее достижимости другой инструкцией.)
None, которое игнорируется.
Эта функция может вызываться несколько раз с обоими значениями is_reachable, чтобы позволить инструкции настраивать свое поведение в зависимости от того, достижима она или нет. (Например, следующая инструкция может быть достижима, только если достижима текущая инструкция.)
Предупреждение
Использование пустой строки в качестве имени для переопределения инструкции say обычно является плохой идеей. Это связано с тем, что при замене встроенной инструкции Ren'Py ее поведение зависит от Эквиваленты операторов. В случае инструкции say эти эквиваленты не поддерживают системы id и перевода. В результате игра, переопределяющая инструкцию по умолчанию, не сможет использовать эти функции (разве что полностью их перереализует).
Объект Lexer
Функция парсинга пользовательского оператора принимает экземпляр объекта Lexer.
- class Lexer
- property init
Имеет значение true, если лексер находится внутри оператора init, иначе false.
- property init_priority
Если init истинно, это приоритет блока init.
- error(msg)
- Параметры:
msg (str) -- Сообщение для добавления в список обнаруженных ошибок парсинга.
Добавляет msg (с текущей позицией) в список обнаруженных ошибок парсинга. Это прерывает разбор текущего оператора, но не препятствует дальнейшему парсингу.
- require(thing, name=None)
Пытается разобрать thing и сообщает об ошибке, если это невозможно.
Если thing — это строка, пытается разобрать её с помощью
match().В противном случае, thing должен быть другим методом этого объекта лексера, который вызывается без аргументов.
Если name не указан, в сообщении будет использоваться имя метода (или thing, если это строка), иначе будет использоваться name.
- eol()
- Результат:
True, если лексер находится в конце строки, иначе False.
- Тип результата:
bool
- expect_eol()
Если не в конце строки, вызывает ошибку.
- expect_noblock(stmt)
Вызывается, чтобы указать, что этот оператор не ожидает блока. Если блок найден, вызывает ошибку. stmt должен быть строкой, он будет добавлен в сообщение об ошибке.
- expect_block(stmt)
Вызывается, чтобы указать, что оператор требует наличия непустого блока. stmt должен быть строкой, он будет добавлен в сообщение об ошибке.
- has_block()
- Результат:
True, если у текущей строки есть непустой блок, иначе False.
- Тип результата:
bool
- match(re)
Сопоставляет произвольную строку с регулярным выражением.
Все операторы в лексере, которые выполняют сопоставление, реализованы через эту функцию. Они сначала пропускают пробельные символы, а затем пытаются выполнить сопоставление со строкой. Если сопоставление успешно, возвращается сопоставленный текст. В противном случае возвращается None, а состояние лексера остаётся неизменным.
- keyword(s)
Сопоставляет s как ключевое слово.
- name()
Сопоставляет имя. Это не сопоставляет встроенные ключевые слова.
- word()
- Результат:
Текст сопоставленного слова.
- Тип результата:
str
Сопоставляет любое слово, включая ключевые слова.
- image_name_component()
Сопоставляет компонент имени изображения. В отличие от слова, компонент имени изображения может начинаться с цифры.
- string()
Сопоставляет строку Ren'Py.
- integer()
- Результат:
Строка, содержащая найденное целое число.
- Тип результата:
str
Сопоставляет целое число.
- float()
- Результат:
Строка, содержащая найденное число с плавающей точкой.
- Тип результата:
str
Сопоставляет число с плавающей точкой.
- label_name(declare=False)
Сопоставляет имя метки, абсолютное или относительное. Если declare истинно, то устанавливается глобальное имя метки. (Обратите внимание, что это не объявляет метку на самом деле — оператор должен сделать это, вернув её из функции label.)
- simple_expression()
Сопоставляет простое выражение Python, возвращая его в виде строки. Это часто используется, когда ожидается имя переменной. Не рекомендуется изменять результат. Правильным действием будет вычислить результат в будущем.
- delimited_python(delim)
Сопоставляет выражение Python, которое заканчивается delim, например, ':'. Это часто используется, когда ожидается условие до разделителя. Не рекомендуется изменять результат. Правильным действием будет вычислить результат в будущем. Вызывает ошибку, если конец строки достигается до разделителя.
- arguments()
Этот метод должен вызываться перед скобками со списком аргументов. Если они не указаны, возвращает None, в противном случае возвращает объект, представляющий аргументы вызова функции. Этот объект имеет метод
evaluate, который принимает необязательный словарь scope и возвращает кортеж, в котором первый компонент — это кортеж позиционных аргументов, а второй — словарь именованных аргументов.
- rest()
Пропускает пробельные символы, затем возвращает оставшуюся часть строки.
- checkpoint()
Возвращает непрозрачный объект, представляющий текущее состояние лексера.
- revert(o)
Когда o — это объект, возвращенный из checkpoint(), возвращает состояние лексера к тому, каким оно было при вызове checkpoint(). (Это используется для отката.)
- subblock_lexer()
- Результат:
Lexer для блока, связанного с текущей строкой.
- advance()
В лексере подблока переходит к следующей строке. Этот метод должен быть вызван перед первой строкой, чтобы можно было разобрать первую строку. Возвращает True, если мы успешно перешли к строке в блоке, или False, если мы вышли за пределы всех строк в блоке.
- renpy_statement()
При вызове этот метод разбирает текущую строку как оператор скрипта Ren'Py, вызывая ошибку, если это невозможно. Метод возвращает непрозрачный объект, который может быть возвращён из функции next, переданной в
renpy.register_statement(), или передан вrenpy.jump()илиrenpy.call(). Этот объект не следует хранить, кроме как в качестве части результата парсинга оператора.Когда оператор, возвращённый этим методом, завершается, управление передаётся оператору, следующему за пользовательским оператором. (Это может быть оператор, созданный с помощью post_execute).
- renpy_block(empty=False)
Разбирает все оставшиеся строки в текущем блоке как скрипт Ren'Py и возвращает SubParse, соответствующий первому оператору в блоке. Блок связывается таким образом, что все операторы в нём выполняются, а затем управление передаётся оператору, следующему за этим пользовательским оператором.
Обратите внимание, что это разбирает текущий блок. В более вероятном случае, когда вы хотите разобрать подблок текущего оператора, правильный способ сделать это:
def mystatement_parse(l): l.require(':') l.expect_eol() l.expect_block("mystatement") child = l.subblock_lexer().renpy_block() return { "child" : child }
- empty
Если True, позволяет разбирать пустой блок. (Пустой блок эквивалентен блоку с одним оператором
pass.)Если False, пустой блок вызывает ошибку.
- catch_error()
Это декоратор контекста, используемый совместно с оператором with, который перехватывает и сообщает об ошибках лексера внутри своего блока контекста, а затем продолжает выполнение после блока.
Вот пример того, как его можно использовать для сообщения о нескольких ошибках в одном подблоке.
def mystatement_parse(l): l.require(':') l.expect_eol() l.expect_block("mystatement") strings = [ ] ll = l.subblock_lexer() while ll.advance(): with ll.catch_error(): strings.append(ll.require(ll.string)) ll.expect_noblock("string inside mystatement") ll.expect_eol() return { "strings" : strings }
Функция, позволяющая выполнять лексический анализ произвольных строк:
- renpy.lex_string(text: str, filename: str = '<string>', linenumber: int = 1, advance: bool = True) renpy.lexer.Lexer
Возвращает объект Lexer, который можно использовать для лексического анализа данного текста.
- text
Текст для лексического анализа.
- filename
Имя файла, для которого будут сообщаться ошибки.
- linenumber
Номер строки, для которого будут сообщаться ошибки.
- advance
Если true, у лексера будет вызван метод .advance().
Вспомогательные функции для Lint
Эти функции полезны при написании функций для lint.
- renpy.check_text_tags(s, check_unclosed=False)
Проверяет текстовые теги в s на корректность. Возвращает строку с ошибкой, если есть ошибка, или None, если ошибки нет.
- renpy.error(msg)
Сообщает msg (строку) пользователю как ошибку. Это логируется как ошибка разбора или анализа (lint), когда это уместно, а в противном случае выбрасывается как исключение.
- renpy.try_compile(where, expr, additional=None)
Пытается скомпилировать выражение и записывает ошибку в lint.txt, если это не удаётся.
- where
Строка, указывающая место, где находится выражение. Используется для генерации сообщения об ошибке вида "Не удалось вычислить expr в where."
- expr
Выражение для попытки компиляции.
- additional
Если задано, дополнительная строка информации, которая добавляется к сообщению об ошибке.
- renpy.try_eval(where, expr, additional=None)
Пытается вычислить выражение и записывает ошибку в lint.txt, если это не удаётся.
- where
Строка, указывающая место, где находится выражение. Используется для генерации сообщения об ошибке вида "Не удалось вычислить expr в where."
- expr
Выражение для попытки вычисления.
- additional
Если задано, дополнительная строка информации, которая добавляется к сообщению об ошибке.